diff --git a/apps/storybook/src/patterns/RepeatableRowsGuidance.stories.tsx b/apps/storybook/src/patterns/RepeatableRowsGuidance.stories.tsx index 11d93a830..c8207b627 100644 --- a/apps/storybook/src/patterns/RepeatableRowsGuidance.stories.tsx +++ b/apps/storybook/src/patterns/RepeatableRowsGuidance.stories.tsx @@ -781,8 +781,8 @@ function RepeatableRowsGuidancePage({ globalTheme }: { globalTheme: string }) { Each control is named by its row and its column, such as “Condition 2 Field”. - Stacked rows, in narrow panels: each field gets its own small label, a{' '} - FormFieldHeader with the muted variant. Column labels + Stacked rows, in narrow panels: each field gets its own label, the same{' '} + FormFieldLabel as cards and other form fields. Column labels can't reach a value that has dropped to its own line. @@ -796,9 +796,10 @@ function RepeatableRowsGuidancePage({ globalTheme }: { globalTheme: string }) { Build labels from the Field Anatomy parts. A standalone field is a{' '} FormField with a FormFieldLabel, or a{' '} FormFieldHeader when its label row carries a badge or actions. - Shared column labels are one muted FormFieldHeader per column, - outside any field, and each control names itself with that label through{' '} - aria-labelledby. + Shared column labels are one FormFieldLabel per column. Wrap + the column labels and the row list together in a FormField so + the first row sits the same distance below its label as any other field. Each control + names itself with its column label through aria-labelledby.
diff --git a/apps/storybook/src/patterns/repeatable-row-primitives.tsx b/apps/storybook/src/patterns/repeatable-row-primitives.tsx index acf19e6eb..c0febf8ae 100644 --- a/apps/storybook/src/patterns/repeatable-row-primitives.tsx +++ b/apps/storybook/src/patterns/repeatable-row-primitives.tsx @@ -33,7 +33,12 @@ import { type ValueMode, ValueModeIndicator, } from '@/components/ui/field-addons'; -import { FormField, FormFieldError, FormFieldHeader } from '@/components/ui/form-field'; +import { + FormField, + FormFieldError, + FormFieldHeader, + FormFieldLabel, +} from '@/components/ui/form-field'; import { Input } from '@/components/ui/input'; import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'; import { MultiSelect } from '@/components/ui/multi-select'; @@ -553,12 +558,12 @@ function ConditionColumnHeaders({ return (
- - - + Field + Operator + Value
{/* Keeps the labels over their columns: the delete button's width. */} -
); } @@ -831,16 +836,16 @@ export function ConditionRow({
- + Field {fieldControl} - + Operator {operatorControl} {valueControl && ( - + Value {valueControl} )} @@ -848,7 +853,7 @@ export function ConditionRow({ {/* A label-height spacer, then a control-height box, so the icon centres on the first line of controls rather than on the labels. */} -
@@ -1087,59 +1092,61 @@ function GroupBody({
)} - {columns && } - -
- {node.children.length === 0 ? ( - {nested ? 'No conditions in this group.' : emptyText} - ) : ( - node.children.map((child, position) => { - if (child.kind === 'group') { - nestedGroups += 1; + + {columns && } + +
+ {node.children.length === 0 ? ( + {nested ? 'No conditions in this group.' : emptyText} + ) : ( + node.children.map((child, position) => { + if (child.kind === 'group') { + nestedGroups += 1; + return ( + deleteChild(child.id, position)} + /> + ); + } return ( - deleteChild(child.id, position)} + onChange={(patch) => + onRootChange( + updateNode( + root, + child.id, + (current) => ({ ...current, ...patch }) as Condition + ) + ) + } + onDelete={() => deleteChild(child.id, position)} /> ); - } - return ( - - onRootChange( - updateNode( - root, - child.id, - (current) => ({ ...current, ...patch }) as Condition - ) - ) - } - onDelete={() => deleteChild(child.id, position)} - /> - ); - }) - )} -
-
+ }) + )} +
+
+
addNode(condition())}> @@ -1335,56 +1342,58 @@ export function KeyValueList({ } /> - {pairs.length > 0 && ( - // Visible column labels: the persistent labels for every row's Name and Value. -
- {reorderable &&
- )} -
- {pairs.map((pair, position) => ( -
- - {label} {position + 1} - - {reorderable && ( - 0 ? () => move(position, position - 1) : undefined} - onMoveDown={ - position < pairs.length - 1 ? () => move(position, position + 1) : undefined - } - /> - )} + + {pairs.length > 0 && ( + // Visible column labels: the persistent labels for every row's Name and Value. +
+ {reorderable &&
- ))} - {pairs.length === 0 && No {itemName}s.} -
+ )} +
+ {pairs.map((pair, position) => ( +
+ + {label} {position + 1} + + {reorderable && ( + 0 ? () => move(position, position - 1) : undefined} + onMoveDown={ + position < pairs.length - 1 ? () => move(position, position + 1) : undefined + } + /> + )} +
+ + +
+ { + focus.afterDelete(position); + setPairs((current) => current.filter((row) => row.id !== pair.id)); + }} + /> +
+ ))} + {pairs.length === 0 && No {itemName}s.} +
+
void }) { - + How to look up the record