@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.js","sourceRoot":"","sources":["../../../src/components/tooltip/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FocusEvent, KeyboardEvent, PointerEvent, ReactElement, ReactNode } from 'react';
|
|
2
|
+
/** Side of the trigger the tooltip prefers. Maps to the `placement` axis of the Figma set. */
|
|
3
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
/** Alignment along that side. Maps to the `align` axis of the Figma set. */
|
|
5
|
+
export type TooltipAlign = 'start' | 'center' | 'end';
|
|
6
|
+
/**
|
|
7
|
+
* Props the tooltip merges onto its trigger. The trigger must forward them to a DOM element.
|
|
8
|
+
*
|
|
9
|
+
* Every member admits `undefined` explicitly because `exactOptionalPropertyTypes` is on: clearing
|
|
10
|
+
* `aria-describedby` when the tooltip closes means passing `undefined`, and an optional property
|
|
11
|
+
* that cannot hold `undefined` would reject exactly that.
|
|
12
|
+
*/
|
|
13
|
+
interface TriggerProps {
|
|
14
|
+
'aria-describedby'?: string | undefined;
|
|
15
|
+
onPointerEnter?: ((event: PointerEvent<HTMLElement>) => void) | undefined;
|
|
16
|
+
onPointerLeave?: ((event: PointerEvent<HTMLElement>) => void) | undefined;
|
|
17
|
+
onFocus?: ((event: FocusEvent<HTMLElement>) => void) | undefined;
|
|
18
|
+
onBlur?: ((event: FocusEvent<HTMLElement>) => void) | undefined;
|
|
19
|
+
onKeyDown?: ((event: KeyboardEvent<HTMLElement>) => void) | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface TooltipProps {
|
|
22
|
+
/**
|
|
23
|
+
* The tooltip text. **Supplementary only.**
|
|
24
|
+
*
|
|
25
|
+
* Nothing essential belongs here: touch devices have no hover, so anything that matters must also
|
|
26
|
+
* exist in visible text, help text, or the control's own name. Never put links or buttons in it —
|
|
27
|
+
* the tooltip cannot be reached to use them.
|
|
28
|
+
*/
|
|
29
|
+
content: ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* The trigger. A single element that forwards props to a DOM node, and that already has its own
|
|
32
|
+
* accessible name — the tooltip describes a control, it never names one.
|
|
33
|
+
*/
|
|
34
|
+
children: ReactElement<TriggerProps>;
|
|
35
|
+
/** @default 'top' */
|
|
36
|
+
placement?: TooltipPlacement;
|
|
37
|
+
/** @default 'center' */
|
|
38
|
+
align?: TooltipAlign;
|
|
39
|
+
/**
|
|
40
|
+
* Milliseconds before an open triggered by pointer or focus takes effect. From the Figma API
|
|
41
|
+
* contract. Closing is always immediate.
|
|
42
|
+
*
|
|
43
|
+
* @default 120
|
|
44
|
+
*/
|
|
45
|
+
delay?: number;
|
|
46
|
+
/** Extra class on the tooltip surface. */
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Supplementary information anchored to a trigger.
|
|
51
|
+
*
|
|
52
|
+
* The contract comes from the Figma API note and the accessibility annotation, and is deliberately
|
|
53
|
+
* narrow:
|
|
54
|
+
*
|
|
55
|
+
* - **It opens on focus as well as hover.** A tooltip that only answers to the pointer is invisible
|
|
56
|
+
* to keyboard users, which is the single most common way this component is got wrong.
|
|
57
|
+
* - **It is connected with `aria-describedby`, and only while open**, so it is announced after the
|
|
58
|
+
* trigger's own name rather than instead of it. It never supplies the name: a control that needs a
|
|
59
|
+
* tooltip to be identifiable is a control that needs a label.
|
|
60
|
+
* - **Escape dismisses it** and focus stays on the trigger.
|
|
61
|
+
* - **It is never a tab stop and never traps focus.** There is no backdrop.
|
|
62
|
+
* - **It holds no interactive content.** If content needs to be operated, it is not a tooltip.
|
|
63
|
+
*
|
|
64
|
+
* `placement` and `align` are a preference. When there is not enough room on the preferred side the
|
|
65
|
+
* tooltip flips to the opposite one, which is the whole of the positioning logic — no dependency,
|
|
66
|
+
* and no attempt at a general floating-element engine.
|
|
67
|
+
*
|
|
68
|
+
* The trigger is cloned so the describedby relationship and the open/close handlers can be attached
|
|
69
|
+
* to the real focusable element. Existing values are merged rather than replaced: a consumer's own
|
|
70
|
+
* `aria-describedby` and handlers keep working.
|
|
71
|
+
*/
|
|
72
|
+
export declare function Tooltip({ content, children, placement, align, delay, className, }: TooltipProps): import("react").JSX.Element;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=tooltip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../../src/components/tooltip/tooltip.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI9F,8FAA8F;AAC9F,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEtD;;;;;;GAMG;AACH,UAAU,YAAY;IACpB,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC1E,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC1E,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACjE,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAChE,SAAS,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACvE;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,OAAO,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;IACrC,qBAAqB;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,wBAAwB;IACxB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAeD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,SAAiB,EACjB,KAAgB,EAChB,KAAW,EACX,SAAS,GACV,EAAE,YAAY,+BAkLd"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useId, useRef, useState, } from 'react';
|
|
3
|
+
import { isDevelopment } from '../../internal/development.js';
|
|
4
|
+
/** Everything a tooltip must not contain, because none of it can be reached inside one. */
|
|
5
|
+
const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex], [contenteditable="true"]';
|
|
6
|
+
/**
|
|
7
|
+
* How long the pointer may be over neither the trigger nor the tooltip before it closes.
|
|
8
|
+
*
|
|
9
|
+
* NO FIGMA VARIABLE, and not the same thing as `delay`: this is the time it takes to cross the 8px
|
|
10
|
+
* gap between the trigger and the tooltip, not a deliberate wait. It is the smallest value that
|
|
11
|
+
* makes the crossing reliable — long enough to survive the pointer being over neither element for a
|
|
12
|
+
* frame or two, short enough that a tooltip left behind does not linger.
|
|
13
|
+
*/
|
|
14
|
+
const POINTER_GRACE = 150;
|
|
15
|
+
/**
|
|
16
|
+
* Supplementary information anchored to a trigger.
|
|
17
|
+
*
|
|
18
|
+
* The contract comes from the Figma API note and the accessibility annotation, and is deliberately
|
|
19
|
+
* narrow:
|
|
20
|
+
*
|
|
21
|
+
* - **It opens on focus as well as hover.** A tooltip that only answers to the pointer is invisible
|
|
22
|
+
* to keyboard users, which is the single most common way this component is got wrong.
|
|
23
|
+
* - **It is connected with `aria-describedby`, and only while open**, so it is announced after the
|
|
24
|
+
* trigger's own name rather than instead of it. It never supplies the name: a control that needs a
|
|
25
|
+
* tooltip to be identifiable is a control that needs a label.
|
|
26
|
+
* - **Escape dismisses it** and focus stays on the trigger.
|
|
27
|
+
* - **It is never a tab stop and never traps focus.** There is no backdrop.
|
|
28
|
+
* - **It holds no interactive content.** If content needs to be operated, it is not a tooltip.
|
|
29
|
+
*
|
|
30
|
+
* `placement` and `align` are a preference. When there is not enough room on the preferred side the
|
|
31
|
+
* tooltip flips to the opposite one, which is the whole of the positioning logic — no dependency,
|
|
32
|
+
* and no attempt at a general floating-element engine.
|
|
33
|
+
*
|
|
34
|
+
* The trigger is cloned so the describedby relationship and the open/close handlers can be attached
|
|
35
|
+
* to the real focusable element. Existing values are merged rather than replaced: a consumer's own
|
|
36
|
+
* `aria-describedby` and handlers keep working.
|
|
37
|
+
*/
|
|
38
|
+
export function Tooltip({ content, children, placement = 'top', align = 'center', delay = 120, className, }) {
|
|
39
|
+
const id = `lutra-tooltip-${useId()}`;
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
41
|
+
const [resolvedPlacement, setResolvedPlacement] = useState(placement);
|
|
42
|
+
const wrapperRef = useRef(null);
|
|
43
|
+
const tooltipRef = useRef(null);
|
|
44
|
+
const openTimer = useRef(undefined);
|
|
45
|
+
const closeTimer = useRef(undefined);
|
|
46
|
+
const cancelPendingOpen = useCallback(() => {
|
|
47
|
+
if (openTimer.current !== undefined) {
|
|
48
|
+
clearTimeout(openTimer.current);
|
|
49
|
+
openTimer.current = undefined;
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
const cancelPendingClose = useCallback(() => {
|
|
53
|
+
if (closeTimer.current !== undefined) {
|
|
54
|
+
clearTimeout(closeTimer.current);
|
|
55
|
+
closeTimer.current = undefined;
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
58
|
+
const openAfterDelay = useCallback(() => {
|
|
59
|
+
cancelPendingClose();
|
|
60
|
+
cancelPendingOpen();
|
|
61
|
+
openTimer.current = setTimeout(() => {
|
|
62
|
+
setOpen(true);
|
|
63
|
+
}, delay);
|
|
64
|
+
}, [cancelPendingClose, cancelPendingOpen, delay]);
|
|
65
|
+
const closeNow = useCallback(() => {
|
|
66
|
+
cancelPendingOpen();
|
|
67
|
+
cancelPendingClose();
|
|
68
|
+
setOpen(false);
|
|
69
|
+
}, [cancelPendingClose, cancelPendingOpen]);
|
|
70
|
+
/**
|
|
71
|
+
* Closes after a grace period rather than at once, which is what makes the tooltip hoverable.
|
|
72
|
+
*
|
|
73
|
+
* WCAG 2.2 SC 1.4.13 requires content shown on hover to stay visible while the pointer is on it,
|
|
74
|
+
* so the pointer has to be able to leave the trigger, cross the gap and land on the tooltip
|
|
75
|
+
* without it vanishing on the way. Entering either the trigger or the tooltip cancels the pending
|
|
76
|
+
* close; leaving both lets it run.
|
|
77
|
+
*/
|
|
78
|
+
const closeAfterGrace = useCallback(() => {
|
|
79
|
+
cancelPendingOpen();
|
|
80
|
+
cancelPendingClose();
|
|
81
|
+
closeTimer.current = setTimeout(() => {
|
|
82
|
+
setOpen(false);
|
|
83
|
+
}, POINTER_GRACE);
|
|
84
|
+
}, [cancelPendingClose, cancelPendingOpen]);
|
|
85
|
+
// A pending open or close must not fire after the component has gone.
|
|
86
|
+
useEffect(() => () => {
|
|
87
|
+
cancelPendingOpen();
|
|
88
|
+
cancelPendingClose();
|
|
89
|
+
}, [cancelPendingClose, cancelPendingOpen]);
|
|
90
|
+
// Flip to the opposite side when the preferred one does not fit. Measured once per open, against
|
|
91
|
+
// the viewport, which is as far as the contract asks positioning to go.
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!open) {
|
|
94
|
+
setResolvedPlacement(placement);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const trigger = wrapperRef.current;
|
|
98
|
+
const tooltip = tooltipRef.current;
|
|
99
|
+
if (!trigger || !tooltip)
|
|
100
|
+
return;
|
|
101
|
+
const anchor = trigger.getBoundingClientRect();
|
|
102
|
+
const box = tooltip.getBoundingClientRect();
|
|
103
|
+
const room = {
|
|
104
|
+
top: anchor.top,
|
|
105
|
+
bottom: window.innerHeight - anchor.bottom,
|
|
106
|
+
left: anchor.left,
|
|
107
|
+
right: window.innerWidth - anchor.right,
|
|
108
|
+
};
|
|
109
|
+
const opposite = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' };
|
|
110
|
+
const needed = placement === 'top' || placement === 'bottom' ? box.height : box.width;
|
|
111
|
+
setResolvedPlacement(room[placement] < needed && room[opposite[placement]] >= needed
|
|
112
|
+
? opposite[placement]
|
|
113
|
+
: placement);
|
|
114
|
+
}, [open, placement]);
|
|
115
|
+
// Interactive content cannot be reached inside a tooltip, so it is a defect rather than a
|
|
116
|
+
// variation. The type system cannot see into `content`, so this is checked at runtime in
|
|
117
|
+
// development, the same way Button checks its icon-only content.
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!isDevelopment || !open)
|
|
120
|
+
return;
|
|
121
|
+
if (tooltipRef.current?.querySelector(FOCUSABLE)) {
|
|
122
|
+
console.warn('Tooltip: interactive content found inside a tooltip. A tooltip cannot be reached to ' +
|
|
123
|
+
'operate it — move links, buttons and fields into the page, or use a different component.');
|
|
124
|
+
}
|
|
125
|
+
}, [open]);
|
|
126
|
+
if (isDevelopment && !isValidElement(children)) {
|
|
127
|
+
console.warn('Tooltip: `children` must be a single element that forwards props to a DOM node.');
|
|
128
|
+
}
|
|
129
|
+
const triggerProps = children.props;
|
|
130
|
+
const trigger = cloneElement(children, {
|
|
131
|
+
// Merged, not replaced: a consumer's own description keeps working alongside this one, and the
|
|
132
|
+
// relationship exists only while the tooltip is open.
|
|
133
|
+
'aria-describedby': [triggerProps['aria-describedby'], open ? id : undefined].filter(Boolean).join(' ') ||
|
|
134
|
+
undefined,
|
|
135
|
+
onPointerEnter: (event) => {
|
|
136
|
+
triggerProps.onPointerEnter?.(event);
|
|
137
|
+
openAfterDelay();
|
|
138
|
+
},
|
|
139
|
+
onPointerLeave: (event) => {
|
|
140
|
+
triggerProps.onPointerLeave?.(event);
|
|
141
|
+
// Not immediate: the pointer has to be able to reach the tooltip. See `closeAfterGrace`.
|
|
142
|
+
closeAfterGrace();
|
|
143
|
+
},
|
|
144
|
+
onFocus: (event) => {
|
|
145
|
+
triggerProps.onFocus?.(event);
|
|
146
|
+
openAfterDelay();
|
|
147
|
+
},
|
|
148
|
+
onBlur: (event) => {
|
|
149
|
+
// Focus is discrete — it does not travel across a gap — so losing it closes at once.
|
|
150
|
+
triggerProps.onBlur?.(event);
|
|
151
|
+
closeNow();
|
|
152
|
+
},
|
|
153
|
+
onKeyDown: (event) => {
|
|
154
|
+
triggerProps.onKeyDown?.(event);
|
|
155
|
+
if (event.key !== 'Escape')
|
|
156
|
+
return;
|
|
157
|
+
// Nothing is showing, so the press is not this component's to take. Cancelling it here would
|
|
158
|
+
// stop an ancestor dialog closing for no reason.
|
|
159
|
+
if (!open)
|
|
160
|
+
return;
|
|
161
|
+
// Escape dismisses the tooltip while focus stays where it is.
|
|
162
|
+
//
|
|
163
|
+
// `preventDefault()` is what keeps one press to one layer. Escape reaching a native modal
|
|
164
|
+
// `<dialog>` is a *close request*, and the close-request algorithm only runs the close watcher
|
|
165
|
+
// when the keydown was not cancelled — so cancelling it here is the platform's own way of
|
|
166
|
+
// saying this press has been spent, and no shared state is needed to say it.
|
|
167
|
+
//
|
|
168
|
+
// Propagation is deliberately left alone. A tooltip is not a layer that owns the keyboard, and
|
|
169
|
+
// nothing else in this library needs to be shielded from a press a tooltip handled.
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
closeNow();
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
return (_jsxs("span", { className: "lutra-tooltip-wrapper", ref: wrapperRef, children: [trigger, open && (_jsx("span", { ref: tooltipRef, id: id, role: "tooltip", className: className === undefined ? 'lutra-tooltip' : `lutra-tooltip ${className}`, "data-placement": resolvedPlacement, "data-align": align,
|
|
175
|
+
// The tooltip is hoverable: reaching it keeps it open, and leaving it starts the same
|
|
176
|
+
// grace period as leaving the trigger. Required by SC 1.4.13, and the reason the surface
|
|
177
|
+
// no longer sets `pointer-events: none`.
|
|
178
|
+
onPointerEnter: cancelPendingClose, onPointerLeave: closeAfterGrace, children: content }))] }));
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../../src/components/tooltip/tooltip.tsx"],"names":[],"mappings":";AAAA,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,EACN,QAAQ,GACT,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAqD9D,2FAA2F;AAC3F,MAAM,SAAS,GAAG,gFAAgF,CAAC;AAEnG;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,SAAS,GAAG,KAAK,EACjB,KAAK,GAAG,QAAQ,EAChB,KAAK,GAAG,GAAG,EACX,SAAS,GACI;IACb,MAAM,EAAE,GAAG,iBAAiB,KAAK,EAAE,EAAE,CAAC;IACtC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CAAmB,SAAS,CAAC,CAAC;IAExF,MAAM,UAAU,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,MAAM,CAA4C,SAAS,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,MAAM,CAA4C,SAAS,CAAC,CAAC;IAEhF,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;QACzC,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACpC,YAAY,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAChC,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC;QAChC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,kBAAkB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC1C,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACrC,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACjC,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC;QACjC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,kBAAkB,EAAE,CAAC;QACrB,iBAAiB,EAAE,CAAC;QACpB,SAAS,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAClC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,iBAAiB,EAAE,CAAC;QACpB,kBAAkB,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE5C;;;;;;;OAOG;IACH,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,iBAAiB,EAAE,CAAC;QACpB,kBAAkB,EAAE,CAAC;QACrB,UAAU,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACnC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EAAE,aAAa,CAAC,CAAC;IACpB,CAAC,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAE5C,sEAAsE;IACtE,SAAS,CACP,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,iBAAiB,EAAE,CAAC;QACpB,kBAAkB,EAAE,CAAC;IACvB,CAAC,EACD,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CACxC,CAAC;IAEF,iGAAiG;IACjG,wEAAwE;IACxE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,oBAAoB,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;YAAE,OAAO;QAEjC,MAAM,MAAM,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG;YACX,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM;YAC1C,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,KAAK;SACxC,CAAC;QACF,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAW,CAAC;QACzF,MAAM,MAAM,GAAG,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;QAEtF,oBAAoB,CAClB,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,MAAM;YAC7D,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;YACrB,CAAC,CAAC,SAAS,CACd,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAEtB,0FAA0F;IAC1F,yFAAyF;IACzF,iEAAiE;IACjE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI;YAAE,OAAO;QACpC,IAAI,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,IAAI,CACV,sFAAsF;gBACpF,0FAA0F,CAC7F,CAAC;QACJ,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,IAAI,aAAa,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC;IAEpC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE;QACrC,+FAA+F;QAC/F,sDAAsD;QACtD,kBAAkB,EAChB,CAAC,YAAY,CAAC,kBAAkB,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACnF,SAAS;QACX,cAAc,EAAE,CAAC,KAAgC,EAAE,EAAE;YACnD,YAAY,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC;YACrC,cAAc,EAAE,CAAC;QACnB,CAAC;QACD,cAAc,EAAE,CAAC,KAAgC,EAAE,EAAE;YACnD,YAAY,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC;YACrC,yFAAyF;YACzF,eAAe,EAAE,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,CAAC,KAA8B,EAAE,EAAE;YAC1C,YAAY,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAC9B,cAAc,EAAE,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,CAAC,KAA8B,EAAE,EAAE;YACzC,qFAAqF;YACrF,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;YAC7B,QAAQ,EAAE,CAAC;QACb,CAAC;QACD,SAAS,EAAE,CAAC,KAAiC,EAAE,EAAE;YAC/C,YAAY,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO;YACnC,6FAA6F;YAC7F,iDAAiD;YACjD,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,8DAA8D;YAC9D,EAAE;YACF,0FAA0F;YAC1F,+FAA+F;YAC/F,0FAA0F;YAC1F,6EAA6E;YAC7E,EAAE;YACF,+FAA+F;YAC/F,oFAAoF;YACpF,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,EAAE,CAAC;QACb,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,CACL,gBAAM,SAAS,EAAC,uBAAuB,EAAC,GAAG,EAAE,UAAU,aACpD,OAAO,EACP,IAAI,IAAI,CACP,eACE,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,EAAE,EACN,IAAI,EAAC,SAAS,EACd,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,SAAS,EAAE,oBACnE,iBAAiB,gBACrB,KAAK;gBACjB,sFAAsF;gBACtF,yFAAyF;gBACzF,yCAAyC;gBACzC,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,eAAe,YAE9B,OAAO,GACH,CACR,IACI,CACR,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public API of `@lutra-ui-system/react`.
|
|
3
|
+
*
|
|
4
|
+
* Everything a consumer is allowed to import is named here explicitly. Adding
|
|
5
|
+
* an export is a deliberate, reviewed act: once a name leaves this file it is
|
|
6
|
+
* a supported part of the library's contract. Do not re-export whole modules
|
|
7
|
+
* with `export *`, and do not export internal helpers or types that only exist
|
|
8
|
+
* to support an implementation.
|
|
9
|
+
*
|
|
10
|
+
* Exports come from each component's own `index.ts` barrel, never from a
|
|
11
|
+
* component implementation file directly.
|
|
12
|
+
*
|
|
13
|
+
* Consumers must also import the stylesheet once, from `@lutra-ui-system/react/styles.css`.
|
|
14
|
+
* The package never imports CSS from JavaScript, so the stylesheet cannot be
|
|
15
|
+
* pulled into a server bundle by accident.
|
|
16
|
+
*
|
|
17
|
+
* See `docs/engineering-guidelines.md` for the rules that govern this file.
|
|
18
|
+
*/
|
|
19
|
+
export { Button } from './components/button/index.js';
|
|
20
|
+
export type { ButtonProps, ButtonVariant, ButtonSize, ButtonContentType, } from './components/button/index.js';
|
|
21
|
+
export { Dialog } from './components/dialog/index.js';
|
|
22
|
+
export type { DialogProps, DialogSize, DialogInitialFocus } from './components/dialog/index.js';
|
|
23
|
+
export { Dropdown, DropdownItem, DropdownSeparator } from './components/dropdown/index.js';
|
|
24
|
+
export type { DropdownProps, DropdownItemProps } from './components/dropdown/index.js';
|
|
25
|
+
export { Tooltip } from './components/tooltip/index.js';
|
|
26
|
+
export type { TooltipProps, TooltipPlacement, TooltipAlign } from './components/tooltip/index.js';
|
|
27
|
+
export { Link } from './components/link/index.js';
|
|
28
|
+
export type { LinkProps, LinkVariant } from './components/link/index.js';
|
|
29
|
+
export { NavigationItem } from './components/navigation-item/index.js';
|
|
30
|
+
export type { NavigationItemProps } from './components/navigation-item/index.js';
|
|
31
|
+
export { MobileNavigation } from './components/mobile-navigation/index.js';
|
|
32
|
+
export type { MobileNavigationProps, MobileNavigationAlign, } from './components/mobile-navigation/index.js';
|
|
33
|
+
export { Card, CardFooter } from './components/card/index.js';
|
|
34
|
+
export type { CardProps, CardElevation, CardFooterProps, CardFooterAlign, HeadingLevel, } from './components/card/index.js';
|
|
35
|
+
export { LinkCard } from './components/link-card/index.js';
|
|
36
|
+
export type { LinkCardProps } from './components/link-card/index.js';
|
|
37
|
+
export { Table, TableRow, TableHeaderCell, TableCell, TableSelectionCell, TableRowActions, TableEmptyState, } from './components/table/index.js';
|
|
38
|
+
export type { TableProps, TableRowProps, TableHeaderCellProps, TableCellProps, TableSelectionCellProps, TableRowActionsProps, TableEmptyStateProps, TableAlign, TableSortState, } from './components/table/index.js';
|
|
39
|
+
export { ErrorSummary } from './components/error-summary/index.js';
|
|
40
|
+
export type { ErrorSummaryProps, ErrorSummaryItem } from './components/error-summary/index.js';
|
|
41
|
+
export { Input } from './components/input/index.js';
|
|
42
|
+
export type { InputProps, InputSize, InputType } from './components/input/index.js';
|
|
43
|
+
export { Select } from './components/select/index.js';
|
|
44
|
+
export type { SelectProps, SelectSize } from './components/select/index.js';
|
|
45
|
+
export { Field } from './components/field/index.js';
|
|
46
|
+
export type { FieldProps, FieldIndicator } from './components/field/index.js';
|
|
47
|
+
export { Checkbox, CheckboxItem } from './components/checkbox/index.js';
|
|
48
|
+
export type { CheckboxProps, CheckboxItemProps } from './components/checkbox/index.js';
|
|
49
|
+
export { Radio, RadioItem } from './components/radio/index.js';
|
|
50
|
+
export type { RadioProps, RadioItemProps } from './components/radio/index.js';
|
|
51
|
+
export { Toggle, ToggleItem } from './components/toggle/index.js';
|
|
52
|
+
export type { ToggleProps, ToggleItemProps } from './components/toggle/index.js';
|
|
53
|
+
export { FieldGroup } from './components/field-group/index.js';
|
|
54
|
+
export type { FieldGroupProps } from './components/field-group/index.js';
|
|
55
|
+
export { SelectField } from './components/select-field/index.js';
|
|
56
|
+
export type { SelectFieldProps } from './components/select-field/index.js';
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,YAAY,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEhG,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEvF,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACxD,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElG,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAEjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAC3E,YAAY,EACV,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,GACb,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EACL,KAAK,EACL,QAAQ,EACR,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,UAAU,EACV,cAAc,GACf,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAE/F,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAEpF,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE5E,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEvF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public API of `@lutra-ui-system/react`.
|
|
3
|
+
*
|
|
4
|
+
* Everything a consumer is allowed to import is named here explicitly. Adding
|
|
5
|
+
* an export is a deliberate, reviewed act: once a name leaves this file it is
|
|
6
|
+
* a supported part of the library's contract. Do not re-export whole modules
|
|
7
|
+
* with `export *`, and do not export internal helpers or types that only exist
|
|
8
|
+
* to support an implementation.
|
|
9
|
+
*
|
|
10
|
+
* Exports come from each component's own `index.ts` barrel, never from a
|
|
11
|
+
* component implementation file directly.
|
|
12
|
+
*
|
|
13
|
+
* Consumers must also import the stylesheet once, from `@lutra-ui-system/react/styles.css`.
|
|
14
|
+
* The package never imports CSS from JavaScript, so the stylesheet cannot be
|
|
15
|
+
* pulled into a server bundle by accident.
|
|
16
|
+
*
|
|
17
|
+
* See `docs/engineering-guidelines.md` for the rules that govern this file.
|
|
18
|
+
*/
|
|
19
|
+
export { Button } from './components/button/index.js';
|
|
20
|
+
export { Dialog } from './components/dialog/index.js';
|
|
21
|
+
export { Dropdown, DropdownItem, DropdownSeparator } from './components/dropdown/index.js';
|
|
22
|
+
export { Tooltip } from './components/tooltip/index.js';
|
|
23
|
+
export { Link } from './components/link/index.js';
|
|
24
|
+
export { NavigationItem } from './components/navigation-item/index.js';
|
|
25
|
+
export { MobileNavigation } from './components/mobile-navigation/index.js';
|
|
26
|
+
export { Card, CardFooter } from './components/card/index.js';
|
|
27
|
+
export { LinkCard } from './components/link-card/index.js';
|
|
28
|
+
export { Table, TableRow, TableHeaderCell, TableCell, TableSelectionCell, TableRowActions, TableEmptyState, } from './components/table/index.js';
|
|
29
|
+
export { ErrorSummary } from './components/error-summary/index.js';
|
|
30
|
+
export { Input } from './components/input/index.js';
|
|
31
|
+
export { Select } from './components/select/index.js';
|
|
32
|
+
export { Field } from './components/field/index.js';
|
|
33
|
+
export { Checkbox, CheckboxItem } from './components/checkbox/index.js';
|
|
34
|
+
export { Radio, RadioItem } from './components/radio/index.js';
|
|
35
|
+
export { Toggle, ToggleItem } from './components/toggle/index.js';
|
|
36
|
+
export { FieldGroup } from './components/field-group/index.js';
|
|
37
|
+
export { SelectField } from './components/select-field/index.js';
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAQtD,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAGtD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAG3F,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGxD,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAM3E,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAS9D,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAG3D,OAAO,EACL,KAAK,EACL,QAAQ,EACR,eAAe,EACf,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,6BAA6B,CAAC;AAarC,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAGnE,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAGpD,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAGtD,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAGpD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGxE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAG/D,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"development.d.ts","sourceRoot":"","sources":["../../src/internal/development.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,aAAa,SACiD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"development.js","sourceRoot":"","sources":["../../src/internal/development.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,aAAa,GACxB,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,YAAY,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The label, indicator, hint and error scaffolding shared by `Field` and `SelectField`.
|
|
4
|
+
*
|
|
5
|
+
* This exists because two components need identical composition around different controls — two
|
|
6
|
+
* real call sites, which is the bar the guidelines set for extracting anything. It is deliberately
|
|
7
|
+
* not a form-control framework: it knows nothing about the control it wraps, has no registry, no
|
|
8
|
+
* context and no validation, and it renders the control as `children`. A third control that needs
|
|
9
|
+
* the same shell should use it; a control that needs something different should not be forced
|
|
10
|
+
* through it.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here is exported from the package root.
|
|
13
|
+
*/
|
|
14
|
+
/** Which word, if any, appears after the label text. */
|
|
15
|
+
export type FieldIndicator = 'none' | 'required' | 'optional';
|
|
16
|
+
interface FieldIdsInput {
|
|
17
|
+
/** Consumer-supplied control id, if any. */
|
|
18
|
+
id: string | undefined;
|
|
19
|
+
hasHint: boolean;
|
|
20
|
+
hasError: boolean;
|
|
21
|
+
/** Anything the consumer already pointed the control at. */
|
|
22
|
+
describedBy: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface FieldIds {
|
|
25
|
+
controlId: string;
|
|
26
|
+
hintId: string | undefined;
|
|
27
|
+
errorId: string | undefined;
|
|
28
|
+
/** The full `aria-describedby` value, or undefined when there is nothing to describe. */
|
|
29
|
+
describedBy: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Derives the ids that tie a field's parts together.
|
|
33
|
+
*
|
|
34
|
+
* `useId` rather than a counter or a random value: it is stable across re-renders and consistent
|
|
35
|
+
* between the server and client render, so hydration does not mismatch. A render-time random id
|
|
36
|
+
* would break both.
|
|
37
|
+
*
|
|
38
|
+
* The consumer's own `aria-describedby` is preserved and listed first, not replaced. A field that
|
|
39
|
+
* silently dropped a relationship the consumer established — a shared format note, a character
|
|
40
|
+
* counter — would be a data-loss bug that only shows up in a screen reader.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useFieldIds({ id, hasHint, hasError, describedBy }: FieldIdsInput): FieldIds;
|
|
43
|
+
interface FieldShellProps {
|
|
44
|
+
controlId: string;
|
|
45
|
+
label: ReactNode;
|
|
46
|
+
indicator: FieldIndicator;
|
|
47
|
+
hint: ReactNode;
|
|
48
|
+
hintId: string | undefined;
|
|
49
|
+
error: ReactNode;
|
|
50
|
+
errorId: string | undefined;
|
|
51
|
+
disabled: boolean;
|
|
52
|
+
className: string | undefined;
|
|
53
|
+
/** The control itself, already wired with the ids this shell produced. */
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Renders the visible parts around a control, in the documented order:
|
|
58
|
+
* label → hint → control → error.
|
|
59
|
+
*
|
|
60
|
+
* The hint sits above the control so it is read before the control is operated; the error sits
|
|
61
|
+
* below it, which is where the Figma annotation places it and where Field Group puts its own.
|
|
62
|
+
*
|
|
63
|
+
* The indicator word is rendered *inside* the `<label>`, so it becomes part of the accessible name
|
|
64
|
+
* and is announced with it rather than being a separate visual flourish the label does not carry.
|
|
65
|
+
*/
|
|
66
|
+
export declare function FieldShell({ controlId, label, indicator, hint, hintId, error, errorId, disabled, className, children, }: FieldShellProps): import("react").JSX.Element;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=field-support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-support.d.ts","sourceRoot":"","sources":["../../src/internal/field-support.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAE9D,UAAU,aAAa;IACrB,4CAA4C;IAC5C,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,yFAAyF;IACzF,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,QAAQ,CAiB3F;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,cAAc,CAAC;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,SAAS,EACT,IAAI,EACJ,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE,eAAe,+BAmCjB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Derives the ids that tie a field's parts together.
|
|
5
|
+
*
|
|
6
|
+
* `useId` rather than a counter or a random value: it is stable across re-renders and consistent
|
|
7
|
+
* between the server and client render, so hydration does not mismatch. A render-time random id
|
|
8
|
+
* would break both.
|
|
9
|
+
*
|
|
10
|
+
* The consumer's own `aria-describedby` is preserved and listed first, not replaced. A field that
|
|
11
|
+
* silently dropped a relationship the consumer established — a shared format note, a character
|
|
12
|
+
* counter — would be a data-loss bug that only shows up in a screen reader.
|
|
13
|
+
*/
|
|
14
|
+
export function useFieldIds({ id, hasHint, hasError, describedBy }) {
|
|
15
|
+
const generated = useId();
|
|
16
|
+
const controlId = id ?? `lutra-field-${generated}`;
|
|
17
|
+
const hintId = hasHint ? `${controlId}-hint` : undefined;
|
|
18
|
+
const errorId = hasError ? `${controlId}-error` : undefined;
|
|
19
|
+
// Order matters: the consumer's own description first, then the hint, then the error. A screen
|
|
20
|
+
// reader reads them in this order, and the error is the thing to leave ringing in the ear.
|
|
21
|
+
const parts = [describedBy, hintId, errorId].filter((part) => Boolean(part));
|
|
22
|
+
return {
|
|
23
|
+
controlId,
|
|
24
|
+
hintId,
|
|
25
|
+
errorId,
|
|
26
|
+
describedBy: parts.length > 0 ? parts.join(' ') : undefined,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Renders the visible parts around a control, in the documented order:
|
|
31
|
+
* label → hint → control → error.
|
|
32
|
+
*
|
|
33
|
+
* The hint sits above the control so it is read before the control is operated; the error sits
|
|
34
|
+
* below it, which is where the Figma annotation places it and where Field Group puts its own.
|
|
35
|
+
*
|
|
36
|
+
* The indicator word is rendered *inside* the `<label>`, so it becomes part of the accessible name
|
|
37
|
+
* and is announced with it rather than being a separate visual flourish the label does not carry.
|
|
38
|
+
*/
|
|
39
|
+
export function FieldShell({ controlId, label, indicator, hint, hintId, error, errorId, disabled, className, children, }) {
|
|
40
|
+
return (_jsxs("div", { className: className === undefined ? 'lutra-field' : `lutra-field ${className}`, "data-invalid": error ? 'true' : undefined, "data-disabled": disabled ? 'true' : undefined, children: [_jsxs("label", { className: "lutra-field__label", htmlFor: controlId, children: [label, indicator !== 'none' && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "lutra-field__indicator", "data-indicator": indicator, children: indicator === 'required' ? 'Required' : 'Optional' })] }))] }), hint !== undefined && hint !== null && hint !== false && (_jsx("p", { className: "lutra-field__hint", id: hintId, children: hint })), children, error !== undefined && error !== null && error !== false && (_jsx("p", { className: "lutra-field__error", id: errorId, children: error }))] }));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=field-support.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-support.js","sourceRoot":"","sources":["../../src/internal/field-support.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAoC9B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAiB;IAC/E,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,EAAE,IAAI,eAAe,SAAS,EAAE,CAAC;IAEnD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAE5D,+FAA+F;IAC/F,2FAA2F;IAC3F,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;QACL,SAAS;QACT,MAAM;QACN,OAAO;QACP,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;KAC5D,CAAC;AACJ,CAAC;AAgBD;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,SAAS,EACT,IAAI,EACJ,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,GACQ;IAChB,OAAO,CACL,eACE,SAAS,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,SAAS,EAAE,kBACjE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,mBACzB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,aAE5C,iBAAO,SAAS,EAAC,oBAAoB,EAAC,OAAO,EAAE,SAAS,aACrD,KAAK,EACL,SAAS,KAAK,MAAM,IAAI,CACvB,8BAEyF,GAAG,EAC1F,eAAM,SAAS,EAAC,wBAAwB,oBAAiB,SAAS,YAC/D,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,GAC9C,IACN,CACJ,IACK,EAEP,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,CACxD,YAAG,SAAS,EAAC,mBAAmB,EAAC,EAAE,EAAE,MAAM,YACxC,IAAI,GACH,CACL,EAEA,QAAQ,EAER,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,CAC3D,YAAG,SAAS,EAAC,oBAAoB,EAAC,EAAE,EAAE,OAAO,YAC1C,KAAK,GACJ,CACL,IACG,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `useLayoutEffect` in the browser, `useEffect` on the server.
|
|
4
|
+
*
|
|
5
|
+
* Some DOM state has no HTML attribute behind it and can only be assigned to the element — a
|
|
6
|
+
* checkbox's `indeterminate` property is the example this library has. Writing it during render
|
|
7
|
+
* would be a side effect in the render phase, which React is entitled to run more than once, to
|
|
8
|
+
* abandon, or to replay; it has to happen after the DOM exists instead.
|
|
9
|
+
*
|
|
10
|
+
* `useLayoutEffect` is the right one of the two, because it runs before the browser paints. With
|
|
11
|
+
* `useEffect` there is a frame in which the element is on screen without the property set, and for
|
|
12
|
+
* `indeterminate` that frame shows the wrong control — a tick or an empty box where a dash belongs.
|
|
13
|
+
*
|
|
14
|
+
* The catch is that React warns when `useLayoutEffect` runs during server rendering, where there is
|
|
15
|
+
* no layout to read and nothing to paint. This package must render on a server without complaint,
|
|
16
|
+
* so the hook is chosen once, at module scope, from whether a document exists.
|
|
17
|
+
*
|
|
18
|
+
* The `typeof` guard matters for the same reason it does in `development.ts`: `document` is not
|
|
19
|
+
* defined in a plain Node process, and an unguarded reference would throw while the module is being
|
|
20
|
+
* evaluated. Choosing between the two hooks once, rather than per render, keeps the rules of hooks
|
|
21
|
+
* satisfied — the same hook is called on every render of a given component.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is exported from the package root.
|
|
24
|
+
*/
|
|
25
|
+
export declare const useIsomorphicLayoutEffect: typeof useEffect;
|
|
26
|
+
//# sourceMappingURL=isomorphic-layout-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isomorphic-layout-effect.d.ts","sourceRoot":"","sources":["../../src/internal/isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,MAAM,OAAO,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB,kBACyB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `useLayoutEffect` in the browser, `useEffect` on the server.
|
|
4
|
+
*
|
|
5
|
+
* Some DOM state has no HTML attribute behind it and can only be assigned to the element — a
|
|
6
|
+
* checkbox's `indeterminate` property is the example this library has. Writing it during render
|
|
7
|
+
* would be a side effect in the render phase, which React is entitled to run more than once, to
|
|
8
|
+
* abandon, or to replay; it has to happen after the DOM exists instead.
|
|
9
|
+
*
|
|
10
|
+
* `useLayoutEffect` is the right one of the two, because it runs before the browser paints. With
|
|
11
|
+
* `useEffect` there is a frame in which the element is on screen without the property set, and for
|
|
12
|
+
* `indeterminate` that frame shows the wrong control — a tick or an empty box where a dash belongs.
|
|
13
|
+
*
|
|
14
|
+
* The catch is that React warns when `useLayoutEffect` runs during server rendering, where there is
|
|
15
|
+
* no layout to read and nothing to paint. This package must render on a server without complaint,
|
|
16
|
+
* so the hook is chosen once, at module scope, from whether a document exists.
|
|
17
|
+
*
|
|
18
|
+
* The `typeof` guard matters for the same reason it does in `development.ts`: `document` is not
|
|
19
|
+
* defined in a plain Node process, and an unguarded reference would throw while the module is being
|
|
20
|
+
* evaluated. Choosing between the two hooks once, rather than per render, keeps the rules of hooks
|
|
21
|
+
* satisfied — the same hook is called on every render of a given component.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here is exported from the package root.
|
|
24
|
+
*/
|
|
25
|
+
export const useIsomorphicLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
26
|
+
//# sourceMappingURL=isomorphic-layout-effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isomorphic-layout-effect.js","sourceRoot":"","sources":["../../src/internal/isomorphic-layout-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,yBAAyB,GACpC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC"}
|