@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,288 @@
1
+ /* AppHeader + ConnectedSettings styles for @wolffm/task-ui-components.
2
+ *
3
+ * All colors come from @wolffm/themes semantic tokens so the header themes with
4
+ * everything else. One import is all an app needs:
5
+ * import '@wolffm/task-ui-components/app-header.css'
6
+ *
7
+ * This used to say "import alongside theme-picker.css", and that convention
8
+ * failed exactly the way conventions do. AppHeader renders the theme picker
9
+ * unconditionally now — it is not a prop — so an app that loads this file
10
+ * without the picker's gets the right component with none of its styling. The
11
+ * command station shipped like that after its old wrapper (the only importer of
12
+ * theme-picker.css on that site) was deleted: correct markup, no CSS.
13
+ *
14
+ * Pulling it in here makes the pair inseparable, which is the same reason the
15
+ * component itself stopped being a prop. */
16
+ @import './theme-picker.css';
17
+
18
+ /* ── Header layout ─────────────────────────────────────────────────────────
19
+ * Title left, action bar right, vertically centered. Full width of whatever
20
+ * column the app places it in. */
21
+ .app-header {
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ gap: 1rem;
26
+ padding-bottom: 1rem;
27
+ margin-bottom: 1.5rem;
28
+ border-bottom: 1px solid var(--color-border);
29
+ }
30
+
31
+ /* Body text, NOT --color-primary. `primary` is a fill colour: sanctioned only
32
+ * under --color-on-primary. As bare text on --color-bg it clears the 3:1
33
+ * large-text floor in just 12 of 18 themes (izakaya-light 1.92, nature-light
34
+ * 2.15, ocean-light 2.28, lavender-light 2.53, pink-light 2.56,
35
+ * strawberry-light 2.61). --color-text clears it everywhere at 8.87–17.58.
36
+ * The brand accent lives in the border-bottom instead. */
37
+ .app-header__title {
38
+ margin: 0;
39
+ color: var(--color-text);
40
+ font-size: var(--hdk-text-lg);
41
+ font-weight: 700;
42
+ line-height: 1.2;
43
+ /* A flex item floors at its min-content width, so without `min-width: 0` a
44
+ * long title pushed the actions off a 320px screen instead of giving way. */
45
+ min-width: 0;
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+ white-space: nowrap;
49
+ }
50
+
51
+ /* Never shrinks: the controls keep their 32px hit areas and the title is what
52
+ * gives way. 0.5rem is enough between icons — the round hit areas already
53
+ * space them visually. */
54
+ .app-header__actions {
55
+ display: flex;
56
+ align-items: center;
57
+ flex-shrink: 0;
58
+ gap: 0.5rem;
59
+ }
60
+
61
+ /* In the header the theme picker sits directly left of the settings gear, and
62
+ * the base ThemePicker opens its dropdown SIDEWAYS (which suits a vertical /
63
+ * sidebar placement). Here that would cover the gear — so scope it to drop
64
+ * straight DOWN, right-aligned, opening into the header away from the gear. */
65
+ .app-header .theme-picker__dropdown,
66
+ .app-header .theme-picker--open-left .theme-picker__dropdown {
67
+ top: calc(100% + 8px);
68
+ left: auto;
69
+ right: 0;
70
+ }
71
+
72
+ /* ── ConnectedSettings: gear toggle ────────────────────────────────────────
73
+ * The gear itself is a .app-header__icon-btn (icon-btn.css, via
74
+ * theme-picker.css), the same rules as the picker trigger beside it. */
75
+ .settings-popout {
76
+ position: relative;
77
+ display: inline-flex;
78
+ }
79
+
80
+ /* Full-viewport click-catcher, same trick ThemePicker uses. */
81
+ .settings-popout__overlay {
82
+ position: fixed;
83
+ inset: 0;
84
+ z-index: 99;
85
+ background: transparent;
86
+ }
87
+
88
+ .settings-popout__panel {
89
+ position: absolute;
90
+ top: calc(100% + 8px);
91
+ right: 0;
92
+ z-index: 100;
93
+ /* These apps ship without a Tailwind preflight, so box-sizing is content-box
94
+ * unless a rule says otherwise: without this, `width: 300px` renders 334px
95
+ * (+32 padding +2 border) and `max-width` clamps the CONTENT box, leaving the
96
+ * popout 2px wider than a 320px viewport. */
97
+ box-sizing: border-box;
98
+ width: 300px;
99
+ max-width: calc(100vw - 32px);
100
+ /* Cap height so an app that adds its own preferences section stays on screen;
101
+ * the popout scrolls rather than overflowing the viewport. */
102
+ max-height: calc(100vh - 80px);
103
+ overflow-y: auto;
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 14px;
107
+ padding: 16px;
108
+ border-radius: 12px;
109
+ background: var(--color-bg-card);
110
+ border: 1px solid var(--color-border);
111
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
112
+ color: var(--color-text);
113
+ }
114
+
115
+ /* Divider above the optional app-specific settings section. */
116
+ .settings-popout__app {
117
+ border-top: 1px solid var(--color-border);
118
+ padding-top: 12px;
119
+ }
120
+
121
+ .settings-popout__header {
122
+ font-weight: 600;
123
+ font-size: 0.95rem;
124
+ color: var(--color-text);
125
+ }
126
+
127
+ .settings-popout__row {
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: space-between;
131
+ gap: 10px;
132
+ }
133
+ .settings-popout__row--stack {
134
+ flex-direction: column;
135
+ align-items: stretch;
136
+ gap: 8px;
137
+ }
138
+
139
+ .settings-popout__label {
140
+ font-size: 0.8rem;
141
+ color: var(--color-text-secondary);
142
+ }
143
+
144
+ .settings-popout__value {
145
+ font-size: 0.85rem;
146
+ font-weight: 500;
147
+ color: var(--color-text);
148
+ }
149
+
150
+ /* Label + control pair on the right of a row. `min-width: 0` is load-bearing:
151
+ * as a flex item of __row it otherwise floors at its own min-content width, and
152
+ * that includes the input's default `size=20` intrinsic width (195px in Chrome).
153
+ * Pair + gap + Save came to 251.4px against a 300px row that also has to hold
154
+ * the label — so the name row overflowed the panel the moment Edit was clicked,
155
+ * and __panel's `overflow-y: auto` computes overflow-x to `auto`, turning that
156
+ * into a horizontal scrollbar rather than a clip. */
157
+ .settings-popout__inline {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: flex-end;
161
+ gap: 6px;
162
+ /* Claim the row's free space rather than shrink-wrapping, so the input below
163
+ * has something to grow into now that its basis is 0. */
164
+ flex: 1 1 auto;
165
+ min-width: 0;
166
+ }
167
+
168
+ .settings-popout__tier {
169
+ font-size: 0.78rem;
170
+ font-weight: 600;
171
+ padding: 2px 10px;
172
+ border-radius: 999px;
173
+ background: var(--color-bg-alt);
174
+ color: var(--color-text);
175
+ border: 1px solid var(--color-border);
176
+ }
177
+ .settings-popout__tier--admin {
178
+ background: var(--color-primary-bg);
179
+ color: var(--color-on-primary-bg);
180
+ border-color: var(--color-primary);
181
+ }
182
+
183
+ .settings-popout__input {
184
+ /* `1 1 0` + `width: 0`, not `1 1 auto`: the basis must not come from the
185
+ * input's `size`-derived intrinsic width, or the row cannot shrink below it. */
186
+ flex: 1 1 0;
187
+ width: 0;
188
+ min-width: 0;
189
+ box-sizing: border-box;
190
+ font-size: 0.85rem;
191
+ padding: 5px 8px;
192
+ border-radius: 7px;
193
+ border: 1px solid var(--color-border);
194
+ background: var(--color-bg);
195
+ color: var(--color-text);
196
+ }
197
+ .settings-popout__input:focus-visible {
198
+ outline: none;
199
+ border-color: var(--color-primary);
200
+ box-shadow: 0 0 0 2px var(--color-primary-bg);
201
+ }
202
+
203
+ .settings-popout__btn {
204
+ font-size: 0.78rem;
205
+ padding: 4px 10px;
206
+ border-radius: 7px;
207
+ border: 1px solid var(--color-border);
208
+ background: var(--color-bg);
209
+ color: var(--color-text);
210
+ cursor: pointer;
211
+ white-space: nowrap;
212
+ text-decoration: none;
213
+ transition:
214
+ background-color 0.15s ease,
215
+ border-color 0.15s ease;
216
+ }
217
+ .settings-popout__btn:hover:not(:disabled) {
218
+ background: var(--color-bg-alt);
219
+ }
220
+ .settings-popout__btn--primary {
221
+ background: var(--color-primary);
222
+ border-color: var(--color-primary);
223
+ color: var(--color-on-primary);
224
+ }
225
+ .settings-popout__btn--primary:hover:not(:disabled) {
226
+ background: var(--color-primary-dark);
227
+ }
228
+ .settings-popout__btn:disabled {
229
+ opacity: 0.6;
230
+ cursor: default;
231
+ }
232
+
233
+ /* Content pill — a chunked "volume slider": segments 1..maxLevel, everything
234
+ * up to and including the selected level reads as filled. */
235
+ .settings-popout__pill {
236
+ display: flex;
237
+ gap: 3px;
238
+ width: 100%;
239
+ }
240
+ .settings-popout__seg {
241
+ flex: 1 1 0;
242
+ padding: 7px 0;
243
+ font-size: 0.8rem;
244
+ font-weight: 600;
245
+ border: 1px solid var(--color-border);
246
+ background: var(--color-bg-alt);
247
+ color: var(--color-text-secondary);
248
+ cursor: pointer;
249
+ transition:
250
+ background-color 0.12s ease,
251
+ color 0.12s ease;
252
+ }
253
+ .settings-popout__seg:first-child {
254
+ border-radius: 8px 0 0 8px;
255
+ }
256
+ .settings-popout__seg:last-child {
257
+ border-radius: 0 8px 8px 0;
258
+ }
259
+ .settings-popout__seg--filled {
260
+ background: var(--color-primary);
261
+ border-color: var(--color-primary);
262
+ color: var(--color-on-primary);
263
+ }
264
+ .settings-popout__seg:disabled {
265
+ cursor: default;
266
+ }
267
+
268
+ /* Inline sign-in: label above, input + submit on one line, error below. */
269
+ .settings-popout__key-form {
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: 6px;
273
+ width: 100%;
274
+ }
275
+ .settings-popout__error {
276
+ font-size: 0.75rem;
277
+ line-height: 1.35;
278
+ padding: 4px 8px;
279
+ border-radius: 6px;
280
+ background: var(--color-danger-bg);
281
+ color: var(--color-on-danger-bg);
282
+ }
283
+
284
+ .settings-popout__hint {
285
+ font-size: 0.72rem;
286
+ line-height: 1.35;
287
+ color: var(--color-text-tertiary);
288
+ }
package/dist/bento.css ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Bento Component Styles
3
+ * Styles for BentoGrid and BentoCard
4
+ */
5
+
6
+ /* ===== BENTO GRID ===== */
7
+ .bento-grid {
8
+ display: grid;
9
+ gap: var(--bento-gap, 1rem);
10
+ padding: 1rem;
11
+ width: 100%;
12
+ max-width: var(--bento-max-width, 1400px);
13
+ margin: 0 auto;
14
+
15
+ /* Mobile first: single column */
16
+ grid-template-columns: 1fr;
17
+ grid-auto-rows: minmax(120px, auto);
18
+ }
19
+
20
+ /* Small tablets: 2 columns */
21
+ @media (min-width: 640px) {
22
+ .bento-grid {
23
+ grid-template-columns: repeat(var(--bento-cols-sm, 2), 1fr);
24
+ gap: calc(var(--bento-gap, 1rem) * 1.25);
25
+ padding: 1.5rem;
26
+ }
27
+ }
28
+
29
+ /* Tablets: 3 columns */
30
+ @media (min-width: 768px) {
31
+ .bento-grid {
32
+ grid-template-columns: repeat(var(--bento-cols-md, 3), 1fr);
33
+ }
34
+ }
35
+
36
+ /* Desktop: 4 columns */
37
+ @media (min-width: 1024px) {
38
+ .bento-grid {
39
+ grid-template-columns: repeat(var(--bento-cols-lg, 4), 1fr);
40
+ gap: calc(var(--bento-gap, 1rem) * 1.5);
41
+ padding: 2rem;
42
+ }
43
+ }
44
+
45
+ /* Large desktop: 6 columns */
46
+ @media (min-width: 1400px) {
47
+ .bento-grid {
48
+ grid-template-columns: repeat(var(--bento-cols-xl, 6), 1fr);
49
+ }
50
+ }
51
+
52
+ /* ===== BENTO CARD ===== */
53
+ .bento-card {
54
+ position: relative;
55
+ overflow: hidden;
56
+ border-radius: var(--hdk-radius-lg);
57
+ background: var(--color-bg-card);
58
+ border: 1px solid var(--color-border-light);
59
+ box-shadow: var(--hdk-shadow-md);
60
+ transition:
61
+ transform var(--transition-fast, 0.15s) ease,
62
+ box-shadow var(--transition-fast, 0.15s) ease;
63
+
64
+ /* GPU acceleration for smooth animations */
65
+ transform: translateZ(0);
66
+ backface-visibility: hidden;
67
+ }
68
+
69
+ .bento-card[role='button'] {
70
+ cursor: pointer;
71
+ }
72
+
73
+ .bento-card[role='button']:hover:not(.bento-card--disabled) {
74
+ transform: scale(1.02) translateZ(0);
75
+ box-shadow: var(--hdk-shadow-lg);
76
+ }
77
+
78
+ .bento-card[role='button']:active:not(.bento-card--disabled) {
79
+ transform: scale(0.98) translateZ(0);
80
+ }
81
+
82
+ .bento-card:focus-visible {
83
+ outline: none;
84
+ box-shadow: var(--hdk-shadow-focus), var(--hdk-shadow-md);
85
+ }
86
+
87
+ /* Card Sizes */
88
+ .bento-card--default {
89
+ grid-column: span 1;
90
+ grid-row: span 1;
91
+ }
92
+
93
+ .bento-card--wide {
94
+ grid-column: span 1;
95
+ }
96
+
97
+ @media (min-width: 640px) {
98
+ .bento-card--wide {
99
+ grid-column: span 2;
100
+ }
101
+ }
102
+
103
+ .bento-card--tall {
104
+ grid-row: span 2;
105
+ }
106
+
107
+ .bento-card--large {
108
+ grid-column: span 1;
109
+ grid-row: span 1;
110
+ }
111
+
112
+ @media (min-width: 640px) {
113
+ .bento-card--large {
114
+ grid-column: span 2;
115
+ grid-row: span 2;
116
+ }
117
+ }
118
+
119
+ .bento-card--hero {
120
+ grid-column: 1 / -1;
121
+ }
122
+
123
+ @media (min-width: 768px) {
124
+ .bento-card--hero {
125
+ grid-column: span 3;
126
+ }
127
+ }
128
+
129
+ /* Disabled State (preloading) */
130
+ .bento-card--disabled {
131
+ pointer-events: none;
132
+ opacity: 0.85;
133
+ }
134
+
135
+ /* Respect reduced motion preference */
136
+ @media (prefers-reduced-motion: reduce) {
137
+ .bento-card,
138
+ .bento-card[role='button']:hover:not(.bento-card--disabled),
139
+ .bento-card[role='button']:active:not(.bento-card--disabled) {
140
+ transform: none;
141
+ transition: none;
142
+ }
143
+ }
@@ -0,0 +1,227 @@
1
+ /* Button styles for @wolffm/task-ui-components.
2
+ *
3
+ * import '@wolffm/task-ui-components/button.css'
4
+ *
5
+ * Each family modifier maps its six theme tokens onto private `--_btn-*`
6
+ * properties, and each variant paints from those. That keeps the matrix at
7
+ * 5 + 5 rules instead of 25, and every pairing below is a sanctioned one:
8
+ * `on-<f>` over `<f>`, `on-<f>-bg` over `<f>-bg` (THEME_USAGE_GUIDE.md).
9
+ */
10
+
11
+ .hdk-btn {
12
+ --_btn-c: var(--color-primary);
13
+ --_btn-c-dark: var(--color-primary-dark);
14
+ --_btn-on: var(--color-on-primary);
15
+ --_btn-tint: var(--color-primary-bg);
16
+ --_btn-on-tint: var(--color-on-primary-bg);
17
+ --_btn-hover: var(--color-primary-hover);
18
+
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ gap: 6px;
23
+ font: inherit;
24
+ font-weight: var(--font-weight-semibold);
25
+ line-height: 1.2;
26
+ white-space: nowrap;
27
+ border: 1px solid transparent;
28
+ border-radius: var(--hdk-radius);
29
+ cursor: pointer;
30
+ user-select: none;
31
+ transition:
32
+ background-color var(--transition-fast),
33
+ border-color var(--transition-fast),
34
+ color var(--transition-fast),
35
+ box-shadow var(--transition-fast),
36
+ transform var(--transition-fast);
37
+ }
38
+
39
+ .hdk-btn:focus-visible {
40
+ outline: none;
41
+ box-shadow: var(--hdk-shadow-focus);
42
+ }
43
+
44
+ .hdk-btn:active:not(:disabled) {
45
+ transform: translateY(1px);
46
+ }
47
+
48
+ .hdk-btn:disabled {
49
+ opacity: 0.5;
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ /* A loading button is disabled for clicks but is not "unavailable", so it
54
+ * keeps full opacity — the spinner is the signal. */
55
+ .hdk-btn--loading:disabled {
56
+ opacity: 1;
57
+ cursor: progress;
58
+ }
59
+
60
+ /* ── families ─────────────────────────────────────────────────────────── */
61
+
62
+ .hdk-btn--success {
63
+ --_btn-c: var(--color-success);
64
+ --_btn-c-dark: var(--color-success-dark);
65
+ --_btn-on: var(--color-on-success);
66
+ --_btn-tint: var(--color-success-bg);
67
+ --_btn-on-tint: var(--color-on-success-bg);
68
+ --_btn-hover: var(--color-success-hover);
69
+ }
70
+
71
+ .hdk-btn--warning {
72
+ --_btn-c: var(--color-warning);
73
+ --_btn-c-dark: var(--color-warning-dark);
74
+ --_btn-on: var(--color-on-warning);
75
+ --_btn-tint: var(--color-warning-bg);
76
+ --_btn-on-tint: var(--color-on-warning-bg);
77
+ --_btn-hover: var(--color-warning-hover);
78
+ }
79
+
80
+ .hdk-btn--danger {
81
+ --_btn-c: var(--color-danger);
82
+ --_btn-c-dark: var(--color-danger-dark);
83
+ --_btn-on: var(--color-on-danger);
84
+ --_btn-tint: var(--color-danger-bg);
85
+ --_btn-on-tint: var(--color-on-danger-bg);
86
+ --_btn-hover: var(--color-danger-hover);
87
+ }
88
+
89
+ .hdk-btn--neutral {
90
+ --_btn-c: var(--color-neutral);
91
+ --_btn-c-dark: var(--color-neutral-dark);
92
+ --_btn-on: var(--color-on-neutral);
93
+ --_btn-tint: var(--color-neutral-bg);
94
+ --_btn-on-tint: var(--color-on-neutral-bg);
95
+ --_btn-hover: var(--color-neutral-hover);
96
+ }
97
+
98
+ /* ── variants ─────────────────────────────────────────────────────────── */
99
+
100
+ .hdk-btn--solid {
101
+ background-color: var(--_btn-c);
102
+ color: var(--_btn-on);
103
+ }
104
+
105
+ .hdk-btn--solid:hover:not(:disabled) {
106
+ background-color: var(--_btn-c-dark);
107
+ }
108
+
109
+ /* The task app's own `.pill-btn--active` look, as a reusable treatment. */
110
+ .hdk-btn--gradient {
111
+ background: linear-gradient(180deg, var(--_btn-c) 0%, var(--_btn-c-dark) 100%);
112
+ color: var(--_btn-on);
113
+ box-shadow: var(--hdk-shadow-sm);
114
+ }
115
+
116
+ .hdk-btn--gradient:hover:not(:disabled) {
117
+ box-shadow: var(--hdk-shadow-md);
118
+ }
119
+
120
+ .hdk-btn--soft {
121
+ background-color: var(--_btn-tint);
122
+ color: var(--_btn-on-tint);
123
+ }
124
+
125
+ .hdk-btn--soft:hover:not(:disabled) {
126
+ border-color: var(--_btn-c);
127
+ }
128
+
129
+ .hdk-btn--outline {
130
+ background-color: transparent;
131
+ border-color: var(--_btn-c);
132
+ color: var(--color-text);
133
+ }
134
+
135
+ .hdk-btn--outline:hover:not(:disabled) {
136
+ background-color: var(--_btn-tint);
137
+ color: var(--_btn-on-tint);
138
+ }
139
+
140
+ .hdk-btn--ghost {
141
+ background-color: transparent;
142
+ color: var(--color-text-secondary);
143
+ }
144
+
145
+ .hdk-btn--ghost:hover:not(:disabled) {
146
+ background-color: var(--_btn-tint);
147
+ color: var(--_btn-on-tint);
148
+ }
149
+
150
+ /* ── sizes ────────────────────────────────────────────────────────────── */
151
+
152
+ .hdk-btn--sm {
153
+ font-size: var(--hdk-text-xs);
154
+ padding: 4px 10px;
155
+ min-height: 28px;
156
+ }
157
+
158
+ .hdk-btn--md {
159
+ font-size: var(--hdk-text-sm);
160
+ padding: 7px 14px;
161
+ min-height: 34px;
162
+ }
163
+
164
+ .hdk-btn--lg {
165
+ font-size: var(--hdk-text-base);
166
+ padding: 10px 18px;
167
+ min-height: 42px;
168
+ }
169
+
170
+ .hdk-btn--icon-only {
171
+ padding: 0;
172
+ aspect-ratio: 1;
173
+ }
174
+
175
+ .hdk-btn--icon-only.hdk-btn--sm {
176
+ width: 28px;
177
+ }
178
+
179
+ .hdk-btn--icon-only.hdk-btn--md {
180
+ width: 34px;
181
+ }
182
+
183
+ .hdk-btn--icon-only.hdk-btn--lg {
184
+ width: 42px;
185
+ }
186
+
187
+ /* ── shapes ───────────────────────────────────────────────────────────── */
188
+
189
+ .hdk-btn--pill {
190
+ border-radius: 999px;
191
+ }
192
+
193
+ .hdk-btn--square {
194
+ border-radius: var(--hdk-radius-sm);
195
+ }
196
+
197
+ .hdk-btn--block {
198
+ display: flex;
199
+ width: 100%;
200
+ }
201
+
202
+ /* ── loading ──────────────────────────────────────────────────────────── */
203
+
204
+ .hdk-btn__spinner {
205
+ width: 1em;
206
+ height: 1em;
207
+ border: 2px solid currentColor;
208
+ border-right-color: transparent;
209
+ border-radius: 50%;
210
+ animation: hdk-btn-spin 0.7s linear infinite;
211
+ }
212
+
213
+ @keyframes hdk-btn-spin {
214
+ to {
215
+ transform: rotate(360deg);
216
+ }
217
+ }
218
+
219
+ @media (prefers-reduced-motion: reduce) {
220
+ .hdk-btn__spinner {
221
+ animation-duration: 2s;
222
+ }
223
+
224
+ .hdk-btn:active:not(:disabled) {
225
+ transform: none;
226
+ }
227
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AppHeader — the ecosystem-standard top bar for every hadoku.me app.
3
+ *
4
+ * Enforces the shared contract so child apps stop drifting:
5
+ * [ title (left) ] ————— <space> ————— [ status? · ThemePicker · Settings gear ] (right)
6
+ *
7
+ * Everything BELOW the header is the app's own concern. The header owns the
8
+ * title, and it OWNS both right-hand controls outright — it renders the theme
9
+ * picker and the settings gear itself. Neither is a prop.
10
+ *
11
+ * WHY THEY ARE NOT PROPS ANY MORE
12
+ * -------------------------------
13
+ * `themePicker?: React.ReactNode` was an open hole: a slot that accepts a node
14
+ * accepts ANY node, so "every app uses the same picker" was a convention, and
15
+ * conventions drift. They did — one app passed a raw `<ThemePicker>` with its
16
+ * own `isOpen` state and a debug logger wired into `getThemeIcon`, and eight
17
+ * apps each kept a hand-copied `useTheme` hook to feed it. Those copies had
18
+ * already diverged on real behaviour (only one wrote `localStorage`, so the
19
+ * rest lost the theme on browser restart and flashed a default on next paint).
20
+ *
21
+ * The control is now rendered here, from `useHadokuTheme()`. An app cannot
22
+ * substitute, restyle or omit it. What an app CAN do is inject its own
23
+ * preferences — see `children`.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * import { AppHeader } from '@wolffm/task-ui-components'
28
+ * import { HadokuThemeRoot } from '@wolffm/themes'
29
+ *
30
+ * // once, at the app root:
31
+ * <HadokuThemeRoot>
32
+ * <AppHeader title="Print Tool" status={<ApiStatus status={apiStatus} />}>
33
+ * <MyAppPreferences /> // optional: appended AFTER the canonical rows
34
+ * </AppHeader>
35
+ * </HadokuThemeRoot>
36
+ * ```
37
+ */
38
+ import React from 'react';
39
+ import { type ConnectedSettingsProps } from './ConnectedSettings';
40
+ export interface AppHeaderProps {
41
+ /** Left-aligned header title. A string is wrapped in an <h1>; pass a node for
42
+ * custom markup (e.g. a clickable title button). */
43
+ title: React.ReactNode;
44
+ /** Optional status/extra node shown left of the theme + settings controls
45
+ * (e.g. an "API Online" indicator). Acceptable per the header contract. */
46
+ status?: React.ReactNode;
47
+ /** Identity hints for the settings popout. Narrow on purpose: `userType` and
48
+ * `name` exist because an app behind a path-prefixed shim may not be able to
49
+ * reach `/session/whoami` (conjure), NOT as a styling or layout hook. */
50
+ settingsProps?: ConnectedSettingsProps;
51
+ /** THE app-specific extension point. Rendered inside the settings popout as a
52
+ * final section, under the four canonical rows. Put your app's own
53
+ * preferences here — checkboxes, selects, whatever it needs; this slot is
54
+ * deliberately unconstrained. What it cannot do is alter the rows above it. */
55
+ children?: React.ReactNode;
56
+ /** Extra class on the <header> root. Layout only — it cannot reach the
57
+ * controls, which own their own markup. */
58
+ className?: string;
59
+ }
60
+ export declare function AppHeader({ title, status, settingsProps, children, className }: AppHeaderProps): React.JSX.Element;
61
+ //# sourceMappingURL=AppHeader.d.ts.map