@dxos/ui-theme 0.9.0 → 0.9.1-staging.ee54ba693a

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 (36) hide show
  1. package/dist/lib/browser/index.mjs +37 -7
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +37 -7
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/plugin/node-cjs/main.css +56 -27
  8. package/dist/plugin/node-cjs/main.css.map +2 -2
  9. package/dist/plugin/node-cjs/meta.json +1 -1
  10. package/dist/plugin/node-esm/main.css +56 -27
  11. package/dist/plugin/node-esm/main.css.map +2 -2
  12. package/dist/plugin/node-esm/meta.json +1 -1
  13. package/dist/types/src/util/index.d.ts +2 -0
  14. package/dist/types/src/util/index.d.ts.map +1 -1
  15. package/dist/types/src/util/mx.d.ts.map +1 -1
  16. package/dist/types/src/util/tv.d.ts +27 -0
  17. package/dist/types/src/util/tv.d.ts.map +1 -0
  18. package/dist/types/src/util/tv.test.d.ts +2 -0
  19. package/dist/types/src/util/tv.test.d.ts.map +1 -0
  20. package/dist/types/src/util/tw-merge-config.d.ts +17 -0
  21. package/dist/types/src/util/tw-merge-config.d.ts.map +1 -0
  22. package/dist/types/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +8 -7
  24. package/src/Theme.stories.tsx +1 -1
  25. package/src/css/components/button.css +10 -9
  26. package/src/css/components/state.css +2 -25
  27. package/src/css/components/surface.css +65 -12
  28. package/src/css/components/tag.css +2 -2
  29. package/src/css/layout/size.css +1 -1
  30. package/src/css/theme/semantic.css +162 -75
  31. package/src/css/theme/spacing.css +7 -5
  32. package/src/util/index.ts +2 -0
  33. package/src/util/mx.ts +5 -35
  34. package/src/util/tv.test.ts +38 -0
  35. package/src/util/tv.ts +45 -0
  36. package/src/util/tw-merge-config.ts +46 -0
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { mx } from './mx';
8
+ import { bridgeTv, tv } from './tv';
9
+
10
+ describe('tv', () => {
11
+ test('resolves standard tailwind conflicts like mx', ({ expect }) => {
12
+ const recipe = tv({ base: 'text-sm', variants: { big: { true: 'text-lg' } } });
13
+ expect(recipe({ big: true })).toBe(mx('text-sm', 'text-lg'));
14
+ expect(recipe({ big: true })).toContain('text-lg');
15
+ expect(recipe({ big: true })).not.toContain('text-sm');
16
+ });
17
+
18
+ test('keeps dxos custom color tokens (text-base-fg over text-description)', ({ expect }) => {
19
+ const recipe = tv({ base: 'text-description', variants: { strong: { true: 'text-base-fg' } } });
20
+ expect(recipe({ strong: true })).toBe(mx('text-description', 'text-base-fg'));
21
+ expect(recipe({ strong: true })).toContain('text-base-fg');
22
+ });
23
+ });
24
+
25
+ describe('bridgeTv', () => {
26
+ test('adapts a slots recipe into Theme<P> functions with overrides', ({ expect }) => {
27
+ const recipe = tv({
28
+ slots: { root: 'p-1', label: 'text-sm' },
29
+ variants: { variant: { settings: { root: 'p-4', label: 'text-lg' } } },
30
+ });
31
+ const theme = bridgeTv(recipe, ['root', 'label']);
32
+ expect(typeof theme.root).toBe('function');
33
+ expect(theme.root({ variant: 'settings' })).toContain('p-4');
34
+ // consumer override wins via per-slot merge.
35
+ expect(theme.label({ variant: 'settings' }, 'text-xl')).toContain('text-xl');
36
+ expect(theme.label({ variant: 'settings' }, 'text-xl')).not.toContain('text-lg');
37
+ });
38
+ });
package/src/util/tv.ts ADDED
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { createTV } from 'tailwind-variants';
6
+
7
+ import { type ClassNameArray, type ClassNameValue, type Theme } from '@dxos/ui-types';
8
+
9
+ import { twMergeConfig } from './tw-merge-config';
10
+
11
+ export type { VariantProps } from 'tailwind-variants';
12
+
13
+ /**
14
+ * Shared tailwind-variants instance bound to the dxos tailwind-merge config (see {@link twMergeConfig}),
15
+ * so recipes resolve class conflicts identically to {@link mx}. All component theme recipes import this,
16
+ * never the bare `tailwind-variants` package.
17
+ */
18
+ export const tv = createTV({ twMerge: true, twMergeConfig });
19
+
20
+ type SlotsRecipe<P extends Record<string, any>, S extends string> = (
21
+ props?: P,
22
+ ) => Record<S, (opts?: { class?: ClassNameValue }) => string>;
23
+
24
+ /**
25
+ * The {@link Theme} sub-tree produced by {@link bridgeTv}, with each slot's resolver explicitly typed
26
+ * so call sites (and tests) stay fully typed instead of falling back to `any`.
27
+ */
28
+ export type BridgedTheme<P extends Record<string, any>, S extends string> = Theme<P> & {
29
+ [K in S]: (styleProps: P, ...etc: ClassNameArray) => string;
30
+ };
31
+
32
+ /**
33
+ * Adapt a tailwind-variants slots recipe into the existing {@link Theme} shape (a map of
34
+ * {@link import('@dxos/ui-types').ComponentFunction}) so it can register in the `tx` theme tree and be
35
+ * consumed via `tx('component.slot', styleProps, ...classNames)`. Slots are enumerated explicitly (not
36
+ * via Proxy) so unknown paths resolve to `undefined` exactly as `getDeep` does today, and the result is
37
+ * a plain, inspectable object. Derive `slots` from `Object.keys(recipe())` at the call site.
38
+ */
39
+ export const bridgeTv = <P extends Record<string, any>, S extends string>(
40
+ recipe: SlotsRecipe<P, S>,
41
+ slots: readonly S[],
42
+ ): BridgedTheme<P, S> =>
43
+ Object.fromEntries(
44
+ slots.map((slot) => [slot, (styleProps: P, ...etc: ClassNameArray) => recipe(styleProps)[slot]({ class: etc })]),
45
+ ) as BridgedTheme<P, S>;
@@ -0,0 +1,46 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ /**
6
+ * Shared tailwind-merge configuration. The single source for both {@link mx} (extendTailwindMerge)
7
+ * and the {@link tv} instance (createTV `twMergeConfig`) so conflict resolution can't drift —
8
+ * notably for dxos custom tokens (`text-base-fg`, density, focus-ring).
9
+ */
10
+ export const twMergeConfig = {
11
+ extend: {
12
+ classGroups: {
13
+ 'font-family': ['font-body', 'font-mono'],
14
+ 'font-weight': [
15
+ 'font-thin',
16
+ 'font-extralight',
17
+ 'font-light',
18
+ 'font-normal',
19
+ 'font-medium',
20
+ 'font-semibold',
21
+ 'font-bold',
22
+ 'font-extrabold',
23
+ 'font-black',
24
+ ],
25
+ 'density': ['dx-density-sm', 'dx-density-md', 'dx-density-lg'],
26
+ 'dx-focus-ring': [
27
+ 'dx-focus-ring',
28
+ 'dx-focus-ring-inset',
29
+ 'dx-focus-ring-always',
30
+ 'dx-focus-ring-inset-always',
31
+ 'dx-focus-ring-group',
32
+ 'dx-focus-ring-group-x',
33
+ 'dx-focus-ring-group-y',
34
+ 'dx-focus-ring-group-always',
35
+ 'dx-focus-ring-group-x-always',
36
+ 'dx-focus-ring-group-y-always',
37
+ 'dx-focus-ring-inset-over-all',
38
+ 'dx-focus-ring-inset-over-all-always',
39
+ 'dx-focus-ring-main',
40
+ 'dx-focus-ring-main-always',
41
+ ],
42
+ },
43
+ },
44
+ } as const;
45
+
46
+ export type AdditionalClassGroups = 'density' | 'dx-focus-ring';