diff --git a/src/lib/components/form/Form.component.tsx b/src/lib/components/form/Form.component.tsx index 750034c179..a45bda71b5 100644 --- a/src/lib/components/form/Form.component.tsx +++ b/src/lib/components/form/Form.component.tsx @@ -35,9 +35,8 @@ type FormProps = Omit< * overflowing. It also lays each `FormSection` out as a two-column grid that * auto-flips to a stacked single column on narrow widths — there is no * breakpoint prop; the flip point is derived from the layout (see below). - * Note: only `Input` currently honors the fluid width — `Select`, `SearchInput`, - * `TextArea` and other size-driven content keep their fixed width for now - * (tracked in CUI-36). + * Note: `Input` and `Select` currently honor the fluid width — `SearchInput`, + * `TextArea` and other size-driven content keep their fixed width for now. */ responsive?: boolean; }; @@ -339,7 +338,20 @@ const FormGroup = ({ {content} {error ? ( @@ -379,9 +391,11 @@ type FormSectionProps = { children: ReactElement | ReactElement[]; title?: { name: string; icon?: IconName; helpTooltip?: string }; /** - * Freezes the label column to exactly this pixel width — a hard cap: labels - * wider than it wrap rather than widening the column. When unset, the column - * auto-sizes to the widest label in the section. + * Caps the label column at this pixel width: labels wider than it wrap rather + * than widening the column. In a `responsive` Form the column keeps this width + * while there is room but shrinks below it (down to the longest word) as the + * field track is squeezed, so rows stay aligned; otherwise it is pinned to this + * exact width. When unset, the column auto-sizes to the widest label. */ forceLabelWidth?: number; rightActions?: ReactNode; @@ -399,20 +413,19 @@ const FormSection = ({ isValidElement(child) ? child.props.required === true : false, ); - // The label column. `forceLabelWidth` is a hard cap that freezes it to an exact - // pixel width (long labels then wrap, mirroring how `Input`'s `size` fixes the - // field width); it also makes every FormGroup row self-sufficient, so nesting a - // group inside another element no longer breaks its layout. When unset, the - // column auto-sizes to the widest label from content, shared across rows via - // `subgrid` — no measurement. Responsive lets the track shrink to its longest - // word (min-content) before the section flips; non-responsive hugs it - // (max-content). + // The label column. `forceLabelWidth` sets the column's upper bound to an exact + // pixel width (labels wider than it wrap, mirroring how `Input`'s `size` fixes + // the field width); it also makes every FormGroup row self-sufficient, so nesting + // a group inside another element no longer breaks its layout. When unset, the + // column's cap is its widest label (`max-content`), shared across rows via + // `subgrid` — no measurement. Responsive then lets the column shrink from its + // longest word (`min-content`) up to that cap before the section flips; + // non-responsive pins it to the cap. const fixedLabel = forceLabelWidth != null; - const labelTrack = fixedLabel - ? `${forceLabelWidth}px` - : responsive - ? 'minmax(min-content, max-content)' - : 'max-content'; + const labelWidthCap = fixedLabel ? `${forceLabelWidth}px` : 'max-content'; + const labelTrack = responsive + ? `minmax(min-content, ${labelWidthCap})` + : labelWidthCap; return ( diff --git a/src/lib/components/selectv2/SelectStyle.ts b/src/lib/components/selectv2/SelectStyle.ts index df49b961c6..706df0cb61 100644 --- a/src/lib/components/selectv2/SelectStyle.ts +++ b/src/lib/components/selectv2/SelectStyle.ts @@ -8,6 +8,7 @@ const SelectStyle = styled(Select)` font-size: ${fontSize.base}; box-sizing: border-box; width: ${({ width }) => width}; + ${({ fluid }) => fluid && `max-width: 100%; min-width: 0;`} ${({ isDefault }) => isDefault ? `height: ${spacing.r32}` : `height: ${spacing.r24}`}; @@ -122,7 +123,7 @@ const SelectStyle = styled(Select)` } .sc-select__menu { - width: ${({ width }) => width}; + width: ${({ fluid, width }) => (fluid ? '100%' : width)}; border: ${spacing.r1} solid ${({ isDefault }) => getThemePropSelector(isDefault ? 'border' : 'selectedActive')}; diff --git a/src/lib/components/selectv2/Selectv2.component.tsx b/src/lib/components/selectv2/Selectv2.component.tsx index c7d1f5277e..cc3e7d6dcf 100644 --- a/src/lib/components/selectv2/Selectv2.component.tsx +++ b/src/lib/components/selectv2/Selectv2.component.tsx @@ -27,6 +27,7 @@ import { FixedSizeList, FixedSizeList as List } from 'react-window'; import { convertRemToPixels } from '../../utils'; import { spacing } from '../../spacing'; import { convertSizeToRem } from '../inputv2/inputv2'; +import { useFieldContext } from '../form/Form.component'; import { ConstrainedText } from '../constrainedtext/Constrainedtext.component'; import ReactSelect from 'react-select/src/Select'; @@ -348,6 +349,9 @@ export type SelectProps = { variant?: 'default' | 'rounded'; size?: '1' | '2/3' | '1/2' | '1/3'; className?: string; + /** When true (or inside a responsive Form), the control fills its container + * down to a min instead of staying fixed at its `size` width. */ + fluid?: boolean; /** use menuPositon='fixed' inside modal to avoid display issue */ menuPosition?: 'fixed' | 'absolute'; /** number of items visible before the option list becomes scrollable @@ -395,11 +399,14 @@ function SelectBox< size = '1', id, selectRef, + fluid, itemsPerScrollWindow = 4, ...rest }: SelectProps & { selectRef?: Ref>; }) { + const { responsive: responsiveFromFieldContext } = useFieldContext(); + const isFluid = !!(fluid || responsiveFromFieldContext); const [keyboardFocusEnabled, setKeyboardFocusEnabled] = useState(false); const [searchSelection, setSearchSelection] = useState(''); const [searchValue, setSearchValue] = useState(''); @@ -571,6 +578,7 @@ function SelectBox< } }} width={convertSizeToRem(size)} + fluid={isFluid} {...rest} /> )} @@ -599,11 +607,14 @@ const SelectWithOptionContext = forwardRef< }); }, []); - const contextValue = useMemo(() => ({ - options, - register, - unregister - }), [options, register, unregister]); + const contextValue = useMemo( + () => ({ + options, + register, + unregister, + }), + [options, register, unregister], + ); return (