@godxjp/ui 28.13.0 → 30.0.0

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 (164) hide show
  1. package/agent/START-HERE.md +29 -10
  2. package/agent/components/Anchor.json +6 -1
  3. package/agent/components/AppShell.json +1 -1
  4. package/agent/components/AreaChart.json +19 -1
  5. package/agent/components/BarChart.json +11 -1
  6. package/agent/components/Cascader.json +1 -1
  7. package/agent/components/CompactBarTrend.json +1 -1
  8. package/agent/components/DataState.json +1 -1
  9. package/agent/components/DataTable.json +4 -4
  10. package/agent/components/FormField.json +1 -1
  11. package/agent/components/InfiniteQueryState.json +1 -1
  12. package/agent/components/Input.json +1 -1
  13. package/agent/components/LineChart.json +20 -2
  14. package/agent/components/ListRow.json +1 -1
  15. package/agent/components/Masonry.json +1 -1
  16. package/agent/components/MasterDetail.json +1 -1
  17. package/agent/components/PasswordStrength.json +1 -1
  18. package/agent/components/PermissionMatrix.json +1 -1
  19. package/agent/components/Select.json +1 -0
  20. package/agent/components/Sidebar.json +1 -1
  21. package/agent/components/Table.json +8 -3
  22. package/agent/components/ThemeScope.json +49 -0
  23. package/agent/components/Topbar.json +1 -0
  24. package/agent/components/TopbarItem.json +2 -1
  25. package/agent/components/Transfer.json +1 -1
  26. package/agent/components/TreeSelect.json +1 -1
  27. package/agent/components/UploadCropDialog.json +1 -1
  28. package/agent/components/formatDate.json +1 -1
  29. package/agent/components-index.json +5 -0
  30. package/agent/components.json +138 -30
  31. package/agent/index.json +19 -9
  32. package/agent/llms.txt +10 -10
  33. package/agent/patterns/tenant-brand-color.json +28 -0
  34. package/agent/patterns-index.json +27 -0
  35. package/agent/patterns.json +28 -0
  36. package/agent/rules.json +15 -0
  37. package/agent/tokens.json +5055 -1060
  38. package/dist/app/index.d.ts +3 -0
  39. package/dist/app/index.js +3 -0
  40. package/dist/app/tenant-theme.d.ts +80 -0
  41. package/dist/app/tenant-theme.js +154 -0
  42. package/dist/app/theme-axes.d.ts +14 -1
  43. package/dist/app/theme-axes.js +24 -31
  44. package/dist/components/charts/chart-cartesian.d.ts +5 -1
  45. package/dist/components/charts/chart-cartesian.js +15 -8
  46. package/dist/components/data-display/badge.d.ts +1 -1
  47. package/dist/components/data-display/badge.js +21 -3
  48. package/dist/components/data-display/carousel.js +4 -4
  49. package/dist/components/data-display/data-table.js +13 -2
  50. package/dist/components/data-display/permission-matrix.js +1 -1
  51. package/dist/components/data-display/table.d.ts +11 -2
  52. package/dist/components/data-display/table.js +19 -3
  53. package/dist/components/data-entry/control-appearance.d.ts +12 -6
  54. package/dist/components/data-entry/control-appearance.js +1 -1
  55. package/dist/components/data-entry/select.js +4 -3
  56. package/dist/components/feedback/dialog.js +6 -3
  57. package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
  58. package/dist/components/feedback/overlay-header-tone.js +4 -4
  59. package/dist/components/feedback/sheet.d.ts +1 -1
  60. package/dist/components/feedback/sheet.js +6 -9
  61. package/dist/components/feedback/sonner.js +16 -3
  62. package/dist/components/general/button.js +22 -5
  63. package/dist/components/layout/affix.js +15 -1
  64. package/dist/components/layout/sidebar.js +7 -1
  65. package/dist/components/navigation/anchor.d.ts +1 -1
  66. package/dist/components/navigation/anchor.js +5 -4
  67. package/dist/components/navigation/app-setting-picker.js +1 -1
  68. package/dist/components/navigation/pagination.js +1 -1
  69. package/dist/components/navigation/tabs.js +15 -2
  70. package/dist/components/query/infinite-query-state.d.ts +22 -6
  71. package/dist/contracts/measurement.json +1 -1
  72. package/dist/lib/control-styles.d.ts +31 -11
  73. package/dist/lib/control-styles.js +6 -6
  74. package/dist/lib/overlay-portal.d.ts +20 -0
  75. package/dist/lib/overlay-portal.js +93 -0
  76. package/dist/props/components/app.prop.d.ts +12 -0
  77. package/dist/props/components/charts.prop.d.ts +30 -0
  78. package/dist/props/components/index.d.ts +1 -1
  79. package/dist/props/components/navigation.prop.d.ts +21 -2
  80. package/dist/props/components/query.prop.d.ts +36 -2
  81. package/dist/props/registry.d.ts +46 -1
  82. package/dist/props/registry.js +38 -3
  83. package/dist/styles/alert-layout.css +39 -19
  84. package/dist/styles/badge-layout.css +11 -7
  85. package/dist/styles/base.css +14 -5
  86. package/dist/styles/card-layout.css +29 -16
  87. package/dist/styles/chart-layout.css +27 -5
  88. package/dist/styles/control.css +225 -103
  89. package/dist/styles/data-display-layout.css +178 -76
  90. package/dist/styles/data-entry-layout.css +35 -99
  91. package/dist/styles/dialog-layout.css +58 -28
  92. package/dist/styles/float-button-layout.css +5 -5
  93. package/dist/styles/focus-ring.css +11 -7
  94. package/dist/styles/layout.css +53 -25
  95. package/dist/styles/logo-layout.css +3 -3
  96. package/dist/styles/motion.css +2 -2
  97. package/dist/styles/navigation-layout.css +117 -44
  98. package/dist/styles/shell-layout.css +90 -124
  99. package/dist/styles/table-layout.css +66 -24
  100. package/dist/styles/text-layout.css +13 -4
  101. package/dist/styles/toggle.css +9 -3
  102. package/dist/tokens/components/actions.css +1 -1
  103. package/dist/tokens/components/attachments.css +4 -4
  104. package/dist/tokens/components/badge.css +4 -4
  105. package/dist/tokens/components/banner.css +1 -1
  106. package/dist/tokens/components/callout.css +1 -1
  107. package/dist/tokens/components/card.css +13 -8
  108. package/dist/tokens/components/chart.css +11 -2
  109. package/dist/tokens/components/chat-bubble.css +2 -2
  110. package/dist/tokens/components/control.css +41 -22
  111. package/dist/tokens/components/conversations.css +2 -1
  112. package/dist/tokens/components/data-display.css +23 -18
  113. package/dist/tokens/components/data-entry.css +1 -1
  114. package/dist/tokens/components/descriptions.css +2 -2
  115. package/dist/tokens/components/draggable-panel.css +2 -2
  116. package/dist/tokens/components/feedback.css +29 -9
  117. package/dist/tokens/components/float-button.css +1 -1
  118. package/dist/tokens/components/legal-document.css +2 -2
  119. package/dist/tokens/components/logo.css +2 -2
  120. package/dist/tokens/components/mega-menu.css +6 -4
  121. package/dist/tokens/components/navigation.css +27 -13
  122. package/dist/tokens/components/segmented.css +11 -6
  123. package/dist/tokens/components/separator.css +1 -1
  124. package/dist/tokens/components/shell.css +24 -10
  125. package/dist/tokens/components/table.css +13 -5
  126. package/dist/tokens/components/thought-chain.css +2 -2
  127. package/dist/tokens/components/toggle.css +3 -1
  128. package/dist/tokens/components/tree.css +3 -1
  129. package/dist/tokens/components/upload.css +8 -8
  130. package/dist/tokens/components/welcome.css +1 -1
  131. package/dist/tokens/foundation.css +30 -3
  132. package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
  133. package/docs/CUSTOMER-THEMING.md +637 -1
  134. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  135. package/docs/GLASSMORPHISM-STANDARD.md +196 -0
  136. package/docs/THEME-API-COVERAGE.md +538 -0
  137. package/docs/TOKEN-RESOLUTION.md +195 -0
  138. package/docs/TOKENS.md +63 -24
  139. package/docs/asset-modules.d.ts +7 -0
  140. package/docs/data-display/charts.tsx +80 -0
  141. package/docs/data-display/data-table/index.tsx +30 -0
  142. package/docs/data-display/popover.tsx +1 -1
  143. package/docs/data-display/table.tsx +52 -0
  144. package/docs/feedback/sheet.tsx +10 -10
  145. package/docs/foundation/density.tsx +4 -4
  146. package/docs/i18n/messages/en.json +506 -0
  147. package/docs/i18n/messages/ja.json +506 -0
  148. package/docs/i18n/messages/vi.json +506 -0
  149. package/docs/layout/account-chip.tsx +2 -2
  150. package/docs/layout/responsive-grid.tsx +1 -1
  151. package/docs/navigation/toolbar.tsx +20 -12
  152. package/docs/providers/theme-scope.tsx +186 -0
  153. package/docs/showcase/caimono-price-comparison.tsx +911 -0
  154. package/docs/showcase/case4-login.tsx +2 -2
  155. package/docs/showcase/permission-matrix.tsx +13 -5
  156. package/docs/showcase/table-pagination.tsx +2 -1
  157. package/docs/showcase/tenant-brand-color.tsx +338 -0
  158. package/docs/showcase/theme-lab.tsx +2125 -0
  159. package/docs/themes/flat.css +462 -0
  160. package/docs/themes/glassmorphism.css +958 -0
  161. package/docs/themes/index.ts +219 -0
  162. package/docs/themes/neubrutalism.css +475 -0
  163. package/package.json +4 -3
  164. package/scripts/explain-token.mjs +382 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * The theme REGISTRY for `/showcase/theme-lab` — the one file a new theme touches.
3
+ *
4
+ * gh#882 asks that adding a theme mean "one new CSS file and one new row, never a page edit".
5
+ * That is what this module is: the page imports `THEMES` and `SEEDS` and knows nothing else about
6
+ * either. A new theme is
7
+ *
8
+ * 1. `docs/themes/<id>.css`, containing NOTHING but `--custom-property` declarations under
9
+ * `[data-theme-style="<id>"]`, and
10
+ * 2. one row below (plus the side-effect import that puts the file in the bundle).
11
+ *
12
+ * WHY THE IMPORT IS WRITTEN OUT rather than globbed. `import.meta.glob` would make the row the
13
+ * only edit, but it is a Vite-only form typed by `vite/client`, and `tsconfig.json` pins
14
+ * `"types": ["node"]` — `tsconfig.docs.json` inherits that, so a glob here is a TS2339 that
15
+ * `typecheck:docs` catches and no amount of local success hides. Two lines in one non-page file is
16
+ * the honest cost of staying inside the type system the rest of `docs/**` is checked by.
17
+ *
18
+ * ── The constraint that makes this a MEASUREMENT (gh#882, and it is the whole exercise) ────────
19
+ * A theme file may declare custom properties and nothing else: no `backdrop-filter:`, no
20
+ * `background:`, no selector into a `.ui-*` class, no `className` or inline `style` on the page.
21
+ * Where a look cannot be expressed that way, the RESULT is the gap — written down in
22
+ * `docs/showcase/theme-lab.tsx`'s "what the token API could not reach" table — never worked around.
23
+ *
24
+ * ── Seeds ─────────────────────────────────────────────────────────────────────────────────────
25
+ * The second half of gh#882: nothing proved the glass theme was not tuned to violet. Every seed
26
+ * below goes through `tenantTheme(hex)` (`@godxjp/ui/app`), which returns the contrast-safe
27
+ * `--primary` / `--primary-foreground` pair plus the hover and pressed steps as real triplets, and
28
+ * the theme files derive their own decoration FROM `--primary` with CSS relative colour so the
29
+ * whole page follows the seed rather than the seed following the theme.
30
+ */
31
+ import "./glassmorphism.css";
32
+ import "./flat.css";
33
+ import "./neubrutalism.css";
34
+
35
+ export type ThemeRow = {
36
+ /**
37
+ * The `data-theme-style` value, or `null` for the library's own default — the control. `null`
38
+ * is a row, not a special case: the page writes the attribute either way and an attribute that
39
+ * matches no selector is exactly "no theme".
40
+ */
41
+ id: string | null;
42
+ /** Message key for the switch label. */
43
+ nameKey: string;
44
+ /** Message key for the one-line note under the switch. */
45
+ noteKey: string;
46
+ /**
47
+ * The LIGHTEST surface brand INK lands on in this theme, as hex — handed to `tenantTheme` as
48
+ * `options.surface`. `null` means the library's own light default (`--accent` #ebe9e5).
49
+ *
50
+ * `tenantTheme` walks the three brand inks AWAY from a surface until they clear 4.5:1, and it
51
+ * has to be told which surface, because it emits them as literals in `style` — which outrank a
52
+ * theme's own `--text-link` however carefully the theme declared it. Omitting this on a DARK
53
+ * theme is not a missing nicety, it is the ink walked the WRONG WAY: measured on glass/citron,
54
+ * `tenantTheme(#FFD400)` alone emitted `--text-link: 49.88 100% 24.4%` — rgb(124,103,0), walked
55
+ * dark for the light default — over a #3b382b panel, so `Button variant="link"` and a `Text`
56
+ * link both read **2.09:1** while the theme's own `--foreground` next to them was near-white.
57
+ * The library says so at `src/app/tenant-theme.ts`: "a region inside a DARK theme must pass the
58
+ * dark surface or its ink is walked the wrong way."
59
+ *
60
+ * WHY ONE HEX COVERS FIVE SEEDS. Every glass surface is a translucent white over
61
+ * `--background`, which derives its hue from the seed, so the composited panel moves per seed.
62
+ * Composited and measured across all five: card #312b3b..#3b382b, popover #434851..#514f43,
63
+ * accent #482d76..#766a2d, muted #232a39..#393523. The value below is the LIGHTEST of the
64
+ * twenty — glass/citron's `--accent` — because ink walked away from a dark surface goes
65
+ * lighter, and lighter ink on a DARKER surface only gains contrast. Clearing the worst case
66
+ * clears all of them.
67
+ *
68
+ * A PAIR, not one hex (gh#896): the surface above was measured while glass was reachable in ONE
69
+ * polarity only — the whole theme rendered permanently dark (the bug gh#896 reports), so every
70
+ * hex the paragraph above lists, `#766A2D` included, is a DARK-branch measurement with no light
71
+ * counterpart. Now that polarity is addressable, both members below are `null` until a real
72
+ * compositing measurement exists for EACH branch — the old single hex is not carried over into
73
+ * either slot, because guessing which polarity it belongs to is exactly the mistake this pair
74
+ * exists to prevent. `null` means the library's own default for that polarity (light `--accent`
75
+ * #ebe9e5; dark reads the built-in dark spine).
76
+ */
77
+ inkSurface: { light: string | null; dark: string | null };
78
+ };
79
+
80
+ export type SeedRow = {
81
+ /** Stable id — also the `?seed=` query value, so a measurement can address it directly. */
82
+ id: string;
83
+ /** The customer hex handed to `tenantTheme()`. */
84
+ hex: string;
85
+ /** Message key for the swatch label. */
86
+ nameKey: string;
87
+ };
88
+
89
+ /**
90
+ * The polarity axis (gh#896): the library ships light AND dark, and until this row existed the lab
91
+ * had no way to address the dark half of a theme × seed cell — a theme's dark branch could regress
92
+ * silently because nothing ever asked for it. `id` is the exact string `applyThemeAxes`
93
+ * (`src/app/theme-axes.ts`) resolves `data-theme` to, so it can be handed straight to
94
+ * `AppProvider`'s own `setTheme` — never written to `document.documentElement` by this page itself.
95
+ */
96
+ export type ModeRow = {
97
+ /** Also the `?mode=` query value. */
98
+ id: "light" | "dark";
99
+ /** Message key for the switch label. */
100
+ nameKey: string;
101
+ };
102
+
103
+ /**
104
+ * Order is reading order in the switch. `base` leads because a control that is not first is a
105
+ * control nobody looks at.
106
+ */
107
+ export const THEMES: readonly ThemeRow[] = [
108
+ {
109
+ id: null,
110
+ nameKey: "themeLab.theme.base.name",
111
+ noteKey: "themeLab.theme.base.note",
112
+ /* `null` IS ONLY CORRECT IN LIGHT, AND THE 30-CELL RUN IS WHAT SHOWED IT. I wrote "the package
113
+ * default IS this surface in both polarities" here and the matrix answered: every dark `base`
114
+ * cell read 552/562, ten failures, identical at all five seeds. `tenantTheme`'s default is
115
+ * `INK_SURFACE_LIGHT` (#ebe9e5) and there is no dark counterpart, so `null` in the dark slot
116
+ * walks the brand ink AWAY FROM A LIGHT SURFACE and lands it dark on a dark page — the same
117
+ * defect gh#887 found for glass, in the package's own theme, seed-independent because the
118
+ * surface is wrong rather than the seed.
119
+ *
120
+ * The light slot stays `null` because there the default really is right: measured, the darkest
121
+ * ground under brand ink is #DFE2E5 against the constant's #EBE9E5, and light `base` reads
122
+ * 562/562 at every seed.
123
+ *
124
+ * #3C3A34, AND THE PAINTED-PIXEL MEASUREMENT PICKED THE WRONG ONE (gh#903). Sampling the
125
+ * actual pixel under each brand-ink element gave #3C3619 and I took it, on the reasoning that a
126
+ * painted pixel beats a token composite. It does — but I sampled every element AT REST, and the
127
+ * ground a hovered MegaMenu trigger sits on is `--accent`, which is #3C3A34 and LIGHTER. For a
128
+ * dark theme the hardest ground is the LIGHTEST one, so the darker sample sent the ink the
129
+ * wrong way by a hair: it walked to L 74.9% instead of 76.4%, and the hovered label measured
130
+ * 4.23:1 on four of five seeds.
131
+ *
132
+ * The method was right and its COVERAGE was not. A ground only exists at rest if nothing ever
133
+ * changes it; an interaction state is a different ground and has to be sampled too. */
134
+ inkSurface: { light: null, dark: "#3C3A34" },
135
+ },
136
+ {
137
+ id: "glass",
138
+ nameKey: "themeLab.theme.glass.name",
139
+ noteKey: "themeLab.theme.glass.note",
140
+ /* THE LIGHT VALUE WAS MEASURED THE WRONG WAY THE FIRST TIME (gh#903). #B59FDB came from
141
+ * compositing the five surface TOKENS; the real ground is the PAINTED PIXEL under each
142
+ * brand-ink element, which includes the gradients a theme paints and the token set does not.
143
+ * Sampled with every glyph hidden, the darkest such ground across the five seeds is navy's
144
+ * #9493CC at L=0.3152 — darker than the #B59FDB (L=0.3973) I had, so the ink was not being
145
+ * walked far enough and 16 strings in light glass sat at 3.41-4.43:1.
146
+ *
147
+ * Measured per branch, and the two are not the same KIND of number (see the type above).
148
+ * `#766A2D` was right and stays on the dark side, where the hardest ground is the LIGHTEST
149
+ * surface; the light side is #B59FDB, the DARKEST surface, because the ink walks the other
150
+ * way there. Carrying one hex into both slots would have been wrong in exactly one of them. */
151
+ inkSurface: { light: "#9493CC", dark: "#766A2D" },
152
+ },
153
+ {
154
+ id: "flat",
155
+ nameKey: "themeLab.theme.flat.name",
156
+ noteKey: "themeLab.theme.flat.note",
157
+ /* Measured per branch now that flat has one. The light side is close enough to the package
158
+ * default that omitting it would very nearly work (#E4E1EA against #EBE9E5, both far above the
159
+ * pivot) — stated anyway, because "near enough to the default" is a fact about today's flat,
160
+ * not a contract it holds anyone to. */
161
+ inkSurface: { light: "#E4E1EA", dark: "#403D30" },
162
+ },
163
+ {
164
+ id: "neubrutalism",
165
+ nameKey: "themeLab.theme.neubrutalism.name",
166
+ noteKey: "themeLab.theme.neubrutalism.note",
167
+ /* Both members are the WORST-CASE ground rather than the average one, and the two are
168
+ * different KINDS of worst case — in light the ink walks dark, so the hardest ground is the
169
+ * DARKEST fill; in dark it walks light, so the hardest is the LIGHTEST. Computed over every
170
+ * fill this theme declares × all five `SEEDS`, with the sRGB relative-luminance formula:
171
+ * light is the violet seed's `--accent` (#D9C5FC, L=0.6171), dark is the citron seed's
172
+ * `--accent` (#615205, L=0.0859). Carrying one hex into both slots is the mistake the pair
173
+ * exists to prevent (gh#896), and taking the average fill is the mistake gh#903 made.
174
+ *
175
+ * Named even though FINDING D means this theme never leans on seed-derived ink: `tenantTheme`
176
+ * emits these as inline literals on an ancestor, so leaving the slot `null` would walk them
177
+ * against the LIGHT default in both polarities — the gh#887 defect, arriving through a door
178
+ * this theme does not otherwise use. */
179
+ inkSurface: { light: "#D9C5FC", dark: "#615205" },
180
+ },
181
+ ];
182
+
183
+ /**
184
+ * Five seeds, chosen to break a theme rather than to flatter it: one cool default, one cool blue,
185
+ * one warm, one at the very light end (`#FFD400`, L≈83% — a white label on it fails AA, so
186
+ * `tenantTheme` picks black) and one at the very dark end (`#0A1F44`, L≈15% — the opposite).
187
+ * A theme tuned to a mid-luminance violet fails at both ends, which is the point of including them.
188
+ */
189
+ export const SEEDS: readonly SeedRow[] = [
190
+ { id: "violet", hex: "#7C3AED", nameKey: "themeLab.seed.violet" },
191
+ { id: "azure", hex: "#2563EB", nameKey: "themeLab.seed.azure" },
192
+ { id: "coral", hex: "#E2564A", nameKey: "themeLab.seed.coral" },
193
+ { id: "citron", hex: "#FFD400", nameKey: "themeLab.seed.citron" },
194
+ { id: "navy", hex: "#0A1F44", nameKey: "themeLab.seed.navy" },
195
+ ];
196
+
197
+ /** `light` leads for the same reason `base` leads `THEMES`: the first option is the control. */
198
+ export const MODES: readonly ModeRow[] = [
199
+ { id: "light", nameKey: "themeLab.mode.light" },
200
+ { id: "dark", nameKey: "themeLab.mode.dark" },
201
+ ];
202
+
203
+ /** The `?theme=` value for a row — `"base"` stands in for the null id in the URL. */
204
+ export const themeQueryValue = (row: ThemeRow): string => row.id ?? "base";
205
+
206
+ /** Resolve `?theme=` back to a row, falling back to the first one. */
207
+ export function themeFromQuery(value: string | null): ThemeRow {
208
+ return THEMES.find((row) => themeQueryValue(row) === value) ?? THEMES[0];
209
+ }
210
+
211
+ /** Resolve `?seed=` back to a row, falling back to the first one. */
212
+ export function seedFromQuery(value: string | null): SeedRow {
213
+ return SEEDS.find((row) => row.id === value) ?? SEEDS[0];
214
+ }
215
+
216
+ /** Resolve `?mode=` back to a row, falling back to the first one (light). */
217
+ export function modeFromQuery(value: string | null): ModeRow {
218
+ return MODES.find((row) => row.id === value) ?? MODES[0];
219
+ }