@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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE-lucide +43 -0
  3. package/README.md +433 -2
  4. package/THEME_USAGE_GUIDE.md +308 -0
  5. package/dist/HadokuThemeRoot.d.ts +53 -0
  6. package/dist/HadokuThemeRoot.js +66 -0
  7. package/dist/ThemeIcons.d.ts +31 -0
  8. package/dist/ThemeIcons.js +91 -0
  9. package/dist/effects.d.ts +25 -0
  10. package/dist/effects.js +25 -0
  11. package/dist/icons/Icon.d.ts +36 -0
  12. package/dist/icons/Icon.js +89 -0
  13. package/dist/icons/index.d.ts +61 -0
  14. package/dist/icons/index.js +87 -0
  15. package/dist/icons/registry.generated.d.ts +158 -0
  16. package/dist/icons/registry.generated.js +446 -0
  17. package/dist/index.d.ts +82 -0
  18. package/dist/index.js +169 -0
  19. package/dist/metadata.d.ts +35 -0
  20. package/dist/metadata.js +108 -0
  21. package/dist/platform/index.d.ts +94 -0
  22. package/dist/platform/index.js +149 -0
  23. package/dist/platform/usePlatform.d.ts +29 -0
  24. package/dist/platform/usePlatform.js +65 -0
  25. package/dist/prefsApiBase.d.ts +37 -0
  26. package/dist/prefsApiBase.js +40 -0
  27. package/dist/theme-types.d.ts +34 -0
  28. package/dist/theme-types.js +1 -0
  29. package/dist/themeContext.d.ts +62 -0
  30. package/dist/themeContext.js +70 -0
  31. package/dist/themePrefs.d.ts +29 -0
  32. package/dist/themePrefs.js +48 -0
  33. package/dist/useRefElement.d.ts +19 -0
  34. package/dist/useRefElement.js +52 -0
  35. package/dist/useTheme.d.ts +46 -0
  36. package/dist/useTheme.js +216 -0
  37. package/package.json +83 -4
  38. package/scripts/audit-tokens.mjs +126 -0
  39. package/scripts/check-contrast.mjs +24 -0
  40. package/scripts/check-docs.mjs +53 -0
  41. package/scripts/check-icons.mjs +473 -0
  42. package/scripts/check-usage.mjs +297 -0
  43. package/scripts/codemod-icons.mjs +396 -0
  44. package/scripts/generate-icons.mjs +199 -0
  45. package/scripts/generate-preview.mjs +374 -0
  46. package/scripts/generate-tailwind-colors.mjs +109 -0
  47. package/scripts/lib/color.mjs +184 -0
  48. package/scripts/lib/contract.d.mts +58 -0
  49. package/scripts/lib/contract.mjs +348 -0
  50. package/scripts/lib/derive.mjs +324 -0
  51. package/scripts/lib/icon-registry.mjs +92 -0
  52. package/scripts/lib/parse-themes.mjs +19 -0
  53. package/scripts/lib/tokens.mjs +188 -0
  54. package/scripts/normalize-tokens.mjs +161 -0
  55. package/src/advanced.css +218 -0
  56. package/src/icons/Icon.tsx +151 -0
  57. package/src/icons/emoji-map.json +202 -0
  58. package/src/icons/index.ts +151 -0
  59. package/src/icons/registry.generated.ts +455 -0
  60. package/src/icons/sources.json +150 -0
  61. package/src/icons.css +145 -0
  62. package/src/style.css +1421 -0
  63. package/src/tailwind-colors.css +88 -0
  64. package/src/tailwind-integration.css +49 -0
@@ -0,0 +1,202 @@
1
+ {
2
+ "$comment": [
3
+ "CANONICAL emoji -> icon name. The migration's single source of truth.",
4
+ "",
5
+ "This exists so `📋` resolves to `clipboard` in every repo instead of `clipboard`",
6
+ "in one and `paste` in the next. Twelve repos migrating independently would each",
7
+ "re-decide these, and the drift would be invisible until two apps sat side by side",
8
+ "on the frontpage showing different glyphs for the same idea.",
9
+ "",
10
+ "`check-icons` reads this to SUGGEST the replacement in its findings, which is what",
11
+ "turns each one from a decision into an edit. Keys are matched with variation",
12
+ "selectors stripped, so `⚙` and `⚙️` both land on `gear`.",
13
+ "",
14
+ "Every value must exist in sources.json — check-icons fails loudly if one does not."
15
+ ],
16
+
17
+ "$comment_ambiguous": [
18
+ "A FLAT MAP CANNOT RESOLVE THESE. One emoji, two right answers depending on what",
19
+ "sits next to it, so `$ambiguous` names them and both tools defer to a human:",
20
+ "the codemod refuses them outright, and check-icons prints the caveat under its",
21
+ "suggestion instead of presenting one arm of a pair as settled.",
22
+ "",
23
+ "`◀`/`▶` is the case that proved it. `▶` lives in the transport row below with",
24
+ "`⏸`/`⏭`/`⏮` because a play button really is `play` — hadoku-dataplatform's",
25
+ "VodPlayer renders `isPlaying ? <Icon name=\"pause\"/> : <Icon name=\"play\"/>`.",
26
+ "But `◀`/`▶` is just as often a mirrored nav pair, and there applying the map",
27
+ "arm by arm yields an outline chevron facing a filled triangle. That is exactly",
28
+ "how promptsmith shipped a mismatched tournament row, and how the same repo's",
29
+ "PartBadge `◀ ▶` had to be hand-corrected to chevron-left/chevron-right.",
30
+ "",
31
+ "Neither value can move without breaking the other site, so the ambiguity is",
32
+ "recorded here rather than papered over with a coin-flip default."
33
+ ],
34
+ "$ambiguous": {
35
+ "◀": "in a mirrored `◀ … ▶` pair use chevron-left WITH chevron-right — never pair it with play",
36
+ "▶": "play ONLY as a transport control beside ⏸; in a mirrored `◀ … ▶` pair use chevron-right"
37
+ },
38
+
39
+ "$comment_catalogue": "hadoku_site/spec/categories.json — the frontpage catalogue.",
40
+ "👋": "wave",
41
+ "📄": "document",
42
+ "📧": "mail",
43
+ "✉": "mail",
44
+ "🎨": "palette",
45
+ "🖨": "printer",
46
+ "✨": "sparkles",
47
+ "🎭": "masks",
48
+ "🔧": "wrench",
49
+ "📡": "antenna",
50
+ "🔍": "magnifier",
51
+ "🔎": "magnifier",
52
+ "💼": "briefcase",
53
+ "⚙": "gear",
54
+ "🖐": "helping-hand",
55
+ "✅": "check",
56
+ "✔": "tick",
57
+ "☑": "tick",
58
+ "🎬": "clapper",
59
+ "🍿": "popcorn",
60
+ "📼": "videotape",
61
+
62
+ "$comment_status": "State and feedback.",
63
+ "⚠": "warning",
64
+ "❌": "error",
65
+ "✖": "close",
66
+ "❎": "error",
67
+ "ℹ": "info",
68
+ "⏳": "hourglass",
69
+ "⌛": "hourglass",
70
+ "⏰": "clock",
71
+ "🕐": "clock",
72
+ "💤": "sleep",
73
+ "⚪": "circle",
74
+ "🔴": "circle-dot",
75
+ "🟢": "circle-dot",
76
+ "🟡": "circle-dot",
77
+ "🔵": "circle-dot",
78
+ "➖": "minus",
79
+ "🚨": "warning",
80
+ "🔥": "fire",
81
+ "💥": "warning",
82
+ "🎉": "party",
83
+ "🏆": "trophy",
84
+ "⭐": "star",
85
+ "🌟": "star",
86
+ "👍": "thumbs-up",
87
+ "👎": "thumbs-down",
88
+ "❤": "heart",
89
+ "🩷": "heart",
90
+ "🖤": "heart",
91
+
92
+ "$comment_actions": "Verbs — buttons and controls.",
93
+ "🗑": "trash",
94
+ "✏": "edit",
95
+ "📝": "note",
96
+ "📋": "clipboard",
97
+ "📌": "pin",
98
+ "🔄": "refresh",
99
+ "♻": "refresh",
100
+ "▶": "play",
101
+ "⏸": "pause",
102
+ "⏭": "next",
103
+ "⏮": "previous",
104
+ "⏹": "stop",
105
+ "🔀": "shuffle",
106
+ "🔗": "link",
107
+ "⛓": "link",
108
+ "👁": "eye",
109
+ "🔒": "lock",
110
+ "🔑": "key",
111
+ "🛡": "shield",
112
+ "🧹": "broom",
113
+ "🪄": "wand",
114
+ "🔔": "bell",
115
+ "🚩": "flag",
116
+ "🏷": "tag",
117
+ "🔖": "bookmark",
118
+ "📤": "upload",
119
+ "⬆": "arrow-up",
120
+ "📥": "download",
121
+ "⬇": "arrow-down",
122
+ "➡": "arrow-right",
123
+ "⬅": "arrow-left",
124
+ "↔": "arrows-horizontal",
125
+ "↗": "arrow-up-right",
126
+ "↩": "return",
127
+ "⤴": "branch-up",
128
+ "◀": "chevron-left",
129
+ "🔼": "chevron-up",
130
+ "🔽": "chevron-down",
131
+ "➕": "plus",
132
+ "📎": "copy",
133
+ "💾": "save",
134
+ "📨": "send",
135
+
136
+ "$comment_objects": "Nouns — things a screen is about.",
137
+ "📁": "folder",
138
+ "📂": "folder",
139
+ "📦": "package",
140
+ "📅": "calendar",
141
+ "🗓": "calendar",
142
+ "🖼": "image",
143
+ "📷": "camera",
144
+ "🎞": "film",
145
+ "🎵": "music",
146
+ "🎧": "music",
147
+ "🔊": "volume-high",
148
+ "🔈": "volume-low",
149
+ "🔇": "volume-mute",
150
+ "💬": "chat",
151
+ "📖": "book",
152
+ "📚": "book",
153
+ "🤝": "handshake",
154
+ "🤖": "robot",
155
+ "👤": "user",
156
+ "👥": "users",
157
+ "👑": "crown",
158
+ "💀": "skull",
159
+ "🐈": "cat",
160
+ "🦴": "bone",
161
+ "🎮": "gamepad",
162
+ "🧰": "toolbox",
163
+ "🧪": "flask",
164
+ "🏥": "hospital",
165
+ "🌐": "globe",
166
+ "🗺": "map",
167
+ "📊": "chart",
168
+ "📈": "chart",
169
+ "📉": "chart",
170
+ "📐": "crop",
171
+ "🖥": "desktop",
172
+ "💻": "desktop",
173
+ "🗄": "database",
174
+ "🗃": "database",
175
+ "⌨": "terminal",
176
+ "🧱": "layers",
177
+ "🎯": "target",
178
+ "🚀": "rocket",
179
+ "⚡": "zap",
180
+ "🧭": "compass",
181
+ "🏠": "home",
182
+ "🏡": "home",
183
+ "📭": "inbox",
184
+ "📬": "inbox",
185
+ "📮": "inbox",
186
+ "☀": "sun",
187
+ "🌙": "moon",
188
+ "🧑": "user",
189
+ "🙋": "wave",
190
+ "💡": "zap",
191
+ "🔦": "magnifier",
192
+ "📏": "crop",
193
+ "✂": "scissors",
194
+ "🩹": "wrench",
195
+ "🔨": "wrench",
196
+ "🛠": "toolbox",
197
+ "⚔": "toolbox",
198
+ "🏹": "target",
199
+ "🔱": "layers",
200
+ "🚫": "ban",
201
+ "⛔": "ban"
202
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The enforced icon set — framework-free half.
3
+ *
4
+ * Every consumer references icons BY NAME from `IconName`. Nobody inlines their own
5
+ * SVG: an icon that is not in the registry does not exist, which is what keeps 16 apps
6
+ * rendering the same pictograph for the same idea. TS consumers get that as a type
7
+ * error; everyone else gets it from `scripts/check-icons.mjs`.
8
+ *
9
+ * This module has no React import on purpose. The frontpage POCs are Astro (one Qwik)
10
+ * and cannot mount a React component just to draw a 16px glyph, so the string path is
11
+ * the primary API and `<Icon>` is a convenience on top of it.
12
+ */
13
+ import { ICON_MARKUP } from './registry.generated'
14
+ import type { IconName } from './registry.generated'
15
+
16
+ export { ICON_MARKUP, ICON_NAMES, ICON_SOURCE_SLUGS, LUCIDE_VERSION } from './registry.generated'
17
+ export type { IconName } from './registry.generated'
18
+
19
+ /** The five accent families. Same set as the colour tokens — no icon-only families. */
20
+ export const ICON_FAMILIES = ['primary', 'success', 'warning', 'danger', 'neutral'] as const
21
+ export type IconFamily = (typeof ICON_FAMILIES)[number]
22
+
23
+ /**
24
+ * How the icon is coloured. Every option resolves through theme tokens — there is
25
+ * no way to pass a raw colour — but WHICH treatment to use is the consumer's call.
26
+ *
27
+ * - `bare` — glyph inherits `currentColor`. The default. Use it inside a button
28
+ * or beside a label and the icon matches that text automatically, in
29
+ * every theme, with no contrast obligation of its own.
30
+ * - `accent` — glyph painted `--color-<family>`. For when the icon is its own
31
+ * element and should NOT match the surrounding text. You own the
32
+ * contrast: a bare accent glyph clears 3:1 on a card/page surface in
33
+ * 28 of 36 theme/surface combinations for `warning`, so this is right
34
+ * when you know the surface and wrong as a blanket default.
35
+ * - `tint` — glyph in a `--color-<f>-bg` tile with `--color-on-<f>-bg` ink.
36
+ * - `filled` — glyph in a `--color-<f>` tile with `--color-on-<f>` ink.
37
+ *
38
+ * `tint` and `filled` clear 3:1 in all 18 themes for all five families.
39
+ */
40
+ export type IconVariant = 'bare' | 'accent' | 'tint' | 'filled'
41
+
42
+ export function isIconName(value: unknown): value is IconName {
43
+ return typeof value === 'string' && Object.prototype.hasOwnProperty.call(ICON_MARKUP, value)
44
+ }
45
+
46
+ const ESCAPES: Record<string, string> = {
47
+ '&': '&amp;',
48
+ '<': '&lt;',
49
+ '>': '&gt;',
50
+ '"': '&quot;',
51
+ "'": '&#39;'
52
+ }
53
+
54
+ /** Escape a caller-supplied string before it goes into the markup we return. */
55
+ function escapeHtml(value: string): string {
56
+ return value.replace(/[&<>"']/g, c => ESCAPES[c])
57
+ }
58
+
59
+ export interface IconSvgOptions {
60
+ /** CSS length. Defaults to `1em` so the glyph scales with its surrounding text. */
61
+ size?: string | number
62
+ /** Extra classes appended after `hdk-icon`. */
63
+ className?: string
64
+ /**
65
+ * Paint the glyph with `--color-<family>` instead of inheriting `currentColor`.
66
+ * Adds `hdk-icon--<family>`; requires icons.css. Omit to inherit.
67
+ */
68
+ family?: IconFamily
69
+ /**
70
+ * Accessible name. Omit for decorative icons (the default) — those are rendered
71
+ * `aria-hidden` so a screen reader does not announce a glyph that repeats an
72
+ * adjacent label. Supply it only when the icon is the sole carrier of meaning.
73
+ */
74
+ title?: string
75
+ /** Stroke width in the 24x24 viewBox. Lucide's default is 2. */
76
+ strokeWidth?: number
77
+ }
78
+
79
+ /**
80
+ * A complete, standalone `<svg>` string for one icon.
81
+ *
82
+ * Astro/Qwik/plain HTML, no client JS:
83
+ * <span set:html={getIconSvg('popcorn')} />
84
+ * <span class="hdk-icon-tile hdk-icon-tile--tint hdk-icon-tile--warning"
85
+ * set:html={getIconSvg('popcorn')} />
86
+ */
87
+ export function getIconSvg(name: IconName, options: IconSvgOptions = {}): string {
88
+ const markup = ICON_MARKUP[name]
89
+ if (markup === undefined) {
90
+ // Loud rather than silent: a blank space where an icon should be is the failure
91
+ // mode that ships to production unnoticed.
92
+ throw new Error(
93
+ `getIconSvg: "${String(name)}" is not in the icon registry. ` +
94
+ `Add it to themes/src/icons/sources.json and run \`pnpm run generate:icons\`.`
95
+ )
96
+ }
97
+ const { size = '1em', className, title, strokeWidth = 2, family } = options
98
+ const dim = typeof size === 'number' ? `${size}px` : size
99
+ const cls = ['hdk-icon', family ? `hdk-icon--${family}` : '', className ?? '']
100
+ .filter(Boolean)
101
+ .join(' ')
102
+ const label = title
103
+ ? `role="img" aria-label="${escapeHtml(title)}"`
104
+ : 'aria-hidden="true" focusable="false"'
105
+
106
+ return (
107
+ `<svg xmlns="http://www.w3.org/2000/svg" class="${escapeHtml(cls)}" ` +
108
+ `width="${escapeHtml(dim)}" height="${escapeHtml(dim)}" viewBox="0 0 24 24" ` +
109
+ `fill="none" stroke="currentColor" stroke-width="${strokeWidth}" ` +
110
+ `stroke-linecap="round" stroke-linejoin="round" ${label}>` +
111
+ (title ? `<title>${escapeHtml(title)}</title>` : '') +
112
+ markup +
113
+ '</svg>'
114
+ )
115
+ }
116
+
117
+ /** Class list for an accent tile, for the framework-free path. */
118
+ export function getIconTileClass(
119
+ family: IconFamily = 'warning',
120
+ variant: Exclude<IconVariant, 'bare'> = 'tint',
121
+ round = false
122
+ ): string {
123
+ return (
124
+ `hdk-icon-tile hdk-icon-tile--${variant} hdk-icon-tile--${family}` +
125
+ (round ? ' hdk-icon-tile--round' : '')
126
+ )
127
+ }
128
+
129
+ // Canonical emoji -> icon name. Generated into registry.generated.ts from
130
+ // emoji-map.json, so nothing loads JSON at runtime and there is one source.
131
+ import { EMOJI_TO_ICON } from './registry.generated'
132
+
133
+ export { EMOJI_TO_ICON } from './registry.generated'
134
+
135
+ /** Variation selectors and skin tones are presentation, not identity. */
136
+ function stripPresentation(s: string): string {
137
+ return [...s]
138
+ .filter(c => {
139
+ const p = c.codePointAt(0) ?? 0
140
+ return p !== 0xfe0f && p !== 0xfe0e && !(p >= 0x1f3fb && p <= 0x1f3ff)
141
+ })
142
+ .join('')
143
+ }
144
+
145
+ /**
146
+ * The registry name a raw emoji should become, or undefined if it has no
147
+ * canonical answer. `⚙` and `⚙️` resolve identically.
148
+ */
149
+ export function emojiToIconName(emoji: string): IconName | undefined {
150
+ return EMOJI_TO_ICON[stripPresentation(emoji)] ?? EMOJI_TO_ICON[emoji]
151
+ }