@molecule/app-ui-react 1.0.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 +115 -0
- package/README.md +2786 -0
- package/dist/components/Accordion.d.ts +12 -0
- package/dist/components/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion.js +80 -0
- package/dist/components/Accordion.js.map +1 -0
- package/dist/components/Alert.d.ts +26 -0
- package/dist/components/Alert.d.ts.map +1 -0
- package/dist/components/Alert.js +53 -0
- package/dist/components/Alert.js.map +1 -0
- package/dist/components/AuthGuard.d.ts +66 -0
- package/dist/components/AuthGuard.d.ts.map +1 -0
- package/dist/components/AuthGuard.js +42 -0
- package/dist/components/AuthGuard.js.map +1 -0
- package/dist/components/Avatar.d.ts +12 -0
- package/dist/components/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar.js +25 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/Badge.d.ts +12 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +18 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/Button.d.ts +12 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +19 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.d.ts +34 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +71 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/Checkbox.d.ts +12 -0
- package/dist/components/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox.js +41 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/Dropdown.d.ts +38 -0
- package/dist/components/Dropdown.d.ts.map +1 -0
- package/dist/components/Dropdown.js +352 -0
- package/dist/components/Dropdown.js.map +1 -0
- package/dist/components/EmptyState.d.ts +15 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +20 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/FloatingInput.d.ts +22 -0
- package/dist/components/FloatingInput.d.ts.map +1 -0
- package/dist/components/FloatingInput.js +38 -0
- package/dist/components/FloatingInput.js.map +1 -0
- package/dist/components/Form.d.ts +22 -0
- package/dist/components/Form.d.ts.map +1 -0
- package/dist/components/Form.js +81 -0
- package/dist/components/Form.js.map +1 -0
- package/dist/components/Icon.d.ts +48 -0
- package/dist/components/Icon.d.ts.map +1 -0
- package/dist/components/Icon.js +46 -0
- package/dist/components/Icon.js.map +1 -0
- package/dist/components/Input.d.ts +12 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +25 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/LanguagePicker.d.ts +60 -0
- package/dist/components/LanguagePicker.d.ts.map +1 -0
- package/dist/components/LanguagePicker.js +45 -0
- package/dist/components/LanguagePicker.js.map +1 -0
- package/dist/components/Layout.d.ts +24 -0
- package/dist/components/Layout.d.ts.map +1 -0
- package/dist/components/Layout.js +104 -0
- package/dist/components/Layout.js.map +1 -0
- package/dist/components/Modal.d.ts +32 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +235 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/PageHeader.d.ts +15 -0
- package/dist/components/PageHeader.d.ts.map +1 -0
- package/dist/components/PageHeader.js +20 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/PageShell.d.ts +15 -0
- package/dist/components/PageShell.d.ts.map +1 -0
- package/dist/components/PageShell.js +20 -0
- package/dist/components/PageShell.js.map +1 -0
- package/dist/components/Pagination.d.ts +12 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +78 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/PanelClose.d.ts +40 -0
- package/dist/components/PanelClose.d.ts.map +1 -0
- package/dist/components/PanelClose.js +32 -0
- package/dist/components/PanelClose.js.map +1 -0
- package/dist/components/Progress.d.ts +45 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +21 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/RadioGroup.d.ts +12 -0
- package/dist/components/RadioGroup.d.ts.map +1 -0
- package/dist/components/RadioGroup.js +34 -0
- package/dist/components/RadioGroup.js.map +1 -0
- package/dist/components/Select.d.ts +12 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +57 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/Separator.d.ts +25 -0
- package/dist/components/Separator.d.ts.map +1 -0
- package/dist/components/Separator.js +18 -0
- package/dist/components/Separator.js.map +1 -0
- package/dist/components/SidebarUserCard.d.ts +60 -0
- package/dist/components/SidebarUserCard.d.ts.map +1 -0
- package/dist/components/SidebarUserCard.js +45 -0
- package/dist/components/SidebarUserCard.js.map +1 -0
- package/dist/components/Skeleton.d.ts +26 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton.js +45 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/Spinner.d.ts +17 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +32 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Switch.d.ts +12 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +52 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/Table.d.ts +12 -0
- package/dist/components/Table.d.ts.map +1 -0
- package/dist/components/Table.js +53 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.d.ts +12 -0
- package/dist/components/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs.js +72 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Textarea.d.ts +12 -0
- package/dist/components/Textarea.d.ts.map +1 -0
- package/dist/components/Textarea.js +66 -0
- package/dist/components/Textarea.js.map +1 -0
- package/dist/components/ThemeToggle.d.ts +43 -0
- package/dist/components/ThemeToggle.d.ts.map +1 -0
- package/dist/components/ThemeToggle.js +30 -0
- package/dist/components/ThemeToggle.js.map +1 -0
- package/dist/components/Toast.d.ts +61 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +159 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Tooltip.d.ts +21 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +245 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/UserMenu.d.ts +59 -0
- package/dist/components/UserMenu.d.ts.map +1 -0
- package/dist/components/UserMenu.js +34 -0
- package/dist/components/UserMenu.js.map +1 -0
- package/dist/components/UserMenuPopover.d.ts +139 -0
- package/dist/components/UserMenuPopover.d.ts.map +1 -0
- package/dist/components/UserMenuPopover.js +173 -0
- package/dist/components/UserMenuPopover.js.map +1 -0
- package/dist/components/index.d.ts +43 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +43 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/index.d.ts +8 -0
- package/dist/utilities/index.d.ts.map +1 -0
- package/dist/utilities/index.js +8 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/initials.d.ts +13 -0
- package/dist/utilities/initials.d.ts.map +1 -0
- package/dist/utilities/initials.js +23 -0
- package/dist/utilities/initials.js.map +1 -0
- package/dist/utilities/renderIcon.d.ts +21 -0
- package/dist/utilities/renderIcon.d.ts.map +1 -0
- package/dist/utilities/renderIcon.js +40 -0
- package/dist/utilities/renderIcon.js.map +1 -0
- package/dist/utilities.d.ts +12 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +14 -0
- package/dist/utilities.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/components/Checkbox.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAc,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAGhF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAChC,CACE,EACE,KAAK,EACL,OAAO,EACP,aAAa,EACb,IAAI,EAAE,KAAK,EACX,KAAK,EACL,SAAS,EACT,KAAK,EACL,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,GAAG,IAAI,EACR,EACD,GAAG,EACH,EAAE;IACF,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,wDAAwD;IACxD,MAAM,WAAW,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAA;IACzD,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,IAA6B,EAAE,EAAE;QAChC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAA;QAC1B,IAAI,OAAO,GAAG,KAAK,UAAU;YAAE,GAAG,CAAC,IAAI,CAAC,CAAA;aACnC,IAAI,GAAG;YAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;IAClC,CAAC,EACD,CAAC,GAAG,CAAC,CACN,CAAA;IAED,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACxB,MAAM,UAAU,GAAG,EAAE,IAAI,IAAI,CAAA;IAC7B,uEAAuE;IACvE,wEAAwE;IACxE,oEAAoE;IACpE,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,MAAM,OAAO,GAAG,GAAG,UAAU,IAAI,WAAW,QAAQ,CAAA;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,WAAW,CAAC,OAAO,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,CAAA;QACrD,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAA;IAEnB,MAAM,eAAe,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,SAAS,CAAC,CAAA;IAEzE,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,gBAAgB,aACjC,iBAAO,SAAS,EAAE,EAAE,CAAC,YAAY,aAC/B,gBACE,GAAG,EAAE,OAAO,EACZ,IAAI,EAAC,UAAU,EACf,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,gBACN,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,kBAC/B,CAAC,CAAC,KAAK,sBACH,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAC7C,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,iBACC,MAAM,EACnB,QAAQ,EAAE,QAAiE,KACtE,IAAoD,GACzD,EACD,CAAC,CAAC,KAAK,IAAI,CACV,eAAM,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,IAAI,EAAE,CAAC,eAAe,CAAC,YACnE,KAAwB,GACpB,CACR,IACK,EACP,KAAK,IAAI,CACR,YAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,YAC3D,KAAK,GACJ,CACL,IACG,CACP,CAAA;AACH,CAAC,CACF,CAAA;AAED,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropdown component.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { DropdownProps } from '@molecule/app-ui';
|
|
8
|
+
/**
|
|
9
|
+
* Dropdown component.
|
|
10
|
+
*
|
|
11
|
+
* Implements the WAI-ARIA APG menu-button pattern. The trigger is made a
|
|
12
|
+
* real, keyboard-operable control: if `trigger` is a single element (a
|
|
13
|
+
* `<button>`, an icon `<Button>`, …) it is cloned with `aria-haspopup`,
|
|
14
|
+
* `aria-expanded`, `aria-controls`, and the open/keyboard handlers merged
|
|
15
|
+
* onto it directly — no extra wrapper, no double focus stop. If `trigger`
|
|
16
|
+
* is not a single element (text, a fragment, multiple nodes), it is wrapped
|
|
17
|
+
* in a `role="button" tabIndex={0}` container instead so keyboard/AT users
|
|
18
|
+
* can still reach it. Enter/Space/ArrowDown open the menu and move focus to
|
|
19
|
+
* the first item (ArrowUp opens to the last item); once open, ArrowUp/Down/
|
|
20
|
+
* Home/End roving-navigate the `role="menuitem"` items, Escape closes and
|
|
21
|
+
* returns focus to the trigger, and Tab closes the menu in sync with focus
|
|
22
|
+
* leaving it (matching native menus, which do not trap Tab).
|
|
23
|
+
*/
|
|
24
|
+
export declare const Dropdown: React.ForwardRefExoticComponent<DropdownProps<string> & React.RefAttributes<HTMLDivElement>>;
|
|
25
|
+
/**
|
|
26
|
+
* Dropdown label for grouping items.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DropdownLabel: React.ForwardRefExoticComponent<{
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
className?: string;
|
|
31
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
32
|
+
/**
|
|
33
|
+
* Dropdown separator for dividing groups.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DropdownSeparator: React.ForwardRefExoticComponent<{
|
|
36
|
+
className?: string;
|
|
37
|
+
} & React.RefAttributes<HTMLDivElement>>;
|
|
38
|
+
//# sourceMappingURL=Dropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAsE,MAAM,OAAO,CAAA;AAI1F,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAwJnE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,8FA2TpB,CAAA;AAID;;GAEG;AACH,eAAO,MAAM,aAAa;cAEZ,KAAK,CAAC,SAAS;gBAAc,MAAM;wCAS/C,CAAA;AAIF;;GAEG;AACH,eAAO,MAAM,iBAAiB;gBAA4C,MAAM;wCAM/E,CAAA"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Dropdown component.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import React, { forwardRef, useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
8
|
+
import { createPortal } from 'react-dom';
|
|
9
|
+
import { useLocation } from 'react-router';
|
|
10
|
+
import { getClassMap } from '@molecule/app-ui';
|
|
11
|
+
/**
|
|
12
|
+
* Calculate dropdown position based on trigger element and placement.
|
|
13
|
+
* @param triggerRect - The bounding rectangle of the trigger element.
|
|
14
|
+
* @param menuRect - The bounding rectangle of the dropdown menu.
|
|
15
|
+
* @param placement - The desired placement direction.
|
|
16
|
+
* @param align - The alignment within the placement axis.
|
|
17
|
+
* @param offset - The pixel offset from the trigger element.
|
|
18
|
+
* @returns The computed top and left position.
|
|
19
|
+
*/
|
|
20
|
+
function calculatePosition(triggerRect, menuRect, placement, align, offset = 4) {
|
|
21
|
+
let top = 0;
|
|
22
|
+
let left;
|
|
23
|
+
// Vertical positioning
|
|
24
|
+
if (placement.startsWith('top')) {
|
|
25
|
+
top = triggerRect.top + window.scrollY - menuRect.height - offset;
|
|
26
|
+
}
|
|
27
|
+
else if (placement.startsWith('bottom')) {
|
|
28
|
+
top = triggerRect.bottom + window.scrollY + offset;
|
|
29
|
+
}
|
|
30
|
+
else if (placement === 'left' || placement === 'right') {
|
|
31
|
+
top = triggerRect.top + window.scrollY + (triggerRect.height - menuRect.height) / 2;
|
|
32
|
+
}
|
|
33
|
+
// Horizontal positioning
|
|
34
|
+
if (placement === 'left') {
|
|
35
|
+
left = triggerRect.left + window.scrollX - menuRect.width - offset;
|
|
36
|
+
}
|
|
37
|
+
else if (placement === 'right') {
|
|
38
|
+
left = triggerRect.right + window.scrollX + offset;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// For top/bottom placements, use align
|
|
42
|
+
switch (align) {
|
|
43
|
+
case 'start':
|
|
44
|
+
left = triggerRect.left + window.scrollX;
|
|
45
|
+
break;
|
|
46
|
+
case 'end':
|
|
47
|
+
left = triggerRect.right + window.scrollX - menuRect.width;
|
|
48
|
+
break;
|
|
49
|
+
case 'center':
|
|
50
|
+
default:
|
|
51
|
+
left = triggerRect.left + window.scrollX + (triggerRect.width - menuRect.width) / 2;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Handle top-start, top-end, bottom-start, bottom-end
|
|
56
|
+
if (placement.endsWith('-start')) {
|
|
57
|
+
left = triggerRect.left + window.scrollX;
|
|
58
|
+
}
|
|
59
|
+
else if (placement.endsWith('-end')) {
|
|
60
|
+
left = triggerRect.right + window.scrollX - menuRect.width;
|
|
61
|
+
}
|
|
62
|
+
return { top, left };
|
|
63
|
+
}
|
|
64
|
+
const DropdownMenuItem = ({ item, isActive, onSelect, onClose, registerRef, }) => {
|
|
65
|
+
const cm = getClassMap();
|
|
66
|
+
if (item.separator) {
|
|
67
|
+
return _jsx("div", { className: cm.dropdownSeparator, role: "separator" });
|
|
68
|
+
}
|
|
69
|
+
const handleClick = () => {
|
|
70
|
+
if (!item.disabled) {
|
|
71
|
+
onSelect(item.value);
|
|
72
|
+
onClose();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const handleKeyDown = (e) => {
|
|
76
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
handleClick();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return (_jsxs("div", { ref: registerRef, role: "menuitem",
|
|
82
|
+
// Roving tabindex (WAI-ARIA APG menu pattern): only the active item is
|
|
83
|
+
// a Tab stop. Previously EVERY enabled item had tabIndex 0, so Tab
|
|
84
|
+
// walked through the whole menu one item at a time instead of exiting
|
|
85
|
+
// it — this makes Tab behave like real native menus (it leaves the
|
|
86
|
+
// menu), while ArrowUp/Down/Home/End move within it.
|
|
87
|
+
tabIndex: item.disabled ? -1 : isActive ? 0 : -1, className: cm.cn(cm.dropdownItem, item.disabled && cm.dropdownItemDisabled), onClick: handleClick, onKeyDown: handleKeyDown, "aria-disabled": item.disabled || undefined, "data-disabled": item.disabled || undefined, children: [!!item.icon && _jsx("span", { className: cm.dropdownItemIcon, children: item.icon }), _jsx("span", { className: cm.dropdownItemLabel, children: item.label }), item.shortcut && _jsx("span", { className: cm.dropdownItemShortcut, children: item.shortcut })] }));
|
|
88
|
+
};
|
|
89
|
+
/** Indices of the non-separator, non-disabled items — the set Arrow/Home/End navigate. */
|
|
90
|
+
function focusableItemIndexes(items) {
|
|
91
|
+
return items.reduce((acc, item, i) => {
|
|
92
|
+
if (!item.separator && !item.disabled)
|
|
93
|
+
acc.push(i);
|
|
94
|
+
return acc;
|
|
95
|
+
}, []);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Dropdown component.
|
|
99
|
+
*
|
|
100
|
+
* Implements the WAI-ARIA APG menu-button pattern. The trigger is made a
|
|
101
|
+
* real, keyboard-operable control: if `trigger` is a single element (a
|
|
102
|
+
* `<button>`, an icon `<Button>`, …) it is cloned with `aria-haspopup`,
|
|
103
|
+
* `aria-expanded`, `aria-controls`, and the open/keyboard handlers merged
|
|
104
|
+
* onto it directly — no extra wrapper, no double focus stop. If `trigger`
|
|
105
|
+
* is not a single element (text, a fragment, multiple nodes), it is wrapped
|
|
106
|
+
* in a `role="button" tabIndex={0}` container instead so keyboard/AT users
|
|
107
|
+
* can still reach it. Enter/Space/ArrowDown open the menu and move focus to
|
|
108
|
+
* the first item (ArrowUp opens to the last item); once open, ArrowUp/Down/
|
|
109
|
+
* Home/End roving-navigate the `role="menuitem"` items, Escape closes and
|
|
110
|
+
* returns focus to the trigger, and Tab closes the menu in sync with focus
|
|
111
|
+
* leaving it (matching native menus, which do not trap Tab).
|
|
112
|
+
*/
|
|
113
|
+
export const Dropdown = forwardRef(({ trigger, items, onSelect, placement = 'bottom-start', open: controlledOpen, onOpenChange, align = 'start', width, className, style, testId, }, _ref) => {
|
|
114
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
115
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
116
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
117
|
+
// The actual interactive trigger node — either the cloned single-element
|
|
118
|
+
// trigger, or the fallback `role="button"` wrapper. Used for position
|
|
119
|
+
// measurement, outside-click detection, AND focus restoration.
|
|
120
|
+
const triggerRef = useRef(null);
|
|
121
|
+
const menuRef = useRef(null);
|
|
122
|
+
const itemRefs = useRef([]);
|
|
123
|
+
// Which end of the menu keyboard-driven opens should focus: ArrowDown
|
|
124
|
+
// (and Enter/Space/click) want the first item, ArrowUp wants the last.
|
|
125
|
+
const pendingOpenFocusRef = useRef('first');
|
|
126
|
+
// Mirrors `items` for the open-transition effect below WITHOUT adding
|
|
127
|
+
// `items` to its dependency array — see that effect for why.
|
|
128
|
+
const itemsRef = useRef(items);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
itemsRef.current = items;
|
|
131
|
+
});
|
|
132
|
+
const location = useLocation();
|
|
133
|
+
const menuId = useId();
|
|
134
|
+
const cm = getClassMap();
|
|
135
|
+
const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
136
|
+
const setOpen = useCallback((open) => {
|
|
137
|
+
if (controlledOpen === undefined) {
|
|
138
|
+
setInternalOpen(open);
|
|
139
|
+
}
|
|
140
|
+
onOpenChange?.(open);
|
|
141
|
+
}, [controlledOpen, onOpenChange]);
|
|
142
|
+
const updatePosition = useCallback(() => {
|
|
143
|
+
if (triggerRef.current && menuRef.current) {
|
|
144
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
145
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
146
|
+
setPosition(calculatePosition(triggerRect, menuRect, placement, align));
|
|
147
|
+
}
|
|
148
|
+
}, [placement, align]);
|
|
149
|
+
const handleTriggerClick = useCallback(() => {
|
|
150
|
+
if (!isOpen)
|
|
151
|
+
pendingOpenFocusRef.current = 'first';
|
|
152
|
+
setOpen(!isOpen);
|
|
153
|
+
}, [isOpen, setOpen]);
|
|
154
|
+
const handleTriggerKeyDown = useCallback((e) => {
|
|
155
|
+
switch (e.key) {
|
|
156
|
+
case 'Enter':
|
|
157
|
+
case ' ':
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
pendingOpenFocusRef.current = 'first';
|
|
160
|
+
setOpen(true);
|
|
161
|
+
break;
|
|
162
|
+
case 'ArrowDown':
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
pendingOpenFocusRef.current = 'first';
|
|
165
|
+
setOpen(true);
|
|
166
|
+
break;
|
|
167
|
+
case 'ArrowUp':
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
pendingOpenFocusRef.current = 'last';
|
|
170
|
+
setOpen(true);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}, [setOpen]);
|
|
176
|
+
const handleClose = useCallback(() => {
|
|
177
|
+
setOpen(false);
|
|
178
|
+
}, [setOpen]);
|
|
179
|
+
// Closes AND returns focus to the trigger — used for every USER-DRIVEN
|
|
180
|
+
// close (Escape, item selection) so keyboard focus never gets dropped
|
|
181
|
+
// onto the (now-removed) menu. Outside-click and route-change closes
|
|
182
|
+
// deliberately do NOT refocus (the user's attention is already elsewhere).
|
|
183
|
+
const closeAndRefocusTrigger = useCallback(() => {
|
|
184
|
+
handleClose();
|
|
185
|
+
triggerRef.current?.focus();
|
|
186
|
+
}, [handleClose]);
|
|
187
|
+
const handleSelect = useCallback((value) => {
|
|
188
|
+
onSelect?.(value);
|
|
189
|
+
}, [onSelect]);
|
|
190
|
+
// Update position when menu opens
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (isOpen) {
|
|
193
|
+
// Use requestAnimationFrame to ensure menu is rendered before calculating position
|
|
194
|
+
requestAnimationFrame(updatePosition);
|
|
195
|
+
}
|
|
196
|
+
}, [isOpen, updatePosition]);
|
|
197
|
+
// Moves focus to the first (or last, for an ArrowUp-driven open) item
|
|
198
|
+
// whenever the menu transitions closed → open. Deps are `[isOpen]`
|
|
199
|
+
// ONLY — reading `items` through a ref instead of the dependency array
|
|
200
|
+
// means a parent re-render that recreates the `items` array (extremely
|
|
201
|
+
// common: `items={[...]}` inline) does NOT re-run this and yank focus
|
|
202
|
+
// back to the first item out from under a user who already arrow-keyed
|
|
203
|
+
// further into the menu.
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!isOpen) {
|
|
206
|
+
setActiveIndex(-1);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const focusable = focusableItemIndexes(itemsRef.current);
|
|
210
|
+
const wantLast = pendingOpenFocusRef.current === 'last';
|
|
211
|
+
pendingOpenFocusRef.current = 'first';
|
|
212
|
+
if (focusable.length === 0)
|
|
213
|
+
return;
|
|
214
|
+
setActiveIndex(wantLast ? focusable[focusable.length - 1] : focusable[0]);
|
|
215
|
+
}, [isOpen]);
|
|
216
|
+
// Syncs real DOM focus to the roving-tabindex active item.
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (!isOpen || activeIndex < 0)
|
|
219
|
+
return;
|
|
220
|
+
itemRefs.current[activeIndex]?.focus();
|
|
221
|
+
}, [isOpen, activeIndex]);
|
|
222
|
+
// Close on outside click and Escape (with focus restoration).
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (!isOpen)
|
|
225
|
+
return;
|
|
226
|
+
const handleClickOutside = (e) => {
|
|
227
|
+
if (triggerRef.current &&
|
|
228
|
+
!triggerRef.current.contains(e.target) &&
|
|
229
|
+
menuRef.current &&
|
|
230
|
+
!menuRef.current.contains(e.target)) {
|
|
231
|
+
handleClose();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const handleEscape = (e) => {
|
|
235
|
+
if (e.key === 'Escape') {
|
|
236
|
+
closeAndRefocusTrigger();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
240
|
+
document.addEventListener('keydown', handleEscape);
|
|
241
|
+
return () => {
|
|
242
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
243
|
+
document.removeEventListener('keydown', handleEscape);
|
|
244
|
+
};
|
|
245
|
+
}, [isOpen, handleClose, closeAndRefocusTrigger]);
|
|
246
|
+
// Close on route change — a dropdown left open over the next page
|
|
247
|
+
// blocks clicks underneath. React Router's `location` updates on
|
|
248
|
+
// both in-app navigation and browser back/forward (popstate).
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
setOpen(false);
|
|
251
|
+
}, [location.pathname, location.search, setOpen]);
|
|
252
|
+
const handleMenuKeyDown = useCallback((e) => {
|
|
253
|
+
const focusable = focusableItemIndexes(items);
|
|
254
|
+
if (focusable.length === 0)
|
|
255
|
+
return;
|
|
256
|
+
const currentPos = focusable.indexOf(activeIndex);
|
|
257
|
+
switch (e.key) {
|
|
258
|
+
case 'ArrowDown': {
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
setActiveIndex(focusable[(currentPos + 1 + focusable.length) % focusable.length]);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'ArrowUp': {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
setActiveIndex(focusable[(currentPos - 1 + focusable.length) % focusable.length]);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case 'Home':
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
setActiveIndex(focusable[0]);
|
|
271
|
+
break;
|
|
272
|
+
case 'End':
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
setActiveIndex(focusable[focusable.length - 1]);
|
|
275
|
+
break;
|
|
276
|
+
case 'Tab':
|
|
277
|
+
// APG: native menus do not trap Tab — it exits the menu. Don't
|
|
278
|
+
// preventDefault (the browser still moves focus natively);
|
|
279
|
+
// just close our state in sync with focus leaving.
|
|
280
|
+
handleClose();
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}, [items, activeIndex, handleClose]);
|
|
286
|
+
// Calculate menu width
|
|
287
|
+
const menuWidth = width === 'trigger'
|
|
288
|
+
? // Not measurable until the trigger ref exists (the first open
|
|
289
|
+
// frame, before layout). Falling through to the raw 'trigger'
|
|
290
|
+
// string here previously set an invalid `width: 'trigger'` inline
|
|
291
|
+
// style (silently ignored) for that frame — `undefined` instead
|
|
292
|
+
// lets the menu size itself; a later re-render (position update)
|
|
293
|
+
// recomputes with the real pixel width once the ref is populated.
|
|
294
|
+
triggerRef.current?.offsetWidth
|
|
295
|
+
: typeof width === 'number'
|
|
296
|
+
? width
|
|
297
|
+
: width;
|
|
298
|
+
const menu = isOpen && (_jsx("div", { ref: menuRef, id: menuId, role: "menu", "data-state": "open", className: cm.cn(cm.dropdownContent, className), style: {
|
|
299
|
+
...style,
|
|
300
|
+
position: 'absolute',
|
|
301
|
+
top: position.top,
|
|
302
|
+
left: position.left,
|
|
303
|
+
width: menuWidth,
|
|
304
|
+
zIndex: 50,
|
|
305
|
+
}, "data-testid": testId ? `${testId}-menu` : undefined, onKeyDown: handleMenuKeyDown, children: items.map((item, index) => (_jsx(DropdownMenuItem, { item: item, isActive: index === activeIndex, onSelect: handleSelect, onClose: closeAndRefocusTrigger, registerRef: (node) => {
|
|
306
|
+
itemRefs.current[index] = node;
|
|
307
|
+
} }, item.separator ? `separator-${index}` : item.value))) }));
|
|
308
|
+
const triggerElement = trigger;
|
|
309
|
+
const isCloneableTrigger = React.isValidElement(triggerElement) &&
|
|
310
|
+
triggerElement.type !== React.Fragment;
|
|
311
|
+
const renderedTrigger = isCloneableTrigger ? (React.cloneElement(triggerElement, {
|
|
312
|
+
ref: (node) => {
|
|
313
|
+
triggerRef.current = node;
|
|
314
|
+
},
|
|
315
|
+
tabIndex: triggerElement.props.tabIndex ?? 0,
|
|
316
|
+
'aria-haspopup': 'menu',
|
|
317
|
+
'aria-expanded': isOpen,
|
|
318
|
+
'aria-controls': isOpen ? menuId : undefined,
|
|
319
|
+
'data-testid': testId,
|
|
320
|
+
onClick: (e) => {
|
|
321
|
+
;
|
|
322
|
+
triggerElement.props.onClick?.(e);
|
|
323
|
+
handleTriggerClick();
|
|
324
|
+
},
|
|
325
|
+
onKeyDown: (e) => {
|
|
326
|
+
;
|
|
327
|
+
triggerElement.props.onKeyDown?.(e);
|
|
328
|
+
handleTriggerKeyDown(e);
|
|
329
|
+
},
|
|
330
|
+
})) : (_jsx("div", { ref: (node) => {
|
|
331
|
+
triggerRef.current = node;
|
|
332
|
+
}, role: "button", tabIndex: 0, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? menuId : undefined, onClick: handleTriggerClick, onKeyDown: handleTriggerKeyDown, className: cm.dropdownTrigger, "data-testid": testId, children: trigger }));
|
|
333
|
+
return (_jsxs(_Fragment, { children: [renderedTrigger, typeof document !== 'undefined' && menu && createPortal(menu, document.body)] }));
|
|
334
|
+
});
|
|
335
|
+
Dropdown.displayName = 'Dropdown';
|
|
336
|
+
/**
|
|
337
|
+
* Dropdown label for grouping items.
|
|
338
|
+
*/
|
|
339
|
+
export const DropdownLabel = forwardRef(({ children, className }, ref) => {
|
|
340
|
+
const cm = getClassMap();
|
|
341
|
+
return (_jsx("div", { ref: ref, className: cm.cn(cm.dropdownLabel, className), children: children }));
|
|
342
|
+
});
|
|
343
|
+
DropdownLabel.displayName = 'DropdownLabel';
|
|
344
|
+
/**
|
|
345
|
+
* Dropdown separator for dividing groups.
|
|
346
|
+
*/
|
|
347
|
+
export const DropdownSeparator = forwardRef(({ className }, ref) => {
|
|
348
|
+
const cm = getClassMap();
|
|
349
|
+
return _jsx("div", { ref: ref, className: cm.cn(cm.dropdownSeparator, className), role: "separator" });
|
|
350
|
+
});
|
|
351
|
+
DropdownSeparator.displayName = 'DropdownSeparator';
|
|
352
|
+
//# sourceMappingURL=Dropdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Dropdown.js","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmptyState component — shown when a list or section has no data.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { EmptyStateProps } from '@molecule/app-ui';
|
|
8
|
+
/**
|
|
9
|
+
* EmptyState component.
|
|
10
|
+
*
|
|
11
|
+
* Displays a centered placeholder with an optional icon, title, description,
|
|
12
|
+
* and a primary action when a list or section has no content.
|
|
13
|
+
*/
|
|
14
|
+
export declare const EmptyState: React.ForwardRefExoticComponent<EmptyStateProps & React.RefAttributes<HTMLDivElement>>;
|
|
15
|
+
//# sourceMappingURL=EmptyState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../src/components/EmptyState.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAqB,MAAM,OAAO,CAAA;AAEzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAGvD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,wFAmBtB,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* EmptyState component — shown when a list or section has no data.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { forwardRef } from 'react';
|
|
8
|
+
import { getClassMap } from '@molecule/app-ui';
|
|
9
|
+
/**
|
|
10
|
+
* EmptyState component.
|
|
11
|
+
*
|
|
12
|
+
* Displays a centered placeholder with an optional icon, title, description,
|
|
13
|
+
* and a primary action when a list or section has no content.
|
|
14
|
+
*/
|
|
15
|
+
export const EmptyState = forwardRef(({ icon, title, description, action, className, style, testId, automationId }, ref) => {
|
|
16
|
+
const cm = getClassMap();
|
|
17
|
+
return (_jsxs("div", { ref: ref, className: cm.cn(cm.emptyState, className), style: style, "data-testid": testId, "data-mol-id": automationId, children: [icon ? _jsx("div", { className: cm.emptyStateIcon, children: icon }) : null, _jsx("h3", { className: cm.emptyStateTitle, children: title }), description ? _jsx("p", { className: cm.emptyStateDescription, children: description }) : null, action ? _jsx("div", { className: cm.emptyStateAction, children: action }) : null] }));
|
|
18
|
+
});
|
|
19
|
+
EmptyState.displayName = 'EmptyState';
|
|
20
|
+
//# sourceMappingURL=EmptyState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyState.js","sourceRoot":"","sources":["../../src/components/EmptyState.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAGzC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAClC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE;IACpF,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IAExB,OAAO,CACL,eACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,EAC1C,KAAK,EAAE,KAAK,iBACC,MAAM,iBACN,YAAY,aAExB,IAAI,CAAC,CAAC,CAAC,cAAK,SAAS,EAAE,EAAE,CAAC,cAAc,YAAG,IAAuB,GAAO,CAAC,CAAC,CAAC,IAAI,EACjF,aAAI,SAAS,EAAE,EAAE,CAAC,eAAe,YAAG,KAAK,GAAM,EAC9C,WAAW,CAAC,CAAC,CAAC,YAAG,SAAS,EAAE,EAAE,CAAC,qBAAqB,YAAG,WAAW,GAAK,CAAC,CAAC,CAAC,IAAI,EAC9E,MAAM,CAAC,CAAC,CAAC,cAAK,SAAS,EAAE,EAAE,CAAC,gBAAgB,YAAG,MAAyB,GAAO,CAAC,CAAC,CAAC,IAAI,IACnF,CACP,CAAA;AACH,CAAC,CACF,CAAA;AAED,UAAU,CAAC,WAAW,GAAG,YAAY,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type FloatingInputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
2
|
+
/**
|
|
3
|
+
* An input with a floating label.
|
|
4
|
+
*
|
|
5
|
+
* Shows the placeholder text as a small uppercase label always visible at
|
|
6
|
+
* the top of the input. On focus the label turns primary-colored.
|
|
7
|
+
*
|
|
8
|
+
* Works as both a controlled and an uncontrolled input: when `value` is
|
|
9
|
+
* omitted, the component tracks its own state seeded from `defaultValue`
|
|
10
|
+
* and still forwards every `onChange` event to the caller.
|
|
11
|
+
*
|
|
12
|
+
* The floating text is a real `<label htmlFor>` (not a bare `<span>`), so
|
|
13
|
+
* it is the input's programmatic accessible name via label association —
|
|
14
|
+
* previously the accessible name came only from `placeholder` (stripped by
|
|
15
|
+
* some AT/browser combos once the field has a value), and clicking the
|
|
16
|
+
* floating text did nothing because it wasn't associated with anything.
|
|
17
|
+
* `id` falls back to `useId()` when the caller doesn't pass one, mirroring
|
|
18
|
+
* Input/Select/Textarea's id-collision fix.
|
|
19
|
+
*/
|
|
20
|
+
export declare const FloatingInput: import("react").ForwardRefExoticComponent<FloatingInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=FloatingInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloatingInput.d.ts","sourceRoot":"","sources":["../../src/components/FloatingInput.tsx"],"names":[],"mappings":"AAIA,KAAK,kBAAkB,GAAG,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAA;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,iHAmCzB,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId, useState } from 'react';
|
|
3
|
+
import { getClassMap } from '@molecule/app-ui';
|
|
4
|
+
/**
|
|
5
|
+
* An input with a floating label.
|
|
6
|
+
*
|
|
7
|
+
* Shows the placeholder text as a small uppercase label always visible at
|
|
8
|
+
* the top of the input. On focus the label turns primary-colored.
|
|
9
|
+
*
|
|
10
|
+
* Works as both a controlled and an uncontrolled input: when `value` is
|
|
11
|
+
* omitted, the component tracks its own state seeded from `defaultValue`
|
|
12
|
+
* and still forwards every `onChange` event to the caller.
|
|
13
|
+
*
|
|
14
|
+
* The floating text is a real `<label htmlFor>` (not a bare `<span>`), so
|
|
15
|
+
* it is the input's programmatic accessible name via label association —
|
|
16
|
+
* previously the accessible name came only from `placeholder` (stripped by
|
|
17
|
+
* some AT/browser combos once the field has a value), and clicking the
|
|
18
|
+
* floating text did nothing because it wasn't associated with anything.
|
|
19
|
+
* `id` falls back to `useId()` when the caller doesn't pass one, mirroring
|
|
20
|
+
* Input/Select/Textarea's id-collision fix.
|
|
21
|
+
*/
|
|
22
|
+
export const FloatingInput = forwardRef(({ placeholder, className, value: valueProp, onChange, defaultValue, id, ...props }, ref) => {
|
|
23
|
+
const cm = getClassMap();
|
|
24
|
+
const [internalValue, setInternalValue] = useState(String(defaultValue ?? ''));
|
|
25
|
+
const isControlled = valueProp !== undefined;
|
|
26
|
+
const value = isControlled ? valueProp : internalValue;
|
|
27
|
+
const generatedId = useId();
|
|
28
|
+
const inputId = id || generatedId;
|
|
29
|
+
const handleChange = (e) => {
|
|
30
|
+
if (!isControlled) {
|
|
31
|
+
setInternalValue(e.target.value);
|
|
32
|
+
}
|
|
33
|
+
onChange?.(e);
|
|
34
|
+
};
|
|
35
|
+
return (_jsxs("span", { className: cm.floatingInputWrapper, children: [_jsx("input", { ref: ref, id: inputId, ...props, placeholder: placeholder, className: cm.cn(cm.input(), cm.floatingInput, className), value: value, onChange: handleChange }), placeholder && (_jsx("label", { htmlFor: inputId, className: cm.floatingLabel, children: placeholder }))] }));
|
|
36
|
+
});
|
|
37
|
+
FloatingInput.displayName = 'FloatingInput';
|
|
38
|
+
//# sourceMappingURL=FloatingInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloatingInput.js","sourceRoot":"","sources":["../../src/components/FloatingInput.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAI9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CACrC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;IAC1F,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACxB,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAA;IAC9E,MAAM,YAAY,GAAG,SAAS,KAAK,SAAS,CAAA;IAC5C,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAA;IACtD,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,MAAM,OAAO,GAAG,EAAE,IAAI,WAAW,CAAA;IAEjC,MAAM,YAAY,GAAG,CAAC,CAAsC,EAAQ,EAAE;QACpE,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAClC,CAAC;QACD,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;IACf,CAAC,CAAA;IAED,OAAO,CACL,gBAAM,SAAS,EAAE,EAAE,CAAC,oBAAoB,aACtC,gBACE,GAAG,EAAE,GAAG,EACR,EAAE,EAAE,OAAO,KACP,KAAK,EACT,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACzD,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,YAAY,GACtB,EACD,WAAW,IAAI,CACd,gBAAO,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,aAAa,YACjD,WAAW,GACN,CACT,IACI,CACR,CAAA;AACH,CAAC,CACF,CAAA;AACD,aAAa,CAAC,WAAW,GAAG,eAAe,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form components.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { FormFieldProps, FormProps } from '@molecule/app-ui';
|
|
8
|
+
/**
|
|
9
|
+
* Form component.
|
|
10
|
+
*/
|
|
11
|
+
export declare const Form: React.ForwardRefExoticComponent<FormProps & React.RefAttributes<HTMLFormElement>>;
|
|
12
|
+
/**
|
|
13
|
+
* Form field wrapper component.
|
|
14
|
+
*/
|
|
15
|
+
export declare const FormField: React.ForwardRefExoticComponent<FormFieldProps & React.RefAttributes<HTMLDivElement>>;
|
|
16
|
+
/**
|
|
17
|
+
* Label component.
|
|
18
|
+
*/
|
|
19
|
+
export declare const Label: React.ForwardRefExoticComponent<React.LabelHTMLAttributes<HTMLLabelElement> & {
|
|
20
|
+
required?: boolean;
|
|
21
|
+
} & React.RefAttributes<HTMLLabelElement>>;
|
|
22
|
+
//# sourceMappingURL=Form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../src/components/Form.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAA4B,MAAM,OAAO,CAAA;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAGjE;;GAEG;AACH,eAAO,MAAM,IAAI,mFAiDhB,CAAA;AAID;;GAEG;AACH,eAAO,MAAM,SAAS,uFAwErB,CAAA;AAID;;GAEG;AACH,eAAO,MAAM,KAAK;eAE2C,OAAO;0CASlE,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Form components.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import React, { forwardRef, useId } from 'react';
|
|
8
|
+
import { getClassMap } from '@molecule/app-ui';
|
|
9
|
+
/**
|
|
10
|
+
* Form component.
|
|
11
|
+
*/
|
|
12
|
+
export const Form = forwardRef(({ children, onFormSubmit, submitting, className, style, testId, onSubmit, action, method, noValidate, ...rest }, ref) => {
|
|
13
|
+
const cm = getClassMap();
|
|
14
|
+
const handleSubmit = (e) => {
|
|
15
|
+
if (onFormSubmit) {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
const formData = new FormData(e.currentTarget);
|
|
18
|
+
const data = {};
|
|
19
|
+
formData.forEach((value, key) => {
|
|
20
|
+
data[key] = value;
|
|
21
|
+
});
|
|
22
|
+
onFormSubmit(data);
|
|
23
|
+
}
|
|
24
|
+
onSubmit?.(e);
|
|
25
|
+
};
|
|
26
|
+
return (_jsx("form", { ref: ref, className: className, style: style, "data-testid": testId, action: action, method: method, noValidate: noValidate, onSubmit: handleSubmit, ...rest, children: _jsx("fieldset", { disabled: submitting, className: cm.formFieldsetContents, children: children }) }));
|
|
27
|
+
});
|
|
28
|
+
Form.displayName = 'Form';
|
|
29
|
+
/**
|
|
30
|
+
* Form field wrapper component.
|
|
31
|
+
*/
|
|
32
|
+
export const FormField = forwardRef(({ children, label, name, error, hint, required, className, style, testId }, ref) => {
|
|
33
|
+
const cm = getClassMap();
|
|
34
|
+
// Fall back to a generated id when `name` is absent so `htmlFor`/the
|
|
35
|
+
// error-message id never resolve to the literal string "undefined" —
|
|
36
|
+
// and so they never collide across multiple id-less FormFields on the
|
|
37
|
+
// same page (same class of bug fixed inside Input/Select/Textarea).
|
|
38
|
+
const generatedId = useId();
|
|
39
|
+
const fieldId = name || generatedId;
|
|
40
|
+
const errorId = `${fieldId}-error`;
|
|
41
|
+
const hintId = `${fieldId}-hint`;
|
|
42
|
+
const describedBy = error ? errorId : hint ? hintId : undefined;
|
|
43
|
+
// The error/hint <p> below is a same-DOM sibling — invisible to a
|
|
44
|
+
// screen reader unless something actually points `aria-describedby` at
|
|
45
|
+
// it. When `children` is a single real element (the common case: one
|
|
46
|
+
// Input/Select/Textarea), inject `id`/`aria-describedby`/`aria-invalid`
|
|
47
|
+
// onto it directly so its own label association and the announced
|
|
48
|
+
// description both resolve correctly. Composite/text children can't be
|
|
49
|
+
// safely cloned into, so they fall back to label-only association —
|
|
50
|
+
// consumers with custom composite fields should wire their own
|
|
51
|
+
// `aria-describedby` (Input/Select/Textarea's own `error` prop remains
|
|
52
|
+
// the fully-automatic path).
|
|
53
|
+
const singleChild = React.isValidElement(children) ? children : null;
|
|
54
|
+
const content = singleChild
|
|
55
|
+
? React.cloneElement(singleChild, {
|
|
56
|
+
id: singleChild.props.id || fieldId,
|
|
57
|
+
...(describedBy
|
|
58
|
+
? {
|
|
59
|
+
'aria-describedby': [
|
|
60
|
+
singleChild.props['aria-describedby'],
|
|
61
|
+
describedBy,
|
|
62
|
+
]
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join(' '),
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
...(error ? { 'aria-invalid': true } : {}),
|
|
68
|
+
})
|
|
69
|
+
: children;
|
|
70
|
+
return (_jsxs("div", { ref: ref, className: cm.cn(cm.inputWrapper, cm.sp('mb', 4), className), style: style, "data-testid": testId, children: [label && (_jsx("label", { htmlFor: singleChild ? singleChild.props.id || fieldId : fieldId, className: cm.cn(cm.label({ required: !!required }), cm.labelBlock), children: label })), content, error && (_jsx("p", { id: errorId, className: cm.formError, children: error })), hint && !error && (_jsx("p", { id: hintId, className: cm.formHint, children: hint }))] }));
|
|
71
|
+
});
|
|
72
|
+
FormField.displayName = 'FormField';
|
|
73
|
+
/**
|
|
74
|
+
* Label component.
|
|
75
|
+
*/
|
|
76
|
+
export const Label = forwardRef(({ children, required, className, ...props }, ref) => {
|
|
77
|
+
const cm = getClassMap();
|
|
78
|
+
return (_jsx("label", { ref: ref, className: cm.cn(cm.label({ required: !!required }), className), ...props, children: children }));
|
|
79
|
+
});
|
|
80
|
+
Label.displayName = 'Label';
|
|
81
|
+
//# sourceMappingURL=Form.js.map
|