Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions src/lib/components/form/Form.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -339,7 +338,20 @@ const FormGroup = ({
<Stack
direction={helpErrorPosition === 'right' ? 'horizontal' : 'vertical'}
gap={helpErrorPosition === 'right' ? 'r8' : 'r4'}
style={responsive ? { minWidth: 0 } : undefined}
style={
responsive
? {
minWidth: 0,
// Left-align rather than stretch, so a width-less control (a
// TextArea sized only by its `cols`) keeps its intrinsic size in
// the fluid `1fr` field column instead of growing to fill it.
// Input is unaffected — it caps itself via `max-width: 100%`.
...(helpErrorPosition === 'right'
? {}
: { alignItems: 'flex-start' }),
Comment thread
JeanMarcMilletScality marked this conversation as resolved.
}
: undefined
}
>
{content}
{error ? (
Expand Down Expand Up @@ -379,9 +391,11 @@ type FormSectionProps = {
children: ReactElement<FormGroupProps> | ReactElement<FormGroupProps>[];
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;
Expand All @@ -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 (
<FormSectionContext.Provider value={{ labelTrack, fixedLabel }}>
Expand Down
3 changes: 2 additions & 1 deletion src/lib/components/selectv2/SelectStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`};

Expand Down Expand Up @@ -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')};
Expand Down
21 changes: 16 additions & 5 deletions src/lib/components/selectv2/Selectv2.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -395,11 +399,14 @@ function SelectBox<
size = '1',
id,
selectRef,
fluid,
itemsPerScrollWindow = 4,
...rest
}: SelectProps & {
selectRef?: Ref<SelectRef<OptionType, IsMulti, GroupType>>;
}) {
const { responsive: responsiveFromFieldContext } = useFieldContext();
const isFluid = !!(fluid || responsiveFromFieldContext);
const [keyboardFocusEnabled, setKeyboardFocusEnabled] = useState(false);
const [searchSelection, setSearchSelection] = useState('');
const [searchValue, setSearchValue] = useState('');
Expand Down Expand Up @@ -571,6 +578,7 @@ function SelectBox<
}
}}
width={convertSizeToRem(size)}
fluid={isFluid}
{...rest}
/>
)}
Expand Down Expand Up @@ -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 (
<OptionContext.Provider value={contextValue}>
Expand Down
Loading