@hadoku/task-ui-components 0.0.0-stage → 4.20.4

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 (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +917 -2
  3. package/dist/app-header.css +288 -0
  4. package/dist/bento.css +143 -0
  5. package/dist/button.css +227 -0
  6. package/dist/components/AppHeader.d.ts +61 -0
  7. package/dist/components/AppHeader.d.ts.map +1 -0
  8. package/dist/components/AppHeader.js +23 -0
  9. package/dist/components/AppHeader.js.map +1 -0
  10. package/dist/components/BentoCard.d.ts +23 -0
  11. package/dist/components/BentoCard.d.ts.map +1 -0
  12. package/dist/components/BentoCard.js +49 -0
  13. package/dist/components/BentoCard.js.map +1 -0
  14. package/dist/components/BentoGrid.d.ts +24 -0
  15. package/dist/components/BentoGrid.d.ts.map +1 -0
  16. package/dist/components/BentoGrid.js +22 -0
  17. package/dist/components/BentoGrid.js.map +1 -0
  18. package/dist/components/Button.d.ts +28 -0
  19. package/dist/components/Button.d.ts.map +1 -0
  20. package/dist/components/Button.js +51 -0
  21. package/dist/components/Button.js.map +1 -0
  22. package/dist/components/ConnectedSettings.d.ts +53 -0
  23. package/dist/components/ConnectedSettings.d.ts.map +1 -0
  24. package/dist/components/ConnectedSettings.js +368 -0
  25. package/dist/components/ConnectedSettings.js.map +1 -0
  26. package/dist/components/ConnectedThemePicker.d.ts +52 -0
  27. package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
  28. package/dist/components/ConnectedThemePicker.js +32 -0
  29. package/dist/components/ConnectedThemePicker.js.map +1 -0
  30. package/dist/components/ContextMenu.d.ts +22 -0
  31. package/dist/components/ContextMenu.d.ts.map +1 -0
  32. package/dist/components/ContextMenu.js +23 -0
  33. package/dist/components/ContextMenu.js.map +1 -0
  34. package/dist/components/HadokuThemePicker.d.ts +27 -0
  35. package/dist/components/HadokuThemePicker.d.ts.map +1 -0
  36. package/dist/components/HadokuThemePicker.js +19 -0
  37. package/dist/components/HadokuThemePicker.js.map +1 -0
  38. package/dist/components/LoadingSkeleton.d.ts +14 -0
  39. package/dist/components/LoadingSkeleton.d.ts.map +1 -0
  40. package/dist/components/LoadingSkeleton.js +62 -0
  41. package/dist/components/LoadingSkeleton.js.map +1 -0
  42. package/dist/components/Modal.d.ts +39 -0
  43. package/dist/components/Modal.d.ts.map +1 -0
  44. package/dist/components/Modal.js +106 -0
  45. package/dist/components/Modal.js.map +1 -0
  46. package/dist/components/NumberInput.d.ts +26 -0
  47. package/dist/components/NumberInput.d.ts.map +1 -0
  48. package/dist/components/NumberInput.js +131 -0
  49. package/dist/components/NumberInput.js.map +1 -0
  50. package/dist/components/Select.d.ts +52 -0
  51. package/dist/components/Select.d.ts.map +1 -0
  52. package/dist/components/Select.js +281 -0
  53. package/dist/components/Select.js.map +1 -0
  54. package/dist/components/SettingsModal.d.ts +69 -0
  55. package/dist/components/SettingsModal.d.ts.map +1 -0
  56. package/dist/components/SettingsModal.js +141 -0
  57. package/dist/components/SettingsModal.js.map +1 -0
  58. package/dist/components/ThemeIcons.d.ts +26 -0
  59. package/dist/components/ThemeIcons.d.ts.map +1 -0
  60. package/dist/components/ThemeIcons.js +74 -0
  61. package/dist/components/ThemeIcons.js.map +1 -0
  62. package/dist/components/ThemePicker.d.ts +28 -0
  63. package/dist/components/ThemePicker.d.ts.map +1 -0
  64. package/dist/components/ThemePicker.js +101 -0
  65. package/dist/components/ThemePicker.js.map +1 -0
  66. package/dist/components/Toast.d.ts +13 -0
  67. package/dist/components/Toast.d.ts.map +1 -0
  68. package/dist/components/Toast.js +70 -0
  69. package/dist/components/Toast.js.map +1 -0
  70. package/dist/components/Toaster.d.ts +19 -0
  71. package/dist/components/Toaster.d.ts.map +1 -0
  72. package/dist/components/Toaster.js +19 -0
  73. package/dist/components/Toaster.js.map +1 -0
  74. package/dist/components/Tooltip.d.ts +49 -0
  75. package/dist/components/Tooltip.d.ts.map +1 -0
  76. package/dist/components/Tooltip.js +143 -0
  77. package/dist/components/Tooltip.js.map +1 -0
  78. package/dist/context-menu.css +11 -0
  79. package/dist/controls.css +137 -0
  80. package/dist/hooks/useReducedMotion.d.ts +7 -0
  81. package/dist/hooks/useReducedMotion.d.ts.map +1 -0
  82. package/dist/hooks/useReducedMotion.js +25 -0
  83. package/dist/hooks/useReducedMotion.js.map +1 -0
  84. package/dist/hooks/useToast.d.ts +18 -0
  85. package/dist/hooks/useToast.d.ts.map +1 -0
  86. package/dist/hooks/useToast.js +28 -0
  87. package/dist/hooks/useToast.js.map +1 -0
  88. package/dist/icon-btn.css +64 -0
  89. package/dist/index.d.ts +49 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +75 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/menuPlacement.d.ts +16 -0
  94. package/dist/lib/menuPlacement.d.ts.map +1 -0
  95. package/dist/lib/menuPlacement.js +19 -0
  96. package/dist/lib/menuPlacement.js.map +1 -0
  97. package/dist/lib/selectMenus.d.ts +30 -0
  98. package/dist/lib/selectMenus.d.ts.map +1 -0
  99. package/dist/lib/selectMenus.js +236 -0
  100. package/dist/lib/selectMenus.js.map +1 -0
  101. package/dist/lib/settingsClient.d.ts +144 -0
  102. package/dist/lib/settingsClient.d.ts.map +1 -0
  103. package/dist/lib/settingsClient.js +157 -0
  104. package/dist/lib/settingsClient.js.map +1 -0
  105. package/dist/lib/themeScope.d.ts +19 -0
  106. package/dist/lib/themeScope.d.ts.map +1 -0
  107. package/dist/lib/themeScope.js +10 -0
  108. package/dist/lib/themeScope.js.map +1 -0
  109. package/dist/lib/titleTooltips.d.ts +38 -0
  110. package/dist/lib/titleTooltips.d.ts.map +1 -0
  111. package/dist/lib/titleTooltips.js +166 -0
  112. package/dist/lib/titleTooltips.js.map +1 -0
  113. package/dist/lib/tooltipPlacement.d.ts +14 -0
  114. package/dist/lib/tooltipPlacement.d.ts.map +1 -0
  115. package/dist/lib/tooltipPlacement.js +34 -0
  116. package/dist/lib/tooltipPlacement.js.map +1 -0
  117. package/dist/modal.css +129 -0
  118. package/dist/number-input.css +131 -0
  119. package/dist/scrollbar.css +165 -0
  120. package/dist/select.css +251 -0
  121. package/dist/styles.css +1866 -0
  122. package/dist/task-ui-components.css +1866 -0
  123. package/dist/theme-picker.css +219 -0
  124. package/dist/toaster.css +159 -0
  125. package/dist/tooltip.css +103 -0
  126. package/dist/types/index.d.ts +37 -0
  127. package/dist/types/index.d.ts.map +1 -0
  128. package/package.json +60 -4
  129. package/src/app-header.css +288 -0
  130. package/src/bento.css +143 -0
  131. package/src/button.css +227 -0
  132. package/src/components/AppHeader.tsx +87 -0
  133. package/src/components/BentoCard.tsx +72 -0
  134. package/src/components/BentoGrid.tsx +48 -0
  135. package/src/components/Button.tsx +81 -0
  136. package/src/components/ConnectedSettings.tsx +545 -0
  137. package/src/components/ConnectedThemePicker.tsx +79 -0
  138. package/src/components/ContextMenu.tsx +48 -0
  139. package/src/components/HadokuThemePicker.tsx +39 -0
  140. package/src/components/LoadingSkeleton.tsx +84 -0
  141. package/src/components/Modal.tsx +171 -0
  142. package/src/components/NumberInput.tsx +161 -0
  143. package/src/components/Select.tsx +359 -0
  144. package/src/components/SettingsModal.tsx +235 -0
  145. package/src/components/ThemeIcons.tsx +127 -0
  146. package/src/components/ThemePicker.tsx +149 -0
  147. package/src/components/Toast.tsx +92 -0
  148. package/src/components/Toaster.tsx +43 -0
  149. package/src/components/Tooltip.tsx +212 -0
  150. package/src/context-menu.css +11 -0
  151. package/src/controls.css +137 -0
  152. package/src/hooks/useReducedMotion.ts +36 -0
  153. package/src/hooks/useToast.ts +57 -0
  154. package/src/icon-btn.css +64 -0
  155. package/src/index.ts +153 -0
  156. package/src/lib/menuPlacement.ts +31 -0
  157. package/src/lib/selectMenus.ts +303 -0
  158. package/src/lib/settingsClient.ts +425 -0
  159. package/src/lib/themeScope.ts +23 -0
  160. package/src/lib/titleTooltips.ts +236 -0
  161. package/src/lib/tooltipPlacement.ts +59 -0
  162. package/src/modal.css +129 -0
  163. package/src/number-input.css +131 -0
  164. package/src/scrollbar.css +165 -0
  165. package/src/select.css +251 -0
  166. package/src/theme-picker.css +219 -0
  167. package/src/toaster.css +159 -0
  168. package/src/tooltip.css +103 -0
  169. package/src/types/index.ts +43 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * LoadingSkeleton Component
3
+ * Displays a loading skeleton with theme support
4
+ * Can be customized with different layouts
5
+ */
6
+ import React from 'react';
7
+ export interface LoadingSkeletonProps {
8
+ isDarkTheme?: boolean;
9
+ className?: string;
10
+ layout?: 'default' | 'minimal' | 'custom';
11
+ customContent?: React.ReactNode;
12
+ }
13
+ export declare function LoadingSkeleton({ isDarkTheme, className, layout, customContent }: LoadingSkeletonProps): React.JSX.Element;
14
+ //# sourceMappingURL=LoadingSkeleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingSkeleton.d.ts","sourceRoot":"","sources":["../../src/components/LoadingSkeleton.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IACzC,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAChC;AAED,wBAAgB,eAAe,CAAC,EAC9B,WAAmB,EACnB,SAAc,EACd,MAAkB,EAClB,aAAa,EACd,EAAE,oBAAoB,qBA+DtB"}
@@ -0,0 +1,62 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ function LoadingSkeleton({
3
+ isDarkTheme = false,
4
+ className = "",
5
+ layout = "default",
6
+ customContent
7
+ }) {
8
+ if (layout === "custom" && customContent) {
9
+ return /* @__PURE__ */ jsx(
10
+ "div",
11
+ {
12
+ className: `loading-skeleton ${className}`,
13
+ "data-dark-theme": isDarkTheme ? "true" : "false",
14
+ children: customContent
15
+ }
16
+ );
17
+ }
18
+ if (layout === "minimal") {
19
+ return /* @__PURE__ */ jsx(
20
+ "div",
21
+ {
22
+ className: `loading-skeleton loading-skeleton--minimal ${className}`,
23
+ "data-dark-theme": isDarkTheme ? "true" : "false",
24
+ children: /* @__PURE__ */ jsx("div", { className: "skeleton-spinner" })
25
+ }
26
+ );
27
+ }
28
+ return /* @__PURE__ */ jsx(
29
+ "div",
30
+ {
31
+ className: `loading-skeleton ${className}`,
32
+ "data-dark-theme": isDarkTheme ? "true" : "false",
33
+ children: /* @__PURE__ */ jsxs("div", { className: "loading-skeleton__content", children: [
34
+ /* @__PURE__ */ jsxs("div", { className: "skeleton-header-row", children: [
35
+ /* @__PURE__ */ jsx("div", { className: "skeleton-header" }),
36
+ /* @__PURE__ */ jsx("div", { className: "skeleton-button" })
37
+ ] }),
38
+ /* @__PURE__ */ jsxs("div", { className: "skeleton-nav", children: [
39
+ /* @__PURE__ */ jsx("div", { className: "skeleton-nav-item" }),
40
+ /* @__PURE__ */ jsx("div", { className: "skeleton-nav-item" }),
41
+ /* @__PURE__ */ jsx("div", { className: "skeleton-nav-item" })
42
+ ] }),
43
+ /* @__PURE__ */ jsx("div", { className: "skeleton-input" }),
44
+ /* @__PURE__ */ jsxs("div", { className: "skeleton-filters", children: [
45
+ /* @__PURE__ */ jsx("div", { className: "skeleton-filter" }),
46
+ /* @__PURE__ */ jsx("div", { className: "skeleton-filter" }),
47
+ /* @__PURE__ */ jsx("div", { className: "skeleton-filter" })
48
+ ] }),
49
+ /* @__PURE__ */ jsxs("div", { className: "skeleton-content", children: [
50
+ /* @__PURE__ */ jsx("div", { className: "skeleton-content-header" }),
51
+ /* @__PURE__ */ jsx("div", { className: "skeleton-item" }),
52
+ /* @__PURE__ */ jsx("div", { className: "skeleton-item" }),
53
+ /* @__PURE__ */ jsx("div", { className: "skeleton-item" })
54
+ ] })
55
+ ] })
56
+ }
57
+ );
58
+ }
59
+ export {
60
+ LoadingSkeleton
61
+ };
62
+ //# sourceMappingURL=LoadingSkeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingSkeleton.js","sources":["../../src/components/LoadingSkeleton.tsx"],"sourcesContent":["/**\r\n * LoadingSkeleton Component\r\n * Displays a loading skeleton with theme support\r\n * Can be customized with different layouts\r\n */\r\n\r\nimport React from 'react'\r\n\r\nexport interface LoadingSkeletonProps {\r\n isDarkTheme?: boolean\r\n className?: string\r\n layout?: 'default' | 'minimal' | 'custom'\r\n customContent?: React.ReactNode\r\n}\r\n\r\nexport function LoadingSkeleton({\r\n isDarkTheme = false,\r\n className = '',\r\n layout = 'default',\r\n customContent\r\n}: LoadingSkeletonProps) {\r\n if (layout === 'custom' && customContent) {\r\n return (\r\n <div\r\n className={`loading-skeleton ${className}`}\r\n data-dark-theme={isDarkTheme ? 'true' : 'false'}\r\n >\r\n {customContent}\r\n </div>\r\n )\r\n }\r\n\r\n if (layout === 'minimal') {\r\n return (\r\n <div\r\n className={`loading-skeleton loading-skeleton--minimal ${className}`}\r\n data-dark-theme={isDarkTheme ? 'true' : 'false'}\r\n >\r\n <div className=\"skeleton-spinner\"></div>\r\n </div>\r\n )\r\n }\r\n\r\n // Default layout - task app specific but can be overridden\r\n return (\r\n <div\r\n className={`loading-skeleton ${className}`}\r\n data-dark-theme={isDarkTheme ? 'true' : 'false'}\r\n >\r\n <div className=\"loading-skeleton__content\">\r\n {/* Header */}\r\n <div className=\"skeleton-header-row\">\r\n <div className=\"skeleton-header\"></div>\r\n <div className=\"skeleton-button\"></div>\r\n </div>\r\n\r\n {/* Navigation */}\r\n <div className=\"skeleton-nav\">\r\n <div className=\"skeleton-nav-item\"></div>\r\n <div className=\"skeleton-nav-item\"></div>\r\n <div className=\"skeleton-nav-item\"></div>\r\n </div>\r\n\r\n {/* Input */}\r\n <div className=\"skeleton-input\"></div>\r\n\r\n {/* Filters */}\r\n <div className=\"skeleton-filters\">\r\n <div className=\"skeleton-filter\"></div>\r\n <div className=\"skeleton-filter\"></div>\r\n <div className=\"skeleton-filter\"></div>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"skeleton-content\">\r\n <div className=\"skeleton-content-header\"></div>\r\n <div className=\"skeleton-item\"></div>\r\n <div className=\"skeleton-item\"></div>\r\n <div className=\"skeleton-item\"></div>\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";AAeO,SAAS,gBAAgB;AAAA,EAC9B,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,SAAS;AAAA,EACT;AACF,GAAyB;AACvB,MAAI,WAAW,YAAY,eAAe;AACxC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,oBAAoB,SAAS;AAAA,QACxC,mBAAiB,cAAc,SAAS;AAAA,QAEvC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,MAAI,WAAW,WAAW;AACxB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,8CAA8C,SAAS;AAAA,QAClE,mBAAiB,cAAc,SAAS;AAAA,QAExC,UAAA,oBAAC,OAAA,EAAI,WAAU,mBAAA,CAAmB;AAAA,MAAA;AAAA,IAAA;AAAA,EAGxC;AAGA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,oBAAoB,SAAS;AAAA,MACxC,mBAAiB,cAAc,SAAS;AAAA,MAExC,UAAA,qBAAC,OAAA,EAAI,WAAU,6BAEb,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,kBAAA,CAAkB;AAAA,UACjC,oBAAC,OAAA,EAAI,WAAU,kBAAA,CAAkB;AAAA,QAAA,GACnC;AAAA,QAGA,qBAAC,OAAA,EAAI,WAAU,gBACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,oBAAA,CAAoB;AAAA,UACnC,oBAAC,OAAA,EAAI,WAAU,oBAAA,CAAoB;AAAA,UACnC,oBAAC,OAAA,EAAI,WAAU,oBAAA,CAAoB;AAAA,QAAA,GACrC;AAAA,QAGA,oBAAC,OAAA,EAAI,WAAU,iBAAA,CAAiB;AAAA,QAGhC,qBAAC,OAAA,EAAI,WAAU,oBACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,kBAAA,CAAkB;AAAA,UACjC,oBAAC,OAAA,EAAI,WAAU,kBAAA,CAAkB;AAAA,UACjC,oBAAC,OAAA,EAAI,WAAU,kBAAA,CAAkB;AAAA,QAAA,GACnC;AAAA,QAGA,qBAAC,OAAA,EAAI,WAAU,oBACb,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,0BAAA,CAA0B;AAAA,UACzC,oBAAC,OAAA,EAAI,WAAU,gBAAA,CAAgB;AAAA,UAC/B,oBAAC,OAAA,EAAI,WAAU,gBAAA,CAAgB;AAAA,UAC/B,oBAAC,OAAA,EAAI,WAAU,gBAAA,CAAgB;AAAA,QAAA,EAAA,CACjC;AAAA,MAAA,EAAA,CACF;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Modal Component
3
+ * Generic modal dialog with keyboard shortcuts and customizable actions
4
+ *
5
+ * Styles ship with the package: import '@wolffm/task-ui-components/modal.css'
6
+ */
7
+ import React from 'react';
8
+ export interface ModalProps {
9
+ isOpen: boolean;
10
+ title: string;
11
+ onClose: () => void;
12
+ onConfirm?: () => void | Promise<void>;
13
+ children?: React.ReactNode;
14
+ inputValue?: string;
15
+ onInputChange?: (value: string) => void;
16
+ inputPlaceholder?: string;
17
+ confirmLabel?: string;
18
+ cancelLabel?: string;
19
+ confirmDisabled?: boolean;
20
+ confirmDanger?: boolean;
21
+ showCancel?: boolean;
22
+ showConfirm?: boolean;
23
+ className?: string;
24
+ overlayClassName?: string;
25
+ /**
26
+ * Replaces the whole action row. A wizard has Back/Cancel/Regenerate/Create,
27
+ * which the fixed Cancel+Confirm pair cannot express — and that is what
28
+ * pushes an app to hand-roll its own dialog instead of adopting this one.
29
+ *
30
+ * `null` means *no* row at all (a progress step, a viewer); omitting it means
31
+ * "use the default row". `??` collapses those two, so the check below is
32
+ * against `undefined` explicitly.
33
+ */
34
+ actions?: React.ReactNode;
35
+ /** Rendered left of the title, e.g. a wizard's Back button. */
36
+ lead?: React.ReactNode;
37
+ }
38
+ export declare function Modal({ isOpen, title, onClose, onConfirm, children, inputValue, onInputChange, inputPlaceholder, confirmLabel, cancelLabel, confirmDisabled, confirmDanger, showCancel, showConfirm, className, overlayClassName, actions, lead }: ModalProps): React.JSX.Element | null;
39
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/components/Modal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmC,MAAM,OAAO,CAAA;AAEvD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACvB;AASD,wBAAgB,KAAK,CAAC,EACpB,MAAM,EACN,KAAK,EACL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,YAAwB,EACxB,WAAsB,EACtB,eAAuB,EACvB,aAAqB,EACrB,UAAiB,EACjB,WAAkB,EAClB,SAAc,EACd,gBAAqB,EACrB,OAAO,EACP,IAAI,EACL,EAAE,UAAU,4BAwGZ"}
@@ -0,0 +1,106 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useId, useRef, useEffect } from "react";
3
+ const escapeStack = [];
4
+ function Modal({
5
+ isOpen,
6
+ title,
7
+ onClose,
8
+ onConfirm,
9
+ children,
10
+ inputValue,
11
+ onInputChange,
12
+ inputPlaceholder,
13
+ confirmLabel = "Confirm",
14
+ cancelLabel = "Cancel",
15
+ confirmDisabled = false,
16
+ confirmDanger = false,
17
+ showCancel = true,
18
+ showConfirm = true,
19
+ className = "",
20
+ overlayClassName = "",
21
+ actions,
22
+ lead
23
+ }) {
24
+ const titleId = useId();
25
+ const cardRef = useRef(null);
26
+ const onCloseRef = useRef(onClose);
27
+ onCloseRef.current = onClose;
28
+ useEffect(() => {
29
+ if (!isOpen) return;
30
+ const id = Symbol("modal");
31
+ escapeStack.push(id);
32
+ const onKey = (e) => {
33
+ if (e.key !== "Escape") return;
34
+ if (escapeStack[escapeStack.length - 1] !== id) return;
35
+ e.preventDefault();
36
+ onCloseRef.current();
37
+ };
38
+ document.addEventListener("keydown", onKey);
39
+ return () => {
40
+ document.removeEventListener("keydown", onKey);
41
+ const at = escapeStack.lastIndexOf(id);
42
+ if (at >= 0) escapeStack.splice(at, 1);
43
+ };
44
+ }, [isOpen]);
45
+ useEffect(() => {
46
+ if (!isOpen) return;
47
+ const card = cardRef.current;
48
+ if (!card) return;
49
+ const focusable = card.querySelector(
50
+ 'input:not([type=hidden]), textarea, select, button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'
51
+ );
52
+ (focusable ?? card).focus();
53
+ }, [isOpen]);
54
+ if (!isOpen) return null;
55
+ return /* @__PURE__ */ jsx("div", { className: `modal-overlay ${overlayClassName}`.trim(), onClick: onClose, children: /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ ref: cardRef,
59
+ className: `modal-card ${className}`.trim(),
60
+ onClick: (e) => e.stopPropagation(),
61
+ role: "dialog",
62
+ "aria-modal": "true",
63
+ "aria-labelledby": titleId,
64
+ tabIndex: -1,
65
+ children: [
66
+ /* @__PURE__ */ jsxs("div", { className: "modal-head", children: [
67
+ lead,
68
+ /* @__PURE__ */ jsx("h3", { id: titleId, children: title })
69
+ ] }),
70
+ children,
71
+ onInputChange && /* @__PURE__ */ jsx(
72
+ "input",
73
+ {
74
+ type: "text",
75
+ className: "modal-input",
76
+ value: inputValue || "",
77
+ onChange: (e) => onInputChange(e.target.value),
78
+ placeholder: inputPlaceholder,
79
+ onKeyDown: (e) => {
80
+ if (e.key === "Enter" && !confirmDisabled && onConfirm) {
81
+ e.preventDefault();
82
+ void onConfirm();
83
+ }
84
+ }
85
+ }
86
+ ),
87
+ actions !== void 0 ? actions : /* @__PURE__ */ jsxs("div", { className: "modal-actions", children: [
88
+ showCancel && /* @__PURE__ */ jsx("button", { className: "modal-button", onClick: onClose, children: cancelLabel }),
89
+ showConfirm && onConfirm && /* @__PURE__ */ jsx(
90
+ "button",
91
+ {
92
+ className: `modal-button ${confirmDanger ? "modal-button--danger" : "modal-button--primary"}`,
93
+ onClick: () => void onConfirm(),
94
+ disabled: confirmDisabled,
95
+ children: confirmLabel
96
+ }
97
+ )
98
+ ] })
99
+ ]
100
+ }
101
+ ) });
102
+ }
103
+ export {
104
+ Modal
105
+ };
106
+ //# sourceMappingURL=Modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.js","sources":["../../src/components/Modal.tsx"],"sourcesContent":["/**\r\n * Modal Component\r\n * Generic modal dialog with keyboard shortcuts and customizable actions\r\n *\r\n * Styles ship with the package: import '@wolffm/task-ui-components/modal.css'\r\n */\r\n\r\nimport React, { useEffect, useId, useRef } from 'react'\r\n\r\nexport interface ModalProps {\r\n isOpen: boolean\r\n title: string\r\n onClose: () => void\r\n onConfirm?: () => void | Promise<void>\r\n children?: React.ReactNode\r\n inputValue?: string\r\n onInputChange?: (value: string) => void\r\n inputPlaceholder?: string\r\n confirmLabel?: string\r\n cancelLabel?: string\r\n confirmDisabled?: boolean\r\n confirmDanger?: boolean\r\n showCancel?: boolean\r\n showConfirm?: boolean\r\n className?: string\r\n overlayClassName?: string\r\n /**\r\n * Replaces the whole action row. A wizard has Back/Cancel/Regenerate/Create,\r\n * which the fixed Cancel+Confirm pair cannot express — and that is what\r\n * pushes an app to hand-roll its own dialog instead of adopting this one.\r\n *\r\n * `null` means *no* row at all (a progress step, a viewer); omitting it means\r\n * \"use the default row\". `??` collapses those two, so the check below is\r\n * against `undefined` explicitly.\r\n */\r\n actions?: React.ReactNode\r\n /** Rendered left of the title, e.g. a wizard's Back button. */\r\n lead?: React.ReactNode\r\n}\r\n\r\n/**\r\n * Open dialogs, outermost first. Each mounted-and-open Modal pushes a token\r\n * and pops it on close, so the handler can tell whether it is the one the\r\n * keypress is meant for.\r\n */\r\nconst escapeStack: symbol[] = []\r\n\r\nexport function Modal({\r\n isOpen,\r\n title,\r\n onClose,\r\n onConfirm,\r\n children,\r\n inputValue,\r\n onInputChange,\r\n inputPlaceholder,\r\n confirmLabel = 'Confirm',\r\n cancelLabel = 'Cancel',\r\n confirmDisabled = false,\r\n confirmDanger = false,\r\n showCancel = true,\r\n showConfirm = true,\r\n className = '',\r\n overlayClassName = '',\r\n actions,\r\n lead\r\n}: ModalProps) {\r\n const titleId = useId()\r\n const cardRef = useRef<HTMLDivElement>(null)\r\n\r\n // Escape closes from anywhere in the dialog, not just from a focused input.\r\n // Bound to the document rather than the card so it works before anything\r\n // inside has been focused — previously the handler hung off the optional\r\n // text input, so a dialog without one could not be dismissed by keyboard.\r\n //\r\n // Only the TOP-MOST open dialog answers, which is what makes a confirm\r\n // nested inside another modal usable: the parent's document listener is\r\n // still live underneath it, so without the stack one Escape closes both.\r\n // onClose is read through a ref so a caller passing an unmemoised handler\r\n // cannot re-run this effect and shuffle itself back to the top of the stack.\r\n const onCloseRef = useRef(onClose)\r\n onCloseRef.current = onClose\r\n useEffect(() => {\r\n if (!isOpen) return\r\n const id = Symbol('modal')\r\n escapeStack.push(id)\r\n const onKey = (e: KeyboardEvent) => {\r\n if (e.key !== 'Escape') return\r\n if (escapeStack[escapeStack.length - 1] !== id) return\r\n e.preventDefault()\r\n onCloseRef.current()\r\n }\r\n document.addEventListener('keydown', onKey)\r\n return () => {\r\n document.removeEventListener('keydown', onKey)\r\n const at = escapeStack.lastIndexOf(id)\r\n if (at >= 0) escapeStack.splice(at, 1)\r\n }\r\n }, [isOpen])\r\n\r\n // Move focus into the dialog on open, so the keyboard starts inside it.\r\n useEffect(() => {\r\n if (!isOpen) return\r\n const card = cardRef.current\r\n if (!card) return\r\n const focusable = card.querySelector<HTMLElement>(\r\n 'input:not([type=hidden]), textarea, select, button:not([disabled]), [href], [tabindex]:not([tabindex=\"-1\"])'\r\n )\r\n ;(focusable ?? card).focus()\r\n }, [isOpen])\r\n\r\n if (!isOpen) return null\r\n\r\n return (\r\n <div className={`modal-overlay ${overlayClassName}`.trim()} onClick={onClose}>\r\n <div\r\n ref={cardRef}\r\n className={`modal-card ${className}`.trim()}\r\n onClick={e => e.stopPropagation()}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n tabIndex={-1}\r\n >\r\n <div className=\"modal-head\">\r\n {lead}\r\n <h3 id={titleId}>{title}</h3>\r\n </div>\r\n\r\n {children}\r\n\r\n {onInputChange && (\r\n <input\r\n type=\"text\"\r\n className=\"modal-input\"\r\n value={inputValue || ''}\r\n onChange={e => onInputChange(e.target.value)}\r\n placeholder={inputPlaceholder}\r\n onKeyDown={e => {\r\n if (e.key === 'Enter' && !confirmDisabled && onConfirm) {\r\n e.preventDefault()\r\n void onConfirm()\r\n }\r\n }}\r\n />\r\n )}\r\n\r\n {actions !== undefined ? (\r\n actions\r\n ) : (\r\n <div className=\"modal-actions\">\r\n {showCancel && (\r\n <button className=\"modal-button\" onClick={onClose}>\r\n {cancelLabel}\r\n </button>\r\n )}\r\n {showConfirm && onConfirm && (\r\n <button\r\n className={`modal-button ${confirmDanger ? 'modal-button--danger' : 'modal-button--primary'}`}\r\n onClick={() => void onConfirm()}\r\n disabled={confirmDisabled}\r\n >\r\n {confirmLabel}\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;AA6CA,MAAM,cAAwB,CAAA;AAEvB,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB;AAAA,EACA;AACF,GAAe;AACb,QAAM,UAAU,MAAA;AAChB,QAAM,UAAU,OAAuB,IAAI;AAY3C,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,KAAK,OAAO,OAAO;AACzB,gBAAY,KAAK,EAAE;AACnB,UAAM,QAAQ,CAAC,MAAqB;AAClC,UAAI,EAAE,QAAQ,SAAU;AACxB,UAAI,YAAY,YAAY,SAAS,CAAC,MAAM,GAAI;AAChD,QAAE,eAAA;AACF,iBAAW,QAAA;AAAA,IACb;AACA,aAAS,iBAAiB,WAAW,KAAK;AAC1C,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,KAAK;AAC7C,YAAM,KAAK,YAAY,YAAY,EAAE;AACrC,UAAI,MAAM,EAAG,aAAY,OAAO,IAAI,CAAC;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AACX,UAAM,YAAY,KAAK;AAAA,MACrB;AAAA,IAAA;AAED,KAAC,aAAa,MAAM,MAAA;AAAA,EACvB,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,CAAC,OAAQ,QAAO;AAEpB,SACE,oBAAC,SAAI,WAAW,iBAAiB,gBAAgB,GAAG,KAAA,GAAQ,SAAS,SACnE,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,cAAc,SAAS,GAAG,KAAA;AAAA,MACrC,SAAS,CAAA,MAAK,EAAE,gBAAA;AAAA,MAChB,MAAK;AAAA,MACL,cAAW;AAAA,MACX,mBAAiB;AAAA,MACjB,UAAU;AAAA,MAEV,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,cACZ,UAAA;AAAA,UAAA;AAAA,UACD,oBAAC,MAAA,EAAG,IAAI,SAAU,UAAA,MAAA,CAAM;AAAA,QAAA,GAC1B;AAAA,QAEC;AAAA,QAEA,iBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAO,cAAc;AAAA,YACrB,UAAU,CAAA,MAAK,cAAc,EAAE,OAAO,KAAK;AAAA,YAC3C,aAAa;AAAA,YACb,WAAW,CAAA,MAAK;AACd,kBAAI,EAAE,QAAQ,WAAW,CAAC,mBAAmB,WAAW;AACtD,kBAAE,eAAA;AACF,qBAAK,UAAA;AAAA,cACP;AAAA,YACF;AAAA,UAAA;AAAA,QAAA;AAAA,QAIH,YAAY,SACX,UAEA,qBAAC,OAAA,EAAI,WAAU,iBACZ,UAAA;AAAA,UAAA,kCACE,UAAA,EAAO,WAAU,gBAAe,SAAS,SACvC,UAAA,aACH;AAAA,UAED,eAAe,aACd;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW,gBAAgB,gBAAgB,yBAAyB,uBAAuB;AAAA,cAC3F,SAAS,MAAM,KAAK,UAAA;AAAA,cACpB,UAAU;AAAA,cAET,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,GAGN;AAEJ;"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * NumberInput Component
3
+ * A themed number field with its own stepper, for the OS spinner arrows that
4
+ * no CSS can style (controls.css removes them from a bare <input type=number>).
5
+ *
6
+ * It is a real <input type="number"> with every input prop passed through, so
7
+ * it works controlled (`value` + `onChange`) or uncontrolled (`defaultValue`),
8
+ * submits in a form, and keeps native min/max/step validation and the arrow
9
+ * keys. The buttons call the input's own stepUp()/stepDown() and then fire
10
+ * `input` and `change`, so `onChange` sees an ordinary edit: React's value
11
+ * tracker compares against what React last wrote, and stepUp() goes around it.
12
+ *
13
+ * Holding a button repeats, like the native spinner. The buttons are not tab
14
+ * stops — the arrow keys on the field already step — but they are labelled.
15
+ */
16
+ import React from 'react';
17
+ import '../number-input.css';
18
+ export type NumberInputSize = 'sm' | 'md' | 'lg';
19
+ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {
20
+ size?: NumberInputSize;
21
+ /** Labels for the two buttons; default "Increase" / "Decrease". */
22
+ increaseLabel?: string;
23
+ decreaseLabel?: string;
24
+ }
25
+ export declare const NumberInput: React.ForwardRefExoticComponent<NumberInputProps & React.RefAttributes<HTMLInputElement>>;
26
+ //# sourceMappingURL=NumberInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/components/NumberInput.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAA;AAEvE,OAAO,qBAAqB,CAAA;AAE5B,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhD,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1E,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAMD,eAAO,MAAM,WAAW,2FA8HvB,CAAA"}
@@ -0,0 +1,131 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { useRef, useState, useEffect, useCallback } from "react";
3
+ import { Icon } from "@wolffm/themes";
4
+ /* empty css */
5
+ const REPEAT_DELAY_MS = 400;
6
+ const REPEAT_EVERY_MS = 60;
7
+ const ICON_SIZE = { sm: 12, md: 14, lg: 16 };
8
+ const NumberInput = React.forwardRef(
9
+ function NumberInput2({
10
+ size = "md",
11
+ increaseLabel = "Increase",
12
+ decreaseLabel = "Decrease",
13
+ className,
14
+ disabled,
15
+ readOnly,
16
+ onInput,
17
+ ...rest
18
+ }, ref) {
19
+ const inputRef = useRef(null);
20
+ const repeat = useRef({});
21
+ const [current, setCurrent] = useState(String(rest.value ?? rest.defaultValue ?? ""));
22
+ useEffect(() => {
23
+ if (rest.value !== void 0) setCurrent(String(rest.value));
24
+ }, [rest.value]);
25
+ const setRef = useCallback(
26
+ (node) => {
27
+ inputRef.current = node;
28
+ if (typeof ref === "function") ref(node);
29
+ else if (ref) ref.current = node;
30
+ },
31
+ [ref]
32
+ );
33
+ const stop = useCallback(() => {
34
+ clearTimeout(repeat.current.delay);
35
+ clearInterval(repeat.current.every);
36
+ repeat.current = {};
37
+ }, []);
38
+ useEffect(() => stop, [stop]);
39
+ const stepOnce = useCallback((dir) => {
40
+ const input = inputRef.current;
41
+ if (!input || input.disabled || input.readOnly) return false;
42
+ const before = input.value;
43
+ if (dir === 1) input.stepUp();
44
+ else input.stepDown();
45
+ if (input.value === before) return false;
46
+ input.dispatchEvent(new Event("input", { bubbles: true }));
47
+ input.dispatchEvent(new Event("change", { bubbles: true }));
48
+ return true;
49
+ }, []);
50
+ const start = (dir) => (e) => {
51
+ if (e.button !== 0) return;
52
+ e.preventDefault();
53
+ inputRef.current?.focus({ preventScroll: true });
54
+ stop();
55
+ if (!stepOnce(dir)) return;
56
+ repeat.current.delay = setTimeout(() => {
57
+ repeat.current.every = setInterval(() => {
58
+ if (!stepOnce(dir)) stop();
59
+ }, REPEAT_EVERY_MS);
60
+ }, REPEAT_DELAY_MS);
61
+ };
62
+ const n = current === "" ? NaN : Number(current);
63
+ const min = rest.min === void 0 || rest.min === "" ? -Infinity : Number(rest.min);
64
+ const max = rest.max === void 0 || rest.max === "" ? Infinity : Number(rest.max);
65
+ const locked = disabled || readOnly;
66
+ return /* @__PURE__ */ jsxs(
67
+ "span",
68
+ {
69
+ className: [
70
+ "hdk-number",
71
+ `hdk-number--${size}`,
72
+ disabled && "hdk-number--disabled",
73
+ className
74
+ ].filter(Boolean).join(" "),
75
+ children: [
76
+ /* @__PURE__ */ jsx(
77
+ "input",
78
+ {
79
+ ref: setRef,
80
+ type: "number",
81
+ className: "hdk-number__input",
82
+ disabled,
83
+ readOnly,
84
+ onInput: (e) => {
85
+ setCurrent(e.currentTarget.value);
86
+ onInput?.(e);
87
+ },
88
+ ...rest
89
+ }
90
+ ),
91
+ /* @__PURE__ */ jsxs("span", { className: "hdk-number__steps", children: [
92
+ /* @__PURE__ */ jsx(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ tabIndex: -1,
97
+ className: "hdk-number__step",
98
+ "aria-label": increaseLabel,
99
+ disabled: locked || n >= max,
100
+ onPointerDown: start(1),
101
+ onPointerUp: stop,
102
+ onPointerLeave: stop,
103
+ onPointerCancel: stop,
104
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-up", size: ICON_SIZE[size] })
105
+ }
106
+ ),
107
+ /* @__PURE__ */ jsx(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ tabIndex: -1,
112
+ className: "hdk-number__step",
113
+ "aria-label": decreaseLabel,
114
+ disabled: locked || n <= min,
115
+ onPointerDown: start(-1),
116
+ onPointerUp: stop,
117
+ onPointerLeave: stop,
118
+ onPointerCancel: stop,
119
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: ICON_SIZE[size] })
120
+ }
121
+ )
122
+ ] })
123
+ ]
124
+ }
125
+ );
126
+ }
127
+ );
128
+ export {
129
+ NumberInput
130
+ };
131
+ //# sourceMappingURL=NumberInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumberInput.js","sources":["../../src/components/NumberInput.tsx"],"sourcesContent":["/**\n * NumberInput Component\n * A themed number field with its own stepper, for the OS spinner arrows that\n * no CSS can style (controls.css removes them from a bare <input type=number>).\n *\n * It is a real <input type=\"number\"> with every input prop passed through, so\n * it works controlled (`value` + `onChange`) or uncontrolled (`defaultValue`),\n * submits in a form, and keeps native min/max/step validation and the arrow\n * keys. The buttons call the input's own stepUp()/stepDown() and then fire\n * `input` and `change`, so `onChange` sees an ordinary edit: React's value\n * tracker compares against what React last wrote, and stepUp() goes around it.\n *\n * Holding a button repeats, like the native spinner. The buttons are not tab\n * stops — the arrow keys on the field already step — but they are labelled.\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { Icon } from '@wolffm/themes'\nimport '../number-input.css'\n\nexport type NumberInputSize = 'sm' | 'md' | 'lg'\n\nexport interface NumberInputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'size'> {\n size?: NumberInputSize\n /** Labels for the two buttons; default \"Increase\" / \"Decrease\". */\n increaseLabel?: string\n decreaseLabel?: string\n}\n\nconst REPEAT_DELAY_MS = 400\nconst REPEAT_EVERY_MS = 60\nconst ICON_SIZE: Record<NumberInputSize, number> = { sm: 12, md: 14, lg: 16 }\n\nexport const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(\n function NumberInput(\n {\n size = 'md',\n increaseLabel = 'Increase',\n decreaseLabel = 'Decrease',\n className,\n disabled,\n readOnly,\n onInput,\n ...rest\n },\n ref\n ) {\n const inputRef = useRef<HTMLInputElement | null>(null)\n const repeat = useRef<{\n delay?: ReturnType<typeof setTimeout>\n every?: ReturnType<typeof setInterval>\n }>({})\n // Mirrors the field so the buttons can disable at min/max, controlled or not.\n const [current, setCurrent] = useState<string>(String(rest.value ?? rest.defaultValue ?? ''))\n useEffect(() => {\n if (rest.value !== undefined) setCurrent(String(rest.value))\n }, [rest.value])\n\n const setRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n const stop = useCallback(() => {\n clearTimeout(repeat.current.delay)\n clearInterval(repeat.current.every)\n repeat.current = {}\n }, [])\n useEffect(() => stop, [stop])\n\n const stepOnce = useCallback((dir: 1 | -1) => {\n const input = inputRef.current\n if (!input || input.disabled || input.readOnly) return false\n const before = input.value\n if (dir === 1) input.stepUp()\n else input.stepDown()\n if (input.value === before) return false\n input.dispatchEvent(new Event('input', { bubbles: true }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n return true\n }, [])\n\n const start = (dir: 1 | -1) => (e: React.PointerEvent) => {\n if (e.button !== 0) return\n // Keep focus (and the caret) in the field, as the native spinner does.\n e.preventDefault()\n inputRef.current?.focus({ preventScroll: true })\n stop()\n if (!stepOnce(dir)) return\n repeat.current.delay = setTimeout(() => {\n repeat.current.every = setInterval(() => {\n if (!stepOnce(dir)) stop()\n }, REPEAT_EVERY_MS)\n }, REPEAT_DELAY_MS)\n }\n\n const n = current === '' ? NaN : Number(current)\n const min = rest.min === undefined || rest.min === '' ? -Infinity : Number(rest.min)\n const max = rest.max === undefined || rest.max === '' ? Infinity : Number(rest.max)\n const locked = disabled || readOnly\n\n return (\n <span\n className={[\n 'hdk-number',\n `hdk-number--${size}`,\n disabled && 'hdk-number--disabled',\n className\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <input\n ref={setRef}\n type=\"number\"\n className=\"hdk-number__input\"\n disabled={disabled}\n readOnly={readOnly}\n onInput={e => {\n setCurrent(e.currentTarget.value)\n onInput?.(e)\n }}\n {...rest}\n />\n <span className=\"hdk-number__steps\">\n <button\n type=\"button\"\n tabIndex={-1}\n className=\"hdk-number__step\"\n aria-label={increaseLabel}\n disabled={locked || n >= max}\n onPointerDown={start(1)}\n onPointerUp={stop}\n onPointerLeave={stop}\n onPointerCancel={stop}\n >\n <Icon name=\"chevron-up\" size={ICON_SIZE[size]} />\n </button>\n <button\n type=\"button\"\n tabIndex={-1}\n className=\"hdk-number__step\"\n aria-label={decreaseLabel}\n disabled={locked || n <= min}\n onPointerDown={start(-1)}\n onPointerUp={stop}\n onPointerLeave={stop}\n onPointerCancel={stop}\n >\n <Icon name=\"chevron-down\" size={ICON_SIZE[size]} />\n </button>\n </span>\n </span>\n )\n }\n)\n"],"names":["NumberInput"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Select Component
3
+ * Themed single-select dropdown. The open list is a real ARIA listbox with
4
+ * full keyboard support, so it can replace a native <select> wherever the
5
+ * browser's own popup (unthemeable, OS-drawn) looks out of place.
6
+ *
7
+ * The list is portalled into the anchor's theme scope (see themeScopeOf) and
8
+ * positioned fixed from the trigger's rect, so it keeps the theme's tokens and
9
+ * escapes both `overflow: hidden` and transformed ancestors.
10
+ */
11
+ import React from 'react';
12
+ import { type IconName } from '@wolffm/themes';
13
+ import '../select.css';
14
+ import '../scrollbar.css';
15
+ export type SelectVariant = 'outline' | 'filled' | 'ghost' | 'underline';
16
+ export type SelectSize = 'sm' | 'md' | 'lg';
17
+ export interface SelectOption<V extends string = string> {
18
+ value: V;
19
+ /** Plain text so typeahead can match it. */
20
+ label: string;
21
+ icon?: IconName;
22
+ /** Secondary line under the label in the open list. */
23
+ description?: string;
24
+ disabled?: boolean;
25
+ }
26
+ export interface SelectProps<V extends string = string> {
27
+ value: V | null;
28
+ onChange: (value: V) => void;
29
+ options: SelectOption<V>[];
30
+ /** Shown while `value` is null. */
31
+ placeholder?: string;
32
+ variant?: SelectVariant;
33
+ size?: SelectSize;
34
+ disabled?: boolean;
35
+ /** Required when no visible <label htmlFor={id}> names the control. */
36
+ 'aria-label'?: string;
37
+ id?: string;
38
+ className?: string;
39
+ }
40
+ export declare function Select<V extends string = string>({ value, onChange, options, placeholder, variant, size, disabled, 'aria-label': ariaLabel, id, className }: SelectProps<V>): React.JSX.Element;
41
+ export interface NativeSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
42
+ variant?: SelectVariant;
43
+ size?: SelectSize;
44
+ }
45
+ /**
46
+ * A native <select> wearing the same trigger as <Select>. Use it where the
47
+ * OS picker is the better UX — long lists on mobile, forms that must submit
48
+ * natively. The popup itself stays OS-drawn; `color-scheme` from the theme
49
+ * gets it to the right light/dark mode.
50
+ */
51
+ export declare function NativeSelect({ variant, size, className, children, ...rest }: NativeSelectProps): React.JSX.Element;
52
+ //# sourceMappingURL=Select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/components/Select.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAA2E,MAAM,OAAO,CAAA;AAE/F,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,eAAe,CAAA;AACtB,OAAO,kBAAkB,CAAA;AAIzB,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAA;AACxE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3C,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACrD,KAAK,EAAE,CAAC,CAAA;IACR,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACpD,KAAK,EAAE,CAAC,GAAG,IAAI,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IAC5B,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1B,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EAChD,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAuB,EACvB,OAAmB,EACnB,IAAW,EACX,QAAgB,EAChB,YAAY,EAAE,SAAS,EACvB,EAAE,EACF,SAAS,EACV,EAAE,WAAW,CAAC,CAAC,CAAC,qBAqQhB;AAED,MAAM,WAAW,iBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACnE,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAmB,EACnB,IAAW,EACX,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,iBAAiB,qBAiBnB"}