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

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 +302 -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 +72 -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 +120 -0
  102. package/dist/lib/settingsClient.d.ts.map +1 -0
  103. package/dist/lib/settingsClient.js +140 -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 +465 -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 +140 -0
  156. package/src/lib/menuPlacement.ts +31 -0
  157. package/src/lib/selectMenus.ts +303 -0
  158. package/src/lib/settingsClient.ts +377 -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,137 @@
1
+ /* Themed fallbacks for the browser's own control styling, for
2
+ * @wolffm/task-ui-components.
3
+ *
4
+ * import '@wolffm/task-ui-components/controls.css'
5
+ * <html class="hdk-controls">
6
+ *
7
+ * hadoku_site puts `hdk-controls` on <html>, which covers every
8
+ * micro-frontend. Every selector is wrapped in `:where()`, so all of it has
9
+ * ZERO specificity: it only fills in where an app styled nothing. An app's
10
+ * own rule for the same property — any selector at all — wins, whatever order
11
+ * the sheets load in. An author rule of any specificity still beats the
12
+ * browser's stylesheet, which is all this needs to.
13
+ *
14
+ * What it covers, each the browser default it replaces:
15
+ * - focus ring Chrome's two-tone `outline: auto` -> the accent
16
+ * - links -webkit-link blue / visited purple -> the text around it
17
+ * - checkbox, radio, range, progress system blue -> accent-color
18
+ * - text selection system highlight -> the accent tint
19
+ * - placeholder darkgray -> tertiary text
20
+ * - <summary> marker text-colour triangle -> secondary text
21
+ * - file picker OS "Choose file" button -> a soft button
22
+ * - number spinners OS up/down arrows -> removed (arrow keys
23
+ * still step; <NumberInput> is the themed stepper)
24
+ * - text fields and textarea, buttons, a select's closed box: the system
25
+ * field/button chrome -> the theme's card surface and border
26
+ *
27
+ * The open list of a <select> is not CSS-reachable at all; installSelectMenus()
28
+ * replaces it. Date and time pickers stay OS-drawn: apps own those.
29
+ *
30
+ * Everything sits in `@layer hdk-fallbacks`. A rule in a cascade layer loses
31
+ * to every unlayered rule and to every layer declared after it — whatever
32
+ * either's specificity — and Tailwind ships its utilities in layers. Unlayered,
33
+ * even a zero-specificity rule here would beat `bg-primary`. The page must
34
+ * declare the layer FIRST, before any other, so it ranks lowest:
35
+ *
36
+ * @layer hdk-fallbacks, base, theme, utilities;
37
+ *
38
+ * hadoku_site's global.css does that. A sheet that never names it just gets
39
+ * these rules below its own unlayered ones, which is also correct.
40
+ */
41
+
42
+ @layer hdk-fallbacks {
43
+ /* accent-color inherits, so it is declared once per theme scope rather than
44
+ * per element: an app's accent-color on a container still reaches its
45
+ * children, and a nested [data-theme] still resolves its own accent. */
46
+ :where(.hdk-controls),
47
+ :where(.hdk-controls [data-theme]) {
48
+ accent-color: var(--color-primary);
49
+ }
50
+
51
+ :where(.hdk-controls) :where(:focus-visible) {
52
+ outline: 2px solid var(--color-primary);
53
+ outline-offset: 2px;
54
+ }
55
+
56
+ /* The browser colours a link directly rather than letting it inherit, so a
57
+ * link nobody styled is blue on any surface. `inherit` is what an app means
58
+ * when it styles the surface and not the link (Tailwind's preflight does the
59
+ * same); the underline keeps it reading as a link. */
60
+ :where(.hdk-controls) :where(a:any-link) {
61
+ color: inherit;
62
+ }
63
+
64
+ :where(.hdk-controls) ::selection {
65
+ background-color: var(--color-primary-bg);
66
+ color: var(--color-on-primary-bg);
67
+ }
68
+
69
+ :where(.hdk-controls) ::placeholder {
70
+ color: var(--color-text-tertiary);
71
+ opacity: 1;
72
+ }
73
+
74
+ :where(.hdk-controls) :where(summary)::marker {
75
+ color: var(--color-text-secondary);
76
+ }
77
+
78
+ :where(.hdk-controls) :where(input[type='file'])::file-selector-button {
79
+ margin-inline-end: 8px;
80
+ padding: 4px 10px;
81
+ font: inherit;
82
+ font-weight: var(--font-weight-semibold);
83
+ color: var(--color-on-neutral-bg);
84
+ background-color: var(--color-neutral-bg);
85
+ border: 1px solid transparent;
86
+ border-radius: var(--hdk-radius);
87
+ cursor: pointer;
88
+ }
89
+
90
+ :where(.hdk-controls) :where(input[type='file'])::file-selector-button:hover {
91
+ border-color: var(--color-neutral);
92
+ }
93
+
94
+ :where(.hdk-controls) :where(input[type='number']) {
95
+ appearance: textfield;
96
+ }
97
+
98
+ :where(.hdk-controls) :where(input[type='number'])::-webkit-inner-spin-button,
99
+ :where(.hdk-controls) :where(input[type='number'])::-webkit-outer-spin-button {
100
+ appearance: none;
101
+ margin: 0;
102
+ }
103
+
104
+ /* Field chrome: only the properties that make up the system look. No padding,
105
+ * radius, font or size: those decide layout and shape, and an app that
106
+ * styled a field's background but not its padding must not see it move. */
107
+ :where(.hdk-controls)
108
+ :where(
109
+ input:not(
110
+ [type='checkbox'],
111
+ [type='radio'],
112
+ [type='range'],
113
+ [type='file'],
114
+ [type='color'],
115
+ [type='button'],
116
+ [type='submit'],
117
+ [type='reset'],
118
+ [type='image']
119
+ ),
120
+ textarea,
121
+ select
122
+ ) {
123
+ color: var(--color-text);
124
+ background-color: var(--color-bg-card);
125
+ border: 1px solid var(--color-border);
126
+ }
127
+
128
+ :where(.hdk-controls)
129
+ :where(button, input[type='button'], input[type='submit'], input[type='reset']) {
130
+ /* inherit, not a token: an app that coloured a button's surface and not
131
+ * its text, or draws with currentColor inside it, keeps the colour it
132
+ * had from the page instead of jumping to the neutral tint's ink. */
133
+ color: inherit;
134
+ background-color: var(--color-neutral-bg);
135
+ border: 1px solid transparent;
136
+ }
137
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * useReducedMotion Hook
3
+ * Detects if the user prefers reduced motion
4
+ * Returns true if animations should be disabled or simplified
5
+ */
6
+
7
+ import { useState, useEffect } from 'react'
8
+
9
+ export function useReducedMotion(): boolean {
10
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => {
11
+ // SSR and jsdom have no matchMedia: default to false.
12
+ if (typeof window === 'undefined' || !window.matchMedia) return false
13
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches
14
+ })
15
+
16
+ useEffect(() => {
17
+ if (typeof window === 'undefined' || !window.matchMedia) return
18
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
19
+
20
+ const handleChange = (event: MediaQueryListEvent) => {
21
+ setPrefersReducedMotion(event.matches)
22
+ }
23
+
24
+ // Modern browsers
25
+ if (mediaQuery.addEventListener) {
26
+ mediaQuery.addEventListener('change', handleChange)
27
+ return () => mediaQuery.removeEventListener('change', handleChange)
28
+ }
29
+
30
+ // Fallback for older browsers
31
+ mediaQuery.addListener(handleChange)
32
+ return () => mediaQuery.removeListener(handleChange)
33
+ }, [])
34
+
35
+ return prefersReducedMotion
36
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * useToast hook
3
+ * Manages toast notification state and display
4
+ */
5
+
6
+ import { useState, useCallback } from 'react'
7
+
8
+ export interface ToastState {
9
+ id: number
10
+ message: string
11
+ type: 'success' | 'error' | 'info' | 'warning'
12
+ duration?: number
13
+ }
14
+
15
+ export interface UseToastReturn {
16
+ toasts: ToastState[]
17
+ showToast: (
18
+ message: string,
19
+ type?: 'success' | 'error' | 'info' | 'warning',
20
+ duration?: number
21
+ ) => void
22
+ dismissToast: (id: number) => void
23
+ clearAll: () => void
24
+ }
25
+
26
+ let toastIdCounter = 0
27
+
28
+ export function useToast(): UseToastReturn {
29
+ const [toasts, setToasts] = useState<ToastState[]>([])
30
+
31
+ const showToast = useCallback(
32
+ (
33
+ message: string,
34
+ type: 'success' | 'error' | 'info' | 'warning' = 'info',
35
+ duration: number = 3000
36
+ ) => {
37
+ const id = ++toastIdCounter
38
+ setToasts(prev => [...prev, { id, message, type, duration }])
39
+ },
40
+ []
41
+ )
42
+
43
+ const dismissToast = useCallback((id: number) => {
44
+ setToasts(prev => prev.filter(toast => toast.id !== id))
45
+ }, [])
46
+
47
+ const clearAll = useCallback(() => {
48
+ setToasts([])
49
+ }, [])
50
+
51
+ return {
52
+ toasts,
53
+ showToast,
54
+ dismissToast,
55
+ clearAll
56
+ }
57
+ }
@@ -0,0 +1,64 @@
1
+ /* The round 32px icon button on the AppHeader bar — the theme picker trigger,
2
+ * the settings gear, and any link an app puts in the actions row.
3
+ *
4
+ * Defined ONCE. The picker and the gear each used to carry their own copy and
5
+ * the two had already drifted (padding 8px vs --hdk-space-sm, a ring on every
6
+ * mouse click vs on :focus-visible, a min-size on one only). `.theme-toggle-btn`
7
+ * and `.settings-toggle-btn` survive as hooks with no values of their own.
8
+ *
9
+ * Lives in its own file because ThemePicker renders outside the header too
10
+ * (HadokuThemePicker on a video overlay), so its trigger cannot depend on
11
+ * app-header.css. The chain is app-header.css -> theme-picker.css -> this.
12
+ *
13
+ * Works on an <a>: child apps ship without a preflight, so a bare anchor would
14
+ * render UA blue, visited purple and underlined. */
15
+
16
+ .app-header__icon-btn {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ flex-shrink: 0;
21
+ box-sizing: border-box;
22
+ width: 32px;
23
+ height: 32px;
24
+ min-width: 32px;
25
+ min-height: 32px;
26
+ padding: 0;
27
+ margin: 0;
28
+ border: none;
29
+ border-radius: 50%;
30
+ background: none;
31
+ color: var(--color-text);
32
+ font: inherit;
33
+ font-size: var(--hdk-text-lg);
34
+ line-height: 1;
35
+ text-decoration: none;
36
+ cursor: pointer;
37
+ opacity: 0.7;
38
+ transition:
39
+ background-color var(--transition-fast),
40
+ opacity var(--transition-fast);
41
+ }
42
+
43
+ /* Same colour visited or not, and no hover recolour from a host stylesheet's
44
+ * `a:hover` — the hover signal is the background below. */
45
+ .app-header__icon-btn:link,
46
+ .app-header__icon-btn:visited,
47
+ .app-header__icon-btn:hover,
48
+ .app-header__icon-btn:active {
49
+ color: var(--color-text);
50
+ text-decoration: none;
51
+ }
52
+
53
+ .app-header__icon-btn:hover {
54
+ background-color: var(--color-primary-hover);
55
+ opacity: 1;
56
+ }
57
+
58
+ /* :focus-visible, not :focus — :focus rings on every mouse click. Full
59
+ * opacity too, so a keyboard user never lands on the dimmed resting state. */
60
+ .app-header__icon-btn:focus-visible {
61
+ outline: none;
62
+ opacity: 1;
63
+ box-shadow: 0 0 0 2px var(--color-primary);
64
+ }
package/src/index.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @wolffm/task-ui-components
3
+ * Reusable UI components for task management applications
4
+ * Production-ready and fully typed
5
+ *
6
+ * 2.2.0 — AppHeader + ConnectedSettings (with a `children` extension slot).
7
+ */
8
+
9
+ // Main components
10
+ //
11
+ // NOTE: `ThemePicker` and `ConnectedThemePicker` are intentionally NOT exported.
12
+ // AppHeader renders the theme picker itself from the HadokuThemeRoot context,
13
+ // so every app gets the same control by construction. Exporting them is what
14
+ // let an app hand-roll its own (with its own open-state and a debug logger in
15
+ // the icon resolver) — a compile error is the only enforcement that actually
16
+ // holds. If you need theme state, use `useTheme()` from @wolffm/themes.
17
+ // The picker itself, for an app with no header row to put it in (watchpart2's
18
+ // UI is a video overlay). It takes NO props — placement is free, configuration
19
+ // is not, which is the property that keeps it identical everywhere.
20
+ export { HadokuThemePicker } from './components/HadokuThemePicker'
21
+ export { AppHeader } from './components/AppHeader'
22
+ export { ConnectedSettings } from './components/ConnectedSettings'
23
+ // Re-exported from @wolffm/themes, which now owns the context AND the provider
24
+ // that fills it. These are RE-EXPORTS, not re-declarations: every consumer gets
25
+ // the one context object from themes, which is what makes a duplicated copy of
26
+ // THIS package harmless. Kept here so existing
27
+ // `import { useHadokuTheme } from '@wolffm/task-ui-components'` call sites
28
+ // across the fleet keep working unchanged.
29
+ export { HadokuThemeProvider, useHadokuTheme, useHadokuThemeOptional } from '@wolffm/themes'
30
+ export type { HadokuThemeValue, HadokuThemeProviderProps } from '@wolffm/themes'
31
+ export { Toast } from './components/Toast'
32
+ export { Toaster } from './components/Toaster'
33
+ export { Modal } from './components/Modal'
34
+ export { ContextMenu } from './components/ContextMenu'
35
+ export { LoadingSkeleton } from './components/LoadingSkeleton'
36
+ export { SettingsModal } from './components/SettingsModal'
37
+ // Themed replacements for the browser's default controls. Scrollbars are
38
+ // CSS only: import '@wolffm/task-ui-components/scrollbar.css' and add
39
+ // `hdk-scroll` to the app root.
40
+ export { Button } from './components/Button'
41
+ export { Select, NativeSelect } from './components/Select'
42
+ export { NumberInput } from './components/NumberInput'
43
+ export { Tooltip } from './components/Tooltip'
44
+ // Framework-free; also its own entry, `@wolffm/task-ui-components/title-tooltips`,
45
+ // so the site shell can install it without React.
46
+ export { installTitleTooltips } from './lib/titleTooltips'
47
+ export type { TitleTooltipOptions } from './lib/titleTooltips'
48
+ // Same, `@wolffm/task-ui-components/select-menus`: the themed list for every
49
+ // native <select>.
50
+ export { installSelectMenus } from './lib/selectMenus'
51
+
52
+ // Bento components
53
+ export { BentoGrid } from './components/BentoGrid'
54
+ export { BentoCard } from './components/BentoCard'
55
+
56
+ // Hooks
57
+ export { useToast } from './hooks/useToast'
58
+ export { useReducedMotion } from './hooks/useReducedMotion'
59
+
60
+ // Theme icons - export individually for flexibility
61
+ export {
62
+ SettingsIcon,
63
+ TagIcon,
64
+ // Generic/Fallback icons
65
+ CircleIcon,
66
+ SquareIcon,
67
+ TriangleIcon,
68
+ DiamondIcon,
69
+ StarIcon,
70
+ HexagonIcon,
71
+ PentagonIcon,
72
+ OctagonIcon,
73
+ FALLBACK_ICONS,
74
+ getFallbackIcon
75
+ } from './components/ThemeIcons'
76
+ // Theme-family icons moved to @wolffm/themes with the rest of the theme model.
77
+ // Re-exported so existing imports from this package keep resolving.
78
+ export {
79
+ SunIcon,
80
+ MoonIcon,
81
+ StrawberryIcon,
82
+ WaveIcon,
83
+ ZapIcon,
84
+ CoffeeIcon,
85
+ FlowerIcon,
86
+ HeartIcon,
87
+ LeafIcon,
88
+ LanternIcon
89
+ } from '@wolffm/themes'
90
+
91
+ // Types
92
+ // ThemeFamily stays — @wolffm/themes builds THEME_FAMILIES against it.
93
+ // ThemePickerProps / ConnectedThemePickerProps are gone with the components:
94
+ // a prop type you cannot render is only useful for reimplementing the control.
95
+ export type { ThemeName, ThemeFamily, DropdownPlacement, ThemeMode } from './types'
96
+ export type { AppHeaderProps } from './components/AppHeader'
97
+ export type { ConnectedSettingsProps } from './components/ConnectedSettings'
98
+ export type { ContentLevelState, Identity, SignIn, SignInResult, Tier } from './lib/settingsClient'
99
+ export { loadSignIn, switchKeyHref } from './lib/settingsClient'
100
+ export type { ToastProps } from './components/Toast'
101
+ export type { ToasterProps, ToastState } from './components/Toaster'
102
+ export type { UseToastReturn } from './hooks/useToast'
103
+ export type { ModalProps } from './components/Modal'
104
+ export type { ContextMenuProps, ContextMenuItem } from './components/ContextMenu'
105
+ export type { LoadingSkeletonProps } from './components/LoadingSkeleton'
106
+ export type {
107
+ ButtonProps,
108
+ ButtonVariant,
109
+ ButtonFamily,
110
+ ButtonSize,
111
+ ButtonShape
112
+ } from './components/Button'
113
+ export type {
114
+ SelectProps,
115
+ SelectOption,
116
+ SelectVariant,
117
+ SelectSize,
118
+ NativeSelectProps
119
+ } from './components/Select'
120
+ export type { TooltipProps, TooltipPlacement, TooltipVariant } from './components/Tooltip'
121
+ export type {
122
+ SettingsModalProps,
123
+ SettingsSection,
124
+ SettingsField,
125
+ SettingsToggleField,
126
+ SettingsTextInputField,
127
+ SettingsPasswordField,
128
+ SettingsSelectField,
129
+ SettingsButtonField,
130
+ SettingsCustomField
131
+ } from './components/SettingsModal'
132
+ export type { BentoGridProps, BentoColumns } from './components/BentoGrid'
133
+ export type { BentoCardProps, CardSize } from './components/BentoCard'
134
+
135
+ // `logger` was removed in v2.0.0. Migrate to `@wolffm/logger/client`:
136
+ // - import { logger } from '@wolffm/task-ui-components'
137
+ // + import { logger } from '@wolffm/logger/client'
138
+ // See packages/logger in hadoku_site for the consolidated cross-runtime
139
+ // implementation. UI helpers (theme, component, preference, apiRequest,
140
+ // apiResponse, fallback) are preserved on the new client logger.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Where a dropdown list goes under (or over) its trigger, shared by <Select>
3
+ * and installSelectMenus so both lists sit and flip identically.
4
+ */
5
+
6
+ export const MENU_GAP = 4
7
+ export const MENU_MAX_HEIGHT = 280
8
+
9
+ export interface MenuPosition {
10
+ left: number
11
+ width: number
12
+ top?: number
13
+ bottom?: number
14
+ maxHeight: number
15
+ }
16
+
17
+ /** `listHeight` is the list's full scrollHeight, so a short list never flips. */
18
+ export function placeMenu(rect: DOMRect, listHeight: number): MenuPosition {
19
+ const below = window.innerHeight - rect.bottom - MENU_GAP
20
+ const above = rect.top - MENU_GAP
21
+ // Flip up only when the list does not fit below AND there is more room above.
22
+ const flip = below < Math.min(MENU_MAX_HEIGHT, listHeight) && above > below
23
+ return {
24
+ left: rect.left,
25
+ width: rect.width,
26
+ ...(flip
27
+ ? { bottom: window.innerHeight - rect.top + MENU_GAP }
28
+ : { top: rect.bottom + MENU_GAP }),
29
+ maxHeight: Math.max(120, Math.min(MENU_MAX_HEIGHT, flip ? above : below) - 8)
30
+ }
31
+ }