@scalepad/ui 0.1.1 → 0.2.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.
- package/README.md +264 -28
- package/package.json +150 -36
- package/scripts/verify-consumption.mjs +275 -0
- package/src/ThemeProvider.tsx +52 -4
- package/src/components/Anchor/Anchor.css.ts +1 -1
- package/src/components/ColorInput/ColorInput.figma.tsx +32 -0
- package/src/components/ColorInput/ColorInput.tsx +94 -0
- package/src/components/ColorInput/index.ts +6 -0
- package/src/components/IconButton/IconButton.tsx +51 -0
- package/src/components/Select/Select.css.ts +8 -3
- package/src/components/Select/Select.tsx +11 -1
- package/src/components/SubNavigation/SubNavigation.css.ts +20 -0
- package/src/index.ts +8 -0
- package/src/mantine.ts +2 -0
- package/src/theme/themeContract.css.ts +27 -3
- package/src/tokens/color-types.ts +28 -5
- package/src/tokens/colors.ts +52 -10
- package/src/tokens/index.ts +6 -2
- package/src/tokens/semantic-colors.ts +177 -73
- package/src/tokens/semantic-tokens-css.ts +34 -12
- package/src/tokens/shadows.ts +30 -6
- package/src/tokens/text-styles.ts +31 -2
- package/src/vite.d.ts +37 -0
- package/src/vite.js +120 -0
package/src/index.ts
CHANGED
|
@@ -287,6 +287,12 @@ export type {
|
|
|
287
287
|
export type { JSONContent } from '@tiptap/react';
|
|
288
288
|
|
|
289
289
|
// Input components with Figma design specifications
|
|
290
|
+
export {
|
|
291
|
+
ColorInput,
|
|
292
|
+
DEFAULT_COLOR_INPUT_SWATCHES,
|
|
293
|
+
DEFAULT_COLOR_INPUT_SWATCHES_PER_ROW,
|
|
294
|
+
} from './components/ColorInput';
|
|
295
|
+
export type { ColorInputProps } from './components/ColorInput';
|
|
290
296
|
export { CommentComposer } from './components/CommentComposer';
|
|
291
297
|
export type { CommentComposerProps } from './components/CommentComposer';
|
|
292
298
|
export { MultiSelect } from './components/MultiSelect';
|
|
@@ -475,8 +481,10 @@ export {
|
|
|
475
481
|
Textarea,
|
|
476
482
|
Tooltip,
|
|
477
483
|
UnstyledButton,
|
|
484
|
+
useMantineColorScheme,
|
|
478
485
|
} from './mantine';
|
|
479
486
|
export type {
|
|
487
|
+
MantineColorScheme,
|
|
480
488
|
PopoverProps,
|
|
481
489
|
ProgressProps,
|
|
482
490
|
UnstyledButtonProps,
|
package/src/mantine.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Theme contract for vanilla-extract: typed references to CSS variables
|
|
3
|
+
* injected by ThemeProvider.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: extended by hand to add `background.surface.*`, `background.entity.*`,
|
|
6
|
+
* and the `color.chart.*` group alongside the auto-generated entries from the
|
|
7
|
+
* Figma export. Once the Figma source catches up these can fold back into the
|
|
8
|
+
* generated output.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { createGlobalThemeContract } from '@vanilla-extract/css';
|
|
@@ -63,6 +67,18 @@ export const tokens = createGlobalThemeContract({
|
|
|
63
67
|
inverseFilledHover: 'color-background-inverse-filled-hover',
|
|
64
68
|
input: 'color-background-input',
|
|
65
69
|
backdrop: 'color-background-backdrop',
|
|
70
|
+
surfaceRaised: 'color-background-surface-raised',
|
|
71
|
+
surfaceOverlay: 'color-background-surface-overlay',
|
|
72
|
+
entityTaskLight: 'color-background-entity-task-light',
|
|
73
|
+
entityTaskIcon: 'color-background-entity-task-icon',
|
|
74
|
+
entityInitiativeLight: 'color-background-entity-initiative-light',
|
|
75
|
+
entityInitiativeIcon: 'color-background-entity-initiative-icon',
|
|
76
|
+
entityGoalLight: 'color-background-entity-goal-light',
|
|
77
|
+
entityGoalIcon: 'color-background-entity-goal-icon',
|
|
78
|
+
entityMeetingLight: 'color-background-entity-meeting-light',
|
|
79
|
+
entityMeetingIcon: 'color-background-entity-meeting-icon',
|
|
80
|
+
entityDeliverableLight: 'color-background-entity-deliverable-light',
|
|
81
|
+
entityDeliverableIcon: 'color-background-entity-deliverable-icon',
|
|
66
82
|
},
|
|
67
83
|
stroke: {
|
|
68
84
|
default: 'color-stroke-default',
|
|
@@ -97,6 +113,14 @@ export const tokens = createGlobalThemeContract({
|
|
|
97
113
|
primaryStrong: 'color-icon-primary-strong',
|
|
98
114
|
medium: 'color-icon-medium',
|
|
99
115
|
},
|
|
116
|
+
chart: {
|
|
117
|
+
series1: 'color-chart-series-1',
|
|
118
|
+
series2: 'color-chart-series-2',
|
|
119
|
+
series3: 'color-chart-series-3',
|
|
120
|
+
series4: 'color-chart-series-4',
|
|
121
|
+
series5: 'color-chart-series-5',
|
|
122
|
+
series6: 'color-chart-series-6',
|
|
123
|
+
},
|
|
100
124
|
},
|
|
101
125
|
radius: {
|
|
102
126
|
xs: 'radius-xs',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AUTO-GENERATED - DO NOT EDIT
|
|
3
|
-
* Generated from: packages/figma-tokens/source/semantic-colors.json
|
|
4
|
-
*
|
|
5
2
|
* Typed color token unions grouped by their intended usage.
|
|
6
3
|
* Use these types to restrict color props to only valid tokens.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: extended by hand to add `background.surface.*`, `background.entity.*`,
|
|
6
|
+
* and the new `ChartColor` type alongside the auto-generated entries from the
|
|
7
|
+
* Figma export of `packages/figma-tokens/source/semantic-colors.json`.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/** Text/foreground color tokens (for the `c` prop on text elements) */
|
|
@@ -62,7 +63,19 @@ export type BackgroundColor =
|
|
|
62
63
|
| 'background.inverse.filled'
|
|
63
64
|
| 'background.inverse.filled-hover'
|
|
64
65
|
| 'background.input'
|
|
65
|
-
| 'background.backdrop'
|
|
66
|
+
| 'background.backdrop'
|
|
67
|
+
| 'background.surface.raised'
|
|
68
|
+
| 'background.surface.overlay'
|
|
69
|
+
| 'background.entity.task.light'
|
|
70
|
+
| 'background.entity.task.icon'
|
|
71
|
+
| 'background.entity.initiative.light'
|
|
72
|
+
| 'background.entity.initiative.icon'
|
|
73
|
+
| 'background.entity.goal.light'
|
|
74
|
+
| 'background.entity.goal.icon'
|
|
75
|
+
| 'background.entity.meeting.light'
|
|
76
|
+
| 'background.entity.meeting.icon'
|
|
77
|
+
| 'background.entity.deliverable.light'
|
|
78
|
+
| 'background.entity.deliverable.icon';
|
|
66
79
|
|
|
67
80
|
/** Stroke/border color tokens (for borders and dividers) */
|
|
68
81
|
export type StrokeColor =
|
|
@@ -99,9 +112,19 @@ export type IconColor =
|
|
|
99
112
|
| 'icon.primary.strong'
|
|
100
113
|
| 'icon.medium';
|
|
101
114
|
|
|
115
|
+
/** Chart series color tokens (for Recharts series colors, legend swatches, etc.) */
|
|
116
|
+
export type ChartColor =
|
|
117
|
+
| 'chart.series.1'
|
|
118
|
+
| 'chart.series.2'
|
|
119
|
+
| 'chart.series.3'
|
|
120
|
+
| 'chart.series.4'
|
|
121
|
+
| 'chart.series.5'
|
|
122
|
+
| 'chart.series.6';
|
|
123
|
+
|
|
102
124
|
/** Any semantic color token */
|
|
103
125
|
export type SemanticColor =
|
|
104
126
|
| TextColor
|
|
105
127
|
| BackgroundColor
|
|
106
128
|
| StrokeColor
|
|
107
|
-
| IconColor
|
|
129
|
+
| IconColor
|
|
130
|
+
| ChartColor;
|
package/src/tokens/colors.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Color primitives synced from Figma Design System (VCLfybgU3OaUUPrQdBaVmP).
|
|
3
3
|
* Figma source of truth: Primitives collection.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: `dark[]`, `greenAlpha`, and `grapeAlpha` have been hand-tuned in code
|
|
6
|
+
* ahead of the Figma back-sync to give dark mode real surface depth and proper
|
|
7
|
+
* brand-tinted alphas. Once those values land in Figma the file can revert to
|
|
8
|
+
* fully auto-generated.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
import type { MantineColorsTuple } from '@mantine/core';
|
|
@@ -21,6 +26,14 @@ export const green: MantineColorsTuple = [
|
|
|
21
26
|
'#005a3e',
|
|
22
27
|
];
|
|
23
28
|
|
|
29
|
+
export const greenAlpha = {
|
|
30
|
+
'08': 'rgba(77, 155, 127, 0.08)',
|
|
31
|
+
'10': 'rgba(77, 155, 127, 0.10)',
|
|
32
|
+
'12': 'rgba(77, 155, 127, 0.12)',
|
|
33
|
+
'15': 'rgba(77, 155, 127, 0.15)',
|
|
34
|
+
'20': 'rgba(77, 155, 127, 0.20)',
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
24
37
|
export const blue: MantineColorsTuple = [
|
|
25
38
|
'#e7f5ff',
|
|
26
39
|
'#d0ebff',
|
|
@@ -72,19 +85,41 @@ export const grayAlpha = {
|
|
|
72
85
|
'9-60': 'rgba(17, 24, 39, 0.60)',
|
|
73
86
|
} as const;
|
|
74
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Dark neutral scale tuned for sophisticated dark-mode surface depth (cool
|
|
90
|
+
* near-black with a subtle blue cast). 0 → lightest, 9 → darkest, matching
|
|
91
|
+
* the Mantine palette convention so dark-mode semantic mappings flip naturally
|
|
92
|
+
* against the light gray scale.
|
|
93
|
+
*
|
|
94
|
+
* Surface roles per index:
|
|
95
|
+
* 0 (#e7eaf2) headings, inverse content
|
|
96
|
+
* 1 (#c8cdd9) body text
|
|
97
|
+
* 2 (#8a92a6) subdued text
|
|
98
|
+
* 3 (#4a5163) disabled text, subdued borders
|
|
99
|
+
* 4 (#323847) default borders
|
|
100
|
+
* 5 (#252b38) hover / pressed surface
|
|
101
|
+
* 6 (#1c222d) elevated surface (popovers, dropdowns)
|
|
102
|
+
* 7 (#161b24) default surface (cards, panels)
|
|
103
|
+
* 8 (#11151c) body background
|
|
104
|
+
* 9 (#0b0e14) outer canvas / chrome
|
|
105
|
+
*/
|
|
75
106
|
export const dark: MantineColorsTuple = [
|
|
76
|
-
'#
|
|
77
|
-
'#
|
|
78
|
-
'#
|
|
79
|
-
'#
|
|
80
|
-
'#
|
|
81
|
-
'#
|
|
82
|
-
'#
|
|
83
|
-
'#
|
|
84
|
-
'#
|
|
85
|
-
'#
|
|
107
|
+
'#e7eaf2',
|
|
108
|
+
'#c8cdd9',
|
|
109
|
+
'#8a92a6',
|
|
110
|
+
'#4a5163',
|
|
111
|
+
'#323847',
|
|
112
|
+
'#252b38',
|
|
113
|
+
'#1c222d',
|
|
114
|
+
'#161b24',
|
|
115
|
+
'#11151c',
|
|
116
|
+
'#0b0e14',
|
|
86
117
|
];
|
|
87
118
|
|
|
119
|
+
export const darkAlpha = {
|
|
120
|
+
'70': 'rgba(11, 14, 20, 0.70)',
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
88
123
|
export const primary: MantineColorsTuple = [
|
|
89
124
|
'#edf7f3',
|
|
90
125
|
'#d5f0e5',
|
|
@@ -164,6 +199,13 @@ export const grape: MantineColorsTuple = [
|
|
|
164
199
|
'#862e9c',
|
|
165
200
|
];
|
|
166
201
|
|
|
202
|
+
export const grapeAlpha = {
|
|
203
|
+
'10': 'rgba(190, 75, 219, 0.10)',
|
|
204
|
+
'12': 'rgba(190, 75, 219, 0.12)',
|
|
205
|
+
'15': 'rgba(190, 75, 219, 0.15)',
|
|
206
|
+
'20': 'rgba(190, 75, 219, 0.20)',
|
|
207
|
+
} as const;
|
|
208
|
+
|
|
167
209
|
export const cyan: MantineColorsTuple = [
|
|
168
210
|
'#e3fafc',
|
|
169
211
|
'#c5f6fa',
|
package/src/tokens/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Design tokens that mirror the LM Design System Figma file
|
|
3
|
+
* (https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System).
|
|
4
|
+
*
|
|
5
|
+
* Source of truth lives in Figma; edit these files by hand to mirror Figma
|
|
6
|
+
* changes and use the Figma MCP (`get_design_context`, `search_design_system`)
|
|
7
|
+
* to fetch authoritative values.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
export * from './colors';
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Semantic color tokens
|
|
2
|
+
* Semantic color tokens.
|
|
3
|
+
*
|
|
3
4
|
* Values reference primitives in colors.ts; inline rgba for alpha variants.
|
|
4
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* NOTE: The dark-mode map and several new tokens (`background.surface.*`,
|
|
7
|
+
* `background.entity.*`, `chart.series.*`) are intentionally diverged from the
|
|
8
|
+
* Figma Design System (VCLfybgU3OaUUPrQdBaVmP) and are hand-tuned in code. The
|
|
9
|
+
* Figma source will be back-synced to match these values; until then, code is
|
|
10
|
+
* the source of truth.
|
|
11
|
+
*
|
|
12
|
+
* Original Figma source of truth: Tokens collection (Light / Dark modes).
|
|
5
13
|
*/
|
|
6
14
|
|
|
7
15
|
import {
|
|
8
16
|
black,
|
|
9
17
|
dark,
|
|
18
|
+
darkAlpha,
|
|
10
19
|
gray,
|
|
11
20
|
grayAlpha,
|
|
12
21
|
green,
|
|
22
|
+
greenAlpha,
|
|
23
|
+
grape,
|
|
24
|
+
grapeAlpha,
|
|
13
25
|
indigo,
|
|
14
26
|
indigoAlpha,
|
|
15
27
|
red,
|
|
@@ -105,91 +117,183 @@ export const semanticColors = {
|
|
|
105
117
|
'background.input': white,
|
|
106
118
|
'background.backdrop': grayAlpha['9-60'],
|
|
107
119
|
'icon.medium': gray[7],
|
|
120
|
+
|
|
121
|
+
// Elevated-surface tokens. In light mode both raised and overlay share the
|
|
122
|
+
// default white surface — depth comes from shadow, not color. In dark mode
|
|
123
|
+
// they step up the dark scale for visual elevation.
|
|
124
|
+
'background.surface.raised': white,
|
|
125
|
+
'background.surface.overlay': white,
|
|
126
|
+
|
|
127
|
+
// Entity-accent tokens (used by Spotlight Quick Create + similar surfaces).
|
|
128
|
+
// Each pair maps to one of the five semantic role families so they auto-flip
|
|
129
|
+
// with mode. The fifth slot (deliverable) borrows the grape family because
|
|
130
|
+
// there's no sixth core semantic role.
|
|
131
|
+
'background.entity.task.light': green[0],
|
|
132
|
+
'background.entity.task.icon': green[7],
|
|
133
|
+
'background.entity.initiative.light': indigoAlpha['10'],
|
|
134
|
+
'background.entity.initiative.icon': indigo[7],
|
|
135
|
+
'background.entity.goal.light': teal[0],
|
|
136
|
+
'background.entity.goal.icon': teal[7],
|
|
137
|
+
'background.entity.meeting.light': yellowAlpha['10'],
|
|
138
|
+
'background.entity.meeting.icon': yellow[7],
|
|
139
|
+
'background.entity.deliverable.light': grape[0],
|
|
140
|
+
'background.entity.deliverable.icon': grape[7],
|
|
141
|
+
|
|
142
|
+
// Chart series — deterministic, tuned-for-stacking palette consumed by
|
|
143
|
+
// budget / dashboard / trend charts. Sorted by visual weight; series 1 is
|
|
144
|
+
// the primary brand and the rest stack underneath.
|
|
145
|
+
'chart.series.1': green[7],
|
|
146
|
+
'chart.series.2': indigo[6],
|
|
147
|
+
'chart.series.3': teal[6],
|
|
148
|
+
'chart.series.4': yellow[7],
|
|
149
|
+
'chart.series.5': red[7],
|
|
150
|
+
'chart.series.6': grape[6],
|
|
108
151
|
} as const;
|
|
109
152
|
|
|
110
153
|
/** Dark mode semantic color values */
|
|
111
154
|
export const semanticColorsDark = {
|
|
112
|
-
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'background.default': dark[6],
|
|
116
|
-
'background.default-hover': dark[5],
|
|
117
|
-
'text.default': white,
|
|
118
|
-
'stroke.default': dark[4],
|
|
119
|
-
'stroke.light': dark[5],
|
|
120
|
-
'background.body': dark[7],
|
|
121
|
-
'text.disabled.default': dark[3],
|
|
155
|
+
// Foundation surfaces — distinct levels for real depth hierarchy.
|
|
156
|
+
'background.body': dark[8],
|
|
157
|
+
'background.default': dark[7],
|
|
158
|
+
'background.default-hover': dark[6],
|
|
122
159
|
'background.disabled.default': dark[6],
|
|
123
160
|
'background.transparent': transparent,
|
|
124
|
-
'background.
|
|
125
|
-
'background.
|
|
126
|
-
|
|
127
|
-
'background.
|
|
128
|
-
'background.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
'background.
|
|
137
|
-
'background.
|
|
138
|
-
'background.
|
|
139
|
-
'background.
|
|
140
|
-
'
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
'
|
|
145
|
-
'background.primary.
|
|
161
|
+
'background.input': dark[7],
|
|
162
|
+
'background.backdrop': darkAlpha['70'],
|
|
163
|
+
|
|
164
|
+
'background.surface.raised': dark[6],
|
|
165
|
+
'background.surface.overlay': dark[5],
|
|
166
|
+
|
|
167
|
+
// Subdued (neutral) surfaces — tied to the dark scale so they keep their
|
|
168
|
+
// relationship with `background.default`. `ultralight` is intentionally a
|
|
169
|
+
// *deeper* recess in dark mode (`dark[9]`, two steps below the card) rather
|
|
170
|
+
// than the single off-white step of light mode — near-black perceptually
|
|
171
|
+
// compresses, so the wrapper needs more delta to read as a distinct frame
|
|
172
|
+
// behind cards (kanban columns, assessment blocks, drawer wells).
|
|
173
|
+
'background.subdued.light': dark[6],
|
|
174
|
+
'background.subdued.light-hover': dark[5],
|
|
175
|
+
'background.subdued.ultralight': dark[9],
|
|
176
|
+
'background.subdued.filled': dark[5],
|
|
177
|
+
'background.subdued.filled-hover': dark[4],
|
|
178
|
+
|
|
179
|
+
// Brand / role filled surfaces — keep the strong saturated hue so primary
|
|
180
|
+
// CTAs still read as primary in dark.
|
|
181
|
+
'background.primary.filled': green[7],
|
|
182
|
+
'background.primary.filled-hover': green[6],
|
|
183
|
+
'background.warning.filled': yellow[6],
|
|
184
|
+
'background.warning.filled-hover': yellow[7],
|
|
185
|
+
'background.success.filled': teal[6],
|
|
186
|
+
'background.success.filled-hover': teal[7],
|
|
187
|
+
'background.danger.filled': red[7],
|
|
188
|
+
'background.danger.filled-hover': red[8],
|
|
189
|
+
'background.information.filled': indigo[6],
|
|
190
|
+
'background.information.filled-hover': indigo[7],
|
|
191
|
+
|
|
192
|
+
// Soft / tinted role surfaces — translucent washes over the dark base, so
|
|
193
|
+
// they read as brand-tinted instead of near-black.
|
|
194
|
+
'background.primary.light': greenAlpha['12'],
|
|
195
|
+
'background.primary.light-hover': greenAlpha['20'],
|
|
196
|
+
'background.warning.light': yellowAlpha['12'],
|
|
146
197
|
'background.warning.light-hover': yellowAlpha['20'],
|
|
198
|
+
'background.success.light': tealAlpha['12'],
|
|
147
199
|
'background.success.light-hover': tealAlpha['20'],
|
|
148
|
-
'background.danger.light
|
|
149
|
-
'background.
|
|
150
|
-
'
|
|
151
|
-
'text.danger.disabled': redAlpha['50'],
|
|
152
|
-
'stroke.success.default': teal[3],
|
|
153
|
-
'stroke.danger.default': red[7],
|
|
154
|
-
'stroke.subdued.strong': gray[3],
|
|
155
|
-
'stroke.warning.default': yellow[3],
|
|
156
|
-
'background.information.filled': indigo[8],
|
|
157
|
-
'background.information.filled-hover': indigo[9],
|
|
158
|
-
'background.information.light': indigoAlpha['15'],
|
|
200
|
+
'background.danger.light': redAlpha['12'],
|
|
201
|
+
'background.danger.light-hover': redAlpha['20'],
|
|
202
|
+
'background.information.light': indigoAlpha['12'],
|
|
159
203
|
'background.information.light-hover': indigoAlpha['20'],
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
204
|
+
|
|
205
|
+
// Inverse fills (e.g. tooltips, default-on-dark anchors) — light surface,
|
|
206
|
+
// dark text/icon.
|
|
163
207
|
'background.inverse.filled': dark[0],
|
|
164
208
|
'background.inverse.filled-hover': dark[1],
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
'
|
|
182
|
-
'
|
|
183
|
-
'text.
|
|
209
|
+
|
|
210
|
+
// Entity accents — soft translucent role washes for the icon block,
|
|
211
|
+
// strong saturated tone for the icon foreground.
|
|
212
|
+
'background.entity.task.light': greenAlpha['12'],
|
|
213
|
+
'background.entity.task.icon': green[3],
|
|
214
|
+
'background.entity.initiative.light': indigoAlpha['15'],
|
|
215
|
+
'background.entity.initiative.icon': indigo[3],
|
|
216
|
+
'background.entity.goal.light': tealAlpha['12'],
|
|
217
|
+
'background.entity.goal.icon': teal[3],
|
|
218
|
+
'background.entity.meeting.light': yellowAlpha['15'],
|
|
219
|
+
'background.entity.meeting.icon': yellow[4],
|
|
220
|
+
'background.entity.deliverable.light': grapeAlpha['15'],
|
|
221
|
+
'background.entity.deliverable.icon': grape[3],
|
|
222
|
+
|
|
223
|
+
// Text colors — sit on the dark surface scale. AA contrast against
|
|
224
|
+
// background.body (dark[8]) verified at component-build time.
|
|
225
|
+
'text.title': dark[0],
|
|
226
|
+
'text.default': dark[1],
|
|
227
|
+
'text.subdued.default': dark[2],
|
|
228
|
+
'text.subdued.strong': dark[1],
|
|
229
|
+
'text.disabled.default': dark[3],
|
|
230
|
+
// `text.inverse` is the label color that sits on FILLED / SATURATED brand
|
|
231
|
+
// backgrounds (primary button green, danger red, avatar colour swatches,
|
|
232
|
+
// dark tooltip surfaces). Those backgrounds keep the same hue in both
|
|
233
|
+
// modes, so their contrast text must stay near-white — flipping this to a
|
|
234
|
+
// dark value produces unreadable black-on-green / black-on-red elements
|
|
235
|
+
// in dark mode. Keep this in sync with the light-mode value.
|
|
236
|
+
'text.inverse': white,
|
|
237
|
+
'text.link': green[3],
|
|
238
|
+
|
|
239
|
+
// Brand / role text — bright, readable on dark surfaces.
|
|
240
|
+
'text.primary.default': green[3],
|
|
241
|
+
'text.primary.light': green[4],
|
|
242
|
+
'text.warning.default': yellow[4],
|
|
243
|
+
'text.warning.strong': yellow[2],
|
|
244
|
+
'text.success.default': teal[3],
|
|
245
|
+
'text.success.strong': teal[2],
|
|
246
|
+
'text.danger.default': red[4],
|
|
247
|
+
'text.danger.strong': red[2],
|
|
248
|
+
'text.danger.disabled': redAlpha['50'],
|
|
249
|
+
'text.information.default': indigo[3],
|
|
250
|
+
'text.information.strong': indigo[2],
|
|
251
|
+
|
|
252
|
+
// Strokes — borders, dividers, focus rings.
|
|
253
|
+
'stroke.default': dark[4],
|
|
254
|
+
'stroke.light': dark[5],
|
|
184
255
|
'stroke.strong': dark[2],
|
|
185
|
-
'
|
|
256
|
+
'stroke.subdued.default': dark[5],
|
|
257
|
+
'stroke.subdued.strong': dark[3],
|
|
258
|
+
// `stroke.inverse` is the border color used by `outline-inverse` button /
|
|
259
|
+
// icon-button variants that sit on top of saturated coloured backgrounds.
|
|
260
|
+
// Same reasoning as `text.inverse` — the host surface doesn't flip, so the
|
|
261
|
+
// border on top stays white in both modes.
|
|
262
|
+
'stroke.inverse': white,
|
|
186
263
|
'stroke.icon': dark[1],
|
|
264
|
+
'stroke.focus.default': green[4],
|
|
265
|
+
'stroke.focus.strong': green[3],
|
|
266
|
+
'stroke.primary.default': green[5],
|
|
267
|
+
'stroke.success.default': teal[4],
|
|
268
|
+
'stroke.danger.default': red[5],
|
|
269
|
+
'stroke.warning.default': yellow[5],
|
|
270
|
+
'stroke.information.default': indigo[4],
|
|
271
|
+
|
|
272
|
+
// Icons.
|
|
273
|
+
'icon.default': dark[1],
|
|
274
|
+
'icon.light': dark[3],
|
|
275
|
+
'icon.medium': dark[2],
|
|
276
|
+
'icon.inverse': dark[9],
|
|
187
277
|
'icon.primary.default': green[3],
|
|
188
|
-
'icon.primary.strong': green[
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'icon.
|
|
278
|
+
'icon.primary.strong': green[2],
|
|
279
|
+
'icon.danger.default': red[4],
|
|
280
|
+
'icon.danger.strong': red[2],
|
|
281
|
+
'icon.danger.disabled': redAlpha['50'],
|
|
282
|
+
'icon.warning.default': yellow[4],
|
|
283
|
+
'icon.warning.strong': yellow[2],
|
|
284
|
+
'icon.success.default': teal[3],
|
|
285
|
+
'icon.success.strong': teal[2],
|
|
286
|
+
'icon.information.default': indigo[3],
|
|
287
|
+
'icon.information.strong': indigo[2],
|
|
288
|
+
|
|
289
|
+
// Chart series — brighter values so series read clearly against the dark
|
|
290
|
+
// surface. Order matches the light map for stable legends.
|
|
291
|
+
'chart.series.1': green[4],
|
|
292
|
+
'chart.series.2': indigo[3],
|
|
293
|
+
'chart.series.3': teal[3],
|
|
294
|
+
'chart.series.4': yellow[4],
|
|
295
|
+
'chart.series.5': red[4],
|
|
296
|
+
'chart.series.6': grape[3],
|
|
193
297
|
} as const;
|
|
194
298
|
|
|
195
299
|
function semanticKeyToCssVarName(key: string): string {
|
|
@@ -1,41 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Semantic design token CSS custom properties (radius, spacing, shadows, z-index, font).
|
|
3
3
|
* Injected by ThemeProvider so components can use var(--radius-*), var(--spacing-*), etc.
|
|
4
|
+
*
|
|
5
|
+
* Shadows are emitted twice — once under the light scheme selector, once under
|
|
6
|
+
* the dark scheme selector — so `--shadow-*` flips automatically with the
|
|
7
|
+
* Mantine color scheme. All other tokens (radius, spacing, z-index, font) are
|
|
8
|
+
* mode-agnostic and live in a single shared block.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
import { figmaRadius } from './radius';
|
|
7
|
-
import { figmaShadows } from './shadows';
|
|
12
|
+
import { figmaShadows, figmaShadowsDark } from './shadows';
|
|
8
13
|
import { figmaSpacing } from './spacing';
|
|
9
14
|
import { figmaFontFamilies } from './typography';
|
|
10
15
|
import { zIndex } from './zIndex';
|
|
11
16
|
|
|
12
|
-
function
|
|
17
|
+
function buildSharedTokensCss(): string {
|
|
13
18
|
const lines: string[] = [
|
|
14
19
|
':root, [data-mantine-color-scheme="light"], [data-mantine-color-scheme="dark"] {',
|
|
15
20
|
];
|
|
16
21
|
|
|
17
|
-
// Radius
|
|
18
22
|
for (const [key, value] of Object.entries(figmaRadius)) {
|
|
19
23
|
lines.push(` --radius-${key}: ${value};`);
|
|
20
24
|
}
|
|
21
25
|
lines.push(` --radius-default: ${figmaRadius.lg};`);
|
|
22
26
|
|
|
23
|
-
// Spacing
|
|
24
27
|
for (const [key, value] of Object.entries(figmaSpacing)) {
|
|
25
28
|
lines.push(` --spacing-${key}: ${value};`);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
// Shadows
|
|
29
|
-
for (const [key, value] of Object.entries(figmaShadows)) {
|
|
30
|
-
lines.push(` --shadow-${key}: ${value};`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Z-index
|
|
34
31
|
for (const [key, value] of Object.entries(zIndex)) {
|
|
35
32
|
lines.push(` --z-index-${key}: ${value};`);
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
// Font families
|
|
39
35
|
lines.push(
|
|
40
36
|
` --font-family-monospace: "${figmaFontFamilies['font-family-monospace']}", "Courier New", monospace;`,
|
|
41
37
|
);
|
|
@@ -50,4 +46,30 @@ function buildSemanticTokensCss(): string {
|
|
|
50
46
|
return lines.join('\n');
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
function buildShadowCss(
|
|
50
|
+
shadowMap: Record<string, string>,
|
|
51
|
+
selector: string,
|
|
52
|
+
): string {
|
|
53
|
+
const lines = [`${selector} {`];
|
|
54
|
+
for (const [key, value] of Object.entries(shadowMap)) {
|
|
55
|
+
// Emit both --shadow-* (used by vanilla-extract `tokens.shadow.*`) and
|
|
56
|
+
// --mantine-shadow-* (consumed by Mantine's Card/Paper/Modal defaults)
|
|
57
|
+
// so the entire component surface flips with the active color scheme.
|
|
58
|
+
lines.push(` --shadow-${key}: ${value};`);
|
|
59
|
+
lines.push(` --mantine-shadow-${key}: ${value};`);
|
|
60
|
+
}
|
|
61
|
+
lines.push('}');
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const sharedTokensCss = buildSharedTokensCss();
|
|
66
|
+
const lightShadowsCss = buildShadowCss(
|
|
67
|
+
figmaShadows,
|
|
68
|
+
':root, [data-mantine-color-scheme="light"]',
|
|
69
|
+
);
|
|
70
|
+
const darkShadowsCss = buildShadowCss(
|
|
71
|
+
figmaShadowsDark,
|
|
72
|
+
'[data-mantine-color-scheme="dark"]',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export const semanticTokensCss = `${sharedTokensCss}\n${lightShadowsCss}\n${darkShadowsCss}`;
|
package/src/tokens/shadows.ts
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Shadow tokens.
|
|
3
|
+
*
|
|
4
|
+
* Both modes deliberately keep cards / papers / modals / drawers / headers
|
|
5
|
+
* flat: depth is carried by the body↔surface background delta plus the 1px
|
|
6
|
+
* `stroke.default` border on each surface. The original Figma export used a
|
|
7
|
+
* 10% black drop-shadow on every elevation level, which stacked up to a
|
|
8
|
+
* visually noisy "everything floats" look once cards were nested inside
|
|
9
|
+
* drawers, modals, and dashboards.
|
|
10
|
+
*
|
|
11
|
+
* `lg` is the one exception: floating overlays (popovers, picker flyouts)
|
|
12
|
+
* sit on top of scrollable canvas content where a border alone does not
|
|
13
|
+
* read as "lifted off the page". They get a single soft drop-shadow tuned
|
|
14
|
+
* per mode so they remain clearly elevated.
|
|
4
15
|
*/
|
|
5
16
|
|
|
6
17
|
export const figmaShadows = {
|
|
7
|
-
sm: '
|
|
8
|
-
md: '
|
|
9
|
-
lg: '0px
|
|
10
|
-
xl: '
|
|
18
|
+
sm: 'none',
|
|
19
|
+
md: 'none',
|
|
20
|
+
lg: '0px 8px 16px -4px rgba(0, 0, 0, 0.08), 0px 2px 4px -2px rgba(0, 0, 0, 0.06)',
|
|
21
|
+
xl: 'none',
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Dark-mode shadow values. Same flat treatment as light mode, but `lg` uses
|
|
26
|
+
* a heavier black alpha because the underlying surfaces are already dark —
|
|
27
|
+
* a 10% black drop-shadow that reads as elevation in light mode disappears
|
|
28
|
+
* entirely against `dark[7]`/`dark[8]`.
|
|
29
|
+
*/
|
|
30
|
+
export const figmaShadowsDark = {
|
|
31
|
+
sm: 'none',
|
|
32
|
+
md: 'none',
|
|
33
|
+
lg: '0px 8px 16px -4px rgba(0, 0, 0, 0.25)',
|
|
34
|
+
xl: 'none',
|
|
11
35
|
} as const;
|