@ankhorage/surface 3.1.3 → 3.2.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.
- package/CHANGELOG.md +6 -0
- package/dist/components/navigation/helpers.d.ts.map +1 -1
- package/dist/components/navigation/helpers.js +16 -8
- package/dist/components/navigation/helpers.js.map +1 -1
- package/dist/internal/resolvers/resolveInteractiveColors.d.ts.map +1 -1
- package/dist/internal/resolvers/resolveInteractiveColors.js +19 -13
- package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
- package/dist/internal/resolvers/resolveSelectionControlColors.d.ts.map +1 -1
- package/dist/internal/resolvers/resolveSelectionControlColors.js +19 -10
- package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
- package/dist/theme/colorEngine.d.ts +4 -3
- package/dist/theme/colorEngine.d.ts.map +1 -1
- package/dist/theme/colorEngine.js +23 -122
- package/dist/theme/colorEngine.js.map +1 -1
- package/dist/theme/createSurfaceColorDiagnosticCollector.d.ts +14 -0
- package/dist/theme/createSurfaceColorDiagnosticCollector.d.ts.map +1 -0
- package/dist/theme/createSurfaceColorDiagnosticCollector.js +53 -0
- package/dist/theme/createSurfaceColorDiagnosticCollector.js.map +1 -0
- package/dist/theme/createSurfaceSemanticPolicy.d.ts +30 -0
- package/dist/theme/createSurfaceSemanticPolicy.d.ts.map +1 -0
- package/dist/theme/createSurfaceSemanticPolicy.js +118 -0
- package/dist/theme/createSurfaceSemanticPolicy.js.map +1 -0
- package/dist/theme/createTheme.d.ts.map +1 -1
- package/dist/theme/createTheme.js +2 -1
- package/dist/theme/createTheme.js.map +1 -1
- package/dist/theme/defaultTokens.d.ts +1 -1
- package/dist/theme/defaultTokens.d.ts.map +1 -1
- package/dist/theme/defaultTokens.js.map +1 -1
- package/dist/theme/resolveGlobalTokens.d.ts +1 -1
- package/dist/theme/resolveGlobalTokens.d.ts.map +1 -1
- package/dist/theme/resolveGlobalTokens.js.map +1 -1
- package/dist/theme/resolveRoleSemantics.d.ts +19 -0
- package/dist/theme/resolveRoleSemantics.d.ts.map +1 -0
- package/dist/theme/resolveRoleSemantics.js +29 -0
- package/dist/theme/resolveRoleSemantics.js.map +1 -0
- package/dist/theme/resolveSurfaceSemanticModel.d.ts +16 -0
- package/dist/theme/resolveSurfaceSemanticModel.d.ts.map +1 -0
- package/dist/theme/resolveSurfaceSemanticModel.js +128 -0
- package/dist/theme/resolveSurfaceSemanticModel.js.map +1 -0
- package/dist/theme/surfaceColorPolicy.d.ts +8 -0
- package/dist/theme/surfaceColorPolicy.d.ts.map +1 -0
- package/dist/theme/surfaceColorPolicy.js +8 -0
- package/dist/theme/surfaceColorPolicy.js.map +1 -0
- package/dist/theme/types.d.ts +52 -1
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/theme/types.js.map +1 -1
- package/package.json +1 -1
- package/src/components/navigation/helpers.test.ts +4 -4
- package/src/components/navigation/helpers.ts +17 -8
- package/src/index.test.ts +12 -0
- package/src/internal/resolvers/resolveInteractiveColors.test.ts +1 -0
- package/src/internal/resolvers/resolveInteractiveColors.ts +19 -13
- package/src/internal/resolvers/resolveSelectionControlColors.test.ts +3 -2
- package/src/internal/resolvers/resolveSelectionControlColors.ts +19 -10
- package/src/primitives/icon/PortableIcon.web.test.tsx +2 -7
- package/src/surfaceColor.test.ts +8 -10
- package/src/theme/colorEngine.test.ts +85 -34
- package/src/theme/colorEngine.ts +28 -147
- package/src/theme/createSurfaceColorDiagnosticCollector.ts +91 -0
- package/src/theme/createSurfaceSemanticPolicy.ts +174 -0
- package/src/theme/createTheme.ts +2 -1
- package/src/theme/defaultTokens.ts +4 -1
- package/src/theme/resolveGlobalTokens.ts +4 -1
- package/src/theme/resolveRoleSemantics.ts +96 -0
- package/src/theme/resolveSurfaceSemanticModel.ts +230 -0
- package/src/theme/surfaceColorPolicy.ts +7 -0
- package/src/theme/types.ts +65 -1
package/src/theme/colorEngine.ts
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ColorSwatch,
|
|
3
2
|
GeneratedThemeModeColors,
|
|
4
3
|
GeneratedThemeSwatches,
|
|
5
4
|
HexColor,
|
|
6
5
|
SemanticColorReferenceMap,
|
|
7
6
|
SemanticColorToken,
|
|
7
|
+
ThemeColorMode,
|
|
8
8
|
} from '@ankhorage/color-theory';
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
DARK_SEMANTIC_COLOR_REFERENCES,
|
|
11
11
|
generateThemeModeColors,
|
|
12
|
-
|
|
12
|
+
LIGHT_SEMANTIC_COLOR_REFERENCES,
|
|
13
13
|
parseHexColorOrThrow,
|
|
14
14
|
} from '@ankhorage/color-theory';
|
|
15
15
|
import type { ThemeConfig } from '@ankhorage/contracts';
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
BorderSemantics,
|
|
20
|
-
ContentSemantics,
|
|
21
|
-
NeutralSemantics,
|
|
22
|
-
RoleSemantics,
|
|
23
|
-
SurfaceSemantics,
|
|
24
|
-
ThemeSemantics,
|
|
25
|
-
ThemeTokens,
|
|
26
|
-
} from './types';
|
|
17
|
+
import { resolveSurfaceSemanticModel } from './resolveSurfaceSemanticModel';
|
|
18
|
+
import type { SurfaceColorDiagnostics, ThemeSemantics, ThemeTokens } from './types';
|
|
27
19
|
|
|
28
20
|
/**
|
|
29
21
|
* Surface semantic resolver: maps color-theory SemanticColorToken references
|
|
@@ -46,141 +38,44 @@ export function resolveSemanticColors(
|
|
|
46
38
|
) as SurfaceSemanticColors;
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
function buildNeutralSemantics(neutralSwatch: ColorSwatch, isDark: boolean): NeutralSemantics {
|
|
50
|
-
if (isDark) {
|
|
51
|
-
return {
|
|
52
|
-
bg: neutralSwatch[950],
|
|
53
|
-
bgSubtle: neutralSwatch[900],
|
|
54
|
-
surface: neutralSwatch[900],
|
|
55
|
-
surfaceHover: neutralSwatch[800],
|
|
56
|
-
surfaceActive: neutralSwatch[700],
|
|
57
|
-
border: neutralSwatch[800],
|
|
58
|
-
borderStrong: neutralSwatch[600],
|
|
59
|
-
divider: neutralSwatch[800],
|
|
60
|
-
text: neutralSwatch[50],
|
|
61
|
-
textMuted: neutralSwatch[200],
|
|
62
|
-
textSubtle: neutralSwatch[300],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
bg: neutralSwatch[50],
|
|
67
|
-
bgSubtle: neutralSwatch[100],
|
|
68
|
-
surface: neutralSwatch[100],
|
|
69
|
-
surfaceHover: neutralSwatch[200],
|
|
70
|
-
surfaceActive: neutralSwatch[300],
|
|
71
|
-
border: neutralSwatch[200],
|
|
72
|
-
borderStrong: neutralSwatch[300],
|
|
73
|
-
divider: neutralSwatch[200],
|
|
74
|
-
text: neutralSwatch[900],
|
|
75
|
-
textMuted: neutralSwatch[700],
|
|
76
|
-
textSubtle: neutralSwatch[600],
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function buildRoleSemantics(swatch: ColorSwatch, isDark: boolean): RoleSemantics {
|
|
81
|
-
const base = swatch[500];
|
|
82
|
-
const { foreground: onSolidText } = getReadableForeground(base);
|
|
83
|
-
|
|
84
|
-
if (isDark) {
|
|
85
|
-
return {
|
|
86
|
-
base,
|
|
87
|
-
hover: swatch[400],
|
|
88
|
-
strong: swatch[300],
|
|
89
|
-
softBg: swatch[900],
|
|
90
|
-
softHover: swatch[800],
|
|
91
|
-
softActive: swatch[700],
|
|
92
|
-
outline: swatch[500],
|
|
93
|
-
onSolidText,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
base,
|
|
99
|
-
hover: swatch[600],
|
|
100
|
-
strong: swatch[700],
|
|
101
|
-
softBg: swatch[100],
|
|
102
|
-
softHover: swatch[200],
|
|
103
|
-
softActive: swatch[300],
|
|
104
|
-
outline: swatch[400],
|
|
105
|
-
onSolidText,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
41
|
export function generatePalette(
|
|
110
42
|
config: ThemeConfig,
|
|
111
|
-
mode:
|
|
43
|
+
mode: ThemeColorMode = 'light',
|
|
112
44
|
): {
|
|
113
45
|
colors: ThemeTokens['colors'];
|
|
114
46
|
swatches: GeneratedThemeSwatches;
|
|
115
47
|
semantics: ThemeSemantics;
|
|
48
|
+
colorDiagnostics: SurfaceColorDiagnostics;
|
|
116
49
|
} {
|
|
117
50
|
const modeConfig = mode === 'dark' ? config.dark : config.light;
|
|
118
51
|
const isDark = mode === 'dark';
|
|
119
52
|
|
|
120
|
-
// Throws deterministically on invalid primary color
|
|
121
53
|
parseHexColorOrThrow(modeConfig.primaryColor);
|
|
122
54
|
|
|
123
55
|
const generated = generateThemeModeColors(modeConfig);
|
|
124
56
|
const { swatches } = generated;
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const semanticStatusSwatches = createDefaultSemanticStatusSwatches().swatches;
|
|
136
|
-
|
|
137
|
-
const danger = buildRoleSemantics(semanticStatusSwatches.danger, isDark);
|
|
138
|
-
const success = buildRoleSemantics(semanticStatusSwatches.success, isDark);
|
|
139
|
-
const warning = buildRoleSemantics(semanticStatusSwatches.warning, isDark);
|
|
140
|
-
// Keep destructive action and feedback error aligned until a dedicated error status swatch is introduced.
|
|
141
|
-
const error = danger;
|
|
142
|
-
const info = buildRoleSemantics(secondarySwatch, isDark);
|
|
143
|
-
const inverseSurface = isDark ? neutralSwatch[50] : neutralSwatch[900];
|
|
144
|
-
const inverseOnSurface = isDark ? neutralSwatch[900] : neutralSwatch[50];
|
|
145
|
-
|
|
146
|
-
const surfaceSemantics: SurfaceSemantics = {
|
|
147
|
-
default: neutral.surface,
|
|
148
|
-
subtle: neutral.bgSubtle,
|
|
149
|
-
raised: neutral.surface,
|
|
150
|
-
inverse: inverseSurface,
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const content: ContentSemantics = {
|
|
154
|
-
default: neutral.text,
|
|
155
|
-
muted: neutral.textMuted,
|
|
156
|
-
subtle: neutral.textSubtle,
|
|
157
|
-
inverse: inverseOnSurface,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const border: BorderSemantics = {
|
|
161
|
-
default: neutral.border,
|
|
162
|
-
strong: neutral.borderStrong,
|
|
163
|
-
focus: brand.outline,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const action: ActionSemantics = {
|
|
167
|
-
primary: brand,
|
|
168
|
-
neutral: buildRoleSemantics(neutralSwatch, isDark),
|
|
169
|
-
danger,
|
|
170
|
-
};
|
|
57
|
+
const references = isDark ? DARK_SEMANTIC_COLOR_REFERENCES : LIGHT_SEMANTIC_COLOR_REFERENCES;
|
|
58
|
+
const resolved = resolveSemanticColors(generated, references);
|
|
59
|
+
const { semantics, colorDiagnostics } = resolveSurfaceSemanticModel({
|
|
60
|
+
generated,
|
|
61
|
+
mode,
|
|
62
|
+
references,
|
|
63
|
+
resolved,
|
|
64
|
+
});
|
|
65
|
+
const { border, content, error, info, neutral, success, surface, warning } = semantics;
|
|
171
66
|
|
|
172
67
|
const colors: ThemeTokens['colors'] = {
|
|
173
|
-
primary:
|
|
174
|
-
secondary:
|
|
175
|
-
accent:
|
|
176
|
-
highlight:
|
|
177
|
-
tertiary:
|
|
178
|
-
quaternary:
|
|
68
|
+
primary: swatches.primary[500],
|
|
69
|
+
secondary: (swatches.secondary ?? swatches.primary)[500],
|
|
70
|
+
accent: (swatches.tertiary ?? swatches.primary)[500],
|
|
71
|
+
highlight: (swatches.quaternary ?? swatches.primary)[500],
|
|
72
|
+
tertiary: (swatches.tertiary ?? swatches.primary)[500],
|
|
73
|
+
quaternary: (swatches.quaternary ?? swatches.primary)[500],
|
|
179
74
|
background: neutral.bg,
|
|
180
|
-
surface:
|
|
181
|
-
text:
|
|
182
|
-
textSecondary:
|
|
183
|
-
border:
|
|
75
|
+
surface: surface.default,
|
|
76
|
+
text: content.default,
|
|
77
|
+
textSecondary: content.muted,
|
|
78
|
+
border: border.default,
|
|
184
79
|
error: error.base,
|
|
185
80
|
success: success.base,
|
|
186
81
|
warning: warning.base,
|
|
@@ -190,21 +85,7 @@ export function generatePalette(
|
|
|
190
85
|
return {
|
|
191
86
|
colors,
|
|
192
87
|
swatches,
|
|
193
|
-
semantics
|
|
194
|
-
|
|
195
|
-
brand,
|
|
196
|
-
secondary: buildRoleSemantics(secondarySwatch, isDark),
|
|
197
|
-
accent: buildRoleSemantics(tertiarySwatch, isDark),
|
|
198
|
-
highlight: buildRoleSemantics(quaternarySwatch, isDark),
|
|
199
|
-
danger,
|
|
200
|
-
success,
|
|
201
|
-
warning,
|
|
202
|
-
error,
|
|
203
|
-
info,
|
|
204
|
-
surface: surfaceSemantics,
|
|
205
|
-
content,
|
|
206
|
-
border,
|
|
207
|
-
action,
|
|
208
|
-
},
|
|
88
|
+
semantics,
|
|
89
|
+
colorDiagnostics,
|
|
209
90
|
};
|
|
210
91
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColorContrastContext,
|
|
3
|
+
ColorSelectionTarget,
|
|
4
|
+
ColorSwatch,
|
|
5
|
+
ColorSwatchTiePolicy,
|
|
6
|
+
HexColor,
|
|
7
|
+
} from '@ankhorage/color-theory';
|
|
8
|
+
import {
|
|
9
|
+
getContrastRatio,
|
|
10
|
+
getReadableForeground,
|
|
11
|
+
selectColorSwatchStep,
|
|
12
|
+
} from '@ankhorage/color-theory';
|
|
13
|
+
|
|
14
|
+
import type { SurfaceColorSelectionDiagnostic, SurfaceContrastDiagnostic } from './types';
|
|
15
|
+
|
|
16
|
+
/** Collect deterministic Color Theory evidence while Surface resolves semantic colors. */
|
|
17
|
+
export function createSurfaceColorDiagnosticCollector() {
|
|
18
|
+
return new SurfaceColorDiagnosticCollector();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class SurfaceColorDiagnosticCollector {
|
|
22
|
+
readonly selections: SurfaceColorSelectionDiagnostic[] = [];
|
|
23
|
+
readonly contrasts: SurfaceContrastDiagnostic[] = [];
|
|
24
|
+
|
|
25
|
+
readonly measureForeground = (
|
|
26
|
+
id: string,
|
|
27
|
+
foreground: HexColor,
|
|
28
|
+
background: HexColor,
|
|
29
|
+
minimumContrast: number,
|
|
30
|
+
): HexColor => {
|
|
31
|
+
const contrast = getContrastRatio(foreground, background);
|
|
32
|
+
this.contrasts.push({
|
|
33
|
+
id,
|
|
34
|
+
foreground,
|
|
35
|
+
background,
|
|
36
|
+
contrast,
|
|
37
|
+
minimumContrast,
|
|
38
|
+
passes: contrast >= minimumContrast,
|
|
39
|
+
});
|
|
40
|
+
return foreground;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
readonly measureSurface = (
|
|
44
|
+
id: string,
|
|
45
|
+
foreground: HexColor,
|
|
46
|
+
background: HexColor,
|
|
47
|
+
minimumContrast: number,
|
|
48
|
+
): SurfaceContrastDiagnostic => {
|
|
49
|
+
const contrast = getContrastRatio(foreground, background);
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
foreground,
|
|
53
|
+
background,
|
|
54
|
+
contrast,
|
|
55
|
+
minimumContrast,
|
|
56
|
+
passes: contrast >= minimumContrast,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
readonly selectColor = (
|
|
61
|
+
id: string,
|
|
62
|
+
swatch: ColorSwatch,
|
|
63
|
+
target: ColorSelectionTarget,
|
|
64
|
+
contexts: readonly ColorContrastContext[],
|
|
65
|
+
tiePolicy: ColorSwatchTiePolicy,
|
|
66
|
+
): HexColor => {
|
|
67
|
+
const result = selectColorSwatchStep(swatch, target, contexts, tiePolicy);
|
|
68
|
+
this.selections.push({ id, result });
|
|
69
|
+
if (!result.selected) {
|
|
70
|
+
throw new Error(`[surface] No '${id}' swatch step satisfies the required contrast.`);
|
|
71
|
+
}
|
|
72
|
+
return result.selected.hex;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
readonly selectForeground = (
|
|
76
|
+
id: string,
|
|
77
|
+
background: HexColor,
|
|
78
|
+
minimumContrast: number,
|
|
79
|
+
): HexColor => {
|
|
80
|
+
const selected = getReadableForeground(background);
|
|
81
|
+
this.contrasts.push({
|
|
82
|
+
id,
|
|
83
|
+
foreground: selected.foreground,
|
|
84
|
+
background,
|
|
85
|
+
contrast: selected.contrast,
|
|
86
|
+
minimumContrast,
|
|
87
|
+
passes: selected.contrast >= minimumContrast,
|
|
88
|
+
});
|
|
89
|
+
return selected.foreground;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColorContrastContext,
|
|
3
|
+
ColorSelectionTarget,
|
|
4
|
+
ColorSwatch,
|
|
5
|
+
ColorSwatchTiePolicy,
|
|
6
|
+
HexColor,
|
|
7
|
+
SemanticColorToken,
|
|
8
|
+
} from '@ankhorage/color-theory';
|
|
9
|
+
import { parseHexColorOrThrow } from '@ankhorage/color-theory';
|
|
10
|
+
|
|
11
|
+
import { SURFACE_COLOR_POLICY } from './surfaceColorPolicy';
|
|
12
|
+
import type {
|
|
13
|
+
ContentSemantics,
|
|
14
|
+
NeutralSemantics,
|
|
15
|
+
SurfaceContrastDiagnostic,
|
|
16
|
+
SurfaceSemantics,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
type ResolvedSemanticColors = Record<SemanticColorToken, HexColor>;
|
|
20
|
+
|
|
21
|
+
interface SurfaceSemanticMeasurements {
|
|
22
|
+
measureForeground: (
|
|
23
|
+
id: string,
|
|
24
|
+
foreground: HexColor,
|
|
25
|
+
background: HexColor,
|
|
26
|
+
minimumContrast: number,
|
|
27
|
+
) => HexColor;
|
|
28
|
+
measureSurface: (
|
|
29
|
+
id: string,
|
|
30
|
+
foreground: HexColor,
|
|
31
|
+
background: HexColor,
|
|
32
|
+
minimumContrast: number,
|
|
33
|
+
) => SurfaceContrastDiagnostic;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Create the canonical Surface policy for selecting and measuring semantic colors. */
|
|
37
|
+
export function createSurfaceSemanticPolicy(
|
|
38
|
+
resolved: ResolvedSemanticColors,
|
|
39
|
+
isDark: boolean,
|
|
40
|
+
measurements: SurfaceSemanticMeasurements,
|
|
41
|
+
) {
|
|
42
|
+
return new SurfaceSemanticPolicy(resolved, isDark, measurements);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class SurfaceSemanticPolicy {
|
|
46
|
+
readonly adjacentSurfaceContexts: readonly ColorContrastContext[];
|
|
47
|
+
readonly textSurfaceContexts: readonly ColorContrastContext[];
|
|
48
|
+
readonly tiePolicy: ColorSwatchTiePolicy;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly resolved: ResolvedSemanticColors,
|
|
52
|
+
private readonly isDark: boolean,
|
|
53
|
+
private readonly measurements: SurfaceSemanticMeasurements,
|
|
54
|
+
) {
|
|
55
|
+
this.adjacentSurfaceContexts = this.createSurfaceContexts(SURFACE_COLOR_POLICY.uiContrast);
|
|
56
|
+
this.textSurfaceContexts = this.createSurfaceContexts(SURFACE_COLOR_POLICY.textContrast);
|
|
57
|
+
this.tiePolicy = isDark ? 'lower-step' : 'higher-step';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
buildNeutral(neutralSwatch: ColorSwatch): NeutralSemantics {
|
|
61
|
+
return {
|
|
62
|
+
bg: this.resolved.background,
|
|
63
|
+
bgSubtle: this.resolved.surface,
|
|
64
|
+
surface: this.resolved.surface,
|
|
65
|
+
surfaceHover: this.isDark ? neutralSwatch[800] : neutralSwatch[200],
|
|
66
|
+
surfaceActive: this.isDark ? neutralSwatch[700] : neutralSwatch[300],
|
|
67
|
+
border: this.resolved.border,
|
|
68
|
+
borderStrong: this.isDark ? neutralSwatch[600] : neutralSwatch[300],
|
|
69
|
+
divider: this.resolved.divider,
|
|
70
|
+
text: this.resolved.text,
|
|
71
|
+
textMuted: this.resolved.textMuted,
|
|
72
|
+
textSubtle: this.isDark ? neutralSwatch[300] : neutralSwatch[600],
|
|
73
|
+
disabledBg: this.resolved.disabledBg,
|
|
74
|
+
disabledText: this.resolved.disabledText,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildSurface(inverse: HexColor): SurfaceSemantics {
|
|
79
|
+
return {
|
|
80
|
+
default: this.resolved.surface,
|
|
81
|
+
subtle: this.resolved.background,
|
|
82
|
+
raised: this.resolved.surfaceRaised,
|
|
83
|
+
sunken: this.resolved.background,
|
|
84
|
+
overlay: this.resolved.surfaceRaised,
|
|
85
|
+
scrim: '#0000008f',
|
|
86
|
+
disabled: this.resolved.disabledBg,
|
|
87
|
+
inverse,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
measureContent(content: ContentSemantics, surface: SurfaceSemantics): void {
|
|
92
|
+
const pairs = [
|
|
93
|
+
[
|
|
94
|
+
'content.default/background',
|
|
95
|
+
content.default,
|
|
96
|
+
surface.sunken,
|
|
97
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
'content.default/surface',
|
|
101
|
+
content.default,
|
|
102
|
+
surface.default,
|
|
103
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
104
|
+
],
|
|
105
|
+
['content.muted/surface', content.muted, surface.default, SURFACE_COLOR_POLICY.textContrast],
|
|
106
|
+
[
|
|
107
|
+
'content.subtle/surface',
|
|
108
|
+
content.subtle,
|
|
109
|
+
surface.default,
|
|
110
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
111
|
+
],
|
|
112
|
+
[
|
|
113
|
+
'content.disabled/disabled',
|
|
114
|
+
content.disabled,
|
|
115
|
+
surface.disabled,
|
|
116
|
+
SURFACE_COLOR_POLICY.disabledContrast,
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
'content.inverse/inverse',
|
|
120
|
+
content.inverse,
|
|
121
|
+
surface.inverse,
|
|
122
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
123
|
+
],
|
|
124
|
+
] as const;
|
|
125
|
+
for (const [id, foreground, background, minimumContrast] of pairs) {
|
|
126
|
+
this.measurements.measureForeground(
|
|
127
|
+
id,
|
|
128
|
+
parseHexColorOrThrow(foreground),
|
|
129
|
+
parseHexColorOrThrow(background),
|
|
130
|
+
minimumContrast,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
measureSurfaceSeparation(surface: SurfaceSemantics): SurfaceContrastDiagnostic[] {
|
|
136
|
+
return [
|
|
137
|
+
this.measureSurface('surface.default', surface.default),
|
|
138
|
+
this.measureSurface('surface.raised', surface.raised),
|
|
139
|
+
this.measureSurface('surface.disabled', surface.disabled),
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
neutralTarget(): ColorSelectionTarget {
|
|
144
|
+
return { lightness: this.isDark ? 0.65 : 0.55, chroma: 0.02 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
roleTarget(): ColorSelectionTarget {
|
|
148
|
+
return { lightness: this.isDark ? 0.65 : 0.5, chroma: 0.15 };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
strongNeutralTarget(): ColorSelectionTarget {
|
|
152
|
+
return { lightness: this.isDark ? 0.8 : 0.4, chroma: 0.02 };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
textTarget(): ColorSelectionTarget {
|
|
156
|
+
return { lightness: this.isDark ? 0.75 : 0.45, chroma: 0.15 };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private createSurfaceContexts(minimumContrast: number): readonly ColorContrastContext[] {
|
|
160
|
+
return [
|
|
161
|
+
{ id: 'background', against: this.resolved.background, minimumContrast },
|
|
162
|
+
{ id: 'surface', against: this.resolved.surface, minimumContrast },
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private measureSurface(id: string, foreground: string): SurfaceContrastDiagnostic {
|
|
167
|
+
return this.measurements.measureSurface(
|
|
168
|
+
id,
|
|
169
|
+
parseHexColorOrThrow(foreground),
|
|
170
|
+
this.resolved.background,
|
|
171
|
+
SURFACE_COLOR_POLICY.surfaceSeparation,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/theme/createTheme.ts
CHANGED
|
@@ -28,12 +28,13 @@ export function createTheme(
|
|
|
28
28
|
mode: 'light' | 'dark' = 'light',
|
|
29
29
|
activeFontId?: string | null,
|
|
30
30
|
): SurfaceTheme {
|
|
31
|
-
const { colors, swatches, semantics } = generatePalette(config, mode);
|
|
31
|
+
const { colors, swatches, semantics, colorDiagnostics } = generatePalette(config, mode);
|
|
32
32
|
const theme: SurfaceTheme = {
|
|
33
33
|
...resolveGlobalTokens(config.tokens),
|
|
34
34
|
colors,
|
|
35
35
|
swatches,
|
|
36
36
|
semantics,
|
|
37
|
+
colorDiagnostics,
|
|
37
38
|
config,
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -14,7 +14,10 @@ const EMPTY_FONT_MAP: Record<FontWeight, string | undefined> = {
|
|
|
14
14
|
normal: undefined,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export const DEFAULT_TOKENS: Omit<
|
|
17
|
+
export const DEFAULT_TOKENS: Omit<
|
|
18
|
+
ThemeTokens,
|
|
19
|
+
'colors' | 'swatches' | 'semantics' | 'colorDiagnostics'
|
|
20
|
+
> = {
|
|
18
21
|
spacing: { none: 0, xs: 4, s: 8, m: 16, l: 24, xl: 32, xxl: 48 },
|
|
19
22
|
radii: { none: 0, s: 4, m: 8, l: 16, full: 9999 },
|
|
20
23
|
typography: {
|
|
@@ -6,7 +6,10 @@ import { resolveNumericTokenOverrides } from './resolveNumericTokenOverrides';
|
|
|
6
6
|
import { resolveWeightTokenOverrides } from './resolveWeightTokenOverrides';
|
|
7
7
|
import type { ThemeTokens } from './types';
|
|
8
8
|
|
|
9
|
-
type GlobalThemeTokens = Omit<
|
|
9
|
+
type GlobalThemeTokens = Omit<
|
|
10
|
+
ThemeTokens,
|
|
11
|
+
'colors' | 'swatches' | 'semantics' | 'colorDiagnostics'
|
|
12
|
+
>;
|
|
10
13
|
|
|
11
14
|
export function resolveGlobalTokens(overrides?: ThemeGlobalTokenOverrides): GlobalThemeTokens {
|
|
12
15
|
validateZeroTokens(overrides);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ColorSwatch, HexColor } from '@ankhorage/color-theory';
|
|
2
|
+
|
|
3
|
+
import { SURFACE_COLOR_POLICY } from './surfaceColorPolicy';
|
|
4
|
+
import type { RoleSemantics } from './types';
|
|
5
|
+
|
|
6
|
+
interface ResolveRoleSemanticsInput {
|
|
7
|
+
base?: HexColor;
|
|
8
|
+
disabledBg: HexColor;
|
|
9
|
+
disabledText: HexColor;
|
|
10
|
+
hover?: HexColor;
|
|
11
|
+
id: string;
|
|
12
|
+
isDark: boolean;
|
|
13
|
+
measureForeground: (
|
|
14
|
+
id: string,
|
|
15
|
+
foreground: HexColor,
|
|
16
|
+
background: HexColor,
|
|
17
|
+
minimumContrast: number,
|
|
18
|
+
) => HexColor;
|
|
19
|
+
onSurfaceText: HexColor;
|
|
20
|
+
outline: HexColor;
|
|
21
|
+
selectForeground: (id: string, background: HexColor, minimumContrast: number) => HexColor;
|
|
22
|
+
swatch: ColorSwatch;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RoleStateColors {
|
|
26
|
+
base: HexColor;
|
|
27
|
+
hover: HexColor;
|
|
28
|
+
softActive: HexColor;
|
|
29
|
+
softBg: HexColor;
|
|
30
|
+
softHover: HexColor;
|
|
31
|
+
strong: HexColor;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Resolve every filled role state together with its measured foreground. */
|
|
35
|
+
export function resolveRoleSemantics({
|
|
36
|
+
base,
|
|
37
|
+
disabledBg,
|
|
38
|
+
disabledText,
|
|
39
|
+
hover,
|
|
40
|
+
id,
|
|
41
|
+
isDark,
|
|
42
|
+
measureForeground,
|
|
43
|
+
onSurfaceText,
|
|
44
|
+
outline,
|
|
45
|
+
selectForeground,
|
|
46
|
+
swatch,
|
|
47
|
+
}: ResolveRoleSemanticsInput): RoleSemantics {
|
|
48
|
+
const states = resolveRoleStateColors(swatch, isDark, base, hover);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...states,
|
|
52
|
+
outline,
|
|
53
|
+
onSurfaceText,
|
|
54
|
+
onSolidText: selectForeground(`${id}.base`, states.base, SURFACE_COLOR_POLICY.textContrast),
|
|
55
|
+
onHoverText: selectForeground(`${id}.hover`, states.hover, SURFACE_COLOR_POLICY.textContrast),
|
|
56
|
+
onStrongText: selectForeground(
|
|
57
|
+
`${id}.strong`,
|
|
58
|
+
states.strong,
|
|
59
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
60
|
+
),
|
|
61
|
+
onSoftText: selectForeground(`${id}.soft`, states.softBg, SURFACE_COLOR_POLICY.textContrast),
|
|
62
|
+
onSoftHoverText: selectForeground(
|
|
63
|
+
`${id}.softHover`,
|
|
64
|
+
states.softHover,
|
|
65
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
66
|
+
),
|
|
67
|
+
onSoftActiveText: selectForeground(
|
|
68
|
+
`${id}.softActive`,
|
|
69
|
+
states.softActive,
|
|
70
|
+
SURFACE_COLOR_POLICY.textContrast,
|
|
71
|
+
),
|
|
72
|
+
disabledBg,
|
|
73
|
+
onDisabledText: measureForeground(
|
|
74
|
+
`${id}.disabled`,
|
|
75
|
+
disabledText,
|
|
76
|
+
disabledBg,
|
|
77
|
+
SURFACE_COLOR_POLICY.disabledContrast,
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveRoleStateColors(
|
|
83
|
+
swatch: ColorSwatch,
|
|
84
|
+
isDark: boolean,
|
|
85
|
+
base?: HexColor,
|
|
86
|
+
hover?: HexColor,
|
|
87
|
+
): RoleStateColors {
|
|
88
|
+
return {
|
|
89
|
+
base: base ?? swatch[500],
|
|
90
|
+
hover: hover ?? (isDark ? swatch[400] : swatch[600]),
|
|
91
|
+
strong: isDark ? swatch[300] : swatch[700],
|
|
92
|
+
softBg: isDark ? swatch[900] : swatch[100],
|
|
93
|
+
softHover: isDark ? swatch[800] : swatch[200],
|
|
94
|
+
softActive: isDark ? swatch[700] : swatch[300],
|
|
95
|
+
};
|
|
96
|
+
}
|