@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.
Files changed (179) hide show
  1. package/LICENSE +115 -0
  2. package/README.md +2786 -0
  3. package/dist/components/Accordion.d.ts +12 -0
  4. package/dist/components/Accordion.d.ts.map +1 -0
  5. package/dist/components/Accordion.js +80 -0
  6. package/dist/components/Accordion.js.map +1 -0
  7. package/dist/components/Alert.d.ts +26 -0
  8. package/dist/components/Alert.d.ts.map +1 -0
  9. package/dist/components/Alert.js +53 -0
  10. package/dist/components/Alert.js.map +1 -0
  11. package/dist/components/AuthGuard.d.ts +66 -0
  12. package/dist/components/AuthGuard.d.ts.map +1 -0
  13. package/dist/components/AuthGuard.js +42 -0
  14. package/dist/components/AuthGuard.js.map +1 -0
  15. package/dist/components/Avatar.d.ts +12 -0
  16. package/dist/components/Avatar.d.ts.map +1 -0
  17. package/dist/components/Avatar.js +25 -0
  18. package/dist/components/Avatar.js.map +1 -0
  19. package/dist/components/Badge.d.ts +12 -0
  20. package/dist/components/Badge.d.ts.map +1 -0
  21. package/dist/components/Badge.js +18 -0
  22. package/dist/components/Badge.js.map +1 -0
  23. package/dist/components/Button.d.ts +12 -0
  24. package/dist/components/Button.d.ts.map +1 -0
  25. package/dist/components/Button.js +19 -0
  26. package/dist/components/Button.js.map +1 -0
  27. package/dist/components/Card.d.ts +34 -0
  28. package/dist/components/Card.d.ts.map +1 -0
  29. package/dist/components/Card.js +71 -0
  30. package/dist/components/Card.js.map +1 -0
  31. package/dist/components/Checkbox.d.ts +12 -0
  32. package/dist/components/Checkbox.d.ts.map +1 -0
  33. package/dist/components/Checkbox.js +41 -0
  34. package/dist/components/Checkbox.js.map +1 -0
  35. package/dist/components/Dropdown.d.ts +38 -0
  36. package/dist/components/Dropdown.d.ts.map +1 -0
  37. package/dist/components/Dropdown.js +352 -0
  38. package/dist/components/Dropdown.js.map +1 -0
  39. package/dist/components/EmptyState.d.ts +15 -0
  40. package/dist/components/EmptyState.d.ts.map +1 -0
  41. package/dist/components/EmptyState.js +20 -0
  42. package/dist/components/EmptyState.js.map +1 -0
  43. package/dist/components/FloatingInput.d.ts +22 -0
  44. package/dist/components/FloatingInput.d.ts.map +1 -0
  45. package/dist/components/FloatingInput.js +38 -0
  46. package/dist/components/FloatingInput.js.map +1 -0
  47. package/dist/components/Form.d.ts +22 -0
  48. package/dist/components/Form.d.ts.map +1 -0
  49. package/dist/components/Form.js +81 -0
  50. package/dist/components/Form.js.map +1 -0
  51. package/dist/components/Icon.d.ts +48 -0
  52. package/dist/components/Icon.d.ts.map +1 -0
  53. package/dist/components/Icon.js +46 -0
  54. package/dist/components/Icon.js.map +1 -0
  55. package/dist/components/Input.d.ts +12 -0
  56. package/dist/components/Input.d.ts.map +1 -0
  57. package/dist/components/Input.js +25 -0
  58. package/dist/components/Input.js.map +1 -0
  59. package/dist/components/LanguagePicker.d.ts +60 -0
  60. package/dist/components/LanguagePicker.d.ts.map +1 -0
  61. package/dist/components/LanguagePicker.js +45 -0
  62. package/dist/components/LanguagePicker.js.map +1 -0
  63. package/dist/components/Layout.d.ts +24 -0
  64. package/dist/components/Layout.d.ts.map +1 -0
  65. package/dist/components/Layout.js +104 -0
  66. package/dist/components/Layout.js.map +1 -0
  67. package/dist/components/Modal.d.ts +32 -0
  68. package/dist/components/Modal.d.ts.map +1 -0
  69. package/dist/components/Modal.js +235 -0
  70. package/dist/components/Modal.js.map +1 -0
  71. package/dist/components/PageHeader.d.ts +15 -0
  72. package/dist/components/PageHeader.d.ts.map +1 -0
  73. package/dist/components/PageHeader.js +20 -0
  74. package/dist/components/PageHeader.js.map +1 -0
  75. package/dist/components/PageShell.d.ts +15 -0
  76. package/dist/components/PageShell.d.ts.map +1 -0
  77. package/dist/components/PageShell.js +20 -0
  78. package/dist/components/PageShell.js.map +1 -0
  79. package/dist/components/Pagination.d.ts +12 -0
  80. package/dist/components/Pagination.d.ts.map +1 -0
  81. package/dist/components/Pagination.js +78 -0
  82. package/dist/components/Pagination.js.map +1 -0
  83. package/dist/components/PanelClose.d.ts +40 -0
  84. package/dist/components/PanelClose.d.ts.map +1 -0
  85. package/dist/components/PanelClose.js +32 -0
  86. package/dist/components/PanelClose.js.map +1 -0
  87. package/dist/components/Progress.d.ts +45 -0
  88. package/dist/components/Progress.d.ts.map +1 -0
  89. package/dist/components/Progress.js +21 -0
  90. package/dist/components/Progress.js.map +1 -0
  91. package/dist/components/RadioGroup.d.ts +12 -0
  92. package/dist/components/RadioGroup.d.ts.map +1 -0
  93. package/dist/components/RadioGroup.js +34 -0
  94. package/dist/components/RadioGroup.js.map +1 -0
  95. package/dist/components/Select.d.ts +12 -0
  96. package/dist/components/Select.d.ts.map +1 -0
  97. package/dist/components/Select.js +57 -0
  98. package/dist/components/Select.js.map +1 -0
  99. package/dist/components/Separator.d.ts +25 -0
  100. package/dist/components/Separator.d.ts.map +1 -0
  101. package/dist/components/Separator.js +18 -0
  102. package/dist/components/Separator.js.map +1 -0
  103. package/dist/components/SidebarUserCard.d.ts +60 -0
  104. package/dist/components/SidebarUserCard.d.ts.map +1 -0
  105. package/dist/components/SidebarUserCard.js +45 -0
  106. package/dist/components/SidebarUserCard.js.map +1 -0
  107. package/dist/components/Skeleton.d.ts +26 -0
  108. package/dist/components/Skeleton.d.ts.map +1 -0
  109. package/dist/components/Skeleton.js +45 -0
  110. package/dist/components/Skeleton.js.map +1 -0
  111. package/dist/components/Spinner.d.ts +17 -0
  112. package/dist/components/Spinner.d.ts.map +1 -0
  113. package/dist/components/Spinner.js +32 -0
  114. package/dist/components/Spinner.js.map +1 -0
  115. package/dist/components/Switch.d.ts +12 -0
  116. package/dist/components/Switch.d.ts.map +1 -0
  117. package/dist/components/Switch.js +52 -0
  118. package/dist/components/Switch.js.map +1 -0
  119. package/dist/components/Table.d.ts +12 -0
  120. package/dist/components/Table.d.ts.map +1 -0
  121. package/dist/components/Table.js +53 -0
  122. package/dist/components/Table.js.map +1 -0
  123. package/dist/components/Tabs.d.ts +12 -0
  124. package/dist/components/Tabs.d.ts.map +1 -0
  125. package/dist/components/Tabs.js +72 -0
  126. package/dist/components/Tabs.js.map +1 -0
  127. package/dist/components/Textarea.d.ts +12 -0
  128. package/dist/components/Textarea.d.ts.map +1 -0
  129. package/dist/components/Textarea.js +66 -0
  130. package/dist/components/Textarea.js.map +1 -0
  131. package/dist/components/ThemeToggle.d.ts +43 -0
  132. package/dist/components/ThemeToggle.d.ts.map +1 -0
  133. package/dist/components/ThemeToggle.js +30 -0
  134. package/dist/components/ThemeToggle.js.map +1 -0
  135. package/dist/components/Toast.d.ts +61 -0
  136. package/dist/components/Toast.d.ts.map +1 -0
  137. package/dist/components/Toast.js +159 -0
  138. package/dist/components/Toast.js.map +1 -0
  139. package/dist/components/Tooltip.d.ts +21 -0
  140. package/dist/components/Tooltip.d.ts.map +1 -0
  141. package/dist/components/Tooltip.js +245 -0
  142. package/dist/components/Tooltip.js.map +1 -0
  143. package/dist/components/UserMenu.d.ts +59 -0
  144. package/dist/components/UserMenu.d.ts.map +1 -0
  145. package/dist/components/UserMenu.js +34 -0
  146. package/dist/components/UserMenu.js.map +1 -0
  147. package/dist/components/UserMenuPopover.d.ts +139 -0
  148. package/dist/components/UserMenuPopover.d.ts.map +1 -0
  149. package/dist/components/UserMenuPopover.js +173 -0
  150. package/dist/components/UserMenuPopover.js.map +1 -0
  151. package/dist/components/index.d.ts +43 -0
  152. package/dist/components/index.d.ts.map +1 -0
  153. package/dist/components/index.js +43 -0
  154. package/dist/components/index.js.map +1 -0
  155. package/dist/index.d.ts +131 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +131 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/types.d.ts +7 -0
  160. package/dist/types.d.ts.map +1 -0
  161. package/dist/types.js +7 -0
  162. package/dist/types.js.map +1 -0
  163. package/dist/utilities/index.d.ts +8 -0
  164. package/dist/utilities/index.d.ts.map +1 -0
  165. package/dist/utilities/index.js +8 -0
  166. package/dist/utilities/index.js.map +1 -0
  167. package/dist/utilities/initials.d.ts +13 -0
  168. package/dist/utilities/initials.d.ts.map +1 -0
  169. package/dist/utilities/initials.js +23 -0
  170. package/dist/utilities/initials.js.map +1 -0
  171. package/dist/utilities/renderIcon.d.ts +21 -0
  172. package/dist/utilities/renderIcon.d.ts.map +1 -0
  173. package/dist/utilities/renderIcon.js +40 -0
  174. package/dist/utilities/renderIcon.js.map +1 -0
  175. package/dist/utilities.d.ts +12 -0
  176. package/dist/utilities.d.ts.map +1 -0
  177. package/dist/utilities.js +14 -0
  178. package/dist/utilities.js.map +1 -0
  179. 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