@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 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,GACb,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/card/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Native `<input type="checkbox">` attributes, minus what this component owns.
|
|
4
|
+
*
|
|
5
|
+
* `type` is fixed, `aria-invalid` is derived from `invalid`, and `children` is dropped because
|
|
6
|
+
* `<input>` is a void element.
|
|
7
|
+
*/
|
|
8
|
+
type NativeCheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'aria-invalid' | 'children' | 'size'>;
|
|
9
|
+
export interface CheckboxProps extends NativeCheckboxProps {
|
|
10
|
+
/**
|
|
11
|
+
* Puts the box in its mixed state.
|
|
12
|
+
*
|
|
13
|
+
* **This is the native DOM property, not a third checked value.** `HTMLInputElement.indeterminate`
|
|
14
|
+
* exists only in the DOM — there is no HTML attribute for it and it is unrelated to `checked`, so
|
|
15
|
+
* React cannot set it from JSX. It is assigned in a layout effect after every render, which is
|
|
16
|
+
* what makes it survive re-renders, prop changes and remounts.
|
|
17
|
+
*
|
|
18
|
+
* A mixed box is still either checked or unchecked underneath; the mixed state is a display of
|
|
19
|
+
* "some of the things below are selected", and clicking it resolves to a real value.
|
|
20
|
+
*
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
indeterminate?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Marks the control as failing validation. Sets the visual treatment and `aria-invalid="true"`
|
|
26
|
+
* from one value, so they cannot disagree.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
invalid?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A checkbox. Renders a native `<input type="checkbox">`.
|
|
34
|
+
*
|
|
35
|
+
* Native on purpose: the space-bar toggle, the label-click target, form participation, the mixed
|
|
36
|
+
* state announcement and the disabled semantics all come from the platform. A `<div role="checkbox">`
|
|
37
|
+
* would have to reimplement every one of them and would still be worse.
|
|
38
|
+
*
|
|
39
|
+
* **Every rendered Checkbox must have an accessible name.** The primitive does not manufacture one —
|
|
40
|
+
* use `CheckboxItem` for the ordinary labelled composition, or supply `aria-label` /
|
|
41
|
+
* `aria-labelledby` for a checkbox whose meaning comes from elsewhere, such as a table row selector.
|
|
42
|
+
*
|
|
43
|
+
* `forwardRef` rather than a plain `ref` prop: this package supports React 18.3 as well as 19. The
|
|
44
|
+
* component keeps its own internal ref as well, so `indeterminate` works whether or not a consumer
|
|
45
|
+
* passes one.
|
|
46
|
+
*/
|
|
47
|
+
export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
48
|
+
export interface CheckboxItemProps extends CheckboxProps {
|
|
49
|
+
/** The visible label. It is the checkbox's accessible name. */
|
|
50
|
+
label: ReactNode;
|
|
51
|
+
/** Supporting text below the label, connected with `aria-describedby`. */
|
|
52
|
+
description?: ReactNode;
|
|
53
|
+
/** Extra class on the item wrapper, not on the control. */
|
|
54
|
+
className?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A checkbox with its visible label, and optionally a description.
|
|
58
|
+
*
|
|
59
|
+
* This is the ordinary way to render a Checkbox — the composition the Figma `Checkbox Item`
|
|
60
|
+
* component draws. The label is a real `<label>` associated by `for`/`id`, so clicking or tapping
|
|
61
|
+
* anywhere on it toggles the box, which also makes the target far larger than the 20px box.
|
|
62
|
+
*
|
|
63
|
+
* The label and description grow with their content. Nothing sets a fixed height.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CheckboxItem: import("react").ForwardRefExoticComponent<CheckboxItemProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=checkbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK5D;;;;;GAKG;AACH,KAAK,mBAAmB,GAAG,IAAI,CAC7B,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,MAAM,CAC9C,CAAC;AAEF,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IACxD;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,4GA8CnB,CAAC;AAEH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,+DAA+D;IAC/D,KAAK,EAAE,SAAS,CAAC;IACjB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,gHAuCvB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useRef } from 'react';
|
|
3
|
+
import { useFieldIds } from '../../internal/field-support.js';
|
|
4
|
+
import { useIsomorphicLayoutEffect } from '../../internal/isomorphic-layout-effect.js';
|
|
5
|
+
/**
|
|
6
|
+
* A checkbox. Renders a native `<input type="checkbox">`.
|
|
7
|
+
*
|
|
8
|
+
* Native on purpose: the space-bar toggle, the label-click target, form participation, the mixed
|
|
9
|
+
* state announcement and the disabled semantics all come from the platform. A `<div role="checkbox">`
|
|
10
|
+
* would have to reimplement every one of them and would still be worse.
|
|
11
|
+
*
|
|
12
|
+
* **Every rendered Checkbox must have an accessible name.** The primitive does not manufacture one —
|
|
13
|
+
* use `CheckboxItem` for the ordinary labelled composition, or supply `aria-label` /
|
|
14
|
+
* `aria-labelledby` for a checkbox whose meaning comes from elsewhere, such as a table row selector.
|
|
15
|
+
*
|
|
16
|
+
* `forwardRef` rather than a plain `ref` prop: this package supports React 18.3 as well as 19. The
|
|
17
|
+
* component keeps its own internal ref as well, so `indeterminate` works whether or not a consumer
|
|
18
|
+
* passes one.
|
|
19
|
+
*/
|
|
20
|
+
export const Checkbox = forwardRef(function Checkbox({ indeterminate = false, invalid = false, className, ...nativeProps }, forwardedRef) {
|
|
21
|
+
const elementRef = useRef(null);
|
|
22
|
+
// The ref callback only records the element and forwards it. It deliberately does not depend on
|
|
23
|
+
// `indeterminate`, so changing that prop no longer detaches and reattaches the ref — which would
|
|
24
|
+
// hand a consumer's callback ref a spurious null on every change.
|
|
25
|
+
const setRef = useCallback((element) => {
|
|
26
|
+
elementRef.current = element;
|
|
27
|
+
if (typeof forwardedRef === 'function')
|
|
28
|
+
forwardedRef(element);
|
|
29
|
+
else if (forwardedRef)
|
|
30
|
+
forwardedRef.current = element;
|
|
31
|
+
}, [forwardedRef]);
|
|
32
|
+
// Synchronised after the DOM exists, not during render.
|
|
33
|
+
//
|
|
34
|
+
// `indeterminate` has no HTML attribute, so it can only be assigned to the element. Doing that in
|
|
35
|
+
// the render body would be a side effect in the render phase, which React is free to run twice,
|
|
36
|
+
// abandon, or replay — and under StrictMode it does. This runs after every render instead, with
|
|
37
|
+
// no dependency array, so it also covers the case where React swaps the underlying DOM node while
|
|
38
|
+
// the prop is unchanged.
|
|
39
|
+
//
|
|
40
|
+
// A layout effect rather than a passive one, so the property is set before the browser paints:
|
|
41
|
+
// with `useEffect` there is a frame showing a tick or an empty box where a dash belongs.
|
|
42
|
+
useIsomorphicLayoutEffect(() => {
|
|
43
|
+
const element = elementRef.current;
|
|
44
|
+
if (element)
|
|
45
|
+
element.indeterminate = indeterminate;
|
|
46
|
+
});
|
|
47
|
+
return (_jsx("input", { ...nativeProps, ref: setRef, type: "checkbox", className: className === undefined ? 'lutra-checkbox' : `lutra-checkbox ${className}`, "aria-invalid": invalid || undefined }));
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* A checkbox with its visible label, and optionally a description.
|
|
51
|
+
*
|
|
52
|
+
* This is the ordinary way to render a Checkbox — the composition the Figma `Checkbox Item`
|
|
53
|
+
* component draws. The label is a real `<label>` associated by `for`/`id`, so clicking or tapping
|
|
54
|
+
* anywhere on it toggles the box, which also makes the target far larger than the 20px box.
|
|
55
|
+
*
|
|
56
|
+
* The label and description grow with their content. Nothing sets a fixed height.
|
|
57
|
+
*/
|
|
58
|
+
export const CheckboxItem = forwardRef(function CheckboxItem({ label, description, className, id, disabled = false, 'aria-describedby': describedBy, ...checkboxProps }, ref) {
|
|
59
|
+
const hasDescription = description !== undefined && description !== null && description !== false;
|
|
60
|
+
const ids = useFieldIds({ id, hasHint: hasDescription, hasError: false, describedBy });
|
|
61
|
+
return (_jsxs("div", { className: className === undefined ? 'lutra-choice' : `lutra-choice ${className}`, "data-disabled": disabled ? 'true' : undefined, children: [_jsx(Checkbox, { ...checkboxProps, ref: ref, id: ids.controlId, disabled: disabled, "aria-describedby": ids.describedBy }), _jsxs("div", { className: "lutra-choice__content", children: [_jsx("label", { className: "lutra-choice__label", htmlFor: ids.controlId, children: label }), hasDescription && (_jsx("p", { className: "lutra-choice__description", id: ids.hintId, children: description }))] })] }));
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkbox.js","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AAqCvF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAkC,SAAS,QAAQ,CACnF,EAAE,aAAa,GAAG,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE,EACrE,YAAY;IAEZ,MAAM,UAAU,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IAEzD,gGAAgG;IAChG,iGAAiG;IACjG,kEAAkE;IAClE,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,OAAgC,EAAE,EAAE;QACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;QAE7B,IAAI,OAAO,YAAY,KAAK,UAAU;YAAE,YAAY,CAAC,OAAO,CAAC,CAAC;aACzD,IAAI,YAAY;YAAE,YAAY,CAAC,OAAO,GAAG,OAAO,CAAC;IACxD,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,wDAAwD;IACxD,EAAE;IACF,kGAAkG;IAClG,gGAAgG;IAChG,gGAAgG;IAChG,kGAAkG;IAClG,yBAAyB;IACzB,EAAE;IACF,+FAA+F;IAC/F,yFAAyF;IACzF,yBAAyB,CAAC,GAAG,EAAE;QAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,IAAI,OAAO;YAAE,OAAO,CAAC,aAAa,GAAG,aAAa,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,mBACM,WAAW,EACf,GAAG,EAAE,MAAM,EACX,IAAI,EAAC,UAAU,EACf,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,kBAAkB,SAAS,EAAE,kBACvE,OAAO,IAAI,SAAS,GAIlC,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAWH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAsC,SAAS,YAAY,CAC/F,EACE,KAAK,EACL,WAAW,EACX,SAAS,EACT,EAAE,EACF,QAAQ,GAAG,KAAK,EAChB,kBAAkB,EAAE,WAAW,EAC/B,GAAG,aAAa,EACjB,EACD,GAAG;IAEH,MAAM,cAAc,GAAG,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,KAAK,CAAC;IAClG,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAEvF,OAAO,CACL,eACE,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB,SAAS,EAAE,mBAClE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,aAE5C,KAAC,QAAQ,OACH,aAAa,EACjB,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,GAAG,CAAC,SAAS,EACjB,QAAQ,EAAE,QAAQ,sBACA,GAAG,CAAC,WAAW,GACjC,EACF,eAAK,SAAS,EAAC,uBAAuB,aACpC,gBAAO,SAAS,EAAC,qBAAqB,EAAC,OAAO,EAAE,GAAG,CAAC,SAAS,YAC1D,KAAK,GACA,EACP,cAAc,IAAI,CACjB,YAAG,SAAS,EAAC,2BAA2B,EAAC,EAAE,EAAE,GAAG,CAAC,MAAM,YACpD,WAAW,GACV,CACL,IACG,IACF,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/checkbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** Drawn width. Maps to the `size` axis of the Figma component set. */
|
|
3
|
+
export type DialogSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
/**
|
|
5
|
+
* Where focus lands when the dialog opens.
|
|
6
|
+
*
|
|
7
|
+
* `'first'` gives it to the first interactive element, which is right for most dialogs.
|
|
8
|
+
* `'dialog'` gives it to the dialog container, which the annotation asks for when the content is
|
|
9
|
+
* long enough that starting at a control would skip the explanation — and is the safer choice for a
|
|
10
|
+
* destructive confirmation, where landing on Delete is exactly wrong.
|
|
11
|
+
*
|
|
12
|
+
* A control marked `autoFocus` wins over either, because that is native `<dialog>` behaviour.
|
|
13
|
+
*/
|
|
14
|
+
export type DialogInitialFocus = 'first' | 'dialog';
|
|
15
|
+
export interface DialogProps {
|
|
16
|
+
/** Whether the dialog is open. Always controlled — the opener has to know. */
|
|
17
|
+
open: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Called on every close path: Escape, the close button, a footer action, or a backdrop press.
|
|
20
|
+
* The consumer sets `open` to false in response.
|
|
21
|
+
*/
|
|
22
|
+
onClose: () => void;
|
|
23
|
+
/** The visible title. Rendered as a heading, and it is what names the dialog. */
|
|
24
|
+
title: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Short explanatory copy under the title, connected with `aria-describedby`.
|
|
27
|
+
*
|
|
28
|
+
* Omit it when the body is long or structured — a description that reads out several paragraphs
|
|
29
|
+
* is worse than none.
|
|
30
|
+
*/
|
|
31
|
+
description?: ReactNode;
|
|
32
|
+
/** @default 'md' */
|
|
33
|
+
size?: DialogSize;
|
|
34
|
+
/** @default true */
|
|
35
|
+
showClose?: boolean;
|
|
36
|
+
/** Accessible name of the close control. @default 'Close' */
|
|
37
|
+
closeLabel?: string;
|
|
38
|
+
/** @default 'first' */
|
|
39
|
+
initialFocus?: DialogInitialFocus;
|
|
40
|
+
/**
|
|
41
|
+
* Whether Escape closes the dialog. Leave it true unless a critical operation is genuinely in
|
|
42
|
+
* progress, and if you turn it off, say why on screen — a dialog with no way out is a trap.
|
|
43
|
+
*
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
dismissible?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether pressing the backdrop closes the dialog. Off by default: it is fine for a low-risk
|
|
49
|
+
* dialog and wrong for one holding unsaved work, so it is opt-in per dialog.
|
|
50
|
+
*
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
dismissOnBackdropPress?: boolean;
|
|
54
|
+
/** Footer actions. Kept visible while the body scrolls. */
|
|
55
|
+
footer?: ReactNode;
|
|
56
|
+
/** The body. */
|
|
57
|
+
children: ReactNode;
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A modal dialog. Renders a native `<dialog>` opened with `showModal()`.
|
|
62
|
+
*
|
|
63
|
+
* ## Why native
|
|
64
|
+
*
|
|
65
|
+
* The annotation allows either a native `<dialog>` or a container with `role="dialog"` and
|
|
66
|
+
* `aria-modal="true"`, and asks for focus entry, containment, an inert background, Escape, and focus
|
|
67
|
+
* restoration. `showModal()` provides the first four *from the browser*: the dialog goes into the
|
|
68
|
+
* top layer, everything behind it becomes inert without a single `aria-hidden` being written across
|
|
69
|
+
* the page, Tab and Shift+Tab cannot leave, and Escape fires `cancel`.
|
|
70
|
+
*
|
|
71
|
+
* A hand-built version would reimplement all of that with a focus-trap loop and manual `inert`, and
|
|
72
|
+
* would be worse at it — brittle index arithmetic around focusable elements is precisely the failure
|
|
73
|
+
* this component must not have. So the browser does the containment and this component does only the
|
|
74
|
+
* parts that are genuinely its own: which element receives focus first, and returning focus to the
|
|
75
|
+
* opener on every close path.
|
|
76
|
+
*
|
|
77
|
+
* ## What that costs
|
|
78
|
+
*
|
|
79
|
+
* jsdom implements no part of `<dialog>` — no `showModal`, no `close`, no `inert`. The unit tests
|
|
80
|
+
* therefore prove semantics, wiring and this component's own focus restoration, and prove **nothing**
|
|
81
|
+
* about containment or inertness. Those need a real browser; the stories exist to make them
|
|
82
|
+
* checkable. See `dialog.md`.
|
|
83
|
+
*
|
|
84
|
+
* Where `showModal` is missing the component falls back to the `open` attribute so the content still
|
|
85
|
+
* renders and is still labelled, but it is **not modal** in that state.
|
|
86
|
+
*/
|
|
87
|
+
export declare function Dialog({ open, onClose, title, description, size, showClose, closeLabel, initialFocus, dismissible, dismissOnBackdropPress, footer, children, className, }: DialogProps): import("react").JSX.Element;
|
|
88
|
+
//# sourceMappingURL=dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/components/dialog/dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,iFAAiF;IACjF,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,oBAAoB;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,oBAAoB;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gBAAgB;IAChB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,IAAW,EACX,SAAgB,EAChB,UAAoB,EACpB,YAAsB,EACtB,WAAkB,EAClB,sBAA8B,EAC9B,MAAM,EACN,QAAQ,EACR,SAAS,GACV,EAAE,WAAW,+BAkKb"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useId, useRef } from 'react';
|
|
3
|
+
import { isDevelopment } from '../../internal/development.js';
|
|
4
|
+
import { useIsomorphicLayoutEffect } from '../../internal/isomorphic-layout-effect.js';
|
|
5
|
+
import { Button } from '../button/index.js';
|
|
6
|
+
/** How many Lutra dialogs are open. Used only for the development warning about stacking. */
|
|
7
|
+
let openDialogCount = 0;
|
|
8
|
+
function CloseIcon() {
|
|
9
|
+
return (_jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", focusable: "false", children: _jsx("path", { d: "M4 4l8 8M12 4l-8 8", strokeLinecap: "round" }) }));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A modal dialog. Renders a native `<dialog>` opened with `showModal()`.
|
|
13
|
+
*
|
|
14
|
+
* ## Why native
|
|
15
|
+
*
|
|
16
|
+
* The annotation allows either a native `<dialog>` or a container with `role="dialog"` and
|
|
17
|
+
* `aria-modal="true"`, and asks for focus entry, containment, an inert background, Escape, and focus
|
|
18
|
+
* restoration. `showModal()` provides the first four *from the browser*: the dialog goes into the
|
|
19
|
+
* top layer, everything behind it becomes inert without a single `aria-hidden` being written across
|
|
20
|
+
* the page, Tab and Shift+Tab cannot leave, and Escape fires `cancel`.
|
|
21
|
+
*
|
|
22
|
+
* A hand-built version would reimplement all of that with a focus-trap loop and manual `inert`, and
|
|
23
|
+
* would be worse at it — brittle index arithmetic around focusable elements is precisely the failure
|
|
24
|
+
* this component must not have. So the browser does the containment and this component does only the
|
|
25
|
+
* parts that are genuinely its own: which element receives focus first, and returning focus to the
|
|
26
|
+
* opener on every close path.
|
|
27
|
+
*
|
|
28
|
+
* ## What that costs
|
|
29
|
+
*
|
|
30
|
+
* jsdom implements no part of `<dialog>` — no `showModal`, no `close`, no `inert`. The unit tests
|
|
31
|
+
* therefore prove semantics, wiring and this component's own focus restoration, and prove **nothing**
|
|
32
|
+
* about containment or inertness. Those need a real browser; the stories exist to make them
|
|
33
|
+
* checkable. See `dialog.md`.
|
|
34
|
+
*
|
|
35
|
+
* Where `showModal` is missing the component falls back to the `open` attribute so the content still
|
|
36
|
+
* renders and is still labelled, but it is **not modal** in that state.
|
|
37
|
+
*/
|
|
38
|
+
export function Dialog({ open, onClose, title, description, size = 'md', showClose = true, closeLabel = 'Close', initialFocus = 'first', dismissible = true, dismissOnBackdropPress = false, footer, children, className, }) {
|
|
39
|
+
const base = useId();
|
|
40
|
+
const titleId = `lutra-dialog-title-${base}`;
|
|
41
|
+
const descriptionId = `lutra-dialog-description-${base}`;
|
|
42
|
+
const dialogRef = useRef(null);
|
|
43
|
+
// The element that had focus when the dialog opened, so it can be given focus back.
|
|
44
|
+
const invokerRef = useRef(null);
|
|
45
|
+
const hasDescription = description !== undefined && description !== null && description !== false;
|
|
46
|
+
// Opening and closing the real element. A layout effect so the dialog is in the top layer before
|
|
47
|
+
// the browser paints, rather than flashing the page underneath for a frame.
|
|
48
|
+
useIsomorphicLayoutEffect(() => {
|
|
49
|
+
const dialog = dialogRef.current;
|
|
50
|
+
if (!dialog)
|
|
51
|
+
return;
|
|
52
|
+
if (open) {
|
|
53
|
+
if (dialog.open)
|
|
54
|
+
return;
|
|
55
|
+
invokerRef.current =
|
|
56
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
57
|
+
if (typeof dialog.showModal === 'function') {
|
|
58
|
+
dialog.showModal();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// No native dialog implementation — jsdom, or a browser old enough not to matter. The
|
|
62
|
+
// content renders and is labelled, but nothing here is modal.
|
|
63
|
+
dialog.setAttribute('open', '');
|
|
64
|
+
}
|
|
65
|
+
if (initialFocus === 'dialog')
|
|
66
|
+
dialog.focus();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!dialog.open)
|
|
70
|
+
return;
|
|
71
|
+
if (typeof dialog.close === 'function')
|
|
72
|
+
dialog.close();
|
|
73
|
+
else
|
|
74
|
+
dialog.removeAttribute('open');
|
|
75
|
+
}, [open, initialFocus]);
|
|
76
|
+
// Focus restoration, on every close path, because this is the part the browser is least reliable
|
|
77
|
+
// about and the part most worth owning. A dialog that drops focus to the top of the document loses
|
|
78
|
+
// the user's place entirely.
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (open)
|
|
81
|
+
return;
|
|
82
|
+
const invoker = invokerRef.current;
|
|
83
|
+
invokerRef.current = null;
|
|
84
|
+
if (!invoker)
|
|
85
|
+
return;
|
|
86
|
+
// The invoker may have been removed or disabled while the dialog was open — restoring focus to
|
|
87
|
+
// it would throw or silently do nothing, so fail gracefully instead.
|
|
88
|
+
if (!invoker.isConnected)
|
|
89
|
+
return;
|
|
90
|
+
if (invoker.hasAttribute('disabled'))
|
|
91
|
+
return;
|
|
92
|
+
invoker.focus();
|
|
93
|
+
}, [open]);
|
|
94
|
+
// Never stack dialogs. The annotation says to resolve or close the first one before opening
|
|
95
|
+
// another; the type system cannot see that, so it is a development warning.
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!open)
|
|
98
|
+
return;
|
|
99
|
+
openDialogCount += 1;
|
|
100
|
+
if (isDevelopment && openDialogCount > 1) {
|
|
101
|
+
console.warn('Dialog: more than one Lutra dialog is open at once. Resolve or close the first before ' +
|
|
102
|
+
'opening another — stacked modals leave people with no way to tell what has focus.');
|
|
103
|
+
}
|
|
104
|
+
return () => {
|
|
105
|
+
openDialogCount -= 1;
|
|
106
|
+
};
|
|
107
|
+
}, [open]);
|
|
108
|
+
// Escape reaches the element as a `cancel` event rather than a keydown, because the browser owns
|
|
109
|
+
// the dismissal.
|
|
110
|
+
//
|
|
111
|
+
// The browser's own close is always prevented, because `open` is controlled here — letting the
|
|
112
|
+
// element close itself would leave the DOM and the prop disagreeing. `onClose` is then called, or
|
|
113
|
+
// not, which is what `dismissible` means.
|
|
114
|
+
//
|
|
115
|
+
// Nothing about layering lives here. A Tooltip, Dropdown or navigation panel open inside the
|
|
116
|
+
// dialog cancels the keydown itself, and the close-request algorithm does not reach this dialog
|
|
117
|
+
// for a press that was already cancelled.
|
|
118
|
+
const onCancel = useCallback((event) => {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
if (dismissible)
|
|
121
|
+
onClose();
|
|
122
|
+
}, [dismissible, onClose]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const dialog = dialogRef.current;
|
|
125
|
+
if (!dialog)
|
|
126
|
+
return;
|
|
127
|
+
dialog.addEventListener('cancel', onCancel);
|
|
128
|
+
return () => {
|
|
129
|
+
dialog.removeEventListener('cancel', onCancel);
|
|
130
|
+
};
|
|
131
|
+
}, [onCancel]);
|
|
132
|
+
// A press on the backdrop lands on the dialog element itself, because the backdrop is its
|
|
133
|
+
// pseudo-element rather than a separate node. Anything inside the panel has a different target.
|
|
134
|
+
//
|
|
135
|
+
// Attached as a native listener rather than a JSX `onClick`, for the same reason Dropdown
|
|
136
|
+
// delegates: a click handler in the markup makes `<dialog>` look like an interactive element that
|
|
137
|
+
// owes a keyboard equivalent, which `jsx-a11y` rightly objects to. It owes none — this is a
|
|
138
|
+
// pointer-only shortcut, and Escape is the keyboard way out, handled above.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const dialog = dialogRef.current;
|
|
141
|
+
if (!open || !dialog || !dismissOnBackdropPress || !dismissible)
|
|
142
|
+
return;
|
|
143
|
+
const onClick = (event) => {
|
|
144
|
+
if (event.target === dialog)
|
|
145
|
+
onClose();
|
|
146
|
+
};
|
|
147
|
+
dialog.addEventListener('click', onClick);
|
|
148
|
+
return () => {
|
|
149
|
+
dialog.removeEventListener('click', onClick);
|
|
150
|
+
};
|
|
151
|
+
}, [open, dismissOnBackdropPress, dismissible, onClose]);
|
|
152
|
+
return (_jsx("dialog", { ref: dialogRef, className: className === undefined ? 'lutra-dialog' : `lutra-dialog ${className}`, "aria-labelledby": titleId, "aria-describedby": hasDescription ? descriptionId : undefined, "data-size": size,
|
|
153
|
+
// Focusable only as a target for `initialFocus="dialog"`; never in the tab sequence.
|
|
154
|
+
tabIndex: -1, children: _jsxs("div", { className: "lutra-dialog__panel", children: [_jsxs("div", { className: "lutra-dialog__header", children: [_jsxs("div", { className: "lutra-dialog__heading-group", children: [_jsx("h2", { className: "lutra-dialog__title", id: titleId, children: title }), hasDescription && (_jsx("p", { className: "lutra-dialog__description", id: descriptionId, children: description }))] }), showClose && (_jsx(Button, { variant: "secondary", contentType: "icon-only", "aria-label": closeLabel, onClick: onClose, children: _jsx(CloseIcon, {}) }))] }), _jsx("div", { className: "lutra-dialog__body", children: children }), footer !== undefined && footer !== null && footer !== false && (_jsx("div", { className: "lutra-dialog__footer", children: footer }))] }) }));
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../../src/components/dialog/dialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/dialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/dialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { ButtonSize, ButtonVariant } from '../button/index.js';
|
|
3
|
+
export interface DropdownProps {
|
|
4
|
+
/**
|
|
5
|
+
* The trigger's accessible name. When `icon` is given the trigger is icon-only and this becomes
|
|
6
|
+
* its `aria-label`; otherwise it is the visible button text.
|
|
7
|
+
*
|
|
8
|
+
* Name what the menu acts on — "More actions for Invoice 4471" beats "More actions" when several
|
|
9
|
+
* triggers share a page.
|
|
10
|
+
*/
|
|
11
|
+
label: string;
|
|
12
|
+
/** Renders an icon-only trigger. `label` then names it. */
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
/** The items. `DropdownItem` and `DropdownSeparator`, or any interactive content. */
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/** Controlled open state. Pair with `onOpenChange`. */
|
|
17
|
+
open?: boolean;
|
|
18
|
+
/** Uncontrolled initial state. @default false */
|
|
19
|
+
defaultOpen?: boolean;
|
|
20
|
+
/** Called whenever the component wants the open state to change. */
|
|
21
|
+
onOpenChange?: (open: boolean) => void;
|
|
22
|
+
/** @default 'secondary' */
|
|
23
|
+
triggerVariant?: ButtonVariant;
|
|
24
|
+
/** @default 'md' */
|
|
25
|
+
triggerSize?: ButtonSize;
|
|
26
|
+
/** Extra class on the wrapper. */
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A disclosure that reveals a panel of actions or links from a button.
|
|
31
|
+
*
|
|
32
|
+
* **It is a disclosure, not an ARIA menu, and that is deliberate.** The accessibility annotation is
|
|
33
|
+
* explicit: the trigger is a button, and the panel is a container of buttons or links depending on
|
|
34
|
+
* what each item does. No `role="menu"`, no `role="menuitem"`, and no arrow-key roving.
|
|
35
|
+
*
|
|
36
|
+
* The reason is that the two models cannot be mixed. A real ARIA menu promises arrow-key navigation
|
|
37
|
+
* *and* takes Tab away from the items inside it; a half-built one promises navigation that does not
|
|
38
|
+
* work, which is worse than no role at all. As a disclosure, everything inside keeps its native
|
|
39
|
+
* semantics and Tab keeps working, which is what the annotation asks for.
|
|
40
|
+
*
|
|
41
|
+
* Behaviour:
|
|
42
|
+
*
|
|
43
|
+
* - Enter or Space opens, because the trigger is a real button.
|
|
44
|
+
* - `aria-expanded` reflects the state and `aria-controls` points at the panel.
|
|
45
|
+
* - Focus moves to the first item on open.
|
|
46
|
+
* - Tab moves through the items in DOM order and out the other side.
|
|
47
|
+
* - Escape closes and returns focus to the trigger.
|
|
48
|
+
* - A pointer press outside closes it, without interfering with what was pressed.
|
|
49
|
+
* - Focus leaving the component closes it, so a Tab out does not strand an open panel.
|
|
50
|
+
*
|
|
51
|
+
* Open state is uncontrolled by default and controllable with `open` + `onOpenChange`, matching the
|
|
52
|
+
* pattern the form controls already use. There is no state machine beyond that.
|
|
53
|
+
*/
|
|
54
|
+
export declare function Dropdown({ label, icon, children, open: controlledOpen, defaultOpen, onOpenChange, triggerVariant, triggerSize, className, }: DropdownProps): import("react").JSX.Element;
|
|
55
|
+
type NativeButtonItem = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'type'>;
|
|
56
|
+
type NativeAnchorItem = AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
57
|
+
export interface DropdownItemProps {
|
|
58
|
+
/** Renders an anchor instead of a button. Items that navigate are links; items that act are not. */
|
|
59
|
+
href?: string;
|
|
60
|
+
/** Visual emphasis for a destructive action. */
|
|
61
|
+
tone?: 'default' | 'danger';
|
|
62
|
+
/** Leading icon. Decorative — the item's text is its accessible name. */
|
|
63
|
+
icon?: ReactNode;
|
|
64
|
+
/** The item's visible text, which is its accessible name. */
|
|
65
|
+
children: ReactNode;
|
|
66
|
+
disabled?: boolean;
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One item in a Dropdown panel.
|
|
71
|
+
*
|
|
72
|
+
* Renders a `<button>`, or an `<a>` when `href` is given. That is the whole of the semantic
|
|
73
|
+
* decision, and it follows the annotation: items that navigate are anchors, items that act are
|
|
74
|
+
* buttons. Neither gets a menu role.
|
|
75
|
+
*
|
|
76
|
+
* `disabled` uses whichever real mechanism the element has — the `disabled` attribute on a button,
|
|
77
|
+
* `aria-disabled` plus a suppressed activation on a link, since an anchor has no disabled attribute
|
|
78
|
+
* and one that merely looks unavailable but still navigates is a trap.
|
|
79
|
+
*
|
|
80
|
+
* `tone="danger"` is a visual cue only. A destructive action still needs its own confirmation step;
|
|
81
|
+
* colour is not a safeguard.
|
|
82
|
+
*/
|
|
83
|
+
export declare const DropdownItem: import("react").ForwardRefExoticComponent<(DropdownItemProps & (NativeButtonItem | NativeAnchorItem)) & import("react").RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
|
|
84
|
+
/** A visual divider between groups of items. Presentational only. */
|
|
85
|
+
export declare function DropdownSeparator({ className }: {
|
|
86
|
+
className?: string;
|
|
87
|
+
}): import("react").JSX.Element;
|
|
88
|
+
export {};
|
|
89
|
+
//# sourceMappingURL=dropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../../../src/components/dropdown/dropdown.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAc,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/F,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,qFAAqF;IACrF,QAAQ,EAAE,SAAS,CAAC;IACpB,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,2BAA2B;IAC3B,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,oBAAoB;IACpB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,IAAI,EAAE,cAAc,EACpB,WAAmB,EACnB,YAAY,EACZ,cAA4B,EAC5B,WAAkB,EAClB,SAAS,GACV,EAAE,aAAa,+BAmIf;AAED,KAAK,gBAAgB,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;AAC9E,KAAK,gBAAgB,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,yEAAyE;IACzE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,+KAgEvB,CAAC;AAEH,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAUtE"}
|