@lutra-ui-system/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/components/button/button.d.ts +105 -0
- package/dist/components/button/button.d.ts.map +1 -0
- package/dist/components/button/button.js +91 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/button/index.d.ts +3 -0
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +2 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/card/card.d.ts +100 -0
- package/dist/components/card/card.d.ts.map +1 -0
- package/dist/components/card/card.js +51 -0
- package/dist/components/card/card.js.map +1 -0
- package/dist/components/card/index.d.ts +3 -0
- package/dist/components/card/index.d.ts.map +1 -0
- package/dist/components/card/index.js +2 -0
- package/dist/components/card/index.js.map +1 -0
- package/dist/components/checkbox/checkbox.d.ts +67 -0
- package/dist/components/checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox/checkbox.js +63 -0
- package/dist/components/checkbox/checkbox.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +3 -0
- package/dist/components/checkbox/index.d.ts.map +1 -0
- package/dist/components/checkbox/index.js +2 -0
- package/dist/components/checkbox/index.js.map +1 -0
- package/dist/components/dialog/dialog.d.ts +88 -0
- package/dist/components/dialog/dialog.d.ts.map +1 -0
- package/dist/components/dialog/dialog.js +156 -0
- package/dist/components/dialog/dialog.js.map +1 -0
- package/dist/components/dialog/index.d.ts +3 -0
- package/dist/components/dialog/index.d.ts.map +1 -0
- package/dist/components/dialog/index.js +2 -0
- package/dist/components/dialog/index.js.map +1 -0
- package/dist/components/dropdown/dropdown.d.ts +89 -0
- package/dist/components/dropdown/dropdown.d.ts.map +1 -0
- package/dist/components/dropdown/dropdown.js +173 -0
- package/dist/components/dropdown/dropdown.js.map +1 -0
- package/dist/components/dropdown/index.d.ts +3 -0
- package/dist/components/dropdown/index.d.ts.map +1 -0
- package/dist/components/dropdown/index.js +2 -0
- package/dist/components/dropdown/index.js.map +1 -0
- package/dist/components/error-summary/error-summary.d.ts +81 -0
- package/dist/components/error-summary/error-summary.d.ts.map +1 -0
- package/dist/components/error-summary/error-summary.js +85 -0
- package/dist/components/error-summary/error-summary.js.map +1 -0
- package/dist/components/error-summary/index.d.ts +3 -0
- package/dist/components/error-summary/index.d.ts.map +1 -0
- package/dist/components/error-summary/index.js +2 -0
- package/dist/components/error-summary/index.js.map +1 -0
- package/dist/components/field/field.d.ts +55 -0
- package/dist/components/field/field.d.ts.map +1 -0
- package/dist/components/field/field.js +26 -0
- package/dist/components/field/field.js.map +1 -0
- package/dist/components/field/index.d.ts +3 -0
- package/dist/components/field/index.d.ts.map +1 -0
- package/dist/components/field/index.js +2 -0
- package/dist/components/field/index.js.map +1 -0
- package/dist/components/field-group/field-group.d.ts +67 -0
- package/dist/components/field-group/field-group.d.ts.map +1 -0
- package/dist/components/field-group/field-group.js +38 -0
- package/dist/components/field-group/field-group.js.map +1 -0
- package/dist/components/field-group/index.d.ts +3 -0
- package/dist/components/field-group/index.d.ts.map +1 -0
- package/dist/components/field-group/index.js +2 -0
- package/dist/components/field-group/index.js.map +1 -0
- package/dist/components/input/index.d.ts +3 -0
- package/dist/components/input/index.d.ts.map +1 -0
- package/dist/components/input/index.js +2 -0
- package/dist/components/input/index.js.map +1 -0
- package/dist/components/input/input.d.ts +96 -0
- package/dist/components/input/input.d.ts.map +1 -0
- package/dist/components/input/input.js +26 -0
- package/dist/components/input/input.js.map +1 -0
- package/dist/components/link/index.d.ts +3 -0
- package/dist/components/link/index.d.ts.map +1 -0
- package/dist/components/link/index.js +2 -0
- package/dist/components/link/index.js.map +1 -0
- package/dist/components/link/link.d.ts +44 -0
- package/dist/components/link/link.d.ts.map +1 -0
- package/dist/components/link/link.js +26 -0
- package/dist/components/link/link.js.map +1 -0
- package/dist/components/link-card/index.d.ts +3 -0
- package/dist/components/link-card/index.d.ts.map +1 -0
- package/dist/components/link-card/index.js +2 -0
- package/dist/components/link-card/index.js.map +1 -0
- package/dist/components/link-card/link-card.d.ts +62 -0
- package/dist/components/link-card/link-card.d.ts.map +1 -0
- package/dist/components/link-card/link-card.js +43 -0
- package/dist/components/link-card/link-card.js.map +1 -0
- package/dist/components/mobile-navigation/index.d.ts +3 -0
- package/dist/components/mobile-navigation/index.d.ts.map +1 -0
- package/dist/components/mobile-navigation/index.js +2 -0
- package/dist/components/mobile-navigation/index.js.map +1 -0
- package/dist/components/mobile-navigation/mobile-navigation.d.ts +88 -0
- package/dist/components/mobile-navigation/mobile-navigation.d.ts.map +1 -0
- package/dist/components/mobile-navigation/mobile-navigation.js +153 -0
- package/dist/components/mobile-navigation/mobile-navigation.js.map +1 -0
- package/dist/components/navigation-item/index.d.ts +3 -0
- package/dist/components/navigation-item/index.d.ts.map +1 -0
- package/dist/components/navigation-item/index.js +2 -0
- package/dist/components/navigation-item/index.js.map +1 -0
- package/dist/components/navigation-item/navigation-item.d.ts +38 -0
- package/dist/components/navigation-item/navigation-item.d.ts.map +1 -0
- package/dist/components/navigation-item/navigation-item.js +22 -0
- package/dist/components/navigation-item/navigation-item.js.map +1 -0
- package/dist/components/radio/index.d.ts +3 -0
- package/dist/components/radio/index.d.ts.map +1 -0
- package/dist/components/radio/index.js +2 -0
- package/dist/components/radio/index.js.map +1 -0
- package/dist/components/radio/radio.d.ts +58 -0
- package/dist/components/radio/radio.d.ts.map +1 -0
- package/dist/components/radio/radio.js +37 -0
- package/dist/components/radio/radio.js.map +1 -0
- package/dist/components/select/index.d.ts +3 -0
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +2 -0
- package/dist/components/select/index.js.map +1 -0
- package/dist/components/select/select.d.ts +67 -0
- package/dist/components/select/select.d.ts.map +1 -0
- package/dist/components/select/select.js +25 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/select-field/index.d.ts +3 -0
- package/dist/components/select-field/index.d.ts.map +1 -0
- package/dist/components/select-field/index.js +2 -0
- package/dist/components/select-field/index.js.map +1 -0
- package/dist/components/select-field/select-field.d.ts +43 -0
- package/dist/components/select-field/select-field.d.ts.map +1 -0
- package/dist/components/select-field/select-field.js +24 -0
- package/dist/components/select-field/select-field.js.map +1 -0
- package/dist/components/table/index.d.ts +3 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +2 -0
- package/dist/components/table/index.js.map +1 -0
- package/dist/components/table/table.d.ts +251 -0
- package/dist/components/table/table.d.ts.map +1 -0
- package/dist/components/table/table.js +209 -0
- package/dist/components/table/table.js.map +1 -0
- package/dist/components/toggle/index.d.ts +3 -0
- package/dist/components/toggle/index.d.ts.map +1 -0
- package/dist/components/toggle/index.js +2 -0
- package/dist/components/toggle/index.js.map +1 -0
- package/dist/components/toggle/toggle.d.ts +66 -0
- package/dist/components/toggle/toggle.d.ts.map +1 -0
- package/dist/components/toggle/toggle.js +49 -0
- package/dist/components/toggle/toggle.js.map +1 -0
- package/dist/components/tooltip/index.d.ts +3 -0
- package/dist/components/tooltip/index.d.ts.map +1 -0
- package/dist/components/tooltip/index.js +2 -0
- package/dist/components/tooltip/index.js.map +1 -0
- package/dist/components/tooltip/tooltip.d.ts +74 -0
- package/dist/components/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip/tooltip.js +180 -0
- package/dist/components/tooltip/tooltip.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/development.d.ts +2 -0
- package/dist/internal/development.d.ts.map +1 -0
- package/dist/internal/development.js +2 -0
- package/dist/internal/development.js.map +1 -0
- package/dist/internal/field-support.d.ts +68 -0
- package/dist/internal/field-support.d.ts.map +1 -0
- package/dist/internal/field-support.js +42 -0
- package/dist/internal/field-support.js.map +1 -0
- package/dist/internal/isomorphic-layout-effect.d.ts +26 -0
- package/dist/internal/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/internal/isomorphic-layout-effect.js +26 -0
- package/dist/internal/isomorphic-layout-effect.js.map +1 -0
- package/dist/styles.css +3474 -0
- package/dist/tokens.json +995 -0
- package/package.json +76 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
3
|
+
import { Button } from '../button/index.js';
|
|
4
|
+
/** Focusable things inside the panel, in DOM order. Disabled buttons are skipped by the browser. */
|
|
5
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
6
|
+
/**
|
|
7
|
+
* A disclosure that reveals a panel of actions or links from a button.
|
|
8
|
+
*
|
|
9
|
+
* **It is a disclosure, not an ARIA menu, and that is deliberate.** The accessibility annotation is
|
|
10
|
+
* explicit: the trigger is a button, and the panel is a container of buttons or links depending on
|
|
11
|
+
* what each item does. No `role="menu"`, no `role="menuitem"`, and no arrow-key roving.
|
|
12
|
+
*
|
|
13
|
+
* The reason is that the two models cannot be mixed. A real ARIA menu promises arrow-key navigation
|
|
14
|
+
* *and* takes Tab away from the items inside it; a half-built one promises navigation that does not
|
|
15
|
+
* work, which is worse than no role at all. As a disclosure, everything inside keeps its native
|
|
16
|
+
* semantics and Tab keeps working, which is what the annotation asks for.
|
|
17
|
+
*
|
|
18
|
+
* Behaviour:
|
|
19
|
+
*
|
|
20
|
+
* - Enter or Space opens, because the trigger is a real button.
|
|
21
|
+
* - `aria-expanded` reflects the state and `aria-controls` points at the panel.
|
|
22
|
+
* - Focus moves to the first item on open.
|
|
23
|
+
* - Tab moves through the items in DOM order and out the other side.
|
|
24
|
+
* - Escape closes and returns focus to the trigger.
|
|
25
|
+
* - A pointer press outside closes it, without interfering with what was pressed.
|
|
26
|
+
* - Focus leaving the component closes it, so a Tab out does not strand an open panel.
|
|
27
|
+
*
|
|
28
|
+
* Open state is uncontrolled by default and controllable with `open` + `onOpenChange`, matching the
|
|
29
|
+
* pattern the form controls already use. There is no state machine beyond that.
|
|
30
|
+
*/
|
|
31
|
+
export function Dropdown({ label, icon, children, open: controlledOpen, defaultOpen = false, onOpenChange, triggerVariant = 'secondary', triggerSize = 'md', className, }) {
|
|
32
|
+
const panelId = `lutra-dropdown-${useId()}`;
|
|
33
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
34
|
+
const isControlled = controlledOpen !== undefined;
|
|
35
|
+
const open = isControlled ? controlledOpen : uncontrolledOpen;
|
|
36
|
+
const wrapperRef = useRef(null);
|
|
37
|
+
const triggerRef = useRef(null);
|
|
38
|
+
const panelRef = useRef(null);
|
|
39
|
+
// Set when the component itself closes, so focus only returns to the trigger on those paths and
|
|
40
|
+
// not when a consumer closes it for their own reasons.
|
|
41
|
+
const returnFocus = useRef(false);
|
|
42
|
+
const setOpen = useCallback((next) => {
|
|
43
|
+
if (!isControlled)
|
|
44
|
+
setUncontrolledOpen(next);
|
|
45
|
+
onOpenChange?.(next);
|
|
46
|
+
}, [isControlled, onOpenChange]);
|
|
47
|
+
const close = useCallback(({ focusTrigger }) => {
|
|
48
|
+
returnFocus.current = focusTrigger;
|
|
49
|
+
setOpen(false);
|
|
50
|
+
}, [setOpen]);
|
|
51
|
+
// The trigger toggles, and one press must produce exactly one notification. It sets the
|
|
52
|
+
// focus-return flag directly rather than going through `close` first: closing and then opening
|
|
53
|
+
// would report `false` before `true` on the way open, and `false` twice on the way closed, which
|
|
54
|
+
// is a state change a consumer never asked for.
|
|
55
|
+
const toggle = useCallback(() => {
|
|
56
|
+
returnFocus.current = false;
|
|
57
|
+
setOpen(!open);
|
|
58
|
+
}, [open, setOpen]);
|
|
59
|
+
// Focus the first item on open; return focus to the trigger when the component closed itself.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (open) {
|
|
62
|
+
panelRef.current?.querySelector(FOCUSABLE)?.focus();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (returnFocus.current) {
|
|
66
|
+
returnFocus.current = false;
|
|
67
|
+
triggerRef.current?.focus();
|
|
68
|
+
}
|
|
69
|
+
}, [open]);
|
|
70
|
+
// A pointer press outside closes the panel. `pointerdown` rather than `click`, and nothing is
|
|
71
|
+
// prevented or stopped, so whatever was pressed still receives its own event normally.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!open)
|
|
74
|
+
return;
|
|
75
|
+
const onPointerDown = (event) => {
|
|
76
|
+
const target = event.target;
|
|
77
|
+
if (target instanceof Node && wrapperRef.current?.contains(target))
|
|
78
|
+
return;
|
|
79
|
+
close({ focusTrigger: false });
|
|
80
|
+
};
|
|
81
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener('pointerdown', onPointerDown);
|
|
84
|
+
};
|
|
85
|
+
}, [open, close]);
|
|
86
|
+
// Escape and focus-loss are delegated with native listeners on the wrapper rather than JSX
|
|
87
|
+
// handlers. The wrapper is a positioning container, not something interactive, and hanging key
|
|
88
|
+
// handlers off a plain div is exactly what `jsx-a11y/no-static-element-interactions` objects to —
|
|
89
|
+
// rightly, because it usually means a div is pretending to be a control. Here the real handling
|
|
90
|
+
// belongs to the button and the items that bubble their events up, so delegation is the honest
|
|
91
|
+
// shape. `focusout` is used because, unlike `blur`, it bubbles.
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const wrapper = wrapperRef.current;
|
|
94
|
+
if (!open || !wrapper)
|
|
95
|
+
return;
|
|
96
|
+
const onKeyDown = (event) => {
|
|
97
|
+
if (event.key !== 'Escape')
|
|
98
|
+
return;
|
|
99
|
+
// One press, one layer, through two different mechanisms.
|
|
100
|
+
//
|
|
101
|
+
// `preventDefault()` is for a native modal `<dialog>` above this panel: Escape reaching one is
|
|
102
|
+
// a *close request*, and the close-request algorithm only runs the close watcher when the
|
|
103
|
+
// keydown was not cancelled. Cancelling it says this press has been spent.
|
|
104
|
+
//
|
|
105
|
+
// `stopPropagation()` is for JavaScript layers that listen for the key themselves, which would
|
|
106
|
+
// otherwise act on the same press.
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
close({ focusTrigger: true });
|
|
110
|
+
};
|
|
111
|
+
// Checked against the element receiving focus rather than a timer, so it is not fooled by focus
|
|
112
|
+
// moving between the panel's own items.
|
|
113
|
+
const onFocusOut = (event) => {
|
|
114
|
+
const next = event.relatedTarget;
|
|
115
|
+
if (next instanceof Node && wrapper.contains(next))
|
|
116
|
+
return;
|
|
117
|
+
close({ focusTrigger: false });
|
|
118
|
+
};
|
|
119
|
+
wrapper.addEventListener('keydown', onKeyDown);
|
|
120
|
+
wrapper.addEventListener('focusout', onFocusOut);
|
|
121
|
+
return () => {
|
|
122
|
+
wrapper.removeEventListener('keydown', onKeyDown);
|
|
123
|
+
wrapper.removeEventListener('focusout', onFocusOut);
|
|
124
|
+
};
|
|
125
|
+
}, [open, close]);
|
|
126
|
+
return (_jsxs("div", { ref: wrapperRef, className: className === undefined ? 'lutra-dropdown' : `lutra-dropdown ${className}`, children: [_jsx(Button, { ref: triggerRef, variant: triggerVariant, size: triggerSize, "aria-expanded": open, "aria-controls": panelId, onClick: toggle, ...(icon
|
|
127
|
+
? { contentType: 'icon-only', 'aria-label': label, children: icon }
|
|
128
|
+
: { children: label }) }), open && (_jsx("div", { ref: panelRef, id: panelId, className: "lutra-dropdown__panel", children: children }))] }));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* One item in a Dropdown panel.
|
|
132
|
+
*
|
|
133
|
+
* Renders a `<button>`, or an `<a>` when `href` is given. That is the whole of the semantic
|
|
134
|
+
* decision, and it follows the annotation: items that navigate are anchors, items that act are
|
|
135
|
+
* buttons. Neither gets a menu role.
|
|
136
|
+
*
|
|
137
|
+
* `disabled` uses whichever real mechanism the element has — the `disabled` attribute on a button,
|
|
138
|
+
* `aria-disabled` plus a suppressed activation on a link, since an anchor has no disabled attribute
|
|
139
|
+
* and one that merely looks unavailable but still navigates is a trap.
|
|
140
|
+
*
|
|
141
|
+
* `tone="danger"` is a visual cue only. A destructive action still needs its own confirmation step;
|
|
142
|
+
* colour is not a safeguard.
|
|
143
|
+
*/
|
|
144
|
+
export const DropdownItem = forwardRef(function DropdownItem({ href, tone = 'default', icon, children, disabled = false, className, ...rest }, ref) {
|
|
145
|
+
const shared = {
|
|
146
|
+
className: className === undefined ? 'lutra-dropdown__item' : `lutra-dropdown__item ${className}`,
|
|
147
|
+
'data-tone': tone,
|
|
148
|
+
};
|
|
149
|
+
// Rendered as real JSX children rather than passed through the shared spread, so the element
|
|
150
|
+
// visibly has content — both to a reader and to `jsx-a11y/anchor-has-content`.
|
|
151
|
+
const body = (_jsxs(_Fragment, { children: [icon !== undefined && icon !== null && (_jsx("span", { className: "lutra-dropdown__item-icon", "aria-hidden": "true", children: icon })), _jsx("span", { className: "lutra-dropdown__item-label", children: children })] }));
|
|
152
|
+
if (href !== undefined) {
|
|
153
|
+
const anchorProps = rest;
|
|
154
|
+
return (_jsx("a", { ...anchorProps, ...shared, ref: ref, href: href, "aria-disabled": disabled || undefined, onClick: (event) => {
|
|
155
|
+
// An anchor has no disabled attribute, so activation is suppressed by hand. Without this
|
|
156
|
+
// an item that looks unavailable would still navigate.
|
|
157
|
+
if (disabled) {
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
anchorProps.onClick?.(event);
|
|
162
|
+
}, children: body }));
|
|
163
|
+
}
|
|
164
|
+
const buttonProps = rest;
|
|
165
|
+
return (_jsx("button", { ...buttonProps, ...shared, ref: ref, type: "button", disabled: disabled, children: body }));
|
|
166
|
+
});
|
|
167
|
+
/** A visual divider between groups of items. Presentational only. */
|
|
168
|
+
export function DropdownSeparator({ className }) {
|
|
169
|
+
return (_jsx("hr", { className: className === undefined
|
|
170
|
+
? 'lutra-dropdown__separator'
|
|
171
|
+
: `lutra-dropdown__separator ${className}` }));
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=dropdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown.js","sourceRoot":"","sources":["../../../src/components/dropdown/dropdown.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/dropdown/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/dropdown/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
export interface ErrorSummaryItem {
|
|
3
|
+
/**
|
|
4
|
+
* The `id` of the control this message belongs to. The entry links to it, and activating the entry
|
|
5
|
+
* moves focus there.
|
|
6
|
+
*
|
|
7
|
+
* The control needs an explicit `id` for this to work — pass the same one to `Field`, `Select
|
|
8
|
+
* Field` or `FieldGroup`.
|
|
9
|
+
*/
|
|
10
|
+
fieldId: string;
|
|
11
|
+
/**
|
|
12
|
+
* The message, **word for word the same as the field's own inline error**. Two wordings for one
|
|
13
|
+
* problem reads as two problems.
|
|
14
|
+
*/
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ErrorSummaryProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
18
|
+
/** One entry per invalid field, in the order the fields appear in the form. */
|
|
19
|
+
errors: ErrorSummaryItem[];
|
|
20
|
+
/** @default 'There is a problem' */
|
|
21
|
+
title?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Whether to take focus when the summary appears.
|
|
24
|
+
*
|
|
25
|
+
* On by default, because that is the whole point of the component: a summary nobody is sent to is
|
|
26
|
+
* a summary nobody reads. Turn it off only if you are managing focus yourself.
|
|
27
|
+
*
|
|
28
|
+
* Named `focusOnMount` rather than `autoFocus` on purpose. The native `autoFocus` attribute is a
|
|
29
|
+
* different mechanism with a deservedly bad reputation — it fires on page load, before anyone has
|
|
30
|
+
* asked for anything. This fires when a submit has already failed, which is the opposite
|
|
31
|
+
* situation, and the distinct name keeps the two from being read as the same thing.
|
|
32
|
+
*
|
|
33
|
+
* @default true
|
|
34
|
+
*/
|
|
35
|
+
focusOnMount?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Replaces `document.title` while the summary is mounted, restoring the previous title when it
|
|
38
|
+
* unmounts.
|
|
39
|
+
*
|
|
40
|
+
* **Opt-in**, and the contract asks for it: someone who navigated away and came back learns from
|
|
41
|
+
* the title alone that the submit failed. It is not automatic because a library silently rewriting
|
|
42
|
+
* the page title would be a surprise, and because the wording belongs to the application —
|
|
43
|
+
* `"Error: Apply for support"` reads better than anything this component could compose.
|
|
44
|
+
*/
|
|
45
|
+
documentTitle?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The list of everything that went wrong, shown above the form after a failed submit — and only
|
|
49
|
+
* then.
|
|
50
|
+
*
|
|
51
|
+
* ## What it is for
|
|
52
|
+
*
|
|
53
|
+
* A long form that fails validation leaves someone with no idea how many problems there are or where
|
|
54
|
+
* they live. The summary answers both at the top of the page, and every entry is a link to the field
|
|
55
|
+
* it names, so the answer comes with a way to act on it.
|
|
56
|
+
*
|
|
57
|
+
* **It never replaces inline errors.** The field keeps its own message, its `aria-invalid` and its
|
|
58
|
+
* `aria-describedby`; the summary is a second, earlier route to the same information. And the entry
|
|
59
|
+
* text must match the inline message word for word — two wordings for one problem reads as two
|
|
60
|
+
* problems.
|
|
61
|
+
*
|
|
62
|
+
* ## Focus
|
|
63
|
+
*
|
|
64
|
+
* The container is focusable **programmatically only** — `tabIndex={-1}`, so it never joins the tab
|
|
65
|
+
* order — and takes focus when it appears. That is what announces the failure immediately rather
|
|
66
|
+
* than leaving it to be discovered.
|
|
67
|
+
*
|
|
68
|
+
* It focuses on mount. A second failed submit while the summary is already on screen will not
|
|
69
|
+
* re-announce by itself: give it a `key` that changes per submission, or call `.focus()` on the ref.
|
|
70
|
+
* Both are shown in the stories.
|
|
71
|
+
*
|
|
72
|
+
* There is no `role="alert"` and no live region. Focus moving into the container is what announces
|
|
73
|
+
* it; a live region as well would say the same thing twice, and out of order.
|
|
74
|
+
*
|
|
75
|
+
* ## What it is not
|
|
76
|
+
*
|
|
77
|
+
* Not a form framework. It takes a list of errors you already have and renders it — validation, when
|
|
78
|
+
* to run it, and what the messages say are all yours.
|
|
79
|
+
*/
|
|
80
|
+
export declare const ErrorSummary: import("react").ForwardRefExoticComponent<ErrorSummaryProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
81
|
+
//# sourceMappingURL=error-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-summary.d.ts","sourceRoot":"","sources":["../../../src/components/error-summary/error-summary.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,OAAO,CAAC;AAIxD,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACzF,+EAA+E;IAC/E,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,YAAY,8GAoFvB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useRef } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect } from '../../internal/isomorphic-layout-effect.js';
|
|
4
|
+
function AlertCircleIcon() {
|
|
5
|
+
return (_jsxs("svg", { className: "lutra-error-summary__icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("path", { d: "M12 7v6", strokeLinecap: "round" }), _jsx("path", { d: "M12 16.5h.01", strokeLinecap: "round" })] }));
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The list of everything that went wrong, shown above the form after a failed submit — and only
|
|
9
|
+
* then.
|
|
10
|
+
*
|
|
11
|
+
* ## What it is for
|
|
12
|
+
*
|
|
13
|
+
* A long form that fails validation leaves someone with no idea how many problems there are or where
|
|
14
|
+
* they live. The summary answers both at the top of the page, and every entry is a link to the field
|
|
15
|
+
* it names, so the answer comes with a way to act on it.
|
|
16
|
+
*
|
|
17
|
+
* **It never replaces inline errors.** The field keeps its own message, its `aria-invalid` and its
|
|
18
|
+
* `aria-describedby`; the summary is a second, earlier route to the same information. And the entry
|
|
19
|
+
* text must match the inline message word for word — two wordings for one problem reads as two
|
|
20
|
+
* problems.
|
|
21
|
+
*
|
|
22
|
+
* ## Focus
|
|
23
|
+
*
|
|
24
|
+
* The container is focusable **programmatically only** — `tabIndex={-1}`, so it never joins the tab
|
|
25
|
+
* order — and takes focus when it appears. That is what announces the failure immediately rather
|
|
26
|
+
* than leaving it to be discovered.
|
|
27
|
+
*
|
|
28
|
+
* It focuses on mount. A second failed submit while the summary is already on screen will not
|
|
29
|
+
* re-announce by itself: give it a `key` that changes per submission, or call `.focus()` on the ref.
|
|
30
|
+
* Both are shown in the stories.
|
|
31
|
+
*
|
|
32
|
+
* There is no `role="alert"` and no live region. Focus moving into the container is what announces
|
|
33
|
+
* it; a live region as well would say the same thing twice, and out of order.
|
|
34
|
+
*
|
|
35
|
+
* ## What it is not
|
|
36
|
+
*
|
|
37
|
+
* Not a form framework. It takes a list of errors you already have and renders it — validation, when
|
|
38
|
+
* to run it, and what the messages say are all yours.
|
|
39
|
+
*/
|
|
40
|
+
export const ErrorSummary = forwardRef(function ErrorSummary({ errors, title = 'There is a problem', focusOnMount = true, documentTitle, className, ...nativeProps }, forwardedRef) {
|
|
41
|
+
const containerRef = useRef(null);
|
|
42
|
+
const setRef = (element) => {
|
|
43
|
+
containerRef.current = element;
|
|
44
|
+
if (typeof forwardedRef === 'function')
|
|
45
|
+
forwardedRef(element);
|
|
46
|
+
else if (forwardedRef)
|
|
47
|
+
forwardedRef.current = element;
|
|
48
|
+
};
|
|
49
|
+
// Before paint, so the summary is focused in the same frame it appears rather than a frame later,
|
|
50
|
+
// which a screen reader can otherwise miss.
|
|
51
|
+
useIsomorphicLayoutEffect(() => {
|
|
52
|
+
if (focusOnMount)
|
|
53
|
+
containerRef.current?.focus();
|
|
54
|
+
// Deliberately on mount only, and the empty dependency list is the point: re-running whenever
|
|
55
|
+
// `errors` changed would steal focus from someone who is mid-correction, which is the opposite
|
|
56
|
+
// of helping. A second failed submit re-announces through a new `key` or the forwarded ref.
|
|
57
|
+
}, []);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (documentTitle === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const previous = document.title;
|
|
62
|
+
document.title = documentTitle;
|
|
63
|
+
return () => {
|
|
64
|
+
document.title = previous;
|
|
65
|
+
};
|
|
66
|
+
}, [documentTitle]);
|
|
67
|
+
const onEntryClick = (fieldId) => (event) => {
|
|
68
|
+
const target = document.getElementById(fieldId);
|
|
69
|
+
if (!target)
|
|
70
|
+
return;
|
|
71
|
+
// The anchor is real and its href works on its own — this only makes the landing better. A
|
|
72
|
+
// fragment link moves focus natively when the target is focusable, and does nothing useful when
|
|
73
|
+
// it is a wrapper, so focus is placed explicitly.
|
|
74
|
+
//
|
|
75
|
+
// `focus()` scrolls the element into view by itself, so there is no `scrollIntoView()` here:
|
|
76
|
+
// calling it as well would be a second scroll doing the same job, and one more browser API to
|
|
77
|
+
// feature-detect for no gain.
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
target.focus();
|
|
80
|
+
};
|
|
81
|
+
return (_jsxs("div", { ...nativeProps, ref: setRef, className: className === undefined ? 'lutra-error-summary' : `lutra-error-summary ${className}`,
|
|
82
|
+
// Focusable on purpose, in the tab order never.
|
|
83
|
+
tabIndex: -1, children: [_jsxs("div", { className: "lutra-error-summary__heading-row", children: [_jsx(AlertCircleIcon, {}), _jsx("h2", { className: "lutra-error-summary__title", children: title })] }), _jsx("ul", { className: "lutra-error-summary__list", children: errors.map((error) => (_jsx("li", { className: "lutra-error-summary__item", children: _jsx("a", { className: "lutra-error-summary__link", href: `#${error.fieldId}`, onClick: onEntryClick(error.fieldId), children: error.message }) }, error.fieldId))) })] }));
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=error-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-summary.js","sourceRoot":"","sources":["../../../src/components/error-summary/error-summary.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/error-summary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/error-summary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FieldIndicator } from '../../internal/field-support.js';
|
|
3
|
+
import type { InputProps } from '../input/index.js';
|
|
4
|
+
export type { FieldIndicator };
|
|
5
|
+
/**
|
|
6
|
+
* Input props that Field derives rather than forwards.
|
|
7
|
+
*
|
|
8
|
+
* `invalid` is derived from `error`, and `aria-invalid` was never on `InputProps` to begin with, so
|
|
9
|
+
* the field's look, its announced state and its visible message all come from the one value.
|
|
10
|
+
*/
|
|
11
|
+
type ForwardedInputProps = Omit<InputProps, 'invalid'>;
|
|
12
|
+
export interface FieldProps extends ForwardedInputProps {
|
|
13
|
+
/**
|
|
14
|
+
* The visible label. Always required — a field without one is not usable, and a placeholder is
|
|
15
|
+
* not a substitute for it.
|
|
16
|
+
*/
|
|
17
|
+
label: ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Supporting text shown above the control and connected with `aria-describedby`. Use it for
|
|
20
|
+
* format guidance the person needs before they start typing.
|
|
21
|
+
*/
|
|
22
|
+
hint?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* The error message. **Its presence is what puts the field in the error state** — it drives the
|
|
25
|
+
* red treatment, `aria-invalid` on the control, and the visible message together, so they cannot
|
|
26
|
+
* disagree. Omit it and the field is valid.
|
|
27
|
+
*/
|
|
28
|
+
error?: ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* The word shown after the label text and included in the accessible name.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to `'required'` when the native `required` attribute is set, and `'none'` otherwise,
|
|
33
|
+
* so the visible word and the announced state agree by default. Pass `'optional'` to mark
|
|
34
|
+
* optional fields in a form where most are required.
|
|
35
|
+
*/
|
|
36
|
+
indicator?: FieldIndicator;
|
|
37
|
+
/** Extra class on the field wrapper, not on the control. */
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A labelled text field: label, optional hint, an `Input`, and an optional error message.
|
|
42
|
+
*
|
|
43
|
+
* This is the ordinary way to render an Input. The primitive deliberately does not manufacture a
|
|
44
|
+
* label; Field is what supplies one, associates it with `for`/`id`, and wires the hint and error to
|
|
45
|
+
* the control with `aria-describedby`.
|
|
46
|
+
*
|
|
47
|
+
* Everything not listed above is forwarded to the underlying `Input`, so `type`, `name`, `value`,
|
|
48
|
+
* `placeholder`, `required`, `readOnly`, `autoComplete`, `size`, `fullWidth`, the event handlers and
|
|
49
|
+
* `data-*` all behave exactly as they do there. A `ref` reaches the native `<input>`.
|
|
50
|
+
*
|
|
51
|
+
* Order is label → hint → control → error. The hint is above the control so it is read before the
|
|
52
|
+
* control is operated; the error is below it, matching the Figma annotation and Field Group.
|
|
53
|
+
*/
|
|
54
|
+
export declare const Field: import("react").ForwardRefExoticComponent<FieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
55
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/components/field/field.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;GAKG;AACH,KAAK,mBAAmB,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAEvD,MAAM,WAAW,UAAW,SAAQ,mBAAmB;IACrD;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,KAAK,yGA4ChB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { FieldShell, useFieldIds } from '../../internal/field-support.js';
|
|
4
|
+
import { Input } from '../input/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* A labelled text field: label, optional hint, an `Input`, and an optional error message.
|
|
7
|
+
*
|
|
8
|
+
* This is the ordinary way to render an Input. The primitive deliberately does not manufacture a
|
|
9
|
+
* label; Field is what supplies one, associates it with `for`/`id`, and wires the hint and error to
|
|
10
|
+
* the control with `aria-describedby`.
|
|
11
|
+
*
|
|
12
|
+
* Everything not listed above is forwarded to the underlying `Input`, so `type`, `name`, `value`,
|
|
13
|
+
* `placeholder`, `required`, `readOnly`, `autoComplete`, `size`, `fullWidth`, the event handlers and
|
|
14
|
+
* `data-*` all behave exactly as they do there. A `ref` reaches the native `<input>`.
|
|
15
|
+
*
|
|
16
|
+
* Order is label → hint → control → error. The hint is above the control so it is read before the
|
|
17
|
+
* control is operated; the error is below it, matching the Figma annotation and Field Group.
|
|
18
|
+
*/
|
|
19
|
+
export const Field = forwardRef(function Field({ label, hint, error, indicator, className, id, required = false, disabled = false, 'aria-describedby': describedBy, ...inputProps }, ref) {
|
|
20
|
+
const hasHint = hint !== undefined && hint !== null && hint !== false;
|
|
21
|
+
const hasError = error !== undefined && error !== null && error !== false;
|
|
22
|
+
const ids = useFieldIds({ id, hasHint, hasError, describedBy });
|
|
23
|
+
const resolvedIndicator = indicator ?? (required ? 'required' : 'none');
|
|
24
|
+
return (_jsx(FieldShell, { controlId: ids.controlId, label: label, indicator: resolvedIndicator, hint: hint, hintId: ids.hintId, error: error, errorId: ids.errorId, disabled: disabled, className: className, children: _jsx(Input, { ...inputProps, ref: ref, id: ids.controlId, required: required, disabled: disabled, invalid: hasError, "aria-describedby": ids.describedBy }) }));
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","sourceRoot":"","sources":["../../../src/components/field/field.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAGnC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE1E,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AA0C1C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EACE,KAAK,EACL,IAAI,EACJ,KAAK,EACL,SAAS,EACT,SAAS,EACT,EAAE,EACF,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,kBAAkB,EAAE,WAAW,EAC/B,GAAG,UAAU,EACd,EACD,GAAG;IAEH,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,CAAC;IAE1E,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,iBAAiB,GAAmB,SAAS,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAExF,OAAO,CACL,KAAC,UAAU,IACT,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,GAAG,CAAC,MAAM,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,YAEpB,KAAC,KAAK,OACA,UAAU,EACd,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,GAAG,CAAC,SAAS,EACjB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,QAAQ,sBACC,GAAG,CAAC,WAAW,GACjC,GACS,CACd,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/field/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/field/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { FieldsetHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Native `<fieldset>` attributes, minus what this component owns.
|
|
4
|
+
*
|
|
5
|
+
* `FieldsetHTMLAttributes` rather than the generic `HTMLAttributes`, so the element's own
|
|
6
|
+
* attributes survive the wrapper: `name` and `form` are specific to `<fieldset>` and would be
|
|
7
|
+
* dropped by the generic type. `disabled` belongs to it too, and is redeclared below only so it can
|
|
8
|
+
* carry documentation and a default.
|
|
9
|
+
*/
|
|
10
|
+
type NativeFieldsetProps = Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'children' | 'disabled'>;
|
|
11
|
+
export interface FieldGroupProps extends NativeFieldsetProps {
|
|
12
|
+
/**
|
|
13
|
+
* The question the controls answer, rendered as a `<legend>`.
|
|
14
|
+
*
|
|
15
|
+
* The legend provides the accessible name for the group, and is the context an assistive
|
|
16
|
+
* technology has available while a user moves through the controls inside it. Keep it short:
|
|
17
|
+
* assistive technologies may expose that context repeatedly as the controls are navigated.
|
|
18
|
+
*/
|
|
19
|
+
legend: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Instructions or supporting text for the group, connected with `aria-describedby`.
|
|
22
|
+
*
|
|
23
|
+
* **There is no default.** "Choose all that apply" is right for a checkbox group and wrong for a
|
|
24
|
+
* radio group, and a generic default would be wrong roughly half the time — which is why the
|
|
25
|
+
* design master removed it. Say what this particular question needs.
|
|
26
|
+
*/
|
|
27
|
+
hint?: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* The error message for the group. Its presence is what puts the group in the error state, so the
|
|
30
|
+
* red treatment and the visible message cannot disagree.
|
|
31
|
+
*
|
|
32
|
+
* A group-level failure is the normal case for radios and checkboxes: the question was not
|
|
33
|
+
* answered, rather than one option being wrong.
|
|
34
|
+
*/
|
|
35
|
+
error?: ReactNode;
|
|
36
|
+
/** Whether the group is disabled. Native — it disables every control inside. */
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
/** The controls: `CheckboxItem`s, `RadioItem`s, or anything else the question needs. */
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A group of related controls that answer one question.
|
|
43
|
+
*
|
|
44
|
+
* Renders a native `<fieldset>` with a `<legend>`, which is the platform's own grouping semantic:
|
|
45
|
+
* the legend names the group, and `disabled` on the fieldset disables every control in it. Neither
|
|
46
|
+
* is reimplemented, and neither needs ARIA to replace it.
|
|
47
|
+
*
|
|
48
|
+
* Order is legend → hint → controls → error, matching `Field`.
|
|
49
|
+
*
|
|
50
|
+
* ## Why there is no `role="group"` or `aria-invalid` here
|
|
51
|
+
*
|
|
52
|
+
* `<fieldset>` already carries the group semantic, so adding `role="group"` would restate what the
|
|
53
|
+
* element says. `aria-invalid` is not placed on the fieldset either: it is defined for widgets that
|
|
54
|
+
* hold a value, and a fieldset holds none — assistive technology would have nothing to attach it to.
|
|
55
|
+
*
|
|
56
|
+
* The group's invalid state is therefore expressed as a relationship rather than a state flag: the
|
|
57
|
+
* error message is connected to the `<fieldset>` with `aria-describedby`, which is the established
|
|
58
|
+
* way to attach supporting text to a group, and the individual controls draw their own error
|
|
59
|
+
* treatment through their `invalid` prop. Exactly how and when a given screen reader surfaces a
|
|
60
|
+
* group-level description varies, and this component does not promise any particular behaviour —
|
|
61
|
+
* which is why the message is also visible on screen and the controls are drawn in their error
|
|
62
|
+
* state. That is the accessibility annotation's model, and it is why `Radio`'s `invalid` is visual
|
|
63
|
+
* only.
|
|
64
|
+
*/
|
|
65
|
+
export declare function FieldGroup({ legend, hint, error, disabled, className, children, 'aria-describedby': describedBy, ...nativeProps }: FieldGroupProps): import("react").JSX.Element;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=field-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-group.d.ts","sourceRoot":"","sources":["../../../src/components/field-group/field-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE/D;;;;;;;GAOG;AACH,KAAK,mBAAmB,GAAG,IAAI,CAC7B,sBAAsB,CAAC,mBAAmB,CAAC,EAC3C,UAAU,GAAG,UAAU,CACxB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,mBAAmB;IAC1D;;;;;;OAMG;IACH,MAAM,EAAE,SAAS,CAAC;IAClB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wFAAwF;IACxF,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,IAAI,EACJ,KAAK,EACL,QAAgB,EAChB,SAAS,EACT,QAAQ,EACR,kBAAkB,EAAE,WAAW,EAC/B,GAAG,WAAW,EACf,EAAE,eAAe,+BAmDjB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* A group of related controls that answer one question.
|
|
5
|
+
*
|
|
6
|
+
* Renders a native `<fieldset>` with a `<legend>`, which is the platform's own grouping semantic:
|
|
7
|
+
* the legend names the group, and `disabled` on the fieldset disables every control in it. Neither
|
|
8
|
+
* is reimplemented, and neither needs ARIA to replace it.
|
|
9
|
+
*
|
|
10
|
+
* Order is legend → hint → controls → error, matching `Field`.
|
|
11
|
+
*
|
|
12
|
+
* ## Why there is no `role="group"` or `aria-invalid` here
|
|
13
|
+
*
|
|
14
|
+
* `<fieldset>` already carries the group semantic, so adding `role="group"` would restate what the
|
|
15
|
+
* element says. `aria-invalid` is not placed on the fieldset either: it is defined for widgets that
|
|
16
|
+
* hold a value, and a fieldset holds none — assistive technology would have nothing to attach it to.
|
|
17
|
+
*
|
|
18
|
+
* The group's invalid state is therefore expressed as a relationship rather than a state flag: the
|
|
19
|
+
* error message is connected to the `<fieldset>` with `aria-describedby`, which is the established
|
|
20
|
+
* way to attach supporting text to a group, and the individual controls draw their own error
|
|
21
|
+
* treatment through their `invalid` prop. Exactly how and when a given screen reader surfaces a
|
|
22
|
+
* group-level description varies, and this component does not promise any particular behaviour —
|
|
23
|
+
* which is why the message is also visible on screen and the controls are drawn in their error
|
|
24
|
+
* state. That is the accessibility annotation's model, and it is why `Radio`'s `invalid` is visual
|
|
25
|
+
* only.
|
|
26
|
+
*/
|
|
27
|
+
export function FieldGroup({ legend, hint, error, disabled = false, className, children, 'aria-describedby': describedBy, ...nativeProps }) {
|
|
28
|
+
const generated = useId();
|
|
29
|
+
const base = `lutra-field-group-${generated}`;
|
|
30
|
+
const hasHint = hint !== undefined && hint !== null && hint !== false;
|
|
31
|
+
const hasError = error !== undefined && error !== null && error !== false;
|
|
32
|
+
const hintId = hasHint ? `${base}-hint` : undefined;
|
|
33
|
+
const errorId = hasError ? `${base}-error` : undefined;
|
|
34
|
+
// The consumer's own description is preserved and listed first, as it is in Field.
|
|
35
|
+
const parts = [describedBy, hintId, errorId].filter((part) => Boolean(part));
|
|
36
|
+
return (_jsxs("fieldset", { ...nativeProps, className: className === undefined ? 'lutra-field-group' : `lutra-field-group ${className}`, disabled: disabled, "aria-describedby": parts.length > 0 ? parts.join(' ') : undefined, "data-invalid": hasError ? 'true' : undefined, children: [_jsx("legend", { className: "lutra-field-group__legend", children: legend }), hasHint && (_jsx("p", { className: "lutra-field-group__hint", id: hintId, children: hint })), _jsx("div", { className: "lutra-field-group__items", children: children }), hasError && (_jsxs("p", { className: "lutra-field-group__error", id: errorId, children: [_jsxs("svg", { className: "lutra-field-group__error-icon", viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "8", cy: "8", r: "7", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("path", { d: "M8 4.5v4.25", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }), _jsx("circle", { cx: "8", cy: "11.5", r: "0.9", fill: "currentColor" })] }), error] }))] }));
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=field-group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-group.js","sourceRoot":"","sources":["../../../src/components/field-group/field-group.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AA+C9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,MAAM,EACN,IAAI,EACJ,KAAK,EACL,QAAQ,GAAG,KAAK,EAChB,SAAS,EACT,QAAQ,EACR,kBAAkB,EAAE,WAAW,EAC/B,GAAG,WAAW,EACE;IAChB,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,qBAAqB,SAAS,EAAE,CAAC;IAE9C,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,CAAC;IAE1E,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvD,mFAAmF;IACnF,MAAM,KAAK,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7F,OAAO,CACL,uBACM,WAAW,EACf,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,qBAAqB,SAAS,EAAE,EAC3F,QAAQ,EAAE,QAAQ,sBACA,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,kBAClD,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,aAE3C,iBAAQ,SAAS,EAAC,2BAA2B,YAAE,MAAM,GAAU,EAE9D,OAAO,IAAI,CACV,YAAG,SAAS,EAAC,yBAAyB,EAAC,EAAE,EAAE,MAAM,YAC9C,IAAI,GACH,CACL,EAED,cAAK,SAAS,EAAC,0BAA0B,YAAE,QAAQ,GAAO,EAEzD,QAAQ,IAAI,CACX,aAAG,SAAS,EAAC,0BAA0B,EAAC,EAAE,EAAE,OAAO,aAIjD,eACE,SAAS,EAAC,+BAA+B,EACzC,OAAO,EAAC,WAAW,iBACP,MAAM,EAClB,SAAS,EAAC,OAAO,aAEjB,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,CAAC,EAAC,GAAG,EAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,KAAK,GAAG,EAClF,eAAM,CAAC,EAAC,aAAa,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,KAAK,EAAC,aAAa,EAAC,OAAO,GAAG,EACtF,iBAAQ,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,MAAM,EAAC,CAAC,EAAC,KAAK,EAAC,IAAI,EAAC,cAAc,GAAG,IACnD,EACL,KAAK,IACJ,CACL,IACQ,CACZ,CAAC;AACJ,CAAC"}
|