@adea-ai/ui 0.102.0 → 0.103.1

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 (56) hide show
  1. package/README.md +27 -11
  2. package/dist/components/composites/appearance-editor/appearance-choices.d.ts +7 -0
  3. package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
  4. package/dist/components/composites/appearance-editor/appearance-choices.js +44 -3
  5. package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
  6. package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
  7. package/dist/components/composites/appearance-editor/appearance-editor.js +7 -3
  8. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  9. package/dist/components/composites/appearance-editor/appearance-types.d.ts +8 -0
  10. package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
  11. package/dist/components/composites/update-dialog/update-dialog.d.ts +15 -2
  12. package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
  13. package/dist/components/composites/update-dialog/update-dialog.js +80 -16
  14. package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
  15. package/dist/components/theme/appearance-panel.d.ts +2 -1
  16. package/dist/components/theme/appearance-panel.d.ts.map +1 -1
  17. package/dist/components/theme/appearance-panel.js +62 -22
  18. package/dist/components/theme/appearance-panel.js.map +1 -1
  19. package/dist/components/theme/theme-provider.d.ts +13 -2
  20. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  21. package/dist/components/theme/theme-provider.js +9 -5
  22. package/dist/components/theme/theme-provider.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -2
  26. package/dist/lib/theme-accents.d.ts +9 -0
  27. package/dist/lib/theme-accents.d.ts.map +1 -0
  28. package/dist/lib/theme-accents.js +41 -0
  29. package/dist/lib/theme-accents.js.map +1 -0
  30. package/dist/lib/themes.d.ts +69 -5
  31. package/dist/lib/themes.d.ts.map +1 -1
  32. package/dist/lib/themes.js +100 -7
  33. package/dist/lib/themes.js.map +1 -1
  34. package/dist/r/appearance-editor.json +1 -1
  35. package/dist/r/lib.json +6 -1
  36. package/dist/r/registry.json +8 -2
  37. package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +41 -4
  38. package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +12 -6
  39. package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +8 -0
  40. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +90 -10
  41. package/dist/r/src/components/theme/appearance-panel.tsx +60 -29
  42. package/dist/r/src/components/theme/theme-provider.tsx +35 -7
  43. package/dist/r/src/lib/theme-accents.ts +44 -0
  44. package/dist/r/src/lib/themes.ts +116 -5
  45. package/dist/r/theming.json +1 -0
  46. package/package.json +2 -2
  47. package/registry.json +8 -2
  48. package/src/components/composites/appearance-editor/appearance-choices.tsx +41 -4
  49. package/src/components/composites/appearance-editor/appearance-editor.tsx +12 -6
  50. package/src/components/composites/appearance-editor/appearance-types.ts +8 -0
  51. package/src/components/composites/update-dialog/update-dialog.tsx +90 -10
  52. package/src/components/theme/appearance-panel.tsx +60 -29
  53. package/src/components/theme/theme-provider.tsx +35 -7
  54. package/src/index.ts +7 -0
  55. package/src/lib/theme-accents.ts +44 -0
  56. package/src/lib/themes.ts +116 -5
package/src/lib/themes.ts CHANGED
@@ -35,9 +35,11 @@
35
35
 
36
36
  import {
37
37
  ACCENTS,
38
+ accentForeground,
38
39
  accentRoles,
39
40
  chartSeries,
40
41
  getAccent,
42
+ getTheme,
41
43
  hasTheme,
42
44
  shadcnDestructiveProjection,
43
45
  statusForeground,
@@ -47,10 +49,16 @@ import {
47
49
  syntaxRoles,
48
50
  themes as catalogue,
49
51
  themesByAppearance,
52
+ themeAccentPresets,
50
53
  validateCatalogue,
54
+ type AccentPreset,
55
+ type AdeaTheme,
51
56
  type AdeaThemeRecord,
52
57
  type ContrastFinding,
53
58
  } from '@adea-ai/themes'
59
+ import { isThemeAccentId } from './theme-accents'
60
+
61
+ export { isThemeAccentId } from './theme-accents'
54
62
 
55
63
  export type ThemeAppearance = 'light' | 'dark'
56
64
 
@@ -320,6 +328,100 @@ export function themeFamilies(): { family: string; label: string; themes: ThemeV
320
328
  /** The accent presets, in the order a picker should show them. */
321
329
  export const accents = ACCENTS
322
330
 
331
+ /**
332
+ * The canonical catalogue records, in catalogue order.
333
+ *
334
+ * {@link builtinThemes} is this system's projection of the catalogue onto the
335
+ * shadcn vocabulary, which is what a document is painted with. A *preview* of a
336
+ * theme — the miniatures `AppearanceEditor` and `ThemeMiniature` draw — reads the
337
+ * catalogue's own roles instead (`surfaceActive`, `borderMuted`, `textSubtle`…),
338
+ * several of which the projection folds together. These are those records, so a
339
+ * consumer hands the editor exactly what it takes rather than reconstructing it
340
+ * from the projection.
341
+ */
342
+ export const themeRecords: readonly AdeaThemeRecord[] = catalogue
343
+
344
+ export function themeRecordById(id: string): AdeaThemeRecord | undefined {
345
+ return getTheme(id)
346
+ }
347
+
348
+ /** A light/dark theme pair by id, with the defaults standing in for an unknown id. */
349
+ function recordPair(lightThemeId: string, darkThemeId: string) {
350
+ const light = getTheme(lightThemeId) ?? getTheme(defaultLightThemeId)!
351
+ const dark = getTheme(darkThemeId) ?? getTheme(defaultDarkThemeId)!
352
+ return { light, dark }
353
+ }
354
+
355
+ /**
356
+ * The accents the selected light/dark pair carries — offered beside the presets.
357
+ *
358
+ * Computed per pair rather than per theme because a theme accent is a *pair* of
359
+ * values, like a preset: a slot is offered only when both themes can offer it.
360
+ */
361
+ export function themeAccentsFor(
362
+ lightThemeId: string,
363
+ darkThemeId: string
364
+ ): readonly AccentPreset[] {
365
+ const { light, dark } = recordPair(lightThemeId, darkThemeId)
366
+ return themeAccentPresets(light, dark)
367
+ }
368
+
369
+ /**
370
+ * An accent id resolved against a theme pair: a preset, a theme accent the pair
371
+ * offers, or `undefined` for `theme`, an unknown id, or a slot this pair cannot
372
+ * offer — each of which means "use the theme's own primary".
373
+ *
374
+ * Theme accent ids are role-shaped (`ansi-blue`), so a stored one survives a theme
375
+ * switch and resolves to the new theme's blue; when the new pair cannot offer the
376
+ * slot it resolves to nothing, the same fallback a stale preset id gets.
377
+ */
378
+ export function resolveAccentPreset(
379
+ accentId: string,
380
+ light: AdeaTheme,
381
+ dark: AdeaTheme
382
+ ): AccentPreset | undefined {
383
+ if (accentId === 'theme') return undefined
384
+ const preset = getAccent(accentId)
385
+ if (preset) return preset
386
+ if (!isThemeAccentId(accentId)) return undefined
387
+ return themeAccentPresets(light, dark).find((option) => option.id === accentId)
388
+ }
389
+
390
+ /**
391
+ * A theme with an accent laid over it, in the catalogue's own shape.
392
+ *
393
+ * The accent replaces `accent` and its measured label, which is all an accent
394
+ * changes about a theme; `undefined` returns the theme untouched. This is the
395
+ * overlay a host needs for `AppearanceEditor`'s `lightTheme`/`darkTheme`.
396
+ */
397
+ export function withAccent<T extends AdeaTheme>(theme: T, accent: AccentPreset | undefined): T {
398
+ if (!accent) return theme
399
+ const value = theme.appearance === 'dark' ? accent.dark : accent.light
400
+ return {
401
+ ...theme,
402
+ colors: { ...theme.colors, accent: value, accentForeground: accentForeground(value) },
403
+ }
404
+ }
405
+
406
+ /**
407
+ * The two preview themes an appearance selection resolves to, accent applied.
408
+ *
409
+ * The adapter between a stored selection and `AppearanceEditor`: it takes ids —
410
+ * what a preference holds — and returns the canonical records the editor's
411
+ * `lightTheme` and `darkTheme` props take, so a host does not map roles by hand.
412
+ * Unknown ids fall back to the defaults and an accent the pair cannot offer falls
413
+ * back to the theme's own, exactly as `ThemeProvider` resolves them.
414
+ */
415
+ export function appearancePreviewThemes(selection: {
416
+ lightThemeId: string
417
+ darkThemeId: string
418
+ accent: string
419
+ }): { lightTheme: AdeaThemeRecord; darkTheme: AdeaThemeRecord } {
420
+ const { light, dark } = recordPair(selection.lightThemeId, selection.darkThemeId)
421
+ const accent = resolveAccentPreset(selection.accent, light, dark)
422
+ return { lightTheme: withAccent(light, accent), darkTheme: withAccent(dark, accent) }
423
+ }
424
+
323
425
  /**
324
426
  * The properties an accent selection overrides, as CSS custom properties.
325
427
  *
@@ -339,16 +441,25 @@ export const accents = ACCENTS
339
441
  * provider and the CSS generator call `accentRoles` out of `@adea-ai/themes`, and
340
442
  * `tests/appearance-axes.test.ts` asserts the two agree value for value.
341
443
  *
342
- * Returns `undefined` for `theme` — the variant's own primary — and for an id the
343
- * catalogue does not have, so a stale stored preference falls back to the theme
344
- * rather than blanking the primary.
444
+ * A theme accent (`ansi-blue`) has no block: its value depends on the theme pair,
445
+ * so it is resolved against `pair` — the selection's light and dark theme ids —
446
+ * and only ever applied here. Without a pair it resolves against the defaults.
447
+ *
448
+ * Returns `undefined` for `theme` — the variant's own primary — for an id the
449
+ * catalogue does not have, and for a theme accent the pair does not offer, so a
450
+ * stale stored preference falls back to the theme rather than blanking the primary.
345
451
  */
346
452
  export function accentVariables(
347
453
  accentId: string,
348
- appearance: ThemeAppearance
454
+ appearance: ThemeAppearance,
455
+ pair: { lightThemeId: string; darkThemeId: string } = {
456
+ lightThemeId: defaultLightThemeId,
457
+ darkThemeId: defaultDarkThemeId,
458
+ }
349
459
  ): Record<string, string> | undefined {
350
460
  if (accentId === 'theme') return undefined
351
- const preset = getAccent(accentId)
461
+ const { light, dark } = recordPair(pair.lightThemeId, pair.darkThemeId)
462
+ const preset = resolveAccentPreset(accentId, light, dark)
352
463
  if (!preset) return undefined
353
464
 
354
465
  const roles = accentRoles(preset, appearance)