Skip to content

Commit cfdd230

Browse files
committed
Merge branch 'improvement/CUI-36-select-fluid' into q/1.0
2 parents ce9a1e1 + 652681e commit cfdd230

3 files changed

Lines changed: 51 additions & 26 deletions

File tree

src/lib/components/form/Form.component.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ type FormProps = Omit<
3535
* overflowing. It also lays each `FormSection` out as a two-column grid that
3636
* auto-flips to a stacked single column on narrow widths — there is no
3737
* breakpoint prop; the flip point is derived from the layout (see below).
38-
* Note: only `Input` currently honors the fluid width — `Select`, `SearchInput`,
39-
* `TextArea` and other size-driven content keep their fixed width for now
40-
* (tracked in CUI-36).
38+
* Note: `Input` and `Select` currently honor the fluid width — `SearchInput`,
39+
* `TextArea` and other size-driven content keep their fixed width for now.
4140
*/
4241
responsive?: boolean;
4342
};
@@ -339,7 +338,20 @@ const FormGroup = ({
339338
<Stack
340339
direction={helpErrorPosition === 'right' ? 'horizontal' : 'vertical'}
341340
gap={helpErrorPosition === 'right' ? 'r8' : 'r4'}
342-
style={responsive ? { minWidth: 0 } : undefined}
341+
style={
342+
responsive
343+
? {
344+
minWidth: 0,
345+
// Left-align rather than stretch, so a width-less control (a
346+
// TextArea sized only by its `cols`) keeps its intrinsic size in
347+
// the fluid `1fr` field column instead of growing to fill it.
348+
// Input is unaffected — it caps itself via `max-width: 100%`.
349+
...(helpErrorPosition === 'right'
350+
? {}
351+
: { alignItems: 'flex-start' }),
352+
}
353+
: undefined
354+
}
343355
>
344356
{content}
345357
{error ? (
@@ -379,9 +391,11 @@ type FormSectionProps = {
379391
children: ReactElement<FormGroupProps> | ReactElement<FormGroupProps>[];
380392
title?: { name: string; icon?: IconName; helpTooltip?: string };
381393
/**
382-
* Freezes the label column to exactly this pixel width — a hard cap: labels
383-
* wider than it wrap rather than widening the column. When unset, the column
384-
* auto-sizes to the widest label in the section.
394+
* Caps the label column at this pixel width: labels wider than it wrap rather
395+
* than widening the column. In a `responsive` Form the column keeps this width
396+
* while there is room but shrinks below it (down to the longest word) as the
397+
* field track is squeezed, so rows stay aligned; otherwise it is pinned to this
398+
* exact width. When unset, the column auto-sizes to the widest label.
385399
*/
386400
forceLabelWidth?: number;
387401
rightActions?: ReactNode;
@@ -399,20 +413,19 @@ const FormSection = ({
399413
isValidElement(child) ? child.props.required === true : false,
400414
);
401415

402-
// The label column. `forceLabelWidth` is a hard cap that freezes it to an exact
403-
// pixel width (long labels then wrap, mirroring how `Input`'s `size` fixes the
404-
// field width); it also makes every FormGroup row self-sufficient, so nesting a
405-
// group inside another element no longer breaks its layout. When unset, the
406-
// column auto-sizes to the widest label from content, shared across rows via
407-
// `subgrid` — no measurement. Responsive lets the track shrink to its longest
408-
// word (min-content) before the section flips; non-responsive hugs it
409-
// (max-content).
416+
// The label column. `forceLabelWidth` sets the column's upper bound to an exact
417+
// pixel width (labels wider than it wrap, mirroring how `Input`'s `size` fixes
418+
// the field width); it also makes every FormGroup row self-sufficient, so nesting
419+
// a group inside another element no longer breaks its layout. When unset, the
420+
// column's cap is its widest label (`max-content`), shared across rows via
421+
// `subgrid` — no measurement. Responsive then lets the column shrink from its
422+
// longest word (`min-content`) up to that cap before the section flips;
423+
// non-responsive pins it to the cap.
410424
const fixedLabel = forceLabelWidth != null;
411-
const labelTrack = fixedLabel
412-
? `${forceLabelWidth}px`
413-
: responsive
414-
? 'minmax(min-content, max-content)'
415-
: 'max-content';
425+
const labelWidthCap = fixedLabel ? `${forceLabelWidth}px` : 'max-content';
426+
const labelTrack = responsive
427+
? `minmax(min-content, ${labelWidthCap})`
428+
: labelWidthCap;
416429

417430
return (
418431
<FormSectionContext.Provider value={{ labelTrack, fixedLabel }}>

src/lib/components/selectv2/SelectStyle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const SelectStyle = styled(Select)`
88
font-size: ${fontSize.base};
99
box-sizing: border-box;
1010
width: ${({ width }) => width};
11+
${({ fluid }) => fluid && `max-width: 100%; min-width: 0;`}
1112
${({ isDefault }) =>
1213
isDefault ? `height: ${spacing.r32}` : `height: ${spacing.r24}`};
1314
@@ -122,7 +123,7 @@ const SelectStyle = styled(Select)`
122123
}
123124
124125
.sc-select__menu {
125-
width: ${({ width }) => width};
126+
width: ${({ fluid, width }) => (fluid ? '100%' : width)};
126127
border: ${spacing.r1} solid
127128
${({ isDefault }) =>
128129
getThemePropSelector(isDefault ? 'border' : 'selectedActive')};

src/lib/components/selectv2/Selectv2.component.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { FixedSizeList, FixedSizeList as List } from 'react-window';
2727
import { convertRemToPixels } from '../../utils';
2828
import { spacing } from '../../spacing';
2929
import { convertSizeToRem } from '../inputv2/inputv2';
30+
import { useFieldContext } from '../form/Form.component';
3031
import { ConstrainedText } from '../constrainedtext/Constrainedtext.component';
3132
import ReactSelect from 'react-select/src/Select';
3233

@@ -348,6 +349,9 @@ export type SelectProps = {
348349
variant?: 'default' | 'rounded';
349350
size?: '1' | '2/3' | '1/2' | '1/3';
350351
className?: string;
352+
/** When true (or inside a responsive Form), the control fills its container
353+
* down to a min instead of staying fixed at its `size` width. */
354+
fluid?: boolean;
351355
/** use menuPositon='fixed' inside modal to avoid display issue */
352356
menuPosition?: 'fixed' | 'absolute';
353357
/** number of items visible before the option list becomes scrollable
@@ -395,11 +399,14 @@ function SelectBox<
395399
size = '1',
396400
id,
397401
selectRef,
402+
fluid,
398403
itemsPerScrollWindow = 4,
399404
...rest
400405
}: SelectProps & {
401406
selectRef?: Ref<SelectRef<OptionType, IsMulti, GroupType>>;
402407
}) {
408+
const { responsive: responsiveFromFieldContext } = useFieldContext();
409+
const isFluid = !!(fluid || responsiveFromFieldContext);
403410
const [keyboardFocusEnabled, setKeyboardFocusEnabled] = useState(false);
404411
const [searchSelection, setSearchSelection] = useState('');
405412
const [searchValue, setSearchValue] = useState('');
@@ -571,6 +578,7 @@ function SelectBox<
571578
}
572579
}}
573580
width={convertSizeToRem(size)}
581+
fluid={isFluid}
574582
{...rest}
575583
/>
576584
)}
@@ -599,11 +607,14 @@ const SelectWithOptionContext = forwardRef<
599607
});
600608
}, []);
601609

602-
const contextValue = useMemo(() => ({
603-
options,
604-
register,
605-
unregister
606-
}), [options, register, unregister]);
610+
const contextValue = useMemo(
611+
() => ({
612+
options,
613+
register,
614+
unregister,
615+
}),
616+
[options, register, unregister],
617+
);
607618

608619
return (
609620
<OptionContext.Provider value={contextValue}>

0 commit comments

Comments
 (0)