@dxos/ui-theme 0.8.4-staging.ac66bdf99f → 0.9.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 (169) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/index.mjs +89 -867
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/node-esm/index.mjs +89 -867
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/plugin/node-cjs/main.css +252 -119
  10. package/dist/plugin/node-cjs/main.css.map +3 -3
  11. package/dist/plugin/node-cjs/meta.json +1 -1
  12. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs +82 -10
  13. package/dist/plugin/node-cjs/plugins/ThemePlugin.cjs.map +3 -3
  14. package/dist/plugin/node-esm/main.css +252 -119
  15. package/dist/plugin/node-esm/main.css.map +3 -3
  16. package/dist/plugin/node-esm/meta.json +1 -1
  17. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs +84 -12
  18. package/dist/plugin/node-esm/plugins/ThemePlugin.mjs.map +3 -3
  19. package/dist/types/src/Theme.stories.d.ts.map +1 -1
  20. package/dist/types/src/defs.d.ts +1 -1
  21. package/dist/types/src/defs.d.ts.map +1 -1
  22. package/dist/types/src/fragments/density.d.ts +2 -2
  23. package/dist/types/src/fragments/density.d.ts.map +1 -1
  24. package/dist/types/src/fragments/hover.d.ts +0 -1
  25. package/dist/types/src/fragments/hover.d.ts.map +1 -1
  26. package/dist/types/src/index.d.ts +0 -1
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/src/plugins/ThemePlugin.d.ts +3 -2
  29. package/dist/types/src/plugins/ThemePlugin.d.ts.map +1 -1
  30. package/dist/types/src/util/hash-styles.d.ts +12 -2
  31. package/dist/types/src/util/hash-styles.d.ts.map +1 -1
  32. package/dist/types/src/util/mx.d.ts +0 -36
  33. package/dist/types/src/util/mx.d.ts.map +1 -1
  34. package/dist/types/src/util/size.d.ts.map +1 -1
  35. package/dist/types/src/util/valence.d.ts +8 -2
  36. package/dist/types/src/util/valence.d.ts.map +1 -1
  37. package/dist/types/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +14 -21
  39. package/src/Theme.stories.tsx +94 -27
  40. package/src/css/DESIGN_SYSTEM.md +187 -0
  41. package/src/css/base/base.css +2 -2
  42. package/src/css/components/button.css +63 -13
  43. package/src/css/components/card.css +14 -0
  44. package/src/css/components/checkbox.css +3 -3
  45. package/src/css/components/focus.css +12 -12
  46. package/src/css/components/link.css +4 -1
  47. package/src/css/components/panel.css +45 -45
  48. package/src/css/components/state.css +99 -0
  49. package/src/css/components/state.md +101 -0
  50. package/src/css/components/surface.css +33 -11
  51. package/src/css/components/tag.css +25 -24
  52. package/src/css/integrations/codemirror.css +4 -3
  53. package/src/css/integrations/tldraw.css +1 -1
  54. package/src/css/layout/main.css +6 -0
  55. package/src/css/layout/size.css +16 -3
  56. package/src/css/theme/animation.css +31 -0
  57. package/src/css/theme/palette.css +34 -0
  58. package/src/css/theme/semantic.css +101 -60
  59. package/src/css/theme/spacing.css +29 -12
  60. package/src/css/theme/styles.css +172 -119
  61. package/src/css/utilities.css +42 -0
  62. package/src/defs.ts +3 -1
  63. package/src/fragments/AUDIT.md +0 -2
  64. package/src/fragments/density.ts +34 -7
  65. package/src/fragments/hover.ts +0 -2
  66. package/src/index.ts +1 -1
  67. package/src/main.css +68 -9
  68. package/src/plugins/ThemePlugin.ts +102 -14
  69. package/src/plugins/main.css +10 -7
  70. package/src/util/hash-styles.ts +54 -42
  71. package/src/util/mx.ts +1 -126
  72. package/src/util/valence.ts +15 -5
  73. package/dist/types/src/theme/components/avatar.d.ts +0 -21
  74. package/dist/types/src/theme/components/avatar.d.ts.map +0 -1
  75. package/dist/types/src/theme/components/breadcrumb.d.ts +0 -9
  76. package/dist/types/src/theme/components/breadcrumb.d.ts.map +0 -1
  77. package/dist/types/src/theme/components/button.d.ts +0 -15
  78. package/dist/types/src/theme/components/button.d.ts.map +0 -1
  79. package/dist/types/src/theme/components/card.d.ts +0 -12
  80. package/dist/types/src/theme/components/card.d.ts.map +0 -1
  81. package/dist/types/src/theme/components/dialog.d.ts +0 -17
  82. package/dist/types/src/theme/components/dialog.d.ts.map +0 -1
  83. package/dist/types/src/theme/components/focus.d.ts +0 -6
  84. package/dist/types/src/theme/components/focus.d.ts.map +0 -1
  85. package/dist/types/src/theme/components/icon-button.d.ts +0 -8
  86. package/dist/types/src/theme/components/icon-button.d.ts.map +0 -1
  87. package/dist/types/src/theme/components/icon.d.ts +0 -10
  88. package/dist/types/src/theme/components/icon.d.ts.map +0 -1
  89. package/dist/types/src/theme/components/index.d.ts +0 -27
  90. package/dist/types/src/theme/components/index.d.ts.map +0 -1
  91. package/dist/types/src/theme/components/input.d.ts +0 -115
  92. package/dist/types/src/theme/components/input.d.ts.map +0 -1
  93. package/dist/types/src/theme/components/link.d.ts +0 -7
  94. package/dist/types/src/theme/components/link.d.ts.map +0 -1
  95. package/dist/types/src/theme/components/list.d.ts +0 -14
  96. package/dist/types/src/theme/components/list.d.ts.map +0 -1
  97. package/dist/types/src/theme/components/main.d.ts +0 -28
  98. package/dist/types/src/theme/components/main.d.ts.map +0 -1
  99. package/dist/types/src/theme/components/menu.d.ts +0 -13
  100. package/dist/types/src/theme/components/menu.d.ts.map +0 -1
  101. package/dist/types/src/theme/components/message.d.ts +0 -12
  102. package/dist/types/src/theme/components/message.d.ts.map +0 -1
  103. package/dist/types/src/theme/components/popover.d.ts +0 -11
  104. package/dist/types/src/theme/components/popover.d.ts.map +0 -1
  105. package/dist/types/src/theme/components/scroll-area.d.ts +0 -32
  106. package/dist/types/src/theme/components/scroll-area.d.ts.map +0 -1
  107. package/dist/types/src/theme/components/select.d.ts +0 -13
  108. package/dist/types/src/theme/components/select.d.ts.map +0 -1
  109. package/dist/types/src/theme/components/separator.d.ts +0 -8
  110. package/dist/types/src/theme/components/separator.d.ts.map +0 -1
  111. package/dist/types/src/theme/components/skeleton.d.ts +0 -7
  112. package/dist/types/src/theme/components/skeleton.d.ts.map +0 -1
  113. package/dist/types/src/theme/components/splitter.d.ts +0 -4
  114. package/dist/types/src/theme/components/splitter.d.ts.map +0 -1
  115. package/dist/types/src/theme/components/status.d.ts +0 -9
  116. package/dist/types/src/theme/components/status.d.ts.map +0 -1
  117. package/dist/types/src/theme/components/tag.d.ts +0 -7
  118. package/dist/types/src/theme/components/tag.d.ts.map +0 -1
  119. package/dist/types/src/theme/components/toast.d.ts +0 -12
  120. package/dist/types/src/theme/components/toast.d.ts.map +0 -1
  121. package/dist/types/src/theme/components/toolbar.d.ts +0 -11
  122. package/dist/types/src/theme/components/toolbar.d.ts.map +0 -1
  123. package/dist/types/src/theme/components/tooltip.d.ts +0 -8
  124. package/dist/types/src/theme/components/tooltip.d.ts.map +0 -1
  125. package/dist/types/src/theme/components/treegrid.d.ts +0 -10
  126. package/dist/types/src/theme/components/treegrid.d.ts.map +0 -1
  127. package/dist/types/src/theme/index.d.ts +0 -4
  128. package/dist/types/src/theme/index.d.ts.map +0 -1
  129. package/dist/types/src/theme/primitives/column.d.ts +0 -29
  130. package/dist/types/src/theme/primitives/column.d.ts.map +0 -1
  131. package/dist/types/src/theme/primitives/index.d.ts +0 -3
  132. package/dist/types/src/theme/primitives/index.d.ts.map +0 -1
  133. package/dist/types/src/theme/primitives/panel.d.ts +0 -13
  134. package/dist/types/src/theme/primitives/panel.d.ts.map +0 -1
  135. package/dist/types/src/theme/theme.d.ts +0 -5
  136. package/dist/types/src/theme/theme.d.ts.map +0 -1
  137. package/src/css/components/selected.css +0 -30
  138. package/src/theme/components/avatar.ts +0 -95
  139. package/src/theme/components/breadcrumb.ts +0 -29
  140. package/src/theme/components/button.ts +0 -48
  141. package/src/theme/components/card.ts +0 -102
  142. package/src/theme/components/dialog.ts +0 -61
  143. package/src/theme/components/focus.ts +0 -33
  144. package/src/theme/components/icon-button.ts +0 -18
  145. package/src/theme/components/icon.ts +0 -28
  146. package/src/theme/components/index.ts +0 -30
  147. package/src/theme/components/input.ts +0 -171
  148. package/src/theme/components/link.ts +0 -25
  149. package/src/theme/components/list.ts +0 -46
  150. package/src/theme/components/main.ts +0 -34
  151. package/src/theme/components/menu.ts +0 -50
  152. package/src/theme/components/message.ts +0 -40
  153. package/src/theme/components/popover.ts +0 -41
  154. package/src/theme/components/scroll-area.ts +0 -115
  155. package/src/theme/components/select.ts +0 -52
  156. package/src/theme/components/separator.ts +0 -24
  157. package/src/theme/components/skeleton.ts +0 -23
  158. package/src/theme/components/splitter.ts +0 -20
  159. package/src/theme/components/status.ts +0 -32
  160. package/src/theme/components/tag.ts +0 -23
  161. package/src/theme/components/toast.ts +0 -53
  162. package/src/theme/components/toolbar.ts +0 -35
  163. package/src/theme/components/tooltip.ts +0 -27
  164. package/src/theme/components/treegrid.ts +0 -37
  165. package/src/theme/index.ts +0 -7
  166. package/src/theme/primitives/column.ts +0 -71
  167. package/src/theme/primitives/index.ts +0 -6
  168. package/src/theme/primitives/panel.ts +0 -43
  169. package/src/theme/theme.ts +0 -87
@@ -4,10 +4,11 @@
4
4
 
5
5
  /* eslint-disable no-console */
6
6
 
7
- import tailwindcss from '@tailwindcss/postcss';
7
+ import tailwindcssPostcss from '@tailwindcss/postcss';
8
+ import tailwindcssVite from '@tailwindcss/vite';
8
9
  import autoprefixer from 'autoprefixer';
9
10
  import { existsSync, readFileSync } from 'node:fs';
10
- import { dirname, resolve } from 'node:path';
11
+ import { resolve } from 'node:path';
11
12
  import postcssImport from 'postcss-import';
12
13
  import postcssNesting from 'postcss-nesting';
13
14
  import { type HtmlTagDescriptor, type Plugin, type UserConfig } from 'vite';
@@ -40,8 +41,9 @@ export type ThemePluginOptions = {
40
41
 
41
42
  /**
42
43
  * Vite plugin to configure theme.
44
+ * Returns the official Tailwind Vite plugin (persistent incremental scanner) alongside the theme plugin.
43
45
  */
44
- export const ThemePlugin = (options: ThemePluginOptions): Plugin => {
46
+ export const ThemePlugin = (options: ThemePluginOptions): Plugin[] => {
45
47
  // Prefer source CSS if available (monorepo dev), fall back to dist for installed package.
46
48
  const srcThemePath = resolve(import.meta.dirname, ROOT, 'src/main.css');
47
49
  const distThemePath = resolve(import.meta.dirname, '../main.css');
@@ -53,23 +55,65 @@ export const ThemePlugin = (options: ThemePluginOptions): Plugin => {
53
55
  const darkModeScriptPath = resolve(pluginsDir, 'dark-mode.ts');
54
56
  const mainCssPath = resolve(pluginsDir, 'main.css');
55
57
 
56
- const config: ThemePluginOptions = {
57
- srcCssPath: isMonorepo ? srcThemePath : distThemePath,
58
- virtualFileId: '@dxos-theme',
59
- ...options,
58
+ const config = {
59
+ srcCssPath: options.srcCssPath ?? (isMonorepo ? srcThemePath : distThemePath),
60
+ virtualFileId: options.virtualFileId ?? '@dxos-theme',
61
+ verbose: options.verbose,
60
62
  };
61
63
 
62
- // Derive project root from the source location so Tailwind scans all packages.
63
- const base = isMonorepo ? resolve(dirname(srcThemePath), ROOT) : undefined;
64
-
65
64
  if (process.env.DEBUG || options.verbose) {
66
65
  console.log('ThemePlugin:\n', JSON.stringify(config, null, 2));
67
66
  }
68
67
 
69
- return {
68
+ // Trailing-edge debounce handle for theme CSS reloads (see `handleHotUpdate`).
69
+ let themeReloadTimer: ReturnType<typeof setTimeout> | undefined;
70
+
71
+ const themePlugin: Plugin = {
70
72
  name: 'vite-plugin-dxos-ui-theme',
71
73
  config: (): UserConfig => {
72
74
  return {
75
+ server: {
76
+ watch: {
77
+ // Stop build outputs from driving HMR — they are the root of the
78
+ // `main.css` HMR storm.
79
+ //
80
+ // Tailwind's `@source` scanning (see `src/main.css`) registers its
81
+ // scanned source files as Vite watch dependencies of the compiled
82
+ // theme CSS. Tailwind's own scanner respects `.gitignore` (and the
83
+ // `@source not` directives), so it never *scans* `dist/`. BUT the
84
+ // scanner hands Vite a coarse `dir-dependency` glob — e.g.
85
+ // `{**/*.html,**/*.ts,**/*.tsx}` — and Vite re-expands that glob
86
+ // itself, ignoring only `node_modules` (not `.gitignore`, not the
87
+ // `@source not` negations). The re-expansion therefore sweeps in
88
+ // every `packages/*/dist/**/*.d.ts` (`.d.ts` matches `**/*.ts`),
89
+ // making each emitted declaration file a watch-dependency of
90
+ // `main.css`. A single package rebuild emits dozens of `.d.ts` in a
91
+ // tight burst, and each write re-invalidates the theme — 40+ HMR
92
+ // pings for `main.css` in one second, repeating on every rebuild.
93
+ //
94
+ // Ignoring build outputs in the watcher is also semantically
95
+ // correct: in dev the workspace resolves `@dxos/*` via the `source`
96
+ // export condition (see `vite-plugin-import-source`), so `dist/`
97
+ // is never consumed at runtime and its churn should never trigger
98
+ // HMR. Vite concatenates these patterns with its built-in ignores
99
+ // (`**/node_modules/**`, `**/.git/**`, …), so this is purely
100
+ // additive.
101
+ //
102
+ // `<root>/.claude/**` covers agent worktrees checked out under the
103
+ // repo root (`.claude/worktrees/<name>/packages/**`): they are full
104
+ // source copies, so the glob re-expansion above sweeps them in and
105
+ // every agent-side edit burst or checkout invalidates the theme in
106
+ // the user's dev server. The pattern is anchored at the resolved
107
+ // repo root (not `**/.claude/**`) because chokidar matches against
108
+ // absolute paths — a bare pattern would match *everything* when the
109
+ // dev server itself runs from inside a worktree whose path contains
110
+ // a `.claude` segment. `*.log` covers runtime log sinks (e.g.
111
+ // vite-plugin-log's `app.log` in the app root), which are appended
112
+ // continuously at runtime and must never feed back into the
113
+ // watcher.
114
+ ignored: ['**/dist/**', '**/out/**', '**/*.log', `${resolve(import.meta.dirname, ROOT, '.claude')}/**`],
115
+ },
116
+ },
73
117
  css: {
74
118
  postcss: {
75
119
  plugins: [
@@ -77,9 +121,11 @@ export const ThemePlugin = (options: ThemePluginOptions): Plugin => {
77
121
  postcssImport(),
78
122
  // Processes CSS nesting syntax.
79
123
  postcssNesting(),
80
- // Processes Tailwind directives and generates utilities from scanned content.
81
- // base points to project root so all packages are scanned (not just ui-theme).
82
- tailwindcss(base !== undefined ? { base } : {}),
124
+ // Resolves @reference/@apply in `.pcss` files (e.g. lit-grid, lit-ui), which the
125
+ // @tailwindcss/vite plugin skips its transform filter only matches `.css`.
126
+ // Theme `.css` files are compiled by @tailwindcss/vite first (enforce: 'pre'),
127
+ // so this plugin's quick-bail check passes them through untouched.
128
+ tailwindcssPostcss(),
83
129
  // Adds vendor prefixes.
84
130
  autoprefixer,
85
131
  ],
@@ -92,6 +138,42 @@ export const ThemePlugin = (options: ThemePluginOptions): Plugin => {
92
138
  return config.srcCssPath;
93
139
  }
94
140
  },
141
+ hotUpdate({ type, file, modules }) {
142
+ // Direct edits to CSS (the theme source or its imports) keep Vite's
143
+ // default immediate update for instant feedback while authoring styles.
144
+ if (this.environment.name !== 'client' || type !== 'update' || file.endsWith('.css')) {
145
+ return;
146
+ }
147
+
148
+ // Every content file Tailwind scans is registered as a dependency of the
149
+ // theme CSS — Vite models it as a file-only entry node whose importer is
150
+ // `main.css` — so each source-file save invalidates `main.css` and
151
+ // re-runs the monorepo-wide Tailwind scan. During an edit wave that
152
+ // serializes one full scan per save. Drop the theme-dep entries from the
153
+ // update (the changed module itself still hot-updates immediately) and
154
+ // reload the theme CSS once on the trailing edge of a quiet window, so a
155
+ // wave costs at most one scan.
156
+ const isThemeDep = (mod: (typeof modules)[number]): boolean =>
157
+ mod.file === config.srcCssPath ||
158
+ (mod.id === null &&
159
+ mod.importers.size > 0 &&
160
+ [...mod.importers].every((importer) => importer.file === config.srcCssPath));
161
+ if (!modules.some(isThemeDep)) {
162
+ return;
163
+ }
164
+
165
+ const environment = this.environment;
166
+ clearTimeout(themeReloadTimer);
167
+ themeReloadTimer = setTimeout(() => {
168
+ for (const mod of environment.moduleGraph.getModulesByFile(config.srcCssPath) ?? []) {
169
+ environment.reloadModule(mod).catch(() => {
170
+ // Server may be mid-restart; the next edit reschedules the reload.
171
+ });
172
+ }
173
+ }, 300);
174
+
175
+ return modules.filter((mod) => !isThemeDep(mod));
176
+ },
95
177
  transformIndexHtml: () => {
96
178
  // Apply .dark class to <html> synchronously before any scripts run, so that
97
179
  // the critical CSS html.dark rules apply on the very first paint.
@@ -122,4 +204,10 @@ export const ThemePlugin = (options: ThemePluginOptions): Plugin => {
122
204
  return [darkModeTag, layersTag, criticalTag];
123
205
  },
124
206
  };
207
+
208
+ // The Tailwind Vite plugins are `enforce: 'pre'`, so they compile theme CSS (resolving
209
+ // `@import 'tailwindcss'`, @source, @plugin, @theme) before the postcss chain runs —
210
+ // postcss-import never sees the raw Tailwind directives. Scan roots come from the @source
211
+ // directives in main.css (relative to that file); no project-root base is needed.
212
+ return [...tailwindcssVite(), themePlugin];
125
213
  };
@@ -24,22 +24,25 @@ body {
24
24
  }
25
25
 
26
26
  /* Pre-CSS color fallbacks so text is readable before and during Vite HMR. */
27
- /* Values are intentionally kept in sync with @theme tokens in semantic.css. */
27
+ /* Values are intentionally kept in sync with @theme tokens in semantic.css:
28
+ * color ← --color-base-fg = light-dark(neutral-950, neutral-150)
29
+ * background-color ← --color-base-surface = elevation-3 = light-dark(neutral-125, neutral-850)
30
+ * The literals carry the ramp tint (chroma 0.001, hue 190) so first paint matches the loaded theme. */
28
31
  :root {
29
32
  color-scheme: light;
30
33
  }
31
34
  html {
32
- color: oklch(0.145 0 0);
33
- background-color: oklch(0.985 0 0);
35
+ color: oklch(0.145 0.001 190);
36
+ background-color: oklch(0.92 0.001 190);
34
37
  }
35
38
  html.dark {
36
39
  color-scheme: dark;
37
- color: oklch(0.985 0 0);
38
- background-color: oklch(0.145 0 0);
40
+ color: oklch(0.905 0.001 190);
41
+ background-color: oklch(0.237 0.001 190);
39
42
  }
40
43
  @media (prefers-color-scheme: dark) {
41
44
  html:not(.dark) {
42
- color: oklch(0.985 0 0);
43
- background-color: oklch(0.145 0 0);
45
+ color: oklch(0.905 0.001 190);
46
+ background-color: oklch(0.237 0.001 190);
44
47
  }
45
48
  }
@@ -6,21 +6,23 @@ import { type ChromaticPalette } from '@dxos/ui-types';
6
6
 
7
7
  export type Hue = ChromaticPalette | 'neutral';
8
8
 
9
- // TODO(burdon): Reconcile with ui-theme/theme/roles.css
9
+ /**
10
+ * See theme.css
11
+ */
10
12
  export type ColorStyles = {
11
13
  hue: Hue;
12
- fill: string; // -fill
13
- surface: string; // -surface
14
- surfaceText: string; // -surface-text
15
- text: string; // -text
16
- border: string; // -border
14
+ bg: string;
15
+ surface: string;
16
+ fg: string;
17
+ text: string;
18
+ border: string;
17
19
  };
18
20
 
19
21
  const neutral: ColorStyles = {
20
22
  hue: 'neutral',
21
- fill: 'bg-neutral-fill',
23
+ bg: 'bg-neutral-bg',
22
24
  surface: 'bg-neutral-surface',
23
- surfaceText: 'text-neutral-surface-text',
25
+ fg: 'text-neutral-fg',
24
26
  text: 'text-neutral-text',
25
27
  border: 'border-neutral-border',
26
28
  };
@@ -30,137 +32,137 @@ const neutral: ColorStyles = {
30
32
  const styles: ColorStyles[] = [
31
33
  {
32
34
  hue: 'red',
33
- fill: 'bg-red-fill',
35
+ bg: 'bg-red-bg',
34
36
  surface: 'bg-red-surface',
35
- surfaceText: 'text-red-surface-text',
37
+ fg: 'text-red-fg',
36
38
  text: 'text-red-text',
37
39
  border: 'border-red-border',
38
40
  },
39
41
  {
40
42
  hue: 'orange',
41
- fill: 'bg-orange-fill',
43
+ bg: 'bg-orange-bg',
42
44
  surface: 'bg-orange-surface',
43
- surfaceText: 'text-orange-surface-text',
45
+ fg: 'text-orange-fg',
44
46
  text: 'text-orange-text',
45
47
  border: 'border-orange-border',
46
48
  },
47
49
  {
48
50
  hue: 'amber',
49
- fill: 'bg-amber-fill',
51
+ bg: 'bg-amber-bg',
50
52
  surface: 'bg-amber-surface',
51
- surfaceText: 'text-amber-surface-text',
53
+ fg: 'text-amber-fg',
52
54
  text: 'text-amber-text',
53
55
  border: 'border-amber-border',
54
56
  },
55
57
  {
56
58
  hue: 'yellow',
57
- fill: 'bg-yellow-fill',
59
+ bg: 'bg-yellow-bg',
58
60
  surface: 'bg-yellow-surface',
59
- surfaceText: 'text-yellow-surface-text',
61
+ fg: 'text-yellow-fg',
60
62
  text: 'text-yellow-text',
61
63
  border: 'border-yellow-border',
62
64
  },
63
65
  {
64
66
  hue: 'lime',
65
- fill: 'bg-lime-fill',
67
+ bg: 'bg-lime-bg',
66
68
  surface: 'bg-lime-surface',
67
- surfaceText: 'text-lime-surface-text',
69
+ fg: 'text-lime-fg',
68
70
  text: 'text-lime-text',
69
71
  border: 'border-lime-border',
70
72
  },
71
73
  {
72
74
  hue: 'green',
73
- fill: 'bg-green-fill',
75
+ bg: 'bg-green-bg',
74
76
  surface: 'bg-green-surface',
75
- surfaceText: 'text-green-surface-text',
77
+ fg: 'text-green-fg',
76
78
  text: 'text-green-text',
77
79
  border: 'border-green-border',
78
80
  },
79
81
  {
80
82
  hue: 'emerald',
81
- fill: 'bg-emerald-fill',
83
+ bg: 'bg-emerald-bg',
82
84
  surface: 'bg-emerald-surface',
83
- surfaceText: 'text-emerald-surface-text',
85
+ fg: 'text-emerald-fg',
84
86
  text: 'text-emerald-text',
85
87
  border: 'border-emerald-border',
86
88
  },
87
89
  {
88
90
  hue: 'teal',
89
- fill: 'bg-teal-fill',
91
+ bg: 'bg-teal-bg',
90
92
  surface: 'bg-teal-surface',
91
- surfaceText: 'text-teal-surface-text',
93
+ fg: 'text-teal-fg',
92
94
  text: 'text-teal-text',
93
95
  border: 'border-teal-border',
94
96
  },
95
97
  {
96
98
  hue: 'cyan',
97
- fill: 'bg-cyan-fill',
99
+ bg: 'bg-cyan-bg',
98
100
  surface: 'bg-cyan-surface',
99
- surfaceText: 'text-cyan-surface-text',
101
+ fg: 'text-cyan-fg',
100
102
  text: 'text-cyan-text',
101
103
  border: 'border-cyan-border',
102
104
  },
103
105
  {
104
106
  hue: 'sky',
105
- fill: 'bg-sky-fill',
107
+ bg: 'bg-sky-bg',
106
108
  surface: 'bg-sky-surface',
107
- surfaceText: 'text-sky-surface-text',
109
+ fg: 'text-sky-fg',
108
110
  text: 'text-sky-text',
109
111
  border: 'border-sky-border',
110
112
  },
111
113
  {
112
114
  hue: 'blue',
113
- fill: 'bg-blue-fill',
115
+ bg: 'bg-blue-bg',
114
116
  surface: 'bg-blue-surface',
115
- surfaceText: 'text-blue-surface-text',
117
+ fg: 'text-blue-fg',
116
118
  text: 'text-blue-text',
117
119
  border: 'border-blue-border',
118
120
  },
119
121
  {
120
122
  hue: 'indigo',
121
- fill: 'bg-indigo-fill',
123
+ bg: 'bg-indigo-bg',
122
124
  surface: 'bg-indigo-surface',
123
- surfaceText: 'text-indigo-surface-text',
125
+ fg: 'text-indigo-fg',
124
126
  text: 'text-indigo-text',
125
127
  border: 'border-indigo-border',
126
128
  },
127
129
  {
128
130
  hue: 'violet',
129
- fill: 'bg-violet-fill',
131
+ bg: 'bg-violet-bg',
130
132
  surface: 'bg-violet-surface',
131
- surfaceText: 'text-violet-surface-text',
133
+ fg: 'text-violet-fg',
132
134
  text: 'text-violet-text',
133
135
  border: 'border-violet-border',
134
136
  },
135
137
  {
136
138
  hue: 'purple',
137
- fill: 'bg-purple-fill',
139
+ bg: 'bg-purple-bg',
138
140
  surface: 'bg-purple-surface',
139
- surfaceText: 'text-purple-surface-text',
141
+ fg: 'text-purple-fg',
140
142
  text: 'text-purple-text',
141
143
  border: 'border-purple-border',
142
144
  },
143
145
  {
144
146
  hue: 'fuchsia',
145
- fill: 'bg-fuchsia-fill',
147
+ bg: 'bg-fuchsia-bg',
146
148
  surface: 'bg-fuchsia-surface',
147
- surfaceText: 'text-fuchsia-surface-text',
149
+ fg: 'text-fuchsia-fg',
148
150
  text: 'text-fuchsia-text',
149
151
  border: 'border-fuchsia-border',
150
152
  },
151
153
  {
152
154
  hue: 'pink',
153
- fill: 'bg-pink-fill',
155
+ bg: 'bg-pink-bg',
154
156
  surface: 'bg-pink-surface',
155
- surfaceText: 'text-pink-surface-text',
157
+ fg: 'text-pink-fg',
156
158
  text: 'text-pink-text',
157
159
  border: 'border-pink-border',
158
160
  },
159
161
  {
160
162
  hue: 'rose',
161
- fill: 'bg-rose-fill',
163
+ bg: 'bg-rose-bg',
162
164
  surface: 'bg-rose-surface',
163
- surfaceText: 'text-rose-surface-text',
165
+ fg: 'text-rose-fg',
164
166
  text: 'text-rose-text',
165
167
  border: 'border-rose-border',
166
168
  },
@@ -171,6 +173,16 @@ export const palette = {
171
173
  hues: styles,
172
174
  };
173
175
 
176
+ const validHues: ReadonlySet<Hue> = new Set<Hue>([neutral.hue, ...styles.map((s) => s.hue)]);
177
+
178
+ /**
179
+ * Normalise an arbitrary string into a known `Hue`, falling back to `'neutral'` when the
180
+ * input doesn't match one of the catalogued palette entries. Useful when accepting hue
181
+ * values from user-authored data (e.g. ECHO objects, plugin settings) that need to be
182
+ * forwarded to a hue-keyed prop like `Tag`'s `palette`.
183
+ */
184
+ export const toHue = (hue: string | undefined): Hue => (hue && validHues.has(hue as Hue) ? (hue as Hue) : 'neutral');
185
+
174
186
  // TODO(burdon): Rename getClassNames.
175
187
  export const getStyles = (hue: string): ColorStyles => {
176
188
  return styles.find((color) => color.hue === hue) || neutral;
package/src/util/mx.ts CHANGED
@@ -2,23 +2,8 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import {
6
- Children,
7
- createElement,
8
- forwardRef,
9
- type ForwardRefExoticComponent,
10
- type ForwardedRef,
11
- type HTMLAttributes,
12
- isValidElement,
13
- type ReactNode,
14
- type RefAttributes,
15
- CSSProperties,
16
- } from 'react';
17
5
  import { extendTailwindMerge, validators } from 'tailwind-merge';
18
6
 
19
- import { log } from '@dxos/log';
20
- import { ThemedClassName, type ComposableProps, type SlottableProps } from '@dxos/ui-types';
21
-
22
7
  type AdditionalClassGroups = 'density' | 'dx-focus-ring';
23
8
 
24
9
  export const mx = extendTailwindMerge<AdditionalClassGroups>({
@@ -40,7 +25,7 @@ export const mx = extendTailwindMerge<AdditionalClassGroups>({
40
25
  validators.isArbitraryNumber,
41
26
  ],
42
27
 
43
- density: ['dx-density-fine', 'dx-density-coarse'],
28
+ density: ['dx-density-sm', 'dx-density-md', 'dx-density-lg'],
44
29
 
45
30
  'dx-focus-ring': [
46
31
  'dx-focus-ring',
@@ -61,113 +46,3 @@ export const mx = extendTailwindMerge<AdditionalClassGroups>({
61
46
  },
62
47
  },
63
48
  });
64
-
65
- /**
66
- * Reconciles className properties from a parent slot.
67
- * - `className` is set by the Slot merge mechanism.
68
- * - `classNames` is the consumer-facing prop for theming overrides.
69
- * Use `composableProps` to reconcile both into a single `className`.
70
- */
71
- // TODO(burdon): Move to react-ui.
72
- export const composableProps = <P extends HTMLElement = HTMLElement>(
73
- { className, classNames, role, style, ...props }: ComposableProps,
74
- { classNames: defaultClassNames, ...defaults }: ThemedClassName<Partial<HTMLAttributes<P>>> | undefined = {},
75
- ) => ({
76
- // Default props.
77
- ...(defaults as object),
78
-
79
- // Spread supplied props.
80
- ...props,
81
-
82
- // Prefer explicit role, then defaults role, then 'none'.
83
- role: role ?? defaults.role ?? 'none',
84
-
85
- // Merge styles.
86
- style: { ...defaults.style, ...style } as CSSProperties,
87
-
88
- // Compose classnames.
89
- className: mx(defaultClassNames, className, classNames),
90
- });
91
-
92
- /**
93
- * Factory for slottable components.
94
- * The implementation receives full `HTMLAttributes<E>` so it can destructure `role`, `style`, etc.
95
- * Consumers see only `SlottableProps<P>` — a narrow type exposing `classNames`, `className`,
96
- * `children`, `asChild`, and the custom props `P`.
97
- *
98
- * @example
99
- * ```tsx
100
- * const MyPanel = slottable<HTMLDivElement, { border?: boolean }>(
101
- * ({ children, asChild, border, ...props }, forwardedRef) => {
102
- * const Comp = asChild ? Slot : Primitive.div;
103
- * return (
104
- * <Comp {...composableProps(props, { classNames: border && 'border' })} ref={forwardedRef}>
105
- * {children}
106
- * </Comp>
107
- * );
108
- * },
109
- * );
110
- * ```
111
- */
112
- /** Symbol used to mark components created by `composable()` or `slottable()`. */
113
- const COMPOSABLE = Symbol.for('dxos.composable');
114
-
115
- export function slottable<E extends HTMLElement, P extends object = {}>(
116
- render: (props: SlottableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => ReactNode,
117
- ): ForwardRefExoticComponent<SlottableProps<P> & RefAttributes<E>> {
118
- const wrapped = (props: SlottableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => {
119
- let warn = false;
120
- if (props.asChild) {
121
- try {
122
- const child = Children.only(props.children);
123
- if (isValidElement(child) && typeof child.type !== 'string' && !(child.type as any)[COMPOSABLE]) {
124
- warn = true;
125
- log.warn('slot child is not composable; create it with composable() or slottable()', {
126
- child: (child.type as any).displayName ?? (child.type as any).name,
127
- });
128
- }
129
- } catch {
130
- // Children.only throws if not exactly one child — Slot handles this.
131
- }
132
- }
133
-
134
- const result = render(props, forwardedRef);
135
- if (warn) {
136
- return createElement('div', { role: 'none', className: 'dx-slot-warning' }, result);
137
- }
138
-
139
- return result;
140
- };
141
-
142
- const component = forwardRef(wrapped as any) as any;
143
- (component as any)[COMPOSABLE] = true;
144
- return component;
145
- }
146
-
147
- /**
148
- * Factory for composable (leaf) components.
149
- * The implementation receives full `HTMLAttributes<E>` so it can destructure `role`, `style`, etc.
150
- * Consumers see only `ComposableProps<P>` — a narrow type exposing `classNames`, `className`,
151
- * `children`, and the custom props `P`.
152
- *
153
- * For generic components, use `any` for the type parameter inside `composable` and
154
- * cast the result to restore the generic signature for consumers.
155
- *
156
- * @example
157
- * ```tsx
158
- * const Leaf = composable<HTMLButtonElement>(({ children, ...props }, forwardedRef) => {
159
- * return (
160
- * <button {...composableProps(props, { classNames: 'btn' })} ref={forwardedRef}>
161
- * {children}
162
- * </button>
163
- * );
164
- * });
165
- * ```
166
- */
167
- export function composable<E extends HTMLElement, P extends object = {}>(
168
- render: (props: ComposableProps<P> & HTMLAttributes<E>, forwardedRef: ForwardedRef<E>) => ReactNode,
169
- ): ForwardRefExoticComponent<ComposableProps<P> & RefAttributes<E>> {
170
- const component = forwardRef(render as any) as any;
171
- (component as any)[COMPOSABLE] = true;
172
- return component;
173
- }
@@ -14,20 +14,30 @@ export const textValence = (valence?: MessageValence) => {
14
14
  return 'font-medium text-warning-text';
15
15
  case 'error':
16
16
  return 'font-medium text-error-text';
17
+ default:
18
+ return 'font-medium';
17
19
  }
18
20
  };
19
21
 
20
22
  export const messageValence = (valence?: MessageValence) => {
21
23
  switch (valence) {
22
24
  case 'success':
23
- return 'font-medium text-success-text border-success-text bg-success-surface';
25
+ return 'font-medium border-success-text text-success-fg bg-success-surface';
24
26
  case 'info':
25
- return 'font-medium text-info-text border-info-text bg-info-surface';
27
+ return 'font-medium border-info-text text-info-fg bg-info-surface';
26
28
  case 'warning':
27
- return 'font-medium text-warning-text border-warning-text bg-warning-surface';
29
+ return 'font-medium border-warning-text text-warning-fg bg-warning-surface';
28
30
  case 'error':
29
- return 'font-medium text-error-text border-error-text bg-error-surface';
31
+ return 'font-medium border-error-text text-error-fg bg-error-surface';
30
32
  default:
31
- return 'font-medium text-neutral-text border-neutral-text bg-neutral-surface';
33
+ return 'font-medium border-neutral-text text-neutral-fg bg-neutral-surface';
32
34
  }
33
35
  };
36
+
37
+ /**
38
+ * Classes for a Button rendered inside a Message.Root that should inherit the message's valence color.
39
+ * Message.Root sets --dx-valence-bg / --dx-valence-bg-hover / --dx-valence-text on its DOM node.
40
+ * Pass variant='valence' to the Button so button.css reads those variables.
41
+ */
42
+ export const buttonValence = (_valence?: MessageValence): string =>
43
+ 'text-(--dx-valence-text) bg-(--dx-valence-bg) hover:bg-(--dx-valence-bg-hover)';
@@ -1,21 +0,0 @@
1
- import { type ComponentFunction, type Size, type Theme } from '@dxos/ui-types';
2
- export type AvatarStyleProps = Partial<{
3
- size: Size;
4
- srOnly: boolean;
5
- status: 'active' | 'inactive' | 'current' | 'error' | 'warning' | 'internal';
6
- animation: 'pulse' | 'none';
7
- variant: 'circle' | 'square';
8
- inGroup: boolean;
9
- }>;
10
- export declare const avatarRoot: ComponentFunction<AvatarStyleProps>;
11
- export declare const avatarLabel: ComponentFunction<AvatarStyleProps>;
12
- export declare const avatarDescription: ComponentFunction<AvatarStyleProps>;
13
- export declare const avatarFrame: ComponentFunction<AvatarStyleProps>;
14
- export declare const avatarStatusIcon: ComponentFunction<AvatarStyleProps>;
15
- export declare const avatarRing: ComponentFunction<AvatarStyleProps>;
16
- export declare const avatarFallbackText: ComponentFunction<AvatarStyleProps>;
17
- export declare const avatarGroup: ComponentFunction<AvatarStyleProps>;
18
- export declare const avatarGroupLabel: ComponentFunction<AvatarStyleProps>;
19
- export declare const avatarGroupDescription: ComponentFunction<AvatarStyleProps>;
20
- export declare const avatarTheme: Theme<AvatarStyleProps>;
21
- //# sourceMappingURL=avatar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../../../src/theme/components/avatar.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAI/E,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IAC7E,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,OAAO,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,gBAAgB,CAMxD,CAAC;AAEJ,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,gBAAgB,CAA2D,CAAC;AAExH,eAAO,MAAM,iBAAiB,EAAE,iBAAiB,CAAC,gBAAgB,CACb,CAAC;AAEtD,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,gBAAgB,CACyC,CAAC;AAEtG,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,gBAAgB,CAU9D,CAAC;AAEJ,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,gBAAgB,CAmBxD,CAAC;AAEJ,eAAO,MAAM,kBAAkB,EAAE,iBAAiB,CAAC,gBAAgB,CAAgD,CAAC;AAEpH,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,gBAAgB,CACpB,CAAC;AAEzC,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,CAAC,gBAAgB,CAO9D,CAAC;AAEJ,eAAO,MAAM,sBAAsB,EAAE,iBAAiB,CAAC,gBAAgB,CAClB,CAAC;AAEtD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAW/C,CAAC"}
@@ -1,9 +0,0 @@
1
- import { type ComponentFunction, type Theme } from '@dxos/ui-types';
2
- export type breadcrumbStyleProps = {};
3
- export declare const breadcrumbRoot: ComponentFunction<breadcrumbStyleProps>;
4
- export declare const breadcrumbList: ComponentFunction<breadcrumbStyleProps>;
5
- export declare const breadcrumbListItem: ComponentFunction<breadcrumbStyleProps>;
6
- export declare const breadcrumbCurrent: ComponentFunction<breadcrumbStyleProps>;
7
- export declare const breadcrumbSeparator: ComponentFunction<breadcrumbStyleProps>;
8
- export declare const breadcrumbTheme: Theme<breadcrumbStyleProps>;
9
- //# sourceMappingURL=breadcrumb.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../../../src/theme/components/breadcrumb.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAIpE,MAAM,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEtC,eAAO,MAAM,cAAc,EAAE,iBAAiB,CAAC,oBAAoB,CACzB,CAAC;AAE3C,eAAO,MAAM,cAAc,EAAE,iBAAiB,CAAC,oBAAoB,CAA8C,CAAC;AAElH,eAAO,MAAM,kBAAkB,EAAE,iBAAiB,CAAC,oBAAoB,CAA8C,CAAC;AAEtH,eAAO,MAAM,iBAAiB,EAAE,iBAAiB,CAAC,oBAAoB,CAAkC,CAAC;AAEzG,eAAO,MAAM,mBAAmB,EAAE,iBAAiB,CAAC,oBAAoB,CAC9C,CAAC;AAE3B,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,oBAAoB,CAMvD,CAAC"}
@@ -1,15 +0,0 @@
1
- import type { Density, Elevation, Theme } from '@dxos/ui-types';
2
- export declare const primaryButtonColors = "text-accent-surface-text bg-accent-surface hover:bg-accent-surface-hover aria-pressed:bg-primary-500 dark:aria-pressed:bg-primary-500 data-[state=open]:bg-primary-500 dark:data-[state=open]:bg-primary-500 aria-checked:bg-primary-500 dark:aria-checked:bg-primary-500 aria-checked:text-primary-100";
3
- export declare const staticDefaultButtonColors = "bg-input-surface text-input-surface-text";
4
- export declare const defaultButtonColors: string;
5
- export declare const ghostButtonColors: string;
6
- export type ButtonStyleProps = Partial<{
7
- inGroup?: boolean;
8
- textWrap?: boolean;
9
- density: Density;
10
- elevation: Elevation;
11
- disabled: boolean;
12
- variant: 'default' | 'primary' | 'ghost' | 'outline';
13
- }>;
14
- export declare const buttonTheme: Theme<ButtonStyleProps>;
15
- //# sourceMappingURL=button.d.ts.map