@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.
- package/LICENSE +21 -0
- package/README.md +917 -2
- package/dist/app-header.css +288 -0
- package/dist/bento.css +143 -0
- package/dist/button.css +227 -0
- package/dist/components/AppHeader.d.ts +61 -0
- package/dist/components/AppHeader.d.ts.map +1 -0
- package/dist/components/AppHeader.js +23 -0
- package/dist/components/AppHeader.js.map +1 -0
- package/dist/components/BentoCard.d.ts +23 -0
- package/dist/components/BentoCard.d.ts.map +1 -0
- package/dist/components/BentoCard.js +49 -0
- package/dist/components/BentoCard.js.map +1 -0
- package/dist/components/BentoGrid.d.ts +24 -0
- package/dist/components/BentoGrid.d.ts.map +1 -0
- package/dist/components/BentoGrid.js +22 -0
- package/dist/components/BentoGrid.js.map +1 -0
- package/dist/components/Button.d.ts +28 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +51 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/ConnectedSettings.d.ts +53 -0
- package/dist/components/ConnectedSettings.d.ts.map +1 -0
- package/dist/components/ConnectedSettings.js +368 -0
- package/dist/components/ConnectedSettings.js.map +1 -0
- package/dist/components/ConnectedThemePicker.d.ts +52 -0
- package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
- package/dist/components/ConnectedThemePicker.js +32 -0
- package/dist/components/ConnectedThemePicker.js.map +1 -0
- package/dist/components/ContextMenu.d.ts +22 -0
- package/dist/components/ContextMenu.d.ts.map +1 -0
- package/dist/components/ContextMenu.js +23 -0
- package/dist/components/ContextMenu.js.map +1 -0
- package/dist/components/HadokuThemePicker.d.ts +27 -0
- package/dist/components/HadokuThemePicker.d.ts.map +1 -0
- package/dist/components/HadokuThemePicker.js +19 -0
- package/dist/components/HadokuThemePicker.js.map +1 -0
- package/dist/components/LoadingSkeleton.d.ts +14 -0
- package/dist/components/LoadingSkeleton.d.ts.map +1 -0
- package/dist/components/LoadingSkeleton.js +62 -0
- package/dist/components/LoadingSkeleton.js.map +1 -0
- package/dist/components/Modal.d.ts +39 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +106 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/NumberInput.d.ts +26 -0
- package/dist/components/NumberInput.d.ts.map +1 -0
- package/dist/components/NumberInput.js +131 -0
- package/dist/components/NumberInput.js.map +1 -0
- package/dist/components/Select.d.ts +52 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +281 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SettingsModal.d.ts +69 -0
- package/dist/components/SettingsModal.d.ts.map +1 -0
- package/dist/components/SettingsModal.js +141 -0
- package/dist/components/SettingsModal.js.map +1 -0
- package/dist/components/ThemeIcons.d.ts +26 -0
- package/dist/components/ThemeIcons.d.ts.map +1 -0
- package/dist/components/ThemeIcons.js +74 -0
- package/dist/components/ThemeIcons.js.map +1 -0
- package/dist/components/ThemePicker.d.ts +28 -0
- package/dist/components/ThemePicker.d.ts.map +1 -0
- package/dist/components/ThemePicker.js +101 -0
- package/dist/components/ThemePicker.js.map +1 -0
- package/dist/components/Toast.d.ts +13 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +70 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Toaster.d.ts +19 -0
- package/dist/components/Toaster.d.ts.map +1 -0
- package/dist/components/Toaster.js +19 -0
- package/dist/components/Toaster.js.map +1 -0
- package/dist/components/Tooltip.d.ts +49 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +143 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/context-menu.css +11 -0
- package/dist/controls.css +137 -0
- package/dist/hooks/useReducedMotion.d.ts +7 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/hooks/useReducedMotion.js +25 -0
- package/dist/hooks/useReducedMotion.js.map +1 -0
- package/dist/hooks/useToast.d.ts +18 -0
- package/dist/hooks/useToast.d.ts.map +1 -0
- package/dist/hooks/useToast.js +28 -0
- package/dist/hooks/useToast.js.map +1 -0
- package/dist/icon-btn.css +64 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/menuPlacement.d.ts +16 -0
- package/dist/lib/menuPlacement.d.ts.map +1 -0
- package/dist/lib/menuPlacement.js +19 -0
- package/dist/lib/menuPlacement.js.map +1 -0
- package/dist/lib/selectMenus.d.ts +30 -0
- package/dist/lib/selectMenus.d.ts.map +1 -0
- package/dist/lib/selectMenus.js +236 -0
- package/dist/lib/selectMenus.js.map +1 -0
- package/dist/lib/settingsClient.d.ts +144 -0
- package/dist/lib/settingsClient.d.ts.map +1 -0
- package/dist/lib/settingsClient.js +157 -0
- package/dist/lib/settingsClient.js.map +1 -0
- package/dist/lib/themeScope.d.ts +19 -0
- package/dist/lib/themeScope.d.ts.map +1 -0
- package/dist/lib/themeScope.js +10 -0
- package/dist/lib/themeScope.js.map +1 -0
- package/dist/lib/titleTooltips.d.ts +38 -0
- package/dist/lib/titleTooltips.d.ts.map +1 -0
- package/dist/lib/titleTooltips.js +166 -0
- package/dist/lib/titleTooltips.js.map +1 -0
- package/dist/lib/tooltipPlacement.d.ts +14 -0
- package/dist/lib/tooltipPlacement.d.ts.map +1 -0
- package/dist/lib/tooltipPlacement.js +34 -0
- package/dist/lib/tooltipPlacement.js.map +1 -0
- package/dist/modal.css +129 -0
- package/dist/number-input.css +131 -0
- package/dist/scrollbar.css +165 -0
- package/dist/select.css +251 -0
- package/dist/styles.css +1866 -0
- package/dist/task-ui-components.css +1866 -0
- package/dist/theme-picker.css +219 -0
- package/dist/toaster.css +159 -0
- package/dist/tooltip.css +103 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +60 -4
- package/src/app-header.css +288 -0
- package/src/bento.css +143 -0
- package/src/button.css +227 -0
- package/src/components/AppHeader.tsx +87 -0
- package/src/components/BentoCard.tsx +72 -0
- package/src/components/BentoGrid.tsx +48 -0
- package/src/components/Button.tsx +81 -0
- package/src/components/ConnectedSettings.tsx +545 -0
- package/src/components/ConnectedThemePicker.tsx +79 -0
- package/src/components/ContextMenu.tsx +48 -0
- package/src/components/HadokuThemePicker.tsx +39 -0
- package/src/components/LoadingSkeleton.tsx +84 -0
- package/src/components/Modal.tsx +171 -0
- package/src/components/NumberInput.tsx +161 -0
- package/src/components/Select.tsx +359 -0
- package/src/components/SettingsModal.tsx +235 -0
- package/src/components/ThemeIcons.tsx +127 -0
- package/src/components/ThemePicker.tsx +149 -0
- package/src/components/Toast.tsx +92 -0
- package/src/components/Toaster.tsx +43 -0
- package/src/components/Tooltip.tsx +212 -0
- package/src/context-menu.css +11 -0
- package/src/controls.css +137 -0
- package/src/hooks/useReducedMotion.ts +36 -0
- package/src/hooks/useToast.ts +57 -0
- package/src/icon-btn.css +64 -0
- package/src/index.ts +153 -0
- package/src/lib/menuPlacement.ts +31 -0
- package/src/lib/selectMenus.ts +303 -0
- package/src/lib/settingsClient.ts +425 -0
- package/src/lib/themeScope.ts +23 -0
- package/src/lib/titleTooltips.ts +236 -0
- package/src/lib/tooltipPlacement.ts +59 -0
- package/src/modal.css +129 -0
- package/src/number-input.css +131 -0
- package/src/scrollbar.css +165 -0
- package/src/select.css +251 -0
- package/src/theme-picker.css +219 -0
- package/src/toaster.css +159 -0
- package/src/tooltip.css +103 -0
- package/src/types/index.ts +43 -0
package/README.md
CHANGED
|
@@ -1,3 +1,918 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wolffm/task-ui-components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable UI components for task management applications. Production-ready and fully typed.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @wolffm/task-ui-components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## SSR Configuration (Astro/Next.js)
|
|
12
|
+
|
|
13
|
+
If using this package in an SSR environment (Astro, Next.js, etc.), add this one-time configuration:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// astro.config.mjs
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
vite: {
|
|
19
|
+
ssr: {
|
|
20
|
+
noExternal: ['@wolffm/task-ui-components']
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// next.config.js
|
|
28
|
+
module.exports = {
|
|
29
|
+
transpilePackages: ['@wolffm/task-ui-components']
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This allows the package to bundle its CSS automatically. **This is a one-time setup** - all future component updates will work automatically without additional configuration.
|
|
34
|
+
|
|
35
|
+
## Components
|
|
36
|
+
|
|
37
|
+
### AppHeader (Overview)
|
|
38
|
+
|
|
39
|
+
The ecosystem-standard top bar for every hadoku.me app: title on the left, an
|
|
40
|
+
action bar on the right holding the theme picker and the unified settings gear.
|
|
41
|
+
Enforces the shared header contract so child apps stop drifting. See
|
|
42
|
+
[Unified app header](#unified-app-header-recommended-for-hadokume-apps) below.
|
|
43
|
+
|
|
44
|
+
### ConnectedSettings (Overview)
|
|
45
|
+
|
|
46
|
+
The unified per-user settings gear + popout (access tier, display name, global
|
|
47
|
+
content-visibility level, and a "Switch key" link to the site's `/auth` page —
|
|
48
|
+
only that page creates sessions). "Connected": it self-wires to the
|
|
49
|
+
hadoku.me edge-router via same-origin fetches, so it needs zero props. Injected
|
|
50
|
+
automatically by `AppHeader`; can also be used standalone.
|
|
51
|
+
|
|
52
|
+
### ThemePicker (Overview)
|
|
53
|
+
|
|
54
|
+
A gorgeous theme picker with light/dark pill design. Fully customizable to work with any theme system.
|
|
55
|
+
|
|
56
|
+
### Modal (Overview)
|
|
57
|
+
|
|
58
|
+
Generic modal dialog with keyboard shortcuts, customizable actions, and optional input field.
|
|
59
|
+
|
|
60
|
+
### ContextMenu (Overview)
|
|
61
|
+
|
|
62
|
+
Position-based context menu with customizable items and danger states.
|
|
63
|
+
|
|
64
|
+
### LoadingSkeleton (Overview)
|
|
65
|
+
|
|
66
|
+
Flexible loading skeleton with multiple layouts (default, minimal, custom) and dark theme support.
|
|
67
|
+
|
|
68
|
+
### SettingsModal (Overview)
|
|
69
|
+
|
|
70
|
+
Generic settings dialog with section-based organization and multiple field types (toggle, text, password, select, button, custom).
|
|
71
|
+
|
|
72
|
+
### Toast & Toaster
|
|
73
|
+
|
|
74
|
+
Toast notification system with success, error, and info variants.
|
|
75
|
+
|
|
76
|
+
### Native-control replacements
|
|
77
|
+
|
|
78
|
+
Themed stand-ins for the browser's default chrome. Each ships its own stylesheet; import them
|
|
79
|
+
**unlayered**, next to `@wolffm/themes/style.css`. Live samples across all 18 themes:
|
|
80
|
+
`pnpm run gallery:controls`.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import '@wolffm/task-ui-components/button.css'
|
|
84
|
+
import '@wolffm/task-ui-components/select.css'
|
|
85
|
+
import '@wolffm/task-ui-components/tooltip.css'
|
|
86
|
+
import '@wolffm/task-ui-components/scrollbar.css'
|
|
87
|
+
import { Button, Select, NativeSelect, Tooltip } from '@wolffm/task-ui-components'
|
|
88
|
+
;<div className="task-app-container hdk-scroll">
|
|
89
|
+
{' '}
|
|
90
|
+
{/* themes every scroller inside */}
|
|
91
|
+
<Tooltip content="Delete task">
|
|
92
|
+
<Button variant="soft" family="danger" icon="trash" aria-label="Delete task" />
|
|
93
|
+
</Tooltip>
|
|
94
|
+
<Select
|
|
95
|
+
aria-label="Status"
|
|
96
|
+
value={status}
|
|
97
|
+
onChange={setStatus}
|
|
98
|
+
options={[
|
|
99
|
+
{ value: 'todo', label: 'To do', icon: 'circle' },
|
|
100
|
+
{ value: 'done', label: 'Done', icon: 'check' }
|
|
101
|
+
]}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **Scrollbars** — CSS only. `hdk-scroll` styles its element and every scroller inside it, thumb in the theme's primary
|
|
107
|
+
colour (resolved per element, so nested `data-theme` scopes get their own);
|
|
108
|
+
modifiers `--subtle`, `--thin`, `--rail`, `--autohide`. Zero specificity (`:where()`) inside
|
|
109
|
+
`@layer hdk-fallbacks`, so any scrollbar rule an app writes itself wins — see below.
|
|
110
|
+
- **Button** — `variant` soft (default) | solid | gradient | outline | ghost × `family` primary | success |
|
|
111
|
+
warning | danger | neutral; `size`, `shape` (rounded | pill | square), `icon`, `iconEnd`,
|
|
112
|
+
`loading`, `block`. An icon with no children is a square icon button — give it `aria-label`.
|
|
113
|
+
- **Select** — ARIA listbox with arrows, Home/End, Enter, Escape and typeahead; `variant`
|
|
114
|
+
outline | filled | ghost | underline. **NativeSelect** is a real `<select>` with the same
|
|
115
|
+
trigger; under the hadoku.me shell its list is the themed one too (see `installSelectMenus`).
|
|
116
|
+
- **NumberInput** — a real `<input type="number">` (all input props pass through, controlled or
|
|
117
|
+
not) with a themed up/down stepper in place of the OS spinner; hold to repeat; `size`.
|
|
118
|
+
- **Tooltip** — replaces `title=`. Hover and keyboard focus, Escape dismisses, hoverable bubble;
|
|
119
|
+
`variant` card (default) | inverse | primary, `placement` with viewport flip.
|
|
120
|
+
|
|
121
|
+
Select and Tooltip portal their popups into the anchor's nearest `[data-theme]` scope (the app
|
|
122
|
+
container, or a nested theme preview inside it), so a popup always paints in its anchor's theme
|
|
123
|
+
and escapes `overflow: hidden` and transformed ancestors.
|
|
124
|
+
|
|
125
|
+
### Page-wide fallbacks (hadoku_site installs these once, for every app)
|
|
126
|
+
|
|
127
|
+
These restyle what an app left to the browser, without touching its markup. An app does not
|
|
128
|
+
install them itself; they are listed here so it knows what it gets and how to opt out.
|
|
129
|
+
|
|
130
|
+
- `controls.css` + `<html class="hdk-controls">` — focus ring, link colour, accent colour on
|
|
131
|
+
checkboxes/radios/ranges/progress, selection, placeholder, `<summary>` marker, the file-picker
|
|
132
|
+
button, no number spinners, and field/button chrome.
|
|
133
|
+
- `scrollbar.css` + `<html class="hdk-scroll">` — every scroller.
|
|
134
|
+
- `installTitleTooltips()` (`/title-tooltips`) — every `title=` as the card tooltip.
|
|
135
|
+
Opt out: `data-native-title`.
|
|
136
|
+
- `installSelectMenus()` (`/select-menus`) — the themed list for every native `<select>`;
|
|
137
|
+
touch keeps the phone's picker. Opt out: `data-native-select`.
|
|
138
|
+
|
|
139
|
+
Both sheets are zero-specificity **and** sit in `@layer hdk-fallbacks`, so ANY rule an app writes
|
|
140
|
+
wins, including Tailwind utilities, which live in layers and would otherwise lose to an unlayered
|
|
141
|
+
rule of any specificity. That only holds if the page declares the layer first:
|
|
142
|
+
`@layer hdk-fallbacks, base, theme, utilities;` — hadoku_site's `global.css` does.
|
|
143
|
+
|
|
144
|
+
## Usage
|
|
145
|
+
|
|
146
|
+
### Unified app header (recommended for hadoku.me apps)
|
|
147
|
+
|
|
148
|
+
Every app under `hadoku.me` should render the same top bar. `AppHeader` gives
|
|
149
|
+
you the enforced layout — title left, `[ status? · ThemePicker · Settings ]`
|
|
150
|
+
right — and injects the hadoku-wired `ConnectedSettings` gear for you. Pass your
|
|
151
|
+
own `ConnectedThemePicker` (theme state stays app-side):
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { AppHeader, ConnectedThemePicker } from '@wolffm/task-ui-components'
|
|
155
|
+
import { useTheme, THEME_FAMILIES, THEME_ICON_MAP } from '@wolffm/themes'
|
|
156
|
+
import '@wolffm/themes/style.css'
|
|
157
|
+
import '@wolffm/task-ui-components/theme-picker.css'
|
|
158
|
+
import '@wolffm/task-ui-components/app-header.css'
|
|
159
|
+
|
|
160
|
+
function App() {
|
|
161
|
+
const { theme, setTheme } = useTheme()
|
|
162
|
+
return (
|
|
163
|
+
<>
|
|
164
|
+
<AppHeader
|
|
165
|
+
title="Print Tool"
|
|
166
|
+
themePicker={
|
|
167
|
+
<ConnectedThemePicker
|
|
168
|
+
themeFamilies={THEME_FAMILIES}
|
|
169
|
+
currentTheme={theme}
|
|
170
|
+
onThemeChange={setTheme}
|
|
171
|
+
getThemeIcon={t => {
|
|
172
|
+
const Icon = THEME_ICON_MAP[t as keyof typeof THEME_ICON_MAP]
|
|
173
|
+
return Icon ? <Icon /> : null
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
{/* app body — the app's own concern */}
|
|
179
|
+
</>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`ConnectedSettings` self-resolves the caller's tier/name via `/session/whoami`
|
|
185
|
+
and hits the edge-router's `/session/*` + `/prefs/api/v1/content-level` routes.
|
|
186
|
+
Off `hadoku.me` (e.g. Storybook) every call resolves to a safe default and the
|
|
187
|
+
gear renders a read-only shell — it never throws. Pass `settingsProps={{ userType,
|
|
188
|
+
name }}` when the app already knows the identity to skip the extra fetch, or
|
|
189
|
+
`showSettings={false}` to omit the gear.
|
|
190
|
+
|
|
191
|
+
The theme picker trigger and the gear are both `.app-header__icon-btn` (32px round,
|
|
192
|
+
one `:focus-visible` ring), defined once in `icon-btn.css`. A link in the actions row
|
|
193
|
+
uses the same class and nothing else — it stays `--color-text` visited or not, with no
|
|
194
|
+
underline, even under a host page's `a:visited` rule:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<a className="app-header__icon-btn" href="https://discord.gg/…" aria-label="Discord">
|
|
198
|
+
<Icon name="chat" size={16} />
|
|
199
|
+
</a>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The title truncates with an ellipsis and the actions never shrink, so the bar holds a
|
|
203
|
+
status and four icons at 320px (`e2e/header-icon-btn.spec.ts`).
|
|
204
|
+
|
|
205
|
+
### Quick Start with @wolffm/themes (Recommended)
|
|
206
|
+
|
|
207
|
+
The easiest way to use the ThemePicker is with the `@wolffm/themes` package:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { ConnectedThemePicker } from '@wolffm/task-ui-components'
|
|
211
|
+
import { useTheme, THEME_FAMILIES, THEME_ICON_MAP } from '@wolffm/themes'
|
|
212
|
+
import '@wolffm/themes/style.css'
|
|
213
|
+
|
|
214
|
+
function App() {
|
|
215
|
+
const { theme, setTheme } = useTheme()
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<ConnectedThemePicker
|
|
219
|
+
themeFamilies={THEME_FAMILIES}
|
|
220
|
+
currentTheme={theme}
|
|
221
|
+
onThemeChange={setTheme}
|
|
222
|
+
getThemeIcon={theme => {
|
|
223
|
+
const Icon = THEME_ICON_MAP[theme]
|
|
224
|
+
return Icon ? <Icon /> : null
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Note:** CSS is automatically imported by components - no manual CSS imports needed!
|
|
232
|
+
|
|
233
|
+
### Using ConnectedThemePicker (Stateful)
|
|
234
|
+
|
|
235
|
+
`ConnectedThemePicker` manages its own open/close state:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { ConnectedThemePicker } from '@wolffm/task-ui-components'
|
|
239
|
+
|
|
240
|
+
function App() {
|
|
241
|
+
const [theme, setTheme] = useState('light')
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<ConnectedThemePicker
|
|
245
|
+
themeFamilies={THEME_FAMILIES}
|
|
246
|
+
currentTheme={theme}
|
|
247
|
+
onThemeChange={setTheme}
|
|
248
|
+
/>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Using ThemePicker (Manual State)
|
|
254
|
+
|
|
255
|
+
For full control over the picker's state:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import { ThemePicker, SunIcon, MoonIcon } from '@wolffm/task-ui-components'
|
|
259
|
+
|
|
260
|
+
const THEME_FAMILIES = [
|
|
261
|
+
{
|
|
262
|
+
lightIcon: <SunIcon />,
|
|
263
|
+
darkIcon: <MoonIcon />,
|
|
264
|
+
lightTheme: 'light',
|
|
265
|
+
darkTheme: 'dark',
|
|
266
|
+
lightLabel: 'Light',
|
|
267
|
+
darkLabel: 'Dark'
|
|
268
|
+
}
|
|
269
|
+
// ... more themes
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
function App() {
|
|
273
|
+
const [theme, setTheme] = useState('light')
|
|
274
|
+
const [showPicker, setShowPicker] = useState(false)
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<ThemePicker
|
|
278
|
+
currentTheme={theme}
|
|
279
|
+
isOpen={showPicker}
|
|
280
|
+
themeFamilies={THEME_FAMILIES}
|
|
281
|
+
onThemeChange={setTheme}
|
|
282
|
+
onToggle={() => setShowPicker(!showPicker)}
|
|
283
|
+
onSettingsClick={() => console.log('Settings clicked')}
|
|
284
|
+
/>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### With Automatic Fallback Icons (No Icons Needed!)
|
|
290
|
+
|
|
291
|
+
Perfect when using your own theme system:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { ThemePicker } from '@wolffm/task-ui-components'
|
|
295
|
+
import './my-custom-style.css' // Your own theme CSS
|
|
296
|
+
|
|
297
|
+
const THEME_FAMILIES = [
|
|
298
|
+
{
|
|
299
|
+
// Icons will be automatically assigned (CircleIcon for first theme)
|
|
300
|
+
lightTheme: 'ocean-light',
|
|
301
|
+
darkTheme: 'ocean-dark',
|
|
302
|
+
lightLabel: 'Ocean Light',
|
|
303
|
+
darkLabel: 'Ocean Dark'
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
// SquareIcon for second theme
|
|
307
|
+
lightTheme: 'forest-light',
|
|
308
|
+
darkTheme: 'forest-dark',
|
|
309
|
+
lightLabel: 'Forest Light',
|
|
310
|
+
darkLabel: 'Forest Dark'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
// TriangleIcon for third theme
|
|
314
|
+
lightTheme: 'sunset-light',
|
|
315
|
+
darkTheme: 'sunset-dark',
|
|
316
|
+
lightLabel: 'Sunset Light',
|
|
317
|
+
darkLabel: 'Sunset Dark'
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
function App() {
|
|
322
|
+
const [theme, setTheme] = useState('ocean-light')
|
|
323
|
+
const [showPicker, setShowPicker] = useState(false)
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<ThemePicker
|
|
327
|
+
currentTheme={theme}
|
|
328
|
+
isOpen={showPicker}
|
|
329
|
+
themeFamilies={THEME_FAMILIES}
|
|
330
|
+
onThemeChange={setTheme}
|
|
331
|
+
onToggle={() => setShowPicker(!showPicker)}
|
|
332
|
+
/>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## CSS Variables
|
|
338
|
+
|
|
339
|
+
The ThemePicker requires these CSS variables to be defined by your theme system:
|
|
340
|
+
|
|
341
|
+
```css
|
|
342
|
+
:root {
|
|
343
|
+
--color-text: #000000;
|
|
344
|
+
--color-primary: #3b82f6;
|
|
345
|
+
--color-primary-hover: rgba(59, 130, 246, 0.1);
|
|
346
|
+
--color-primary-bg: rgba(59, 130, 246, 0.05);
|
|
347
|
+
--color-bg-card: #ffffff;
|
|
348
|
+
--color-text-secondary: #64748b;
|
|
349
|
+
--shadow-focus-sm: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
|
350
|
+
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
351
|
+
--shadow-modal: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
352
|
+
--transition-fast: 0.15s ease;
|
|
353
|
+
--spacing-sm: 8px;
|
|
354
|
+
--spacing-md: 12px;
|
|
355
|
+
--spacing-lg: 16px;
|
|
356
|
+
--font-size-lg: 18px;
|
|
357
|
+
--font-weight-semibold: 600;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Icons
|
|
362
|
+
|
|
363
|
+
### Theme-Specific Icons
|
|
364
|
+
|
|
365
|
+
Available themed icons for specific aesthetics:
|
|
366
|
+
|
|
367
|
+
- `SunIcon` - Sun/light mode icon
|
|
368
|
+
- `MoonIcon` - Moon/dark mode icon
|
|
369
|
+
- `StrawberryIcon` - Strawberry fruit icon
|
|
370
|
+
- `WaveIcon` - Ocean wave icon
|
|
371
|
+
- `ZapIcon` - Lightning bolt icon (cyberpunk)
|
|
372
|
+
- `CoffeeIcon` - Coffee cup icon
|
|
373
|
+
- `FlowerIcon` - Flower/lavender icon
|
|
374
|
+
- `HeartIcon` - Heart icon (pink theme)
|
|
375
|
+
- `LeafIcon` - Leaf icon (nature theme)
|
|
376
|
+
- `SettingsIcon` - Gear/settings icon
|
|
377
|
+
- `TagIcon` - Tag/label icon
|
|
378
|
+
- `SpaIcon` - Hot spring/spa icon (izakaya theme)
|
|
379
|
+
|
|
380
|
+
### Generic/Fallback Icons
|
|
381
|
+
|
|
382
|
+
When you don't have specific icons for your themes, use generic shape icons for visual differentiation:
|
|
383
|
+
|
|
384
|
+
- `CircleIcon` - Simple circle
|
|
385
|
+
- `SquareIcon` - Rounded square
|
|
386
|
+
- `TriangleIcon` - Triangle
|
|
387
|
+
- `DiamondIcon` - Diamond shape
|
|
388
|
+
- `StarIcon` - Star shape
|
|
389
|
+
- `HexagonIcon` - Hexagon
|
|
390
|
+
- `PentagonIcon` - Pentagon
|
|
391
|
+
- `OctagonIcon` - Octagon
|
|
392
|
+
|
|
393
|
+
**Automatic Fallback:**
|
|
394
|
+
|
|
395
|
+
The `ThemePicker` component automatically uses fallback icons if you don't provide any:
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
import { ThemePicker } from '@wolffm/task-ui-components'
|
|
399
|
+
|
|
400
|
+
const THEME_FAMILIES = [
|
|
401
|
+
{
|
|
402
|
+
// No icons provided - will use CircleIcon automatically
|
|
403
|
+
lightTheme: 'custom-light',
|
|
404
|
+
darkTheme: 'custom-dark',
|
|
405
|
+
lightLabel: 'Custom Light',
|
|
406
|
+
darkLabel: 'Custom Dark'
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
// No icons provided - will use SquareIcon automatically
|
|
410
|
+
lightTheme: 'another-light',
|
|
411
|
+
darkTheme: 'another-dark',
|
|
412
|
+
lightLabel: 'Another Light',
|
|
413
|
+
darkLabel: 'Another Dark'
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Manual Fallback Usage:**
|
|
419
|
+
|
|
420
|
+
You can also manually use fallback icons:
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import { getFallbackIcon, FALLBACK_ICONS } from '@wolffm/task-ui-components'
|
|
424
|
+
|
|
425
|
+
// Get icon by index (0-7, repeats after 8)
|
|
426
|
+
const icon = getFallbackIcon(0) // Returns CircleIcon
|
|
427
|
+
const icon2 = getFallbackIcon(5) // Returns HexagonIcon
|
|
428
|
+
|
|
429
|
+
// Or use the array directly
|
|
430
|
+
const CircleIconComponent = FALLBACK_ICONS[0]
|
|
431
|
+
const iconElement = <CircleIconComponent />
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Fallback Icon Sequence:**
|
|
435
|
+
|
|
436
|
+
When icons are omitted from `ThemeFamily`, they're assigned in this order:
|
|
437
|
+
|
|
438
|
+
1. `CircleIcon` (index 0)
|
|
439
|
+
2. `SquareIcon` (index 1)
|
|
440
|
+
3. `TriangleIcon` (index 2)
|
|
441
|
+
4. `DiamondIcon` (index 3)
|
|
442
|
+
5. `StarIcon` (index 4)
|
|
443
|
+
6. `HexagonIcon` (index 5)
|
|
444
|
+
7. `PentagonIcon` (index 6)
|
|
445
|
+
8. `OctagonIcon` (index 7)
|
|
446
|
+
9. Pattern repeats from `CircleIcon` for 9+ themes
|
|
447
|
+
|
|
448
|
+
This ensures each theme gets a visually distinct icon while maintaining consistency across app reloads.
|
|
449
|
+
|
|
450
|
+
## Logger
|
|
451
|
+
|
|
452
|
+
Production-safe logging utility that respects development mode and admin status.
|
|
453
|
+
|
|
454
|
+
### Basic Usage
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { logger } from '@wolffm/task-ui-components'
|
|
458
|
+
|
|
459
|
+
// Informational logs (dev/admin only)
|
|
460
|
+
logger.info('[Component] Loading data', { userId: '123' })
|
|
461
|
+
|
|
462
|
+
// Debug logs (dev/admin only)
|
|
463
|
+
logger.debug('[Component] State updated', { isOpen: true, count: 5 })
|
|
464
|
+
|
|
465
|
+
// Warnings (always shown)
|
|
466
|
+
logger.warn('[Component] Deprecated API used', { feature: 'old-api' })
|
|
467
|
+
|
|
468
|
+
// Errors (always shown)
|
|
469
|
+
logger.error('[Component] Failed to save', {
|
|
470
|
+
error: err instanceof Error ? err.message : String(err),
|
|
471
|
+
taskId: 'task-123'
|
|
472
|
+
})
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Advanced Features
|
|
476
|
+
|
|
477
|
+
**Enable admin mode** for production debugging:
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// After user authentication
|
|
481
|
+
if (userIsAdmin) {
|
|
482
|
+
logger.setAdminStatus(true)
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Specialized log methods**:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Component lifecycle logging
|
|
490
|
+
logger.component('mount', 'ThemePicker', { props })
|
|
491
|
+
logger.component('update', 'TaskList', { taskCount: 42 })
|
|
492
|
+
|
|
493
|
+
// Theme changes
|
|
494
|
+
logger.theme('strawberry-dark', { source: 'user-preference' })
|
|
495
|
+
logger.theme('ocean-light', { source: 'system' })
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Best Practices
|
|
499
|
+
|
|
500
|
+
**Always use structured logging** with a message and context object:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// ✅ Good - Structured with context
|
|
504
|
+
logger.info('[TaskList] Loading tasks', { boardId, filter })
|
|
505
|
+
|
|
506
|
+
// ❌ Bad - String concatenation
|
|
507
|
+
logger.info(`Loading tasks for board ${boardId}`)
|
|
508
|
+
|
|
509
|
+
// ✅ Good - Proper error extraction
|
|
510
|
+
logger.error('[API] Request failed', {
|
|
511
|
+
error: err instanceof Error ? err.message : String(err),
|
|
512
|
+
endpoint: '/api/tasks'
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
// ❌ Bad - Logging error object directly
|
|
516
|
+
logger.error('Request failed:', err)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**Use component prefixes** for easy filtering:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Component-based prefixes
|
|
523
|
+
logger.info('[TaskList] Mounted')
|
|
524
|
+
logger.info('[ThemePicker] Theme changed')
|
|
525
|
+
|
|
526
|
+
// Feature-based prefixes
|
|
527
|
+
logger.info('[API] Request completed')
|
|
528
|
+
logger.info('[Auth] User logged in')
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Behavior
|
|
532
|
+
|
|
533
|
+
| Mode | Development | Production (Admin) | Production (User) |
|
|
534
|
+
| -------------------- | ----------- | ------------------ | ----------------- |
|
|
535
|
+
| `logger.info()` | ✅ | ✅ | ❌ |
|
|
536
|
+
| `logger.debug()` | ✅ | ✅ | ❌ |
|
|
537
|
+
| `logger.component()` | ✅ | ✅ | ❌ |
|
|
538
|
+
| `logger.theme()` | ✅ | ✅ | ❌ |
|
|
539
|
+
| `logger.warn()` | ✅ | ✅ | ✅ |
|
|
540
|
+
| `logger.error()` | ✅ | ✅ | ✅ |
|
|
541
|
+
|
|
542
|
+
**Development Detection:**
|
|
543
|
+
|
|
544
|
+
- Automatically enabled on `localhost` or `127.0.0.1`
|
|
545
|
+
- Or when `import.meta.env.DEV` is true
|
|
546
|
+
|
|
547
|
+
**Admin Mode:**
|
|
548
|
+
|
|
549
|
+
- Call `logger.setAdminStatus(true)` after authentication
|
|
550
|
+
- Enables all logs in production for debugging
|
|
551
|
+
- Useful for troubleshooting production issues
|
|
552
|
+
|
|
553
|
+
## Props
|
|
554
|
+
|
|
555
|
+
### ThemePicker
|
|
556
|
+
|
|
557
|
+
| Prop | Type | Required | Description |
|
|
558
|
+
| ----------------- | ------------------------------ | -------- | ----------------------------------------------- |
|
|
559
|
+
| `currentTheme` | `string` | ✅ | Currently active theme name |
|
|
560
|
+
| `isOpen` | `boolean` | ✅ | Whether the picker dropdown is visible |
|
|
561
|
+
| `themeFamilies` | `ThemeFamily[]` | ✅ | Array of theme families to display |
|
|
562
|
+
| `onThemeChange` | `(theme: string) => void` | ✅ | Callback when theme is selected |
|
|
563
|
+
| `onToggle` | `() => void` | ✅ | Callback to toggle picker visibility |
|
|
564
|
+
| `onSettingsClick` | `() => void` | ❌ | Optional callback for settings button |
|
|
565
|
+
| `getThemeIcon` | `(theme: string) => ReactNode` | ❌ | Optional function to get icon for current theme |
|
|
566
|
+
| `className` | `string` | ❌ | Optional CSS class for container |
|
|
567
|
+
|
|
568
|
+
### ThemeFamily
|
|
569
|
+
|
|
570
|
+
| Prop | Type | Required | Description |
|
|
571
|
+
| ------------ | ----------- | -------- | ------------------------------------------------- |
|
|
572
|
+
| `lightIcon` | `ReactNode` | ❌ | Icon for light variant (auto-fallback if omitted) |
|
|
573
|
+
| `darkIcon` | `ReactNode` | ❌ | Icon for dark variant (auto-fallback if omitted) |
|
|
574
|
+
| `lightTheme` | `string` | ✅ | Theme name for light variant |
|
|
575
|
+
| `darkTheme` | `string` | ✅ | Theme name for dark variant |
|
|
576
|
+
| `lightLabel` | `string` | ✅ | Label for light variant (tooltip) |
|
|
577
|
+
| `darkLabel` | `string` | ✅ | Label for dark variant (tooltip) |
|
|
578
|
+
|
|
579
|
+
## License
|
|
580
|
+
|
|
581
|
+
MIT
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Additional Components Documentation
|
|
586
|
+
|
|
587
|
+
### Modal (API Reference)
|
|
588
|
+
|
|
589
|
+
Generic modal dialog with keyboard shortcuts and customizable actions.
|
|
590
|
+
|
|
591
|
+
**Features:**
|
|
592
|
+
|
|
593
|
+
- `role="dialog"` + `aria-modal` + `aria-labelledby` wired to the title, and
|
|
594
|
+
focus moved into the dialog on open
|
|
595
|
+
- Escape closes from anywhere in the dialog, with or without an input
|
|
596
|
+
- Enter confirms from the optional input field
|
|
597
|
+
- Configurable buttons (show/hide cancel/confirm), or replace the whole action
|
|
598
|
+
row via `actions` for flows the fixed pair can't express
|
|
599
|
+
- Danger variant for destructive actions
|
|
600
|
+
- Custom class names for styling
|
|
601
|
+
|
|
602
|
+
**Styles ship with the package** — import them once:
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
import '@wolffm/task-ui-components/modal.css'
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Usage:**
|
|
609
|
+
|
|
610
|
+
```tsx
|
|
611
|
+
import { Modal } from '@wolffm/task-ui-components'
|
|
612
|
+
|
|
613
|
+
function MyComponent() {
|
|
614
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
615
|
+
const [value, setValue] = useState('')
|
|
616
|
+
|
|
617
|
+
return (
|
|
618
|
+
<Modal
|
|
619
|
+
isOpen={isOpen}
|
|
620
|
+
title="Create Item"
|
|
621
|
+
onClose={() => setIsOpen(false)}
|
|
622
|
+
onConfirm={async () => {
|
|
623
|
+
await createItem(value)
|
|
624
|
+
setIsOpen(false)
|
|
625
|
+
}}
|
|
626
|
+
inputValue={value}
|
|
627
|
+
onInputChange={setValue}
|
|
628
|
+
inputPlaceholder="Enter name..."
|
|
629
|
+
confirmLabel="Create"
|
|
630
|
+
confirmDisabled={!value.trim()}
|
|
631
|
+
>
|
|
632
|
+
<p>Enter a name for your new item.</p>
|
|
633
|
+
</Modal>
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Props:**
|
|
639
|
+
|
|
640
|
+
| Prop | Type | Required | Description |
|
|
641
|
+
| -------------------- | ------------------------------- | -------- | -------------------------------------- |
|
|
642
|
+
| \`isOpen\` | \`boolean\` | ✅ | Whether modal is visible |
|
|
643
|
+
| \`title\` | \`string\` | ✅ | Modal title |
|
|
644
|
+
| \`onClose\` | \`() => void\` | ✅ | Close callback |
|
|
645
|
+
| \`onConfirm\` | \`() => void \| Promise<void>\` | ❌ | Confirm callback |
|
|
646
|
+
| \`children\` | \`ReactNode\` | ❌ | Modal content |
|
|
647
|
+
| \`inputValue\` | \`string\` | ❌ | Input field value |
|
|
648
|
+
| \`onInputChange\` | \`(value: string) => void\` | ❌ | Input change callback |
|
|
649
|
+
| \`inputPlaceholder\` | \`string\` | ❌ | Input placeholder |
|
|
650
|
+
| \`confirmLabel\` | \`string\` | ❌ | Confirm button text (default: Confirm) |
|
|
651
|
+
| \`cancelLabel\` | \`string\` | ❌ | Cancel button text (default: Cancel) |
|
|
652
|
+
| \`confirmDisabled\` | \`boolean\` | ❌ | Disable confirm button |
|
|
653
|
+
| \`confirmDanger\` | \`boolean\` | ❌ | Danger styling for confirm |
|
|
654
|
+
| \`showCancel\` | \`boolean\` | ❌ | Show cancel button (default: true) |
|
|
655
|
+
| \`showConfirm\` | \`boolean\` | ❌ | Show confirm button (default: true) |
|
|
656
|
+
| \`className\` | \`string\` | ❌ | Custom CSS class |
|
|
657
|
+
| \`overlayClassName\` | \`string\` | ❌ | Custom overlay CSS class |
|
|
658
|
+
| \`actions\` | \`ReactNode\` | ❌ | Replaces the whole action row |
|
|
659
|
+
| \`lead\` | \`ReactNode\` | ❌ | Rendered left of the title |
|
|
660
|
+
|
|
661
|
+
`actions` is checked against `undefined`, not with `??` — passing `null`
|
|
662
|
+
renders **no** action row (a progress step, a viewer), while omitting it keeps
|
|
663
|
+
the default Cancel/Confirm pair.
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
### ContextMenu (API Reference)
|
|
668
|
+
|
|
669
|
+
Position-based context menu for right-click or contextual actions.
|
|
670
|
+
|
|
671
|
+
**Features:**
|
|
672
|
+
|
|
673
|
+
- Position-based rendering (x, y coordinates)
|
|
674
|
+
- Configurable menu items
|
|
675
|
+
- Danger state for destructive items
|
|
676
|
+
- Disabled state support
|
|
677
|
+
- Fixed positioning with z-index
|
|
678
|
+
|
|
679
|
+
**Usage:**
|
|
680
|
+
|
|
681
|
+
```tsx
|
|
682
|
+
import { ContextMenu, type ContextMenuItem } from '@wolffm/task-ui-components'
|
|
683
|
+
|
|
684
|
+
function MyComponent() {
|
|
685
|
+
const [menu, setMenu] = useState<{ x: number; y: number } | null>(null)
|
|
686
|
+
|
|
687
|
+
const items: ContextMenuItem[] = [
|
|
688
|
+
{
|
|
689
|
+
label: 'Edit',
|
|
690
|
+
onClick: () => handleEdit()
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
label: 'Delete',
|
|
694
|
+
onClick: () => handleDelete(),
|
|
695
|
+
isDanger: true
|
|
696
|
+
}
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
return (
|
|
700
|
+
<div
|
|
701
|
+
onContextMenu={e => {
|
|
702
|
+
e.preventDefault()
|
|
703
|
+
setMenu({ x: e.clientX, y: e.clientY })
|
|
704
|
+
}}
|
|
705
|
+
>
|
|
706
|
+
<ContextMenu
|
|
707
|
+
isOpen={!!menu}
|
|
708
|
+
x={menu?.x || 0}
|
|
709
|
+
y={menu?.y || 0}
|
|
710
|
+
items={items}
|
|
711
|
+
className="my-context-menu"
|
|
712
|
+
/>
|
|
713
|
+
</div>
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**Props:**
|
|
719
|
+
|
|
720
|
+
| Prop | Type | Required | Description |
|
|
721
|
+
| ------------- | --------------------- | -------- | ----------------------- |
|
|
722
|
+
| \`isOpen\` | \`boolean\` | ✅ | Whether menu is visible |
|
|
723
|
+
| \`x\` | \`number\` | ✅ | X position (pixels) |
|
|
724
|
+
| \`y\` | \`number\` | ✅ | Y position (pixels) |
|
|
725
|
+
| \`items\` | \`ContextMenuItem[]\` | ✅ | Menu items |
|
|
726
|
+
| \`className\` | \`string\` | ❌ | Custom CSS class |
|
|
727
|
+
|
|
728
|
+
**ContextMenuItem:**
|
|
729
|
+
|
|
730
|
+
| Prop | Type | Required | Description |
|
|
731
|
+
| ------------ | --------------- | --------- | -------------- | ------------- |
|
|
732
|
+
| \`label\` | \`string\` | ✅ | Item label |
|
|
733
|
+
| \`onClick\` | \`() => void \\ | Promise\` | ✅ | Click handler |
|
|
734
|
+
| \`isDanger\` | \`boolean\` | ❌ | Danger styling |
|
|
735
|
+
| \`disabled\` | \`boolean\` | ❌ | Disable item |
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
### LoadingSkeleton (API Reference)
|
|
740
|
+
|
|
741
|
+
Flexible loading skeleton with multiple layout options.
|
|
742
|
+
|
|
743
|
+
**Features:**
|
|
744
|
+
|
|
745
|
+
- Multiple layout options (default, minimal, custom)
|
|
746
|
+
- Dark theme support
|
|
747
|
+
- Customizable content via \`customContent\` prop
|
|
748
|
+
|
|
749
|
+
**Usage:**
|
|
750
|
+
|
|
751
|
+
```tsx
|
|
752
|
+
import { LoadingSkeleton } from '@wolffm/task-ui-components'
|
|
753
|
+
|
|
754
|
+
function MyComponent() {
|
|
755
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
756
|
+
const isDark = useTheme() === 'dark'
|
|
757
|
+
|
|
758
|
+
if (isLoading) {
|
|
759
|
+
return <LoadingSkeleton isDarkTheme={isDark} layout="minimal" />
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return <div>Content loaded!</div>
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Props:**
|
|
767
|
+
|
|
768
|
+
| Prop | Type | Required | Description |
|
|
769
|
+
| ----------------- | -------------- | ------------ | ----------------------- | --- | -------------------------------- |
|
|
770
|
+
| \`isDarkTheme\` | \`boolean\` | ❌ | Dark theme mode |
|
|
771
|
+
| \`className\` | \`string\` | ❌ | Custom CSS class |
|
|
772
|
+
| \`layout\` | \`'default' \\ | 'minimal' \\ | 'custom'\` | ❌ | Layout type (default: 'default') |
|
|
773
|
+
| \`customContent\` | \`ReactNode\` | ❌ | Custom skeleton content |
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
### SettingsModal (API Reference)
|
|
778
|
+
|
|
779
|
+
Generic settings dialog with section-based organization.
|
|
780
|
+
|
|
781
|
+
**Features:**
|
|
782
|
+
|
|
783
|
+
- Section-based organization
|
|
784
|
+
- Multiple field types: toggle, text, password, select, button, custom
|
|
785
|
+
- Validation support
|
|
786
|
+
- Description text for fields
|
|
787
|
+
|
|
788
|
+
**Usage:**
|
|
789
|
+
|
|
790
|
+
```tsx
|
|
791
|
+
import { SettingsModal, type SettingsSection } from '@wolffm/task-ui-components'
|
|
792
|
+
|
|
793
|
+
function MyApp() {
|
|
794
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
795
|
+
const [darkMode, setDarkMode] = useState(false)
|
|
796
|
+
const [language, setLanguage] = useState('en')
|
|
797
|
+
|
|
798
|
+
const sections: SettingsSection[] = [
|
|
799
|
+
{
|
|
800
|
+
id: 'display',
|
|
801
|
+
title: 'Display Settings',
|
|
802
|
+
fields: [
|
|
803
|
+
{
|
|
804
|
+
type: 'toggle',
|
|
805
|
+
id: 'darkMode',
|
|
806
|
+
label: 'Dark Mode',
|
|
807
|
+
description: 'Use dark color scheme',
|
|
808
|
+
value: darkMode,
|
|
809
|
+
onChange: setDarkMode
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
type: 'select',
|
|
813
|
+
id: 'language',
|
|
814
|
+
label: 'Language',
|
|
815
|
+
value: language,
|
|
816
|
+
onChange: setLanguage,
|
|
817
|
+
options: [
|
|
818
|
+
{ value: 'en', label: 'English' },
|
|
819
|
+
{ value: 'es', label: 'Spanish' }
|
|
820
|
+
]
|
|
821
|
+
}
|
|
822
|
+
]
|
|
823
|
+
}
|
|
824
|
+
]
|
|
825
|
+
|
|
826
|
+
return <SettingsModal isOpen={isOpen} sections={sections} onClose={() => setIsOpen(false)} />
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
**Props:**
|
|
831
|
+
|
|
832
|
+
| Prop | Type | Required | Description |
|
|
833
|
+
| ------------------- | --------------------- | -------- | --------------------------------- |
|
|
834
|
+
| \`isOpen\` | \`boolean\` | ✅ | Whether modal is visible |
|
|
835
|
+
| \`sections\` | \`SettingsSection[]\` | ✅ | Settings sections |
|
|
836
|
+
| \`onClose\` | \`() => void\` | ✅ | Close callback |
|
|
837
|
+
| \`title\` | \`string\` | ❌ | Modal title (default: 'Settings') |
|
|
838
|
+
| \`className\` | \`string\` | ❌ | Custom CSS class |
|
|
839
|
+
| \`showCloseButton\` | \`boolean\` | ❌ | Show close button (default: true) |
|
|
840
|
+
|
|
841
|
+
**Field Types:**
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
// Toggle field
|
|
845
|
+
{
|
|
846
|
+
type: 'toggle',
|
|
847
|
+
id: string,
|
|
848
|
+
label: string,
|
|
849
|
+
description?: string,
|
|
850
|
+
value: boolean,
|
|
851
|
+
onChange: (value: boolean) => void,
|
|
852
|
+
disabled?: boolean
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Text input field
|
|
856
|
+
{
|
|
857
|
+
type: 'text',
|
|
858
|
+
id: string,
|
|
859
|
+
label: string,
|
|
860
|
+
description?: string,
|
|
861
|
+
value: string,
|
|
862
|
+
onChange: (value: string) => void,
|
|
863
|
+
placeholder?: string,
|
|
864
|
+
disabled?: boolean
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Password field
|
|
868
|
+
{
|
|
869
|
+
type: 'password',
|
|
870
|
+
id: string,
|
|
871
|
+
label: string,
|
|
872
|
+
description?: string,
|
|
873
|
+
value: string,
|
|
874
|
+
onChange: (value: string) => void,
|
|
875
|
+
placeholder?: string,
|
|
876
|
+
showButton?: boolean,
|
|
877
|
+
buttonLabel?: string,
|
|
878
|
+
onButtonClick?: () => void | Promise<void>,
|
|
879
|
+
buttonDisabled?: boolean,
|
|
880
|
+
error?: string | null,
|
|
881
|
+
autoComplete?: string,
|
|
882
|
+
disabled?: boolean
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Select dropdown
|
|
886
|
+
{
|
|
887
|
+
type: 'select',
|
|
888
|
+
id: string,
|
|
889
|
+
label: string,
|
|
890
|
+
description?: string,
|
|
891
|
+
value: string,
|
|
892
|
+
onChange: (value: string) => void,
|
|
893
|
+
options: Array<{ value: string; label: string }>,
|
|
894
|
+
disabled?: boolean
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Action button
|
|
898
|
+
{
|
|
899
|
+
type: 'button',
|
|
900
|
+
id: string,
|
|
901
|
+
label?: string,
|
|
902
|
+
description?: string,
|
|
903
|
+
buttonLabel: string,
|
|
904
|
+
onClick: () => void | Promise<void>,
|
|
905
|
+
variant?: 'primary' | 'danger' | 'default',
|
|
906
|
+
disabled?: boolean
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Custom component
|
|
910
|
+
{
|
|
911
|
+
type: 'custom',
|
|
912
|
+
id: string,
|
|
913
|
+
label?: string,
|
|
914
|
+
description?: string,
|
|
915
|
+
render: () => ReactNode,
|
|
916
|
+
disabled?: boolean
|
|
917
|
+
}
|
|
918
|
+
```
|