@dxos/ui-theme 0.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 (97) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +25 -0
  3. package/package.json +80 -0
  4. package/src/Tokens.stories.tsx +88 -0
  5. package/src/config/index.ts +6 -0
  6. package/src/config/tailwind.ts +250 -0
  7. package/src/config/tokens/alias-colors.ts +39 -0
  8. package/src/config/tokens/index.ts +92 -0
  9. package/src/config/tokens/lengths.ts +97 -0
  10. package/src/config/tokens/physical-colors.ts +125 -0
  11. package/src/config/tokens/semantic-colors.ts +27 -0
  12. package/src/config/tokens/sememes-calls.ts +17 -0
  13. package/src/config/tokens/sememes-codemirror.ts +50 -0
  14. package/src/config/tokens/sememes-hue.ts +54 -0
  15. package/src/config/tokens/sememes-sheet.ts +62 -0
  16. package/src/config/tokens/sememes-system.ts +302 -0
  17. package/src/config/tokens/sizes.ts +7 -0
  18. package/src/config/tokens/types.ts +9 -0
  19. package/src/docs/theme.drawio.svg +635 -0
  20. package/src/index.ts +19 -0
  21. package/src/plugins/esbuild-plugin.ts +65 -0
  22. package/src/plugins/plugin.ts +130 -0
  23. package/src/plugins/resolveContent.ts +51 -0
  24. package/src/styles/components/README.md +6 -0
  25. package/src/styles/components/anchored-overflow.ts +20 -0
  26. package/src/styles/components/avatar.ts +96 -0
  27. package/src/styles/components/breadcrumb.ts +29 -0
  28. package/src/styles/components/button.ts +48 -0
  29. package/src/styles/components/dialog.ts +36 -0
  30. package/src/styles/components/icon-button.ts +20 -0
  31. package/src/styles/components/icon.ts +19 -0
  32. package/src/styles/components/index.ts +27 -0
  33. package/src/styles/components/input.ts +177 -0
  34. package/src/styles/components/link.ts +26 -0
  35. package/src/styles/components/list.ts +46 -0
  36. package/src/styles/components/main.ts +36 -0
  37. package/src/styles/components/menu.ts +60 -0
  38. package/src/styles/components/message.ts +36 -0
  39. package/src/styles/components/popover.ts +40 -0
  40. package/src/styles/components/scroll-area.ts +43 -0
  41. package/src/styles/components/select.ts +60 -0
  42. package/src/styles/components/separator.ts +24 -0
  43. package/src/styles/components/status.ts +32 -0
  44. package/src/styles/components/tag.ts +23 -0
  45. package/src/styles/components/toast.ts +55 -0
  46. package/src/styles/components/toolbar.ts +29 -0
  47. package/src/styles/components/tooltip.ts +29 -0
  48. package/src/styles/components/treegrid.ts +37 -0
  49. package/src/styles/fragments/density.ts +17 -0
  50. package/src/styles/fragments/dimension.ts +8 -0
  51. package/src/styles/fragments/disabled.ts +6 -0
  52. package/src/styles/fragments/elevation.ts +29 -0
  53. package/src/styles/fragments/focus.ts +16 -0
  54. package/src/styles/fragments/group.ts +12 -0
  55. package/src/styles/fragments/hover.ts +25 -0
  56. package/src/styles/fragments/index.ts +20 -0
  57. package/src/styles/fragments/layout.ts +7 -0
  58. package/src/styles/fragments/motion.ts +6 -0
  59. package/src/styles/fragments/ornament.ts +10 -0
  60. package/src/styles/fragments/selected.ts +45 -0
  61. package/src/styles/fragments/shimmer.ts +9 -0
  62. package/src/styles/fragments/size.ts +117 -0
  63. package/src/styles/fragments/surface.ts +29 -0
  64. package/src/styles/fragments/text.ts +12 -0
  65. package/src/styles/fragments/valence.ts +46 -0
  66. package/src/styles/index.ts +7 -0
  67. package/src/styles/layers/README.md +15 -0
  68. package/src/styles/layers/anchored-overflow.css +9 -0
  69. package/src/styles/layers/animation.css +17 -0
  70. package/src/styles/layers/attention.css +8 -0
  71. package/src/styles/layers/base.css +25 -0
  72. package/src/styles/layers/button.css +76 -0
  73. package/src/styles/layers/can-scroll.css +26 -0
  74. package/src/styles/layers/checkbox.css +50 -0
  75. package/src/styles/layers/dialog.css +42 -0
  76. package/src/styles/layers/drag-preview.css +18 -0
  77. package/src/styles/layers/focus-ring.css +224 -0
  78. package/src/styles/layers/hues.css +110 -0
  79. package/src/styles/layers/index.css +26 -0
  80. package/src/styles/layers/main.css +160 -0
  81. package/src/styles/layers/native.css +20 -0
  82. package/src/styles/layers/positioning.css +23 -0
  83. package/src/styles/layers/size.css +397 -0
  84. package/src/styles/layers/surfaces.css +31 -0
  85. package/src/styles/layers/tag.css +132 -0
  86. package/src/styles/layers/tldraw.css +91 -0
  87. package/src/styles/layers/tokens.css +45 -0
  88. package/src/styles/layers/typography.css +157 -0
  89. package/src/styles/theme.ts +69 -0
  90. package/src/tailwind.ts +5 -0
  91. package/src/theme.css +9 -0
  92. package/src/types.ts +7 -0
  93. package/src/typings.d.ts +8 -0
  94. package/src/util/hash-styles.ts +168 -0
  95. package/src/util/index.ts +6 -0
  96. package/src/util/mx.ts +51 -0
  97. package/src/util/withLogical.ts +114 -0
@@ -0,0 +1,125 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { AccompanyingSeries, ColorsPhysicalLayer, Gamut, HelicalArcSeries, PhysicalSeries } from '@ch-ui/tokens';
6
+
7
+ import { type PhysicalPalette } from './types';
8
+
9
+ const reflectiveRelation = {
10
+ initial: 1_000,
11
+ slope: -1_000,
12
+ method: 'floor',
13
+ } satisfies AccompanyingSeries;
14
+
15
+ const gamuts: (Gamut & string)[] = ['srgb', 'p3', 'rec2020'];
16
+
17
+ const DEG_RAD = Math.PI / 180;
18
+
19
+ /**
20
+ * Creates a color palette configuration for a given hue value.
21
+ *
22
+ * @param hue - A number from 0-16 representing different hue angles
23
+ * @returns A PhysicalPalette configuration with:
24
+ * - keyPoint: [lightness, chroma, hue] in LCH color space
25
+ * - lightness: Fixed at 0.5 (50%)
26
+ * - chroma: Varies sinusoidally around 0.13 based on hue angle
27
+ * - hue: Calculated by mapping input 0-16 to 26-386 degrees
28
+ * - Control points and torsion for color interpolation
29
+ */
30
+ const hueKeyPoint = (hue: number): PhysicalPalette => {
31
+ const hueDeg = (360 * (hue / 17) + 26) % 360;
32
+ return {
33
+ keyPoint: [0.5, 0.13 + 0.024 * Math.sin((hueDeg - 15) * DEG_RAD), hueDeg],
34
+ lowerCp: 1,
35
+ upperCp: 1,
36
+ torsion: 0,
37
+ };
38
+ };
39
+
40
+ export const huePalettes = {
41
+ red: hueKeyPoint(0),
42
+ orange: hueKeyPoint(1),
43
+ amber: hueKeyPoint(2),
44
+ yellow: hueKeyPoint(3),
45
+ lime: hueKeyPoint(4),
46
+ green: hueKeyPoint(5),
47
+ emerald: hueKeyPoint(6),
48
+ teal: hueKeyPoint(7),
49
+ cyan: hueKeyPoint(8),
50
+ sky: hueKeyPoint(9),
51
+ blue: hueKeyPoint(10),
52
+ indigo: hueKeyPoint(11),
53
+ violet: hueKeyPoint(12),
54
+ purple: hueKeyPoint(13),
55
+ fuchsia: hueKeyPoint(14),
56
+ pink: hueKeyPoint(15),
57
+ rose: hueKeyPoint(16),
58
+ };
59
+
60
+ /**
61
+ * The keyPoint represents the LCH value:
62
+ * - Lightness: 0-1, should usually set the keyPoint at or near 0.5
63
+ * - Chroma: min 0, max 0.08–0.5 depending on hue and gamut, theme will clamp final value to within gamut’s range
64
+ * - Hue: 0-360 (~26 “red”, ~141 “green”, ~262 “blue”)
65
+ *
66
+ * NOTE: Rebuild the theme and restart the dev server to see changes.
67
+ *
68
+ * Theme references:
69
+ * https://oklch.com
70
+ * https://colorsublime.github.io
71
+ * https://github.com/microsoft/vscode-docs/blob/main/api/extension-guides/color-theme.md#create-a-new-color-theme
72
+ * https://raw.githubusercontent.com/microsoft/vscode/main/src/vs/workbench/services/themes/common/colorThemeSchema.ts
73
+ * https://tailwindcss.com/docs/colors
74
+ */
75
+ const systemPalettes = {
76
+ neutral: {
77
+ keyPoint: [0.5, 0.001, 260],
78
+ lowerCp: 0,
79
+ upperCp: 0,
80
+ torsion: 0,
81
+ // Values used directly.
82
+ // TODO(burdon): Audit.
83
+ values: [25, 50, 75, 100, 150, 200, 250, 300, 400, 500, 600, 700, 750, 800, 850, 900],
84
+ } satisfies PhysicalPalette,
85
+
86
+ // https://oklch.com/#0.5,0.2,260,100 (#0559d2)
87
+ primary: {
88
+ keyPoint: [0.5, 0.2, 260],
89
+ lowerCp: 0.86,
90
+ upperCp: 1,
91
+ torsion: -30,
92
+ // Values used directly.
93
+ // TODO(burdon): Audit.
94
+ values: [100, 150, 200, 350, 400, 450, 500, 750, 800, 850],
95
+ } satisfies PhysicalPalette,
96
+ };
97
+
98
+ const physicalSeries = {
99
+ ...huePalettes,
100
+ ...systemPalettes,
101
+ };
102
+
103
+ export const physicalColors: ColorsPhysicalLayer = {
104
+ namespace: 'dx-',
105
+ definitions: {
106
+ // @ts-ignore
107
+ series: physicalSeries,
108
+ accompanyingSeries: { reflectiveRelation },
109
+ },
110
+ conditions: {
111
+ srgb: [':root, .dark'],
112
+ p3: ['@media (color-gamut: p3)', ':root, .dark'],
113
+ rec2020: ['@media (color-gamut: rec2020)', ':root, .dark'],
114
+ },
115
+ series: Object.entries(physicalSeries).reduce((acc: ColorsPhysicalLayer['series'], [id]) => {
116
+ acc[id] = gamuts.reduce((acc: PhysicalSeries<Gamut & string, HelicalArcSeries>, gamut) => {
117
+ acc[gamut] = {
118
+ extends: id,
119
+ physicalValueRelation: { extends: 'reflectiveRelation' },
120
+ };
121
+ return acc;
122
+ }, {});
123
+ return acc;
124
+ }, {}),
125
+ };
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { HelicalArcValue, SemanticLayer } from '@ch-ui/tokens';
6
+
7
+ import { callsSememes } from './sememes-calls';
8
+ import { codemirrorSememes } from './sememes-codemirror';
9
+ import { hueSememes } from './sememes-hue';
10
+ import { sheetSememes } from './sememes-sheet';
11
+ import { systemSememes } from './sememes-system';
12
+
13
+ export const semanticColors = {
14
+ conditions: {
15
+ light: [':root'],
16
+ dark: ['.dark'],
17
+ },
18
+ sememes: {
19
+ // Define each set of sememes in its own file.
20
+ ...callsSememes,
21
+ ...codemirrorSememes,
22
+ ...hueSememes,
23
+ ...sheetSememes,
24
+ ...systemSememes,
25
+ },
26
+ namespace: 'dx-',
27
+ } satisfies SemanticLayer<string, string, HelicalArcValue>;
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type ColorSememes } from './types';
6
+
7
+ // TODO(burdon): This should be broader than just calls.
8
+ export const callsSememes: ColorSememes = {
9
+ callActive: {
10
+ light: ['green', '500'],
11
+ dark: ['green', '500'],
12
+ },
13
+ callAlert: {
14
+ light: ['rose', '500'],
15
+ dark: ['rose', '500'],
16
+ },
17
+ };
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type ColorSememes } from './types';
6
+
7
+ export const codemirrorSememes = {
8
+ // NOTE: background styles for the main content area must have transparency otherwise they will mask the selection.
9
+ cmCodeblock: {
10
+ light: ['neutral', '500/.1'],
11
+ dark: ['neutral', '500/.1'],
12
+ },
13
+ cmActiveLine: {
14
+ light: ['neutral', '200/.5'],
15
+ dark: ['neutral', '800/.5'],
16
+ },
17
+ cmSeparator: {
18
+ light: ['primary', 500],
19
+ dark: ['primary', 500],
20
+ },
21
+ cmCursor: {
22
+ light: ['neutral', 900],
23
+ dark: ['neutral', 100],
24
+ },
25
+ cmSelection: {
26
+ light: ['primary', '400/.5'],
27
+ dark: ['primary', '600/.5'],
28
+ },
29
+ cmFocusedSelection: {
30
+ light: ['primary', 400],
31
+ dark: ['primary', 600],
32
+ },
33
+ cmHighlight: {
34
+ light: ['neutral', 950],
35
+ dark: ['neutral', 50],
36
+ },
37
+ cmHighlightSurface: {
38
+ light: ['sky', 200],
39
+ dark: ['cyan', 600],
40
+ },
41
+ // TODO(burdon): Factor out defs in common with sheet.
42
+ cmCommentText: {
43
+ light: ['neutral', 50],
44
+ dark: ['neutral', 950],
45
+ },
46
+ cmCommentSurface: {
47
+ light: ['amber', 700],
48
+ dark: ['amber', 200],
49
+ },
50
+ } satisfies ColorSememes;
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { huePalettes } from './physical-colors';
6
+ import { type ColorAliases, type ColorSememes } from './types';
7
+
8
+ export const hueSememes: ColorSememes = [...Object.keys(huePalettes), 'neutral', 'primary'].reduce(
9
+ (acc: ColorSememes, palette) => {
10
+ acc[`${palette}Cursor`] = {
11
+ light: [palette, 400],
12
+ dark: [palette, 300],
13
+ };
14
+ acc[`${palette}Text`] = {
15
+ light: [palette, 550],
16
+ dark: [palette, 300],
17
+ };
18
+ acc[`${palette}Fill`] = {
19
+ light: [palette, 500],
20
+ dark: [palette, 500],
21
+ };
22
+ acc[`${palette}Surface`] = {
23
+ light: [palette, 200],
24
+ dark: [palette, 700],
25
+ };
26
+ acc[`${palette}SurfaceText`] = {
27
+ light: [palette, 700],
28
+ dark: [palette, 200],
29
+ };
30
+ acc[`${palette}Screen`] = {
31
+ light: [palette, 100],
32
+ dark: [palette, 800],
33
+ };
34
+ return acc;
35
+ },
36
+ {},
37
+ );
38
+
39
+ const valenceAliasSememeStems = ['Text', 'Surface', 'SurfaceText', 'Fill', 'Cursor'];
40
+ const valenceMapping = {
41
+ emerald: ['success'],
42
+ cyan: ['info'],
43
+ amber: ['warning'],
44
+ rose: ['error'],
45
+ primary: ['current'],
46
+ fuchsia: ['internal'],
47
+ };
48
+
49
+ export const valenceAliases: ColorAliases = valenceAliasSememeStems.reduce((acc: ColorAliases, stem) => {
50
+ return Object.entries(valenceMapping).reduce((acc: ColorAliases, [hue, valences]) => {
51
+ acc[`${hue}${stem}`] = { root: valences.map((valence) => `${valence}${stem}`) };
52
+ return acc;
53
+ }, acc);
54
+ }, {});
@@ -0,0 +1,62 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { ColorAliases, ColorSememes } from './types';
6
+
7
+ export const sheetSememes = {
8
+ // NOTE: background styles for the main content area must have transparency otherwise they will mask the selection.
9
+ axisSurface: {
10
+ light: ['neutral', 50],
11
+ dark: ['neutral', 800],
12
+ },
13
+ axisText: {
14
+ light: ['neutral', 800],
15
+ dark: ['neutral', 200],
16
+ },
17
+ axisSelectedSurface: {
18
+ light: ['neutral', 100],
19
+ dark: ['neutral', 900],
20
+ },
21
+ axisSelectedText: {
22
+ light: ['neutral', 100],
23
+ dark: ['neutral', 900],
24
+ },
25
+ gridCell: {
26
+ // TODO(thure): Why override only dark?
27
+ light: ['neutral', '50/0'],
28
+ dark: ['neutral', 850],
29
+ },
30
+ gridCellSelected: {
31
+ // TODO(thure): Can this not just use `attention`?
32
+ light: ['neutral', 50],
33
+ dark: ['neutral', 800],
34
+ },
35
+ gridOverlay: {
36
+ light: ['primary', '500/.5'],
37
+ dark: ['primary', '500/.5'],
38
+ },
39
+ gridSelectionOverlay: {
40
+ light: ['primary', '500/.2'],
41
+ dark: ['primary', '500/.2'],
42
+ },
43
+ gridHighlight: {
44
+ light: ['emerald', '500/.5'],
45
+ dark: ['emerald', '500/.5'],
46
+ },
47
+ // TODO(burdon): Factor out def (in common with editor).
48
+ gridCommented: {
49
+ light: ['green', 200],
50
+ dark: ['green', 600],
51
+ },
52
+ gridCommentedActive: {
53
+ light: ['green', '200/.5'],
54
+ dark: ['green', '600/.5'],
55
+ },
56
+ } satisfies ColorSememes;
57
+
58
+ export const sheetAliases = {
59
+ activeSurface: { root: ['gridLine'] },
60
+ accentFocusIndicator: { root: ['gridFocusIndicatorColor'] },
61
+ neutralFocusIndicator: { gridFocusStack: ['gridFocusIndicatorColor'] },
62
+ } satisfies ColorAliases;
@@ -0,0 +1,302 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ // TODO(thure): TS2742
6
+ /* eslint-disable unused-imports/no-unused-imports */
7
+ import * as _colors from '@ch-ui/colors';
8
+
9
+ import { type ColorAliases, type ColorSememes } from './types';
10
+
11
+ // TODO(burdon): Move to util.
12
+ const getMapValue = <T>(map: Record<string, T>, key: string, defaultValue: () => T): T => {
13
+ let value = map[key];
14
+ if (!value) {
15
+ value = defaultValue();
16
+ map[key] = value;
17
+ }
18
+ return value;
19
+ };
20
+
21
+ type Sememe = ColorSememes[string];
22
+
23
+ const applyAlpha = (sememe: Sememe, alpha: number): Sememe => {
24
+ if (alpha >= 1) {
25
+ return sememe;
26
+ } else {
27
+ return {
28
+ light: [sememe.light![0], `${sememe.light![1]}/${alpha}`],
29
+ dark: [sememe.dark![0], `${sememe.dark![1]}/${alpha}`],
30
+ };
31
+ }
32
+ };
33
+
34
+ // Luminosity extrema and key points.
35
+
36
+ // Both elevation cadences go from darker to lighter from “elevation” 0 to `ELEVATION_SCALE`,
37
+ // whereas both contrast cadences go from highest contrast at 0 to lowest contrast at `CONTRAST_SCALE`.
38
+
39
+ const DARK_ELEVATION_MIN = 855;
40
+ const DARK_ELEVATION_MAX = 731;
41
+
42
+ const DARK_CONTRAST_MIN = 750;
43
+ const DARK_CONTRAST_MAX = 665;
44
+
45
+ const LIGHT_ELEVATION_MIN = 0;
46
+ const LIGHT_ELEVATION_MAX = 0;
47
+
48
+ const LIGHT_CONTRAST_MIN = 82;
49
+ const LIGHT_CONTRAST_MAX = 24;
50
+
51
+ const ELEVATION_SCALE = 2;
52
+ const CONTRAST_SCALE = 3;
53
+
54
+ const darkElevationCadence = (depth: number) =>
55
+ Math.round(
56
+ DARK_ELEVATION_MAX + (DARK_ELEVATION_MIN - DARK_ELEVATION_MAX) * ((ELEVATION_SCALE - depth) / ELEVATION_SCALE),
57
+ );
58
+ const darkContrastCadence = (depth: number) =>
59
+ Math.round(
60
+ DARK_CONTRAST_MAX + (DARK_CONTRAST_MIN - DARK_CONTRAST_MAX) * ((ELEVATION_SCALE - depth) / ELEVATION_SCALE),
61
+ );
62
+
63
+ const lightElevationCadence = (depth: number) =>
64
+ Math.round(
65
+ LIGHT_ELEVATION_MIN + (LIGHT_ELEVATION_MAX - LIGHT_ELEVATION_MIN) * ((CONTRAST_SCALE - depth) / CONTRAST_SCALE),
66
+ );
67
+ const lightContrastCadence = (depth: number) =>
68
+ Math.round(LIGHT_CONTRAST_MAX + (LIGHT_CONTRAST_MIN - LIGHT_CONTRAST_MAX) * (depth / CONTRAST_SCALE));
69
+
70
+ const elevationCadence = (lightDepth: number, darkDepth: number = lightDepth, alpha: number = 1): Sememe =>
71
+ applyAlpha(
72
+ {
73
+ light: ['neutral', lightElevationCadence(lightDepth)],
74
+ dark: ['neutral', darkElevationCadence(darkDepth)],
75
+ },
76
+ alpha,
77
+ );
78
+
79
+ const contrastCadence = (lightDepth: number, darkDepth: number = lightDepth, alpha: number = 1): Sememe =>
80
+ applyAlpha(
81
+ {
82
+ light: ['neutral', lightContrastCadence(lightDepth)],
83
+ dark: ['neutral', darkContrastCadence(darkDepth)],
84
+ },
85
+ alpha,
86
+ );
87
+
88
+ export const systemSememes = {
89
+ //
90
+ // Elevation cadence tokens
91
+ //
92
+
93
+ baseSurface: elevationCadence(0),
94
+ groupSurface: elevationCadence(1),
95
+ modalSurface: elevationCadence(2, 1.7),
96
+
97
+ //
98
+ // Contrast cadence tokens
99
+ //
100
+
101
+ textInputSurfaceBase: contrastCadence(0, 0),
102
+ textInputSurfaceGroup: contrastCadence(0, 0.5),
103
+ textInputSurfaceModal: contrastCadence(0, 1),
104
+
105
+ inputSurfaceBase: contrastCadence(0.8, 0.33),
106
+ inputSurfaceGroup: contrastCadence(0.8, 0.66),
107
+ inputSurfaceModal: contrastCadence(0.8, 1),
108
+
109
+ hoverSurfaceBase: contrastCadence(2, 1.5),
110
+ hoverSurfaceGroup: contrastCadence(2, 2),
111
+ hoverSurfaceModal: contrastCadence(2, 2.5),
112
+
113
+ separatorBase: contrastCadence(3, 2),
114
+ separatorGroup: contrastCadence(3, 2.5),
115
+ separatorModal: contrastCadence(3, 3),
116
+
117
+ subduedSeparator: contrastCadence(3, 1),
118
+
119
+ unAccent: {
120
+ light: ['neutral', 400],
121
+ dark: ['neutral', 400],
122
+ },
123
+ unAccentHover: {
124
+ light: ['neutral', 450],
125
+ dark: ['neutral', 450],
126
+ },
127
+ hoverOverlay: {
128
+ light: ['neutral', '450/.1'],
129
+ dark: ['neutral', '450/.1'],
130
+ },
131
+
132
+ //
133
+ // Special surfaces.
134
+ //
135
+
136
+ // Screen overlay for modal dialogs.
137
+ scrimSurface: applyAlpha(
138
+ {
139
+ light: ['neutral', LIGHT_CONTRAST_MAX],
140
+ dark: ['neutral', DARK_ELEVATION_MIN],
141
+ },
142
+ 0.65,
143
+ ),
144
+
145
+ // High contrast for focused interactive elements. (Technically this is part of the surface cadence, but the contrast cadence is on the opposite side of the elevation cadence as this point.)
146
+ focusSurface: {
147
+ light: ['neutral', 0],
148
+ dark: ['neutral', 1000],
149
+ },
150
+
151
+ deckSurface: {
152
+ light: ['neutral', 50],
153
+ dark: ['neutral', 950],
154
+ },
155
+
156
+ // For tooltips only; the highest elevation from the opposite theme
157
+ inverseSurface: {
158
+ light: ['neutral', DARK_ELEVATION_MIN],
159
+ dark: ['neutral', LIGHT_ELEVATION_MIN],
160
+ },
161
+
162
+ //
163
+ // Accent surfaces
164
+ //
165
+
166
+ accentSurfaceRelated: {
167
+ light: ['primary', '300/.1'],
168
+ dark: ['primary', '400/.1'],
169
+ },
170
+ accentSurfaceHover: {
171
+ light: ['primary', 600],
172
+ dark: ['primary', 475],
173
+ },
174
+ accentSurface: {
175
+ light: ['primary', 500],
176
+ dark: ['primary', 500],
177
+ },
178
+
179
+ //
180
+ // Text (text-) and other foregrounds
181
+ // TODO(thure): Establish contrast-order cadence for text.
182
+ //
183
+
184
+ baseText: {
185
+ light: ['neutral', 1000],
186
+ dark: ['neutral', 50],
187
+ },
188
+ inverseSurfaceText: {
189
+ light: ['neutral', 50],
190
+ dark: ['neutral', 1000],
191
+ },
192
+ description: {
193
+ light: ['neutral', 550],
194
+ dark: ['neutral', 350],
195
+ },
196
+ subdued: {
197
+ light: ['neutral', 700],
198
+ dark: ['neutral', 300],
199
+ },
200
+ placeholder: {
201
+ light: ['neutral', 500],
202
+ dark: ['neutral', 500],
203
+ },
204
+ accentText: {
205
+ light: ['primary', 550],
206
+ dark: ['primary', 400],
207
+ },
208
+ accentSurfaceText: {
209
+ light: ['neutral', 0],
210
+ dark: ['neutral', 0],
211
+ },
212
+ accentTextHover: {
213
+ light: ['primary', 500],
214
+ dark: ['primary', 350],
215
+ },
216
+ accentFocusIndicator: {
217
+ light: ['primary', 300],
218
+ dark: ['primary', 450],
219
+ },
220
+ neutralFocusIndicator: {
221
+ light: ['neutral', 300],
222
+ dark: ['neutral', 550],
223
+ },
224
+ } satisfies ColorSememes;
225
+
226
+ type SememeName = keyof typeof systemSememes;
227
+ type SememeKey = 'root' | 'group' | 'modal';
228
+
229
+ /**
230
+ * Alias map.
231
+ */
232
+ const aliasDefs: Record<string, Partial<Record<SememeKey, SememeName>>> = {
233
+ // Selected items, current items, other surfaces needing special contrast against baseSurface.
234
+ activeSurface: {
235
+ root: 'inputSurfaceBase',
236
+ },
237
+
238
+ // Main sidebar panel.
239
+ sidebarSurface: {
240
+ root: 'groupSurface',
241
+ },
242
+
243
+ // Plank header.
244
+ headerSurface: {
245
+ root: 'groupSurface',
246
+ },
247
+
248
+ // Toolbars, table/sheet headers, etc.
249
+ toolbarSurface: {
250
+ root: 'groupSurface',
251
+ },
252
+
253
+ // Forms, cards, etc.
254
+ cardSurface: {
255
+ root: 'groupSurface',
256
+ },
257
+
258
+ // Secondary aliases.
259
+ textInputSurface: {
260
+ root: 'textInputSurfaceBase',
261
+ group: 'textInputSurfaceGroup',
262
+ modal: 'textInputSurfaceModal',
263
+ },
264
+ inputSurface: {
265
+ root: 'inputSurfaceBase',
266
+ group: 'inputSurfaceGroup',
267
+ modal: 'inputSurfaceModal',
268
+ },
269
+ hoverSurface: {
270
+ root: 'hoverSurfaceBase',
271
+ group: 'hoverSurfaceGroup',
272
+ modal: 'hoverSurfaceModal',
273
+ },
274
+
275
+ // TODO(thure): Rename uses of this token to `focusSurface` and remove this alias.
276
+ attention: {
277
+ root: 'focusSurface',
278
+ },
279
+
280
+ // In “master-detail” patterns, the background of the item in the list which is enumerated in the adjacent view.
281
+ // TODO(burdon): Review tokens.
282
+ currentRelated: {
283
+ root: 'modalSurface',
284
+ },
285
+
286
+ // Borders and dividers.
287
+ separator: {
288
+ root: 'separatorBase',
289
+ group: 'separatorGroup',
290
+ modal: 'separatorModal',
291
+ },
292
+ };
293
+
294
+ export const systemAliases: ColorAliases = Object.entries(aliasDefs).reduce((aliases, [alias, values]) => {
295
+ Object.entries(values).forEach(([key, sememe]) => {
296
+ const record = getMapValue(aliases, sememe, () => ({}));
297
+ const list = getMapValue<string[]>(record, key, () => []);
298
+ list.push(alias);
299
+ });
300
+
301
+ return aliases;
302
+ }, {} as ColorAliases);
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export const cardMinInlineSize = 18;
6
+ export const cardDefaultInlineSize = 20;
7
+ export const cardMaxInlineSize = 25;
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { AliasLayer, HelicalArcValue, ResolvedHelicalArcSeries, SemanticLayer } from '@ch-ui/tokens';
6
+
7
+ export type PhysicalPalette = Omit<ResolvedHelicalArcSeries, 'extends' | 'physicalValueRelation'>;
8
+ export type ColorSememes = SemanticLayer<string, string, HelicalArcValue>['sememes'];
9
+ export type ColorAliases = AliasLayer<string>['aliases'];