@evolve.ui/theme-system 1.1.0 → 1.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/{allThemes-CFS_9-tk.cjs → allThemes-3d8nhymo.cjs} +12 -2
- package/allThemes-3d8nhymo.cjs.map +1 -0
- package/{allThemes-OV-ayn4C.mjs → allThemes-Abn6KDm7.mjs} +7 -3
- package/allThemes-Abn6KDm7.mjs.map +1 -0
- package/allThemes.cjs +1 -1
- package/allThemes.d.cts +1 -0
- package/allThemes.d.cts.map +1 -1
- package/allThemes.d.mts +1 -0
- package/allThemes.d.mts.map +1 -1
- package/allThemes.mjs +1 -1
- package/brands-CE0wIkKX.d.cts +58 -0
- package/brands-CE0wIkKX.d.cts.map +1 -0
- package/brands-CE0wIkKX.d.mts +58 -0
- package/brands-CE0wIkKX.d.mts.map +1 -0
- package/brands.cjs +16 -13
- package/brands.cjs.map +1 -1
- package/brands.d.cts +2 -2
- package/brands.d.mts +2 -2
- package/brands.mjs +16 -13
- package/brands.mjs.map +1 -1
- package/css/EverydayHarbor.css +377 -0
- package/css/EverydayHarbor.effects.css +650 -0
- package/{default-b-tgfyxR.cjs → default-B6VWF0Js.cjs} +237 -36
- package/default-B6VWF0Js.cjs.map +1 -0
- package/{default-B2n4PnsJ.mjs → default-BogJDhKE.mjs} +238 -37
- package/default-BogJDhKE.mjs.map +1 -0
- package/effects.cjs +26 -0
- package/effects.cjs.map +1 -0
- package/effects.d.cts +22 -0
- package/effects.d.cts.map +1 -0
- package/effects.d.mts +22 -0
- package/effects.d.mts.map +1 -0
- package/effects.mjs +22 -0
- package/effects.mjs.map +1 -0
- package/evolve-ui.entries.json +5 -0
- package/index.cjs +12 -5
- package/index.cjs.map +1 -1
- package/index.d.cts +108 -4
- package/index.d.cts.map +1 -1
- package/index.d.mts +108 -4
- package/index.d.mts.map +1 -1
- package/index.mjs +9 -6
- package/index.mjs.map +1 -1
- package/package.json +5 -1
- package/{plugin-DPtvp4lp.cjs → plugin-B3PF4RMI.cjs} +299 -110
- package/plugin-B3PF4RMI.cjs.map +1 -0
- package/{plugin-D4eCyUJe.mjs → plugin-D6aLCeNB.mjs} +258 -111
- package/plugin-D6aLCeNB.mjs.map +1 -0
- package/src/effects/__fixtures__/fixtureLayer.ts +88 -0
- package/src/effects/brands/EverydayHarbor.ts +392 -0
- package/src/effects/classes.ts +19 -0
- package/src/effects/emit.ts +314 -0
- package/src/effects/hooks.ts +493 -0
- package/src/effects/index.ts +2 -0
- package/src/effects/layers.ts +18 -0
- package/src/effects/rules.ts +90 -0
- package/src/effects/rules.typecheck.ts +50 -0
- package/src/effects/tokens.ts +148 -0
- package/src/effects/validate.ts +683 -0
- package/src/effects/vocabulary.ts +32 -0
- package/src/index.ts +3 -0
- package/src/tailwind/animation.ts +30 -0
- package/src/tailwind/borderWidth.ts +14 -0
- package/src/tailwind/plugin.ts +230 -239
- package/src/tailwind/reducedMotion.ts +164 -0
- package/src/tailwind/text.ts +4 -2
- package/src/themeValues.snapshot.ts +6735 -0
- package/src/themes/EverydayHarbor.ts +147 -0
- package/src/themes/allThemes.ts +2 -0
- package/src/themes/brandSources.ts +37 -0
- package/src/themes/brands.ts +41 -15
- package/src/themes/contrastFloors.ts +26 -0
- package/src/themes/inkRamps.ts +43 -0
- package/src/themes/runtime/buildTheme.ts +6 -19
- package/src/themes/runtime/everydayHarbor.ts +3 -0
- package/src/themes/semanticColorTokenSteps.ts +5 -2
- package/src/themes/themeValues.ts +374 -0
- package/src/themes/wcag.ts +48 -0
- package/tailwind/plugin.cjs +5 -1
- package/tailwind/plugin.d.cts +458 -1
- package/tailwind/plugin.d.cts.map +1 -1
- package/tailwind/plugin.d.mts +458 -1
- package/tailwind/plugin.d.mts.map +1 -1
- package/tailwind/plugin.mjs +2 -2
- package/themes/runtime/default.cjs +1 -1
- package/themes/runtime/default.mjs +1 -1
- package/themes/themeType.d.cts +1 -1
- package/themes/themeType.d.mts +1 -1
- package/allThemes-CFS_9-tk.cjs.map +0 -1
- package/allThemes-OV-ayn4C.mjs.map +0 -1
- package/brands-n30rolHQ.d.cts +0 -33
- package/brands-n30rolHQ.d.cts.map +0 -1
- package/brands-n30rolHQ.d.mts +0 -33
- package/brands-n30rolHQ.d.mts.map +0 -1
- package/default-B2n4PnsJ.mjs.map +0 -1
- package/default-b-tgfyxR.cjs.map +0 -1
- package/plugin-D4eCyUJe.mjs.map +0 -1
- package/plugin-DPtvp4lp.cjs.map +0 -1
- package/src/themes/generate.ts +0 -46
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ColorAliasesObject, NonColorCSSVariables } from './generateCSSVars';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Harbor Ink" palette (brief of 2026-10-07), solid tokens only. The effect layer comes later.
|
|
5
|
+
*
|
|
6
|
+
* Ink-first: every ramp is reversed (inkRamps.ts) so the shared semantic map keeps working on a navy
|
|
7
|
+
* canvas. The brand differs from Default's map ONLY in `inkOverrides` and `inkShape`;
|
|
8
|
+
* EverydayHarbor.spec pins that the diff is exactly those keys.
|
|
9
|
+
*
|
|
10
|
+
* Where the approved hexes land (exact, asserted by EverydayHarbor.spec):
|
|
11
|
+
* surface-50 #0B1626 canvas (background-primary, text-inverse) surface-100 #12223A card
|
|
12
|
+
* surface-200 #1A2E4A raised surface-300 #56708F border / ring track
|
|
13
|
+
* surface-500 #A9B6C6 muted text (solid) surface-600 #B9C5D3 muted text (glass)
|
|
14
|
+
* surface-900 #EEF2F6 text-primary
|
|
15
|
+
* primary-400 #2E6BA6 harbor blue (seed) primary-500 #70B2EE the on-ink action step (see `harbor`)
|
|
16
|
+
* info-400 #8EC5E8 links / info (seed) info-500 #A5D1ED ring arc (icon-secondary, ring option B)
|
|
17
|
+
* tertiary-400 #E3B341 gold focus ring (seed)
|
|
18
|
+
* success-400 #A3B04E olive (seed) warning-400 #D9963E (seed, CTA gradient end)
|
|
19
|
+
* critical-400 #E5534B (seed, NOT in the brief: proposed, Dave to confirm)
|
|
20
|
+
*
|
|
21
|
+
* Surface ramp: seven steps are approved anchors (50, 100, 200, 300, 500, 600, 900). The other three
|
|
22
|
+
* are filled monotonically by per-channel sRGB interpolation, rounded to nearest:
|
|
23
|
+
* 400 = midpoint of 300 and 500; 700 and 800 = one third and two thirds of the way from 600 to 900.
|
|
24
|
+
* EverydayHarbor.spec recomputes them, so the rule is pinned rather than prose.
|
|
25
|
+
*/
|
|
26
|
+
import {
|
|
27
|
+
colorInfo400,
|
|
28
|
+
colorInfo500,
|
|
29
|
+
colorInfo600,
|
|
30
|
+
colorSuccess400,
|
|
31
|
+
colorSurface100,
|
|
32
|
+
colorSurface50,
|
|
33
|
+
colorTertiary400,
|
|
34
|
+
colorWarning100,
|
|
35
|
+
colorWarning400,
|
|
36
|
+
colorWarning500,
|
|
37
|
+
} from '../tailwind/cssVars';
|
|
38
|
+
import { wrapVar } from '../utils/wrapVar';
|
|
39
|
+
import { nonColorVariables as sharedSemanticMap } from './Default';
|
|
40
|
+
import { anchoredSteps, inkNormalSteps } from './inkRamps';
|
|
41
|
+
import { hexToChannels } from './wcag';
|
|
42
|
+
|
|
43
|
+
const harborGenerated = inkNormalSteps('#2E6BA6');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Harbor primary ramp. 50-400 are the generated ink steps (seed #2E6BA6 stays on 400). Two steps are
|
|
47
|
+
* pinned by hand because the generated 500 (#4489CB) is too dark for the pairs core hard-codes
|
|
48
|
+
* around it:
|
|
49
|
+
* - 500 #70B2EE is "the on-ink action step": navy text-inverse on it (filled button) 8.1:1, it as text
|
|
50
|
+
* on the canvas / card / raised panel (outlined, transparent, filledInverse) 8.1 / 7.1 / 6.1:1, and
|
|
51
|
+
* as text on the dark status tints (transparent Callout actions) >= 4.9:1 on the tightest, info-light.
|
|
52
|
+
* The reference orb blue #5B9BD8 was tried first and only reaches 3.7:1 on info-light.
|
|
53
|
+
* - 600 #95C3ED is the midpoint of 500 and the generated 700, so the ramp stays strictly lighter.
|
|
54
|
+
* 700-900 (hover / active text) are the generated steps.
|
|
55
|
+
*/
|
|
56
|
+
const harbor = {
|
|
57
|
+
...harborGenerated,
|
|
58
|
+
'500': hexToChannels('#70B2EE'),
|
|
59
|
+
'600': hexToChannels('#95C3ED'),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const coreColors = {
|
|
63
|
+
amber: inkNormalSteps('#D9963E'), // CTA gradient end
|
|
64
|
+
coral: inkNormalSteps('#E5534B'), // proposed critical
|
|
65
|
+
gold: inkNormalSteps('#E3B341'),
|
|
66
|
+
harbor,
|
|
67
|
+
ink: anchoredSteps({
|
|
68
|
+
'50': '#0B1626',
|
|
69
|
+
'100': '#12223A',
|
|
70
|
+
'200': '#1A2E4A',
|
|
71
|
+
'300': '#56708F',
|
|
72
|
+
'400': '#8093AB', // interpolated: midpoint of 300 and 500
|
|
73
|
+
'500': '#A9B6C6',
|
|
74
|
+
'600': '#B9C5D3',
|
|
75
|
+
'700': '#CBD4DF', // interpolated: 1/3 from 600 to 900
|
|
76
|
+
'800': '#DCE3EA', // interpolated: 2/3 from 600 to 900
|
|
77
|
+
'900': '#EEF2F6',
|
|
78
|
+
}),
|
|
79
|
+
olive: inkNormalSteps('#A3B04E'),
|
|
80
|
+
sky: inkNormalSteps('#8EC5E8'),
|
|
81
|
+
slate: inkNormalSteps('#56708F'),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const colorAliases = {
|
|
85
|
+
critical: coreColors.coral,
|
|
86
|
+
info: coreColors.sky,
|
|
87
|
+
primary: coreColors.harbor,
|
|
88
|
+
secondary: coreColors.slate,
|
|
89
|
+
success: coreColors.olive,
|
|
90
|
+
surface: coreColors.ink,
|
|
91
|
+
tertiary: coreColors.gold,
|
|
92
|
+
warning: coreColors.amber,
|
|
93
|
+
} satisfies ColorAliasesObject;
|
|
94
|
+
|
|
95
|
+
const BLACK_SHADOW = (y: number, blur: number, alpha: number) => `0px ${y}px ${blur}px rgb(0 0 0 / ${alpha}%)`;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The only colour bindings that differ from the shared map. Reasons:
|
|
99
|
+
* - overlay: the shared map scrims with surface-900, which is now the palest step; use the navy.
|
|
100
|
+
* - background-secondary: the card colour (#12223A), not slate-50.
|
|
101
|
+
* - status text/icons/strong fills: the shared map picks far-from-canvas steps (700-900) to darken
|
|
102
|
+
* on white; on reversed ramps those are near-white and lose hue, so bind the seed (400) or 500.
|
|
103
|
+
* - warning-dark: bold Badge / BannerNotification warning put text-primary (pale) and icon-primary on it
|
|
104
|
+
* (Badge.tsx 'text-text-primary', BannerIcon color='primary'), so it needs a DEEP amber (warning-100
|
|
105
|
+
* #754C17: 6.7:1 text, 3.6:1 icon). critical-dark / info-dark / success-dark carry text-inverse
|
|
106
|
+
* (navy) and keep the shared light bindings.
|
|
107
|
+
* - focus-primary: gold ring (brief). link-*: light blue links (brief).
|
|
108
|
+
* - icon-secondary: ring arc #A5D1ED (option B). icon-disabled is NOT overridden: the shared
|
|
109
|
+
* surface-300 binding already resolves to the ring track colour #56708F.
|
|
110
|
+
*/
|
|
111
|
+
const inkOverrides = {
|
|
112
|
+
'--color-background-overlay': wrapVar(colorSurface50),
|
|
113
|
+
'--color-background-secondary': wrapVar(colorSurface100),
|
|
114
|
+
'--color-background-success-dark': wrapVar(colorSuccess400),
|
|
115
|
+
'--color-background-warning-dark': wrapVar(colorWarning100),
|
|
116
|
+
'--color-focus-primary': wrapVar(colorTertiary400),
|
|
117
|
+
'--color-icon-secondary': wrapVar(colorInfo500),
|
|
118
|
+
'--color-icon-success': wrapVar(colorSuccess400),
|
|
119
|
+
'--color-icon-warning': wrapVar(colorWarning400),
|
|
120
|
+
'--color-link-hover': wrapVar(colorInfo500),
|
|
121
|
+
'--color-link-pressed': wrapVar(colorInfo600),
|
|
122
|
+
'--color-link-primary': wrapVar(colorInfo400),
|
|
123
|
+
'--color-text-info': wrapVar(colorInfo400),
|
|
124
|
+
'--color-text-success': wrapVar(colorSuccess400),
|
|
125
|
+
'--color-text-warning': wrapVar(colorWarning500),
|
|
126
|
+
} as const satisfies Partial<Record<NonColorCSSVariables, string>>;
|
|
127
|
+
|
|
128
|
+
/** Shape tokens: Inter for every heading (h1-h4 read --font-family-secondary), black drop shadows. */
|
|
129
|
+
const inkShape = {
|
|
130
|
+
'--box-shadow-01': BLACK_SHADOW(4, 4, 45),
|
|
131
|
+
'--box-shadow-02': `${BLACK_SHADOW(1, 4, 45)}, ${BLACK_SHADOW(1, 8, 35)}`,
|
|
132
|
+
'--box-shadow-03': `${BLACK_SHADOW(2, 8, 45)}, ${BLACK_SHADOW(4, 16, 35)}`,
|
|
133
|
+
'--box-shadow-04': `${BLACK_SHADOW(3, 12, 50)}, ${BLACK_SHADOW(6, 24, 40)}`,
|
|
134
|
+
'--box-shadow-05': BLACK_SHADOW(-4, 4, 45),
|
|
135
|
+
'--container-border-radius': '12px',
|
|
136
|
+
'--font-family-secondary': "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
137
|
+
// --text-transform-button stays 'uppercase' (inherited). OPEN for Dave: the brief is silent.
|
|
138
|
+
} as const satisfies Partial<Record<NonColorCSSVariables, string>>;
|
|
139
|
+
|
|
140
|
+
export const nonColorVariables = {
|
|
141
|
+
...sharedSemanticMap,
|
|
142
|
+
...inkOverrides,
|
|
143
|
+
...inkShape,
|
|
144
|
+
} satisfies Record<NonColorCSSVariables, string>;
|
|
145
|
+
|
|
146
|
+
/** Exported for EverydayHarbor.spec ("differs from the shared map exactly here"). */
|
|
147
|
+
export const everydayHarborOverrides = { ...inkOverrides, ...inkShape };
|
package/src/themes/allThemes.ts
CHANGED
|
@@ -3,10 +3,12 @@ import type { CleanTheme } from './themeType';
|
|
|
3
3
|
|
|
4
4
|
import { basketballTrainingTheme } from './runtime/basketballTraining';
|
|
5
5
|
import { defaultTheme } from './runtime/default';
|
|
6
|
+
import { everydayHarborTheme } from './runtime/everydayHarbor';
|
|
6
7
|
import { professionalBrandTheme } from './runtime/professionalBrand';
|
|
7
8
|
|
|
8
9
|
export const allThemes = {
|
|
9
10
|
BasketballTraining: basketballTrainingTheme,
|
|
10
11
|
Default: defaultTheme,
|
|
12
|
+
EverydayHarbor: everydayHarborTheme,
|
|
11
13
|
ProfessionalBrand: professionalBrandTheme,
|
|
12
14
|
} as const satisfies Record<Brands, CleanTheme>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one build-time table of brand palette modules. Internal: not a tsdown entry and not
|
|
3
|
+
* re-exported from index.ts (brands.ts stays pure data for the browser). Consumers read this instead
|
|
4
|
+
* of hand-listing brands: tsdown.config.ts onSuccess, runtime/buildTheme.ts, themeValues.ts,
|
|
5
|
+
* contrast.spec.ts and tailwind/text.spec.ts. allThemes.ts keeps its explicit map because index.ts
|
|
6
|
+
* re-exports the per-brand runtime themes; `satisfies Record<Brands, …>` makes a missing brand a compile error.
|
|
7
|
+
*
|
|
8
|
+
* Key order is load-bearing: the themeValues snapshot is an ORDERED entry list built by iterating
|
|
9
|
+
* this object, so keep BasketballTraining, Default, ProfessionalBrand and append new brands (EverydayHarbor).
|
|
10
|
+
*/
|
|
11
|
+
import type { StepObject } from '../utils/color';
|
|
12
|
+
import type { Brands } from './brands';
|
|
13
|
+
|
|
14
|
+
import * as BasketballTraining from './BasketballTraining';
|
|
15
|
+
import * as Default from './Default';
|
|
16
|
+
import * as EverydayHarbor from './EverydayHarbor';
|
|
17
|
+
import { type ColorAliasesObject, type NonColorCSSVariables, makeColorCSSVarsFromObject } from './generateCSSVars';
|
|
18
|
+
import * as ProfessionalBrand from './ProfessionalBrand';
|
|
19
|
+
|
|
20
|
+
export type BrandModule = {
|
|
21
|
+
readonly coreColors: Readonly<Record<string, StepObject>>;
|
|
22
|
+
readonly colorAliases: ColorAliasesObject;
|
|
23
|
+
readonly nonColorVariables: Readonly<Record<NonColorCSSVariables, string>>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const brandSources = {
|
|
27
|
+
BasketballTraining,
|
|
28
|
+
Default,
|
|
29
|
+
ProfessionalBrand,
|
|
30
|
+
EverydayHarbor,
|
|
31
|
+
} as const satisfies Record<Brands, BrandModule>;
|
|
32
|
+
|
|
33
|
+
/** `{ ...nonColorVariables, ...palette vars }` in that order: exactly what the CSS generator receives. */
|
|
34
|
+
export const brandCssVariables = (brand: Brands): Record<string, string> => ({
|
|
35
|
+
...brandSources[brand].nonColorVariables,
|
|
36
|
+
...makeColorCSSVarsFromObject(brandSources[brand].colorAliases),
|
|
37
|
+
});
|
package/src/themes/brands.ts
CHANGED
|
@@ -1,37 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The `Brands` union is the ONE authored list of brand names. Every other per-brand table is derived
|
|
3
|
+
* from `brandRegistry` or checked against this union with `satisfies Record<Brands, …>`, so a missing
|
|
4
|
+
* entry is a compile error rather than a silent omission.
|
|
5
|
+
*
|
|
6
|
+
* Stays pure data: this module is the published `@evolve.ui/theme-system/brands` subpath and is
|
|
7
|
+
* imported by Storybook in the browser, so it must not import brand palette modules.
|
|
8
|
+
*/
|
|
9
|
+
export type Brands = 'BasketballTraining' | 'Default' | 'EverydayHarbor' | 'ProfessionalBrand';
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ProfessionalBrand: 'brand-professional',
|
|
9
|
-
};
|
|
11
|
+
/**
|
|
12
|
+
* `paper`: light canvas; the brand renders on `bg-background-primary`.
|
|
13
|
+
* `ink`: inverted canvas; Storybook frames it separately instead of in the white "All together" grid.
|
|
14
|
+
*/
|
|
15
|
+
export type BrandCanvas = 'ink' | 'paper';
|
|
10
16
|
|
|
11
17
|
export type BrandRegistryEntry = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
readonly canvas: BrandCanvas;
|
|
19
|
+
readonly cssClass: `brand-${string}`;
|
|
20
|
+
readonly label: string;
|
|
21
|
+
/** Kept for API compatibility; always equal to the registry key. */
|
|
22
|
+
readonly storybookKey: Brands;
|
|
23
|
+
readonly themeKey: Brands;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
26
|
export const brandRegistry = {
|
|
19
27
|
BasketballTraining: {
|
|
20
|
-
|
|
28
|
+
canvas: 'paper',
|
|
29
|
+
cssClass: 'brand-basketball',
|
|
21
30
|
label: 'Basketball Training',
|
|
22
31
|
storybookKey: 'BasketballTraining',
|
|
23
32
|
themeKey: 'BasketballTraining',
|
|
24
33
|
},
|
|
25
34
|
Default: {
|
|
26
|
-
|
|
35
|
+
canvas: 'paper',
|
|
36
|
+
cssClass: 'brand-default',
|
|
27
37
|
label: 'Default (Neutral)',
|
|
28
38
|
storybookKey: 'Default',
|
|
29
39
|
themeKey: 'Default',
|
|
30
40
|
},
|
|
31
41
|
ProfessionalBrand: {
|
|
32
|
-
|
|
42
|
+
canvas: 'paper',
|
|
43
|
+
cssClass: 'brand-professional',
|
|
33
44
|
label: 'Professional Brand',
|
|
34
45
|
storybookKey: 'ProfessionalBrand',
|
|
35
46
|
themeKey: 'ProfessionalBrand',
|
|
36
47
|
},
|
|
48
|
+
// Appended after the paper brands: the themeValues snapshot and `brands` order follow key order.
|
|
49
|
+
EverydayHarbor: {
|
|
50
|
+
canvas: 'ink',
|
|
51
|
+
cssClass: 'brand-harbor',
|
|
52
|
+
label: 'Everyday Harbor',
|
|
53
|
+
storybookKey: 'EverydayHarbor',
|
|
54
|
+
themeKey: 'EverydayHarbor',
|
|
55
|
+
},
|
|
37
56
|
} as const satisfies Record<Brands, BrandRegistryEntry>;
|
|
57
|
+
|
|
58
|
+
/** Registry key order (previously a hand list ordered Basketball, Professional, Default). */
|
|
59
|
+
export const brands: Brands[] = Object.keys(brandRegistry) as Brands[];
|
|
60
|
+
|
|
61
|
+
export const brandsToCSS: Record<Brands, string> = Object.fromEntries(
|
|
62
|
+
brands.map((brand) => [brand, brandRegistry[brand].cssClass])
|
|
63
|
+
) as Record<Brands, string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const TEXT_AA = 4.5;
|
|
2
|
+
|
|
3
|
+
export const TEXT_AAA_BODY = 7;
|
|
4
|
+
|
|
5
|
+
export const UI_AA = 3;
|
|
6
|
+
|
|
7
|
+
export const TEXT_CONTRAST_TOKENS = [
|
|
8
|
+
['--color-link-primary', TEXT_AA],
|
|
9
|
+
['--color-text-critical', TEXT_AA],
|
|
10
|
+
['--color-text-secondary', TEXT_AA],
|
|
11
|
+
['--color-text-info', TEXT_AA],
|
|
12
|
+
['--color-text-warning', TEXT_AA],
|
|
13
|
+
['--color-text-success', TEXT_AA],
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export const UI_CONTRAST_TOKENS = [
|
|
17
|
+
'--color-focus-primary',
|
|
18
|
+
'--color-icon-critical',
|
|
19
|
+
'--color-icon-info',
|
|
20
|
+
'--color-icon-warning',
|
|
21
|
+
'--color-icon-success',
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export const FILLED_BUTTON_FAMILIES = ['primary', 'critical', 'surface'] as const;
|
|
25
|
+
|
|
26
|
+
export const FILLED_BUTTON_STEPS = ['500', '700', '900'] as const;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ramp builders for canvas-dark ("ink") brands. Deliberately NOT in utils/color.ts: the Ground
|
|
3
|
+
* baseline records that file's export list, and the themeValues snapshot pins its generators.
|
|
4
|
+
* These builders only rearrange generator output or parse approved hexes; they never change
|
|
5
|
+
* what generateNormalColorSteps returns.
|
|
6
|
+
*
|
|
7
|
+
* Why invert: core-components hard-code palette steps relative to a light canvas
|
|
8
|
+
* (`bg-primary-500 text-text-inverse` filled, `text-primary-500` on `bg-background-primary`
|
|
9
|
+
* outlined, `hover:bg-primary-100 hover:text-primary-700`, ...). Those pairs only keep their
|
|
10
|
+
* contrast when step 50 sits next to the canvas, 900 sits far from it, and text-inverse equals the
|
|
11
|
+
* canvas. Reversing every ramp keeps that geometry on an ink canvas, so the brand can reuse the
|
|
12
|
+
* shared semantic map almost unchanged (see EverydayHarbor.ts).
|
|
13
|
+
*/
|
|
14
|
+
import { type StepObject, type Steps, colorStepArrayToObject, generateNormalColorSteps, steps } from '../utils/color';
|
|
15
|
+
import { hexToChannels, luminance, parseChannels } from './wcag';
|
|
16
|
+
|
|
17
|
+
/** A `#RRGGBB` literal. Validated when the theme module loads; a bad literal throws there. */
|
|
18
|
+
export type Hex = `#${string}`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* generateNormalColorSteps(seed), reversed: 50 is the deepest shade, 900 the palest tint.
|
|
22
|
+
* The seed lands on step 400 (the generator's 500), and the generator's 400 tint lands on 500.
|
|
23
|
+
* Example: seed #8EC5E8 -> 400 '142 197 232' (#8EC5E8), 500 '165 209 237' (#A5D1ED).
|
|
24
|
+
*/
|
|
25
|
+
export const inkNormalSteps = (seed: Hex): StepObject =>
|
|
26
|
+
colorStepArrayToObject([...generateNormalColorSteps(seed)].reverse());
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A hand-authored ramp from approved hexes, for families no seed reproduces (the ink surface ramp
|
|
30
|
+
* has seven approved anchors). Every step is required; output is the generator's channel format.
|
|
31
|
+
* Throws at load when a literal is malformed or the ramp is not strictly lighter at each step, so a
|
|
32
|
+
* swapped anchor fails immediately instead of in review.
|
|
33
|
+
*/
|
|
34
|
+
export const anchoredSteps = (hexes: Readonly<Record<Steps, Hex>>): StepObject => {
|
|
35
|
+
const ramp = Object.fromEntries(steps.map((step) => [step, hexToChannels(hexes[step])])) as StepObject;
|
|
36
|
+
steps.slice(1).forEach((step, index) => {
|
|
37
|
+
const previous = steps[index];
|
|
38
|
+
if (luminance(parseChannels(ramp[step])) <= luminance(parseChannels(ramp[previous]))) {
|
|
39
|
+
throw new Error(`anchoredSteps: step ${step} (${hexes[step]}) is not lighter than step ${previous}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return ramp;
|
|
43
|
+
};
|
|
@@ -4,23 +4,8 @@ import type { CleanTheme, Theme, TypographySpec } from '../themeType';
|
|
|
4
4
|
import { boxShadow } from '../../tailwind/boxShadow';
|
|
5
5
|
import * as cssVars from '../../tailwind/cssVars';
|
|
6
6
|
import { fontFamily } from '../../tailwind/fontFamily';
|
|
7
|
-
import
|
|
7
|
+
import { brandSources } from '../brandSources';
|
|
8
8
|
import { breakpoints, radius, spacing, width } from '../common';
|
|
9
|
-
import * as Default from '../Default';
|
|
10
|
-
import * as ProfessionalBrand from '../ProfessionalBrand';
|
|
11
|
-
|
|
12
|
-
const themeSources = {
|
|
13
|
-
BasketballTraining,
|
|
14
|
-
Default,
|
|
15
|
-
ProfessionalBrand,
|
|
16
|
-
} satisfies Record<
|
|
17
|
-
Brands,
|
|
18
|
-
{
|
|
19
|
-
colorAliases: Record<string, unknown>;
|
|
20
|
-
coreColors: Record<string, unknown>;
|
|
21
|
-
nonColorVariables: Record<string, string>;
|
|
22
|
-
}
|
|
23
|
-
>;
|
|
24
9
|
|
|
25
10
|
const variable = (name: string): string => `var(${name})`;
|
|
26
11
|
|
|
@@ -136,10 +121,12 @@ const buildTypography = (): Theme['typography'] => {
|
|
|
136
121
|
spacing: variable(cssVars.letterSpacingH3),
|
|
137
122
|
weight: variable(cssVars.fontWeightH3),
|
|
138
123
|
}),
|
|
124
|
+
// Mirrors `.hui-text-h3`'s mobile block in `tailwind/text.ts` — keep in lockstep
|
|
125
|
+
// (DS-97 / SA-04; `tailwind/text.spec.ts` binds the two).
|
|
139
126
|
mobile: textSpec({
|
|
140
127
|
family: fontFamily.secondary,
|
|
141
|
-
height: '
|
|
142
|
-
size: '
|
|
128
|
+
height: '27px',
|
|
129
|
+
size: '18px',
|
|
143
130
|
spacing: variable(cssVars.letterSpacingH3Mobile),
|
|
144
131
|
weight: variable(cssVars.fontWeightH3),
|
|
145
132
|
}),
|
|
@@ -184,7 +171,7 @@ const buildTypography = (): Theme['typography'] => {
|
|
|
184
171
|
};
|
|
185
172
|
|
|
186
173
|
export const buildRuntimeTheme = (brand: Brands): CleanTheme => {
|
|
187
|
-
const source =
|
|
174
|
+
const source = brandSources[brand];
|
|
188
175
|
const v = variable;
|
|
189
176
|
|
|
190
177
|
return {
|
|
@@ -3,8 +3,11 @@ import { nonColorVariables } from './Default';
|
|
|
3
3
|
/**
|
|
4
4
|
* Maps each semantic color token (e.g. `text-critical`) to the core palette step
|
|
5
5
|
* it resolves to (e.g. `critical-500`), parsed from the Default brand's
|
|
6
|
-
* `nonColorVariables`. The semantic → step mapping is identical across
|
|
7
|
-
* (only the core hex values differ per brand), so Default is canonical.
|
|
6
|
+
* `nonColorVariables`. The semantic → step mapping is identical across the paper
|
|
7
|
+
* brands (only the core hex values differ per brand), so Default is canonical.
|
|
8
|
+
* Ink brands reuse this map on reversed ramps and rebind a handful of tokens
|
|
9
|
+
* (EverydayHarbor: `everydayHarborOverrides`), so this table describes Default and
|
|
10
|
+
* is not valid for them.
|
|
8
11
|
*
|
|
9
12
|
* Single source of truth for docs/tooling that need to show which step a semantic
|
|
10
13
|
* token points at — derive from this rather than hand-maintaining a duplicate table.
|