@hadoku/themes 0.0.0-stage → 5.12.16
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/LICENSE-lucide +43 -0
- package/README.md +433 -2
- package/THEME_USAGE_GUIDE.md +308 -0
- package/dist/HadokuThemeRoot.d.ts +53 -0
- package/dist/HadokuThemeRoot.js +66 -0
- package/dist/ThemeIcons.d.ts +31 -0
- package/dist/ThemeIcons.js +91 -0
- package/dist/effects.d.ts +25 -0
- package/dist/effects.js +25 -0
- package/dist/icons/Icon.d.ts +36 -0
- package/dist/icons/Icon.js +89 -0
- package/dist/icons/index.d.ts +61 -0
- package/dist/icons/index.js +87 -0
- package/dist/icons/registry.generated.d.ts +158 -0
- package/dist/icons/registry.generated.js +446 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +169 -0
- package/dist/metadata.d.ts +35 -0
- package/dist/metadata.js +108 -0
- package/dist/platform/index.d.ts +94 -0
- package/dist/platform/index.js +149 -0
- package/dist/platform/usePlatform.d.ts +29 -0
- package/dist/platform/usePlatform.js +65 -0
- package/dist/prefsApiBase.d.ts +37 -0
- package/dist/prefsApiBase.js +40 -0
- package/dist/theme-types.d.ts +34 -0
- package/dist/theme-types.js +1 -0
- package/dist/themeContext.d.ts +62 -0
- package/dist/themeContext.js +70 -0
- package/dist/themePrefs.d.ts +29 -0
- package/dist/themePrefs.js +48 -0
- package/dist/useRefElement.d.ts +19 -0
- package/dist/useRefElement.js +52 -0
- package/dist/useTheme.d.ts +46 -0
- package/dist/useTheme.js +216 -0
- package/package.json +83 -4
- package/scripts/audit-tokens.mjs +126 -0
- package/scripts/check-contrast.mjs +24 -0
- package/scripts/check-docs.mjs +53 -0
- package/scripts/check-icons.mjs +473 -0
- package/scripts/check-usage.mjs +297 -0
- package/scripts/codemod-icons.mjs +396 -0
- package/scripts/generate-icons.mjs +199 -0
- package/scripts/generate-preview.mjs +374 -0
- package/scripts/generate-tailwind-colors.mjs +109 -0
- package/scripts/lib/color.mjs +184 -0
- package/scripts/lib/contract.d.mts +58 -0
- package/scripts/lib/contract.mjs +348 -0
- package/scripts/lib/derive.mjs +324 -0
- package/scripts/lib/icon-registry.mjs +92 -0
- package/scripts/lib/parse-themes.mjs +19 -0
- package/scripts/lib/tokens.mjs +188 -0
- package/scripts/normalize-tokens.mjs +161 -0
- package/src/advanced.css +218 -0
- package/src/icons/Icon.tsx +151 -0
- package/src/icons/emoji-map.json +202 -0
- package/src/icons/index.ts +151 -0
- package/src/icons/registry.generated.ts +455 -0
- package/src/icons/sources.json +150 -0
- package/src/icons.css +145 -0
- package/src/style.css +1421 -0
- package/src/tailwind-colors.css +88 -0
- package/src/tailwind-integration.css +49 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Theme Usage Guide — which token do I use?
|
|
2
|
+
|
|
3
|
+
**This is the doc for _using_ colors.** For authoring a new theme's palette, see
|
|
4
|
+
[`THEME_CREATION_GUIDE.md`](./THEME_CREATION_GUIDE.md). For the accessibility
|
|
5
|
+
contracts, see [`docs/THEME_SYSTEM_RULES.md`](../docs/THEME_SYSTEM_RULES.md).
|
|
6
|
+
|
|
7
|
+
## The mental model, in one line
|
|
8
|
+
|
|
9
|
+
**A token names a semantic role, not a hue. Light/dark is automatic — never
|
|
10
|
+
branch on theme mode.**
|
|
11
|
+
|
|
12
|
+
There are 18 themes. `--color-success` is sky blue in one and gold in another.
|
|
13
|
+
Pick the token that matches what the element _means_; the theme decides the color.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Decision table
|
|
18
|
+
|
|
19
|
+
`<f>` is one of `primary` · `success` · `warning` · `danger` · `neutral`.
|
|
20
|
+
|
|
21
|
+
| You're styling… | Use |
|
|
22
|
+
| -------------------------------- | ---------------------------------------------------------------- |
|
|
23
|
+
| Filled / primary button | `bg-<f>` + `text-on-<f>` |
|
|
24
|
+
| …its gradient or pressed state | `bg-<f>-dark` (still pair with `text-on-<f>`) |
|
|
25
|
+
| Tint badge, chip, banner, alert | `bg-<f>-bg` + `text-on-<f>-bg` |
|
|
26
|
+
| Ghost button / row hover tint | `hover:bg-<f>-hover` |
|
|
27
|
+
| Body text | `text-text` |
|
|
28
|
+
| Secondary / muted text | `text-text-secondary` → `text-text-tertiary` → `text-text-muted` |
|
|
29
|
+
| Page background | `bg-bg` |
|
|
30
|
+
| Card / panel surface | `bg-bg-card` |
|
|
31
|
+
| Inset or alternate surface | `bg-bg-alt` |
|
|
32
|
+
| Neutral row/button hover surface | `bg-bg-hover` |
|
|
33
|
+
| Modal scrim | `bg-bg-overlay` |
|
|
34
|
+
| Border / divider | `border-border` |
|
|
35
|
+
| Subtle divider | `border-border-light` |
|
|
36
|
+
|
|
37
|
+
### The one rule people get wrong
|
|
38
|
+
|
|
39
|
+
`--color-<f>` and `--color-<f>-bg` are **different surfaces with different text
|
|
40
|
+
colors.** They are not interchangeable.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
bg-<f> (solid fill) → text-on-<f>
|
|
44
|
+
bg-<f>-bg (faint tint) → text-on-<f>-bg ← NOT text-<f>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Using `text-<f>` on `bg-<f>-bg` fails WCAG AA in 62 of 90 theme/family
|
|
48
|
+
combinations. That pairing is why badges kept turning up unreadable.
|
|
49
|
+
|
|
50
|
+
### The same rule, one step further out
|
|
51
|
+
|
|
52
|
+
A fill colour is not a text colour **on any surface**, not just on its own tint.
|
|
53
|
+
`text-<f>` with no background at all lands on the page or a card, and that pair
|
|
54
|
+
was validated by nothing: `check-contrast` checks `on-<f>` over `<f>`, so
|
|
55
|
+
accent-over-surface passed by never being looked at.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
text-<f> with no background → lands on bg / bg-card ← unvalidated, usually fails
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Measured against `--color-bg`: `--color-primary` clears the 3:1 large-text floor
|
|
62
|
+
in only 12 of 18 themes (izakaya-light 1.92, nature-light 2.15, ocean-light 2.28,
|
|
63
|
+
lavender-light 2.53, pink-light 2.56, strawberry-light 2.61). `--color-danger`
|
|
64
|
+
misses AA on a card in 11 of 18. This shipped in `.app-header__title` and reached
|
|
65
|
+
every consuming app at once.
|
|
66
|
+
|
|
67
|
+
Text on a surface is `text-text` (8.87–17.58 across all 18) or
|
|
68
|
+
`text-text-secondary`. To carry an accent, either pair `bg-<f>-bg` with
|
|
69
|
+
`text-on-<f>-bg`, or move the accent off the glyph entirely — a border, rule, or
|
|
70
|
+
underline is unconstrained by text contrast. `check-usage` gates this, in CSS
|
|
71
|
+
and as Tailwind classes in markup (`text-danger` with no `bg-danger` fill).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Complete token list
|
|
76
|
+
|
|
77
|
+
Every token below is defined in **all 18 themes**. Nothing else exists — if a
|
|
78
|
+
name isn't here, it isn't a token.
|
|
79
|
+
|
|
80
|
+
### Semantic families
|
|
81
|
+
|
|
82
|
+
| family | fill | gradient/pressed | tint surface | hover overlay | text on fill | text on tint |
|
|
83
|
+
| --------- | ----------------- | ---------------------- | -------------------- | ----------------------- | -------------------- | ----------------------- |
|
|
84
|
+
| `primary` | `--color-primary` | `--color-primary-dark` | `--color-primary-bg` | `--color-primary-hover` | `--color-on-primary` | `--color-on-primary-bg` |
|
|
85
|
+
| `success` | `--color-success` | `--color-success-dark` | `--color-success-bg` | `--color-success-hover` | `--color-on-success` | `--color-on-success-bg` |
|
|
86
|
+
| `warning` | `--color-warning` | `--color-warning-dark` | `--color-warning-bg` | `--color-warning-hover` | `--color-on-warning` | `--color-on-warning-bg` |
|
|
87
|
+
| `danger` | `--color-danger` | `--color-danger-dark` | `--color-danger-bg` | `--color-danger-hover` | `--color-on-danger` | `--color-on-danger-bg` |
|
|
88
|
+
| `neutral` | `--color-neutral` | `--color-neutral-dark` | `--color-neutral-bg` | `--color-neutral-hover` | `--color-on-neutral` | `--color-on-neutral-bg` |
|
|
89
|
+
|
|
90
|
+
The table is a **rectangle on purpose.** Every family has exactly the same six
|
|
91
|
+
tokens, so the symmetric guess is always the right one.
|
|
92
|
+
|
|
93
|
+
### Structural
|
|
94
|
+
|
|
95
|
+
| role | token | AA-safe for text? |
|
|
96
|
+
| --------------- | ------------------------ | --------------------- |
|
|
97
|
+
| body text | `--color-text` | ✅ all 18 themes |
|
|
98
|
+
| secondary text | `--color-text-secondary` | ✅ all 18 themes |
|
|
99
|
+
| tertiary text | `--color-text-tertiary` | ⚠️ fails in 5 themes |
|
|
100
|
+
| muted text | `--color-text-muted` | ⚠️ fails in 14 themes |
|
|
101
|
+
| border | `--color-border` | — |
|
|
102
|
+
| subtle border | `--color-border-light` | — |
|
|
103
|
+
| page background | `--color-bg` | — |
|
|
104
|
+
| card surface | `--color-bg-card` | — |
|
|
105
|
+
| alt surface | `--color-bg-alt` | — |
|
|
106
|
+
| hover surface | `--color-bg-hover` | — |
|
|
107
|
+
| modal scrim | `--color-bg-overlay` | — |
|
|
108
|
+
|
|
109
|
+
> **`-tertiary` and `-muted` are decorative-only.** Measured against
|
|
110
|
+
> `bg`/`bg-card`/`bg-alt` in all 18 themes, `-tertiary` drops below 4.5:1 in 5
|
|
111
|
+
> and `-muted` in 14. Use them for de-emphasised ornament (placeholder glyphs,
|
|
112
|
+
> disabled states — WCAG exempts inactive controls). **Any text a user must
|
|
113
|
+
> read gets `--color-text` or `--color-text-secondary`**, both of which clear
|
|
114
|
+
> AA on every surface in every theme. `check-contrast.mjs` gates those two and
|
|
115
|
+
> deliberately does not gate the other two.
|
|
116
|
+
|
|
117
|
+
**41 tokens total.** Non-color tokens (spacing, radius, shadows, type) use the
|
|
118
|
+
`--hdk-*` prefix — see [`README.md`](./README.md).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Anti-patterns — hard rules
|
|
123
|
+
|
|
124
|
+
| ❌ Never | ✅ Instead | Why |
|
|
125
|
+
| --------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `text-white`, `color: #fff` on a filled button | `text-on-<f>` | `--color-<f>` is light in many themes; white-on-light is invisible |
|
|
127
|
+
| `var(--color-x, #hex)` | `var(--color-x)` | A fallback renders the hardcoded value, so a wrong token looks right in light mode and breaks in the other 17 |
|
|
128
|
+
| `text-<f>` on `bg-<f>-bg` | `text-on-<f>-bg` | Fails AA in 62/90 theme×family combinations |
|
|
129
|
+
| `[data-theme='dark'] .foo { color: … }` | one token, no branch | Tokens already switch per theme; branching double-applies |
|
|
130
|
+
| Inventing `--color-<f>-light` / `-lighter` / `-darker` / `--color-muted-bg` | the six canonical family tokens | Removed in v3.0.0 — see [`THEME_GRAVEYARD.md`](./THEME_GRAVEYARD.md) |
|
|
131
|
+
| `--color-<f>` as body text | `--color-text` | Solid fills are for backgrounds; §4 of the rules doc |
|
|
132
|
+
| `@import "@wolffm/themes/style.css" layer(base)` | import it **unlayered** | Layering it makes every color resolve to nothing |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Icons
|
|
137
|
+
|
|
138
|
+
Icons are part of the theme contract, not each app's business. Raw emoji were the old
|
|
139
|
+
answer and they are retired: the same codepoint renders differently on every platform,
|
|
140
|
+
and no CSS can fix it. The package ships **78 vendored line icons** (artwork from
|
|
141
|
+
[Lucide](https://lucide.dev), ISC — see `LICENSE-lucide`) behind an enforced allowlist.
|
|
142
|
+
|
|
143
|
+
**Reference icons by name. Never inline your own SVG, and never add an emoji.**
|
|
144
|
+
|
|
145
|
+
### Three consumption paths
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// React, in an app that also uses the theming stack
|
|
149
|
+
import { Icon } from '@wolffm/themes'
|
|
150
|
+
;<Icon name="popcorn" />
|
|
151
|
+
;<Icon name="popcorn" variant="tint" family="warning" />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
// React, icons only — same component, none of the theming stack.
|
|
156
|
+
// The root barrel re-exports useTheme, which imports @wolffm/prefs-client
|
|
157
|
+
// and zod. If all you want is glyphs, take them from here instead.
|
|
158
|
+
import { Icon } from '@wolffm/themes/icons/react'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```astro
|
|
162
|
+
---
|
|
163
|
+
// Astro / Qwik / plain HTML — no client JS, CSP-safe
|
|
164
|
+
import { getIconSvg, getIconTileClass } from '@wolffm/themes/icons'
|
|
165
|
+
---
|
|
166
|
+
<span set:html={getIconSvg('popcorn')} />
|
|
167
|
+
<span class={getIconTileClass('warning', 'tint')} set:html={getIconSvg('popcorn')} />
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Both paths render byte-identical geometry — that is asserted in `e2e/icons.spec.ts`.
|
|
171
|
+
|
|
172
|
+
Import the stylesheet **unlayered**, next to `style.css`:
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
@import '@wolffm/themes/style.css';
|
|
176
|
+
@import '@wolffm/themes/icons.css';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Colour: bare glyphs inherit, accents go on the tile
|
|
180
|
+
|
|
181
|
+
A bare icon inherits `currentColor`, so it is exactly as legible as the text beside it
|
|
182
|
+
and carries no contrast obligation of its own. **That is the default, and usually the
|
|
183
|
+
right answer.**
|
|
184
|
+
|
|
185
|
+
Do **not** paint the glyph with an accent token. Measured across all 18 themes on both
|
|
186
|
+
`--color-bg-card` and `--color-bg`, a bare `--color-warning` glyph misses the WCAG
|
|
187
|
+
1.4.11 non-text minimum of 3:1 in 8 of 36 combinations (light 2.15, ocean-light 1.92,
|
|
188
|
+
pink-light 1.67); every other family fails somewhere too, and `--color-<f>-dark` does
|
|
189
|
+
not rescue it. This is the same accent-as-text anti-pattern listed above, and
|
|
190
|
+
`check:contrast` does **not** cover it — it validates `on-<f>` over `<f>`, never an
|
|
191
|
+
accent over a surface.
|
|
192
|
+
|
|
193
|
+
When you want the accent, put it on the tile. Both treatments pass 3:1 in 18/18 themes
|
|
194
|
+
for all five families, because they reuse pairings the system already guarantees:
|
|
195
|
+
|
|
196
|
+
| Variant | Background | Glyph | Contrast |
|
|
197
|
+
| -------- | ---------------- | ------------------- | ----------------------------- |
|
|
198
|
+
| `bare` | — (inherits) | `currentColor` | matches its text, always safe |
|
|
199
|
+
| `accent` | — (inherits) | `--color-<f>` | **yours to own** — see below |
|
|
200
|
+
| `tint` | `--color-<f>-bg` | `--color-on-<f>-bg` | 3:1 in 18/18 themes |
|
|
201
|
+
| `filled` | `--color-<f>` | `--color-on-<f>` | 3:1 in 18/18 themes |
|
|
202
|
+
|
|
203
|
+
**How you colour an icon is your call; where the colour comes from is not.** Every
|
|
204
|
+
variant resolves through family tokens, and no raw colour can travel through this
|
|
205
|
+
API — so an icon cannot drift from the theme.
|
|
206
|
+
|
|
207
|
+
Two cases, both first-class:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// Inline in a button — inherit, so the icon matches the button's own text in
|
|
211
|
+
// every theme with no extra thought.
|
|
212
|
+
<button className="pill-btn"><Icon name="plus" /> Add</button>
|
|
213
|
+
|
|
214
|
+
// Its own element, deliberately a different colour from the surrounding text.
|
|
215
|
+
<Icon name="warning" variant="accent" family="danger" />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`accent` is opt-in rather than default because contrast moves to you when you use
|
|
219
|
+
it: a bare accent glyph clears 3:1 on a card or page surface in 28 of 36
|
|
220
|
+
theme/surface combinations for `warning`. That is fine when you know the surface —
|
|
221
|
+
an accent glyph on a tinted row, or beside a label you have checked — and wrong as
|
|
222
|
+
a blanket default. When the accent must be safe everywhere, use a tile.
|
|
223
|
+
|
|
224
|
+
Framework-free gets the same control:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
getIconSvg('warning', { family: 'danger' }) // accent glyph
|
|
228
|
+
getIconSvg('plus') // inherits
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### The set
|
|
232
|
+
|
|
233
|
+
| Name | Name | Name | Name | Name | Name |
|
|
234
|
+
| -------------- | ------------- | ------------ | ------------- | ------------ | -------------- |
|
|
235
|
+
| `activity` | `antenna` | `arrow-left` | `arrow-right` | `book` | `briefcase` |
|
|
236
|
+
| `broom` | `calendar` | `camera` | `chat` | `check` | `chevron-down` |
|
|
237
|
+
| `circle` | `clapper` | `clipboard` | `clock` | `close` | `compass` |
|
|
238
|
+
| `crop` | `crown` | `desktop` | `document` | `download` | `edit` |
|
|
239
|
+
| `error` | `eye` | `fire` | `folder` | `gear` | `handshake` |
|
|
240
|
+
| `helping-hand` | `home` | `hourglass` | `image` | `inbox` | `info` |
|
|
241
|
+
| `link` | `lock` | `magnifier` | `mail` | `masks` | `minus` |
|
|
242
|
+
| `music` | `note` | `package` | `palette` | `panel-left` | `panel-right` |
|
|
243
|
+
| `paste` | `play` | `plus` | `popcorn` | `printer` | `redo` |
|
|
244
|
+
| `refresh` | `robot` | `scissors` | `shuffle` | `skull` | `sleep` |
|
|
245
|
+
| `sparkles` | `star` | `stop` | `thumbs-down` | `thumbs-up` | `trash` |
|
|
246
|
+
| `trophy` | `undo` | `upload` | `user` | `videotape` | `volume-high` |
|
|
247
|
+
| `volume-low` | `volume-mute` | `wand` | `warning` | `wave` | `wrench` |
|
|
248
|
+
|
|
249
|
+
### See them all
|
|
250
|
+
|
|
251
|
+
`pnpm run gallery` (in hadoku-task) serves a live page at
|
|
252
|
+
`http://localhost:5200/dev/gallery/index.html`: every icon, the real
|
|
253
|
+
`<HadokuThemePicker>` to sweep all 18 themes, variant/family/size controls, the
|
|
254
|
+
React-vs-string comparison, and the icons sitting in real app chrome. It is the
|
|
255
|
+
fastest way to check a new icon before committing it.
|
|
256
|
+
|
|
257
|
+
### Adding an icon
|
|
258
|
+
|
|
259
|
+
1. Add `"name": "lucide-slug"` to `themes/src/icons/sources.json`.
|
|
260
|
+
2. `pnpm run generate:icons` — fails loudly if the slug is not in lucide.
|
|
261
|
+
3. Commit the regenerated `registry.generated.ts`.
|
|
262
|
+
|
|
263
|
+
### Verify it
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
node node_modules/@wolffm/themes/scripts/check-icons.mjs ./src
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Fails on raw emoji in an icon position and on icon names not in the registry. The name
|
|
270
|
+
check matters most outside TypeScript — Astro templates and JSON catalogues never see
|
|
271
|
+
the `IconName` union, so a typo there renders nothing at all, silently.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Setup
|
|
276
|
+
|
|
277
|
+
```css
|
|
278
|
+
@import '@wolffm/themes/style.css'; /* 1. tokens — MUST be unlayered */
|
|
279
|
+
@import 'tailwindcss'; /* 2. Tailwind */
|
|
280
|
+
@import '@wolffm/themes/tailwind-colors.css'; /* 3. the color mapping */
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
That's the whole integration. **Do not hand-write an `@theme` color block** —
|
|
284
|
+
`tailwind-colors.css` maps all 41 tokens, and a local copy is exactly the
|
|
285
|
+
drift this package exists to prevent.
|
|
286
|
+
|
|
287
|
+
Not using Tailwind? Import step 1 only and use `var(--color-*)` directly.
|
|
288
|
+
|
|
289
|
+
### Why step 1 must stay unlayered
|
|
290
|
+
|
|
291
|
+
Tailwind emits each `@theme` entry into `@layer theme` as `:root { --x: var(--x) }`
|
|
292
|
+
— self-referential, and invalid on its own. It resolves only because
|
|
293
|
+
`style.css` declares the same properties **outside** any cascade layer, and
|
|
294
|
+
unlayered declarations win. Put it in a layer and all 41 colors silently
|
|
295
|
+
become `transparent`.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Verify it
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
node node_modules/@wolffm/themes/scripts/check-usage.mjs ./src
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Fails the build on: unknown tokens, Tailwind classes with no matching token,
|
|
306
|
+
`var()` fallbacks, and layered imports of `style.css`. Wire it into `lint` and
|
|
307
|
+
pre-commit — an unmapped class produces **no CSS rule at all**, so neither
|
|
308
|
+
typecheck nor build will ever catch it for you.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HadokuThemeRoot — the single thing a hadoku app mounts to get the platform's
|
|
3
|
+
* theming and the locked header.
|
|
4
|
+
*
|
|
5
|
+
* Wrap the app root once:
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { HadokuThemeRoot } from '@wolffm/themes'
|
|
9
|
+
*
|
|
10
|
+
* export function mount(el: HTMLElement) {
|
|
11
|
+
* createRoot(el).render(
|
|
12
|
+
* <HadokuThemeRoot>
|
|
13
|
+
* <App />
|
|
14
|
+
* </HadokuThemeRoot>
|
|
15
|
+
* )
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* That is the entire integration. `<AppHeader>` then renders the shared theme
|
|
20
|
+
* picker and the shared settings gear from this context — an app does not pass
|
|
21
|
+
* them, cannot substitute them, and no longer needs a local `useTheme`,
|
|
22
|
+
* `themePrefs` or `themeConfig` module.
|
|
23
|
+
*
|
|
24
|
+
* WHY THE CONTEXT LIVES HERE TOO
|
|
25
|
+
* -------------------------------
|
|
26
|
+
* It did not always. The context was defined in task-ui-components (where
|
|
27
|
+
* AppHeader consumes it) and only FILLED here, because this package used to
|
|
28
|
+
* import the theme icons from there and the reverse import would have closed a
|
|
29
|
+
* cycle.
|
|
30
|
+
*
|
|
31
|
+
* A context defined in one package and provided from another only works while
|
|
32
|
+
* exactly one copy of the defining module exists across every install tree,
|
|
33
|
+
* bundle and import map that touches it. That held until it didn't: on
|
|
34
|
+
* 2026-08-05 three apps threw "No <HadokuThemeRoot> above this component" with
|
|
35
|
+
* the provider plainly mounted, because a second copy of task-ui-components
|
|
36
|
+
* meant a second createContext() — provider filling one, consumer reading the
|
|
37
|
+
* other.
|
|
38
|
+
*
|
|
39
|
+
* Moving the icons here reversed the arrow, so the context now ships beside the
|
|
40
|
+
* provider that fills it, in the package every app already resolves through the
|
|
41
|
+
* parent's import map.
|
|
42
|
+
*/
|
|
43
|
+
import React, { type RefObject } from 'react';
|
|
44
|
+
export interface HadokuThemeRootProps {
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
/** Theme forced by a parent shell (the portfolio passes its resolved theme
|
|
47
|
+
* into micro-frontend mounts). Omit for a standalone app. */
|
|
48
|
+
theme?: string;
|
|
49
|
+
/** Mirror `data-theme` onto this container as well as <html> — needed when
|
|
50
|
+
* the app mounts into a subtree rather than owning the document. */
|
|
51
|
+
containerRef?: RefObject<HTMLElement | null>;
|
|
52
|
+
}
|
|
53
|
+
export declare function HadokuThemeRoot({ children, theme: propsTheme, containerRef }: HadokuThemeRootProps): React.JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HadokuThemeRoot — the single thing a hadoku app mounts to get the platform's
|
|
3
|
+
* theming and the locked header.
|
|
4
|
+
*
|
|
5
|
+
* Wrap the app root once:
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { HadokuThemeRoot } from '@wolffm/themes'
|
|
9
|
+
*
|
|
10
|
+
* export function mount(el: HTMLElement) {
|
|
11
|
+
* createRoot(el).render(
|
|
12
|
+
* <HadokuThemeRoot>
|
|
13
|
+
* <App />
|
|
14
|
+
* </HadokuThemeRoot>
|
|
15
|
+
* )
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* That is the entire integration. `<AppHeader>` then renders the shared theme
|
|
20
|
+
* picker and the shared settings gear from this context — an app does not pass
|
|
21
|
+
* them, cannot substitute them, and no longer needs a local `useTheme`,
|
|
22
|
+
* `themePrefs` or `themeConfig` module.
|
|
23
|
+
*
|
|
24
|
+
* WHY THE CONTEXT LIVES HERE TOO
|
|
25
|
+
* -------------------------------
|
|
26
|
+
* It did not always. The context was defined in task-ui-components (where
|
|
27
|
+
* AppHeader consumes it) and only FILLED here, because this package used to
|
|
28
|
+
* import the theme icons from there and the reverse import would have closed a
|
|
29
|
+
* cycle.
|
|
30
|
+
*
|
|
31
|
+
* A context defined in one package and provided from another only works while
|
|
32
|
+
* exactly one copy of the defining module exists across every install tree,
|
|
33
|
+
* bundle and import map that touches it. That held until it didn't: on
|
|
34
|
+
* 2026-08-05 three apps threw "No <HadokuThemeRoot> above this component" with
|
|
35
|
+
* the provider plainly mounted, because a second copy of task-ui-components
|
|
36
|
+
* meant a second createContext() — provider filling one, consumer reading the
|
|
37
|
+
* other.
|
|
38
|
+
*
|
|
39
|
+
* Moving the icons here reversed the arrow, so the context now ships beside the
|
|
40
|
+
* provider that fills it, in the package every app already resolves through the
|
|
41
|
+
* parent's import map.
|
|
42
|
+
*/
|
|
43
|
+
import React, { useMemo } from 'react';
|
|
44
|
+
import { HadokuThemeProvider } from './themeContext.js';
|
|
45
|
+
import { useTheme } from './useTheme.js';
|
|
46
|
+
import { THEME_ICON_MAP } from './metadata.js';
|
|
47
|
+
/** Resolve a theme name to its icon. Central so the picker's toggle button
|
|
48
|
+
* looks identical in every app — this used to be a per-app arrow function,
|
|
49
|
+
* and one app had a debug logger inside it. */
|
|
50
|
+
function getThemeIcon(theme) {
|
|
51
|
+
const Icon = THEME_ICON_MAP[theme];
|
|
52
|
+
return Icon ? React.createElement(Icon, null) : null;
|
|
53
|
+
}
|
|
54
|
+
export function HadokuThemeRoot({ children, theme: propsTheme, containerRef }) {
|
|
55
|
+
const t = useTheme({ propsTheme, containerRef });
|
|
56
|
+
const value = useMemo(() => ({
|
|
57
|
+
theme: t.theme,
|
|
58
|
+
setTheme: t.setTheme,
|
|
59
|
+
themeFamilies: t.themeFamilies,
|
|
60
|
+
getThemeIcon,
|
|
61
|
+
isDarkTheme: t.isDarkTheme,
|
|
62
|
+
isThemeReady: t.isThemeReady,
|
|
63
|
+
isInitialThemeLoad: t.isInitialThemeLoad
|
|
64
|
+
}), [t.theme, t.setTheme, t.themeFamilies, t.isDarkTheme, t.isThemeReady, t.isInitialThemeLoad]);
|
|
65
|
+
return React.createElement(HadokuThemeProvider, { value: value }, children);
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme-family icons — one per theme pair in THEME_FAMILIES.
|
|
3
|
+
*
|
|
4
|
+
* These lived in @wolffm/task-ui-components, which is why @wolffm/themes had to
|
|
5
|
+
* import from it, which is why the dependency arrow ran themes -> ui-components,
|
|
6
|
+
* which is why HadokuThemeContext could not be defined next to the provider that
|
|
7
|
+
* fills it. That split is what let a duplicated ui-components module produce two
|
|
8
|
+
* distinct React contexts and take three apps down on 2026-08-05.
|
|
9
|
+
*
|
|
10
|
+
* They belong here: they are theme metadata, they are consumed by THEME_FAMILIES
|
|
11
|
+
* a few lines away, and they depend on nothing but React. Moving them reverses
|
|
12
|
+
* the arrow and lets the context live with its provider.
|
|
13
|
+
*
|
|
14
|
+
* UI chrome (SettingsIcon, TagIcon) and the fallback shapes stay in
|
|
15
|
+
* task-ui-components — those belong to the components that draw them.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
export declare const SunIcon: () => React.JSX.Element;
|
|
19
|
+
export declare const MoonIcon: () => React.JSX.Element;
|
|
20
|
+
export declare const StrawberryIcon: () => React.JSX.Element;
|
|
21
|
+
export declare const WaveIcon: () => React.JSX.Element;
|
|
22
|
+
export declare const ZapIcon: () => React.JSX.Element;
|
|
23
|
+
export declare const CoffeeIcon: () => React.JSX.Element;
|
|
24
|
+
export declare const FlowerIcon: () => React.JSX.Element;
|
|
25
|
+
export declare const HeartIcon: () => React.JSX.Element;
|
|
26
|
+
export declare const LeafIcon: () => React.JSX.Element;
|
|
27
|
+
export declare const LanternIcon: () => React.JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Generic/Fallback icons for custom themes without specific icons
|
|
30
|
+
* These provide visual differentiation when using the ThemePicker with custom themes
|
|
31
|
+
*/
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme-family icons — one per theme pair in THEME_FAMILIES.
|
|
3
|
+
*
|
|
4
|
+
* These lived in @wolffm/task-ui-components, which is why @wolffm/themes had to
|
|
5
|
+
* import from it, which is why the dependency arrow ran themes -> ui-components,
|
|
6
|
+
* which is why HadokuThemeContext could not be defined next to the provider that
|
|
7
|
+
* fills it. That split is what let a duplicated ui-components module produce two
|
|
8
|
+
* distinct React contexts and take three apps down on 2026-08-05.
|
|
9
|
+
*
|
|
10
|
+
* They belong here: they are theme metadata, they are consumed by THEME_FAMILIES
|
|
11
|
+
* a few lines away, and they depend on nothing but React. Moving them reverses
|
|
12
|
+
* the arrow and lets the context live with its provider.
|
|
13
|
+
*
|
|
14
|
+
* UI chrome (SettingsIcon, TagIcon) and the fallback shapes stay in
|
|
15
|
+
* task-ui-components — those belong to the components that draw them.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
/** Shared SVG attributes. Duplicated from task-ui-components' icon file rather
|
|
19
|
+
* than imported: this package must not depend on that one — that dependency is
|
|
20
|
+
* the whole reason the icons moved. Seventeen lines of constants is the right
|
|
21
|
+
* price for a one-way graph. */
|
|
22
|
+
const iconProps = {
|
|
23
|
+
width: 20,
|
|
24
|
+
height: 20,
|
|
25
|
+
viewBox: '0 0 24 24',
|
|
26
|
+
fill: 'none',
|
|
27
|
+
stroke: 'currentColor',
|
|
28
|
+
strokeWidth: 2,
|
|
29
|
+
strokeLinecap: 'round',
|
|
30
|
+
strokeLinejoin: 'round'
|
|
31
|
+
};
|
|
32
|
+
export const SunIcon = () => (React.createElement("svg", { ...iconProps },
|
|
33
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "5" }),
|
|
34
|
+
React.createElement("line", { x1: "12", y1: "1", x2: "12", y2: "3" }),
|
|
35
|
+
React.createElement("line", { x1: "12", y1: "21", x2: "12", y2: "23" }),
|
|
36
|
+
React.createElement("line", { x1: "4.22", y1: "4.22", x2: "5.64", y2: "5.64" }),
|
|
37
|
+
React.createElement("line", { x1: "18.36", y1: "18.36", x2: "19.78", y2: "19.78" }),
|
|
38
|
+
React.createElement("line", { x1: "1", y1: "12", x2: "3", y2: "12" }),
|
|
39
|
+
React.createElement("line", { x1: "21", y1: "12", x2: "23", y2: "12" }),
|
|
40
|
+
React.createElement("line", { x1: "4.22", y1: "19.78", x2: "5.64", y2: "18.36" }),
|
|
41
|
+
React.createElement("line", { x1: "18.36", y1: "5.64", x2: "19.78", y2: "4.22" })));
|
|
42
|
+
export const MoonIcon = () => (React.createElement("svg", { ...iconProps },
|
|
43
|
+
React.createElement("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })));
|
|
44
|
+
export const StrawberryIcon = () => (React.createElement("svg", { ...iconProps },
|
|
45
|
+
React.createElement("path", { d: "M12 21 C12 21 6.5 15 6.5 11 C6.5 8.5 8 7 10 7 C11 7 12 7.5 12 7.5 C12 7.5 13 7 14 7 C16 7 17.5 8.5 17.5 11 C17.5 15 12 21 12 21 Z", fill: "currentColor" }),
|
|
46
|
+
React.createElement("path", { d: "M9.5 7.5 L9 5 L11 5.5 Z", fill: "currentColor" }),
|
|
47
|
+
React.createElement("path", { d: "M14.5 7.5 L15 5 L13 5.5 Z", fill: "currentColor" }),
|
|
48
|
+
React.createElement("path", { d: "M12 7.5 L12 4 L12 5", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }),
|
|
49
|
+
React.createElement("line", { x1: "10", y1: "10", x2: "10", y2: "11", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" }),
|
|
50
|
+
React.createElement("line", { x1: "14", y1: "10", x2: "14", y2: "11", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" }),
|
|
51
|
+
React.createElement("line", { x1: "9", y1: "13", x2: "9", y2: "14", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" }),
|
|
52
|
+
React.createElement("line", { x1: "15", y1: "13", x2: "15", y2: "14", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" }),
|
|
53
|
+
React.createElement("line", { x1: "11", y1: "16", x2: "11", y2: "17", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" }),
|
|
54
|
+
React.createElement("line", { x1: "13", y1: "16", x2: "13", y2: "17", stroke: "currentColor", strokeWidth: "1", opacity: "0.4" })));
|
|
55
|
+
export const WaveIcon = () => (React.createElement("svg", { ...iconProps },
|
|
56
|
+
React.createElement("path", { d: "M2 12c2-2 4-2 6 0s4 2 6 0 4-2 6 0" }),
|
|
57
|
+
React.createElement("path", { d: "M2 17c2-2 4-2 6 0s4 2 6 0 4-2 6 0" }),
|
|
58
|
+
React.createElement("path", { d: "M2 7c2-2 4-2 6 0s4 2 6 0 4-2 6 0" })));
|
|
59
|
+
export const ZapIcon = () => (React.createElement("svg", { ...iconProps },
|
|
60
|
+
React.createElement("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" })));
|
|
61
|
+
export const CoffeeIcon = () => (React.createElement("svg", { ...iconProps },
|
|
62
|
+
React.createElement("path", { d: "M18 8h1a4 4 0 0 1 0 8h-1" }),
|
|
63
|
+
React.createElement("path", { d: "M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z" }),
|
|
64
|
+
React.createElement("line", { x1: "6", y1: "1", x2: "6", y2: "4" }),
|
|
65
|
+
React.createElement("line", { x1: "10", y1: "1", x2: "10", y2: "4" }),
|
|
66
|
+
React.createElement("line", { x1: "14", y1: "1", x2: "14", y2: "4" })));
|
|
67
|
+
export const FlowerIcon = () => (React.createElement("svg", { ...iconProps },
|
|
68
|
+
React.createElement("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor" }),
|
|
69
|
+
React.createElement("circle", { cx: "12", cy: "6", r: "2.5", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
|
|
70
|
+
React.createElement("circle", { cx: "18", cy: "10", r: "2.5", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
|
|
71
|
+
React.createElement("circle", { cx: "16", cy: "16", r: "2.5", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
|
|
72
|
+
React.createElement("circle", { cx: "8", cy: "16", r: "2.5", fill: "none", stroke: "currentColor", strokeWidth: "2" }),
|
|
73
|
+
React.createElement("circle", { cx: "6", cy: "10", r: "2.5", fill: "none", stroke: "currentColor", strokeWidth: "2" })));
|
|
74
|
+
export const HeartIcon = () => (React.createElement("svg", { ...iconProps },
|
|
75
|
+
React.createElement("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z", fill: "currentColor" })));
|
|
76
|
+
export const LeafIcon = () => (React.createElement("svg", { ...iconProps },
|
|
77
|
+
React.createElement("path", { d: "M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z", fill: "currentColor" }),
|
|
78
|
+
React.createElement("path", { d: "M2 21c0-3 1.85-5.36 5.08-6C9 14.5 11 14 11 20", stroke: "currentColor", strokeWidth: "2", fill: "none" }),
|
|
79
|
+
React.createElement("path", { d: "M11 8c3 2 5 4 7 7", stroke: "white", strokeWidth: "1.5", opacity: "0.4" })));
|
|
80
|
+
export const LanternIcon = () => (React.createElement("svg", { ...iconProps },
|
|
81
|
+
React.createElement("path", { d: "M12 1.5v2" }),
|
|
82
|
+
React.createElement("path", { d: "M9 3.5h6" }),
|
|
83
|
+
React.createElement("path", { d: "M8.5 5.5h7c3 1.8 4.5 4 4.5 6.5s-1.5 4.7-4.5 6.5h-7C5.5 16.7 4 14.5 4 12s1.5-4.7 4.5-6.5z" }),
|
|
84
|
+
React.createElement("path", { d: "M5 9.5q7 1.6 14 0", strokeWidth: "1.5" }),
|
|
85
|
+
React.createElement("path", { d: "M5 14.5q7 -1.6 14 0", strokeWidth: "1.5" }),
|
|
86
|
+
React.createElement("path", { d: "M9 20.5h6" }),
|
|
87
|
+
React.createElement("path", { d: "M12 20.5v2" })));
|
|
88
|
+
/**
|
|
89
|
+
* Generic/Fallback icons for custom themes without specific icons
|
|
90
|
+
* These provide visual differentiation when using the ThemePicker with custom themes
|
|
91
|
+
*/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-theme advanced effect map
|
|
3
|
+
*
|
|
4
|
+
* Declares which effect modifier each surface type uses for a given
|
|
5
|
+
* theme. Components that want to apply effects automatically (Path B
|
|
6
|
+
* — themed primitive components) read this map. Until then, consumers
|
|
7
|
+
* apply hdk-advanced-surface[--modifier] classes directly in JSX.
|
|
8
|
+
*
|
|
9
|
+
* To add an advanced contract to a theme:
|
|
10
|
+
* 1. Define --advanced-stop-* and --advanced-gradient in the theme's
|
|
11
|
+
* [data-theme='X'] block in style.css
|
|
12
|
+
* 2. Add an entry to THEME_EFFECTS below
|
|
13
|
+
*/
|
|
14
|
+
import type { Theme } from './index';
|
|
15
|
+
export type AdvancedEffect = 'shift' | 'cascade-rich' | 'sat-rich' | 'none';
|
|
16
|
+
export interface ThemeEffectMap {
|
|
17
|
+
card: AdvancedEffect;
|
|
18
|
+
modal: AdvancedEffect;
|
|
19
|
+
button: AdvancedEffect;
|
|
20
|
+
}
|
|
21
|
+
export declare const THEME_EFFECTS: Partial<Record<Theme, ThemeEffectMap>>;
|
|
22
|
+
/** True when the theme ships an advanced visual contract. */
|
|
23
|
+
export declare function hasAdvanced(theme: string): boolean;
|
|
24
|
+
/** Returns the effect map for a theme, or null if it has no advanced contract. */
|
|
25
|
+
export declare function getThemeEffects(theme: string): ThemeEffectMap | null;
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-theme advanced effect map
|
|
3
|
+
*
|
|
4
|
+
* Declares which effect modifier each surface type uses for a given
|
|
5
|
+
* theme. Components that want to apply effects automatically (Path B
|
|
6
|
+
* — themed primitive components) read this map. Until then, consumers
|
|
7
|
+
* apply hdk-advanced-surface[--modifier] classes directly in JSX.
|
|
8
|
+
*
|
|
9
|
+
* To add an advanced contract to a theme:
|
|
10
|
+
* 1. Define --advanced-stop-* and --advanced-gradient in the theme's
|
|
11
|
+
* [data-theme='X'] block in style.css
|
|
12
|
+
* 2. Add an entry to THEME_EFFECTS below
|
|
13
|
+
*/
|
|
14
|
+
export const THEME_EFFECTS = {
|
|
15
|
+
light: { card: 'shift', modal: 'none', button: 'none' },
|
|
16
|
+
'cyberpunk-dark': { card: 'cascade-rich', modal: 'none', button: 'none' }
|
|
17
|
+
};
|
|
18
|
+
/** True when the theme ships an advanced visual contract. */
|
|
19
|
+
export function hasAdvanced(theme) {
|
|
20
|
+
return theme in THEME_EFFECTS;
|
|
21
|
+
}
|
|
22
|
+
/** Returns the effect map for a theme, or null if it has no advanced contract. */
|
|
23
|
+
export function getThemeEffects(theme) {
|
|
24
|
+
return THEME_EFFECTS[theme] ?? null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React `<Icon>` — a thin wrapper over the string registry.
|
|
3
|
+
*
|
|
4
|
+
* The glyph is emitted as real JSX children rather than dangerouslySetInnerHTML: the
|
|
5
|
+
* markup is ours and build-time-generated, but React apps run under the same strict
|
|
6
|
+
* CSP as the frontpage, and parsing 78 vendored strings at runtime buys nothing when
|
|
7
|
+
* the shapes can be elements. So the paths are parsed once, lazily, per icon actually
|
|
8
|
+
* rendered — an app that uses six icons never touches the other seventy-two.
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import type { IconName } from './registry.generated';
|
|
12
|
+
import type { IconFamily, IconVariant } from './index';
|
|
13
|
+
export interface IconProps {
|
|
14
|
+
name: IconName;
|
|
15
|
+
/** CSS length. Defaults to `1em` so the glyph scales with surrounding text. */
|
|
16
|
+
size?: string | number;
|
|
17
|
+
/**
|
|
18
|
+
* Which family token colours this icon. Applies to `accent` (the glyph itself)
|
|
19
|
+
* and to `tint`/`filled` (the tile). Ignored for `bare`, which inherits
|
|
20
|
+
* `currentColor` by design.
|
|
21
|
+
*/
|
|
22
|
+
family?: IconFamily;
|
|
23
|
+
/** `bare` (default), `accent`, `tint`, or `filled` — see IconVariant. */
|
|
24
|
+
variant?: IconVariant;
|
|
25
|
+
/** Round the accent tile. No effect when `variant` is `bare`. */
|
|
26
|
+
round?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Accessible name. Omit for decorative icons — those render `aria-hidden` so a
|
|
29
|
+
* screen reader does not announce a glyph that repeats an adjacent text label.
|
|
30
|
+
*/
|
|
31
|
+
title?: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
/** Stroke width in the 24x24 viewBox. Lucide's default is 2. */
|
|
34
|
+
strokeWidth?: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const Icon: React.FC<IconProps>;
|