diff --git a/.changeset/inline-field-variant.md b/.changeset/inline-field-variant.md new file mode 100644 index 00000000..2923ef28 --- /dev/null +++ b/.changeset/inline-field-variant.md @@ -0,0 +1,20 @@ +--- +"@gouvfr-lasuite/ui-components": minor +--- + +Add an `inline` field variant to Input, TextArea and Select + +The label — and its new optional `labelDescription` — sits in a left column while the +field takes the right one, on a single row. The label column hugs its content by default; +use the new `labelWidth` prop to pin it and align several fields of the same form. +`labelDescription` is associated with the control through `aria-describedby`. + +As a prerequisite, the `Select` label is now rendered as a sibling of `.c__select` instead +of one of its children, so that every field component exposes the same `.c__field` +structure. Styling that targets `.c__select__label` is unaffected; only stylesheets +relying on the descendant selector `.c__select .c__select__label` need updating. + +Disabled labels also change colour across **every** variant: the +`forms-labelledbox--label-color--{small,big}--disabled` tokens resolved to +`content.semantic.neutral.secondary`, a regular text colour, and now resolve to +`content.semantic.disabled.primary` as the design requires. diff --git a/packages/ui-components/src/components/form/input/input.stories.tsx b/packages/ui-components/src/components/form/input/input.stories.tsx index 8a9da138..5abf5274 100644 --- a/packages/ui-components/src/components/form/input/input.stories.tsx +++ b/packages/ui-components/src/components/form/input/input.stories.tsx @@ -85,6 +85,25 @@ export const IconRight = { }, }; +export const Inline = { + args: { + label: "Label", + labelDescription: "Text info", + variant: "inline", + placeholder: "Text input", + }, +}; + +export const InlineDisabled = { + args: { + label: "Label", + labelDescription: "Text info", + variant: "inline", + placeholder: "Text input", + disabled: true, + }, +}; + export const FullWidth = { args: { defaultValue: "Hello world", diff --git a/packages/ui-components/src/components/forms/checkbox/index.spec.tsx b/packages/ui-components/src/components/forms/checkbox/index.spec.tsx index 2183e89a..93f5d103 100644 --- a/packages/ui-components/src/components/forms/checkbox/index.spec.tsx +++ b/packages/ui-components/src/components/forms/checkbox/index.spec.tsx @@ -131,6 +131,7 @@ describe("", () => { rightText: "my right text", indeterminate: true, disabled: false, + labelWidth: "10rem", }; render(); diff --git a/packages/ui-components/src/components/forms/checkbox/index.tsx b/packages/ui-components/src/components/forms/checkbox/index.tsx index 73958f79..f8801667 100644 --- a/packages/ui-components/src/components/forms/checkbox/index.tsx +++ b/packages/ui-components/src/components/forms/checkbox/index.tsx @@ -44,6 +44,7 @@ export const Checkbox = ({ const { compact, fullWidth, + labelWidth, rightText, state, text, diff --git a/packages/ui-components/src/components/forms/classic-label/index.tsx b/packages/ui-components/src/components/forms/classic-label/index.tsx index 0c8bb3eb..a448090b 100644 --- a/packages/ui-components/src/components/forms/classic-label/index.tsx +++ b/packages/ui-components/src/components/forms/classic-label/index.tsx @@ -3,6 +3,16 @@ import classNames from "classnames"; export interface ClassicLabelProps { label?: string; + /** Secondary text rendered under the label. */ + description?: string; + /** Id given to the description, so the control can reference it via aria-describedby. */ + descriptionId?: string; + /** + * Wraps the label (and its description) in a `.c__field__label-block` container so the + * pair behaves as a single item. Required by the "inline" variant, whose grid places + * the whole block in the label column. + */ + withContainer?: boolean; hideLabel?: boolean; disabled?: boolean; className?: string; @@ -13,13 +23,19 @@ export interface ClassicLabelProps { } /** - * Renders a label for the "classic" field variant. + * Renders a label for the "classic" and "inline" field variants. * - When hideLabel is false: renders a visible label with the given className. * - When hideLabel is true: renders an offscreen label for accessibility. * - When label is falsy: renders nothing. + * + * The label block is only introduced when a description or a container is asked for, so + * variants that need neither keep their historical markup. */ export const ClassicLabel = ({ label, + description, + descriptionId, + withContainer, hideLabel, disabled, className, @@ -34,13 +50,20 @@ export const ClassicLabel = ({ if (hideLabel) { return ( - + <> + + {description && ( + + {description} + + )} + ); } - return ( + const labelElement = (