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 = (