From 091f30a8304bab7d5f5d94d5a658d57aad1c706b Mon Sep 17 00:00:00 2001 From: jbpenrath Date: Wed, 5 Aug 2026 18:51:29 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8(ui-components)=20add=20an=20inlin?= =?UTF-8?q?e=20variant=20to=20form=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Input, TextArea and Select now accept `variant="inline"` and `labelDescription`, associated with the control through `aria-describedby`. The label column hugs its content so it never wraps mid-label; `labelWidth` pins it when several fields of a form must share a left edge. --- .changeset/inline-field-variant.md | 15 ++ .../components/form/input/input.stories.tsx | 19 +++ .../components/forms/checkbox/index.spec.tsx | 1 + .../src/components/forms/checkbox/index.tsx | 1 + .../components/forms/classic-label/index.tsx | 57 +++++++- .../forms/date-picker/DateField.tsx | 4 +- .../forms/date-picker/DatePickerAux.tsx | 4 +- .../src/components/forms/field/_index.scss | 78 +++++++++++ .../src/components/forms/field/index.mdx | 9 ++ .../src/components/forms/field/index.tsx | 17 ++- .../src/components/forms/field/tokens.ts | 15 ++ .../src/components/forms/input/_index.scss | 29 ++++ .../src/components/forms/input/index.spec.tsx | 128 ++++++++++++++++++ .../components/forms/input/index.stories.tsx | 104 +++++++++++++- .../src/components/forms/input/index.tsx | 36 ++++- .../src/components/forms/input/tokens.ts | 10 ++ .../components/forms/labelled-box/index.tsx | 4 +- .../src/components/forms/radio/index.spec.tsx | 1 + .../src/components/forms/radio/index.tsx | 1 + .../src/components/forms/select/_index.scss | 4 +- .../src/components/forms/select/index.tsx | 5 + .../components/forms/select/mono-common.tsx | 58 ++++++-- .../forms/select/mono-searchable.tsx | 5 + .../src/components/forms/select/mono.spec.tsx | 79 ++++++++++- .../components/forms/select/mono.stories.tsx | 51 ++++++- .../components/forms/select/multi-common.tsx | 57 ++++++-- .../forms/select/multi-searchable.tsx | 5 + .../components/forms/select/multi.spec.tsx | 46 +++++++ .../components/forms/select/multi.stories.tsx | 24 +++- .../components/forms/switch/index.spec.tsx | 1 + .../src/components/forms/switch/index.tsx | 1 + .../src/components/forms/text-area/index.scss | 23 +++- .../components/forms/text-area/index.spec.tsx | 41 ++++++ .../forms/text-area/index.stories.tsx | 29 +++- .../src/components/forms/text-area/index.tsx | 46 ++++++- .../src/components/forms/types.ts | 10 +- .../src/cunningham-tokens-sass.scss | 114 ++++++++++++++-- .../ui-components/src/cunningham-tokens.css | 90 ++++++++++++ .../ui-components/src/cunningham-tokens.scss | 114 ++++++++++++++-- .../ui-components/src/cunningham-tokens.ts | 2 +- packages/ui-components/src/cunningham.ts | 5 +- packages/ui-components/src/index.ts | 1 + 42 files changed, 1255 insertions(+), 89 deletions(-) create mode 100644 .changeset/inline-field-variant.md diff --git a/.changeset/inline-field-variant.md b/.changeset/inline-field-variant.md new file mode 100644 index 00000000..ffe14de3 --- /dev/null +++ b/.changeset/inline-field-variant.md @@ -0,0 +1,15 @@ +--- +"@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. 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 = (