@multiplatform.one/theme 7.34.4 → 7.36.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/package.json +20 -5
- package/src/audit/constraintAudit.spec.ts +274 -87
- package/src/audit/constraintAudit.ts +135 -120
- package/src/audit/constraintScope.spec.ts +1 -1
- package/src/audit/designTokens.ts +42 -0
- package/src/audit/themeMatrix.spec.ts +83 -2
- package/src/audit/themeMatrix.ts +33 -1
- package/src/figma/figmaTokens.spec.ts +26 -34
- package/src/figma/figmaTokens.ts +26 -50
- package/src/fonts/createFontLoader.ts +4 -1
- package/src/menuRow.ts +4 -1
- package/src/theme/FontKnobStyles.tsx +2 -1
- package/src/theme/Preset.stories.tsx +0 -1
- package/src/theme/Preset.tsx +6 -1
- package/src/theme/PresetContext.ts +4 -0
- package/src/theme/Surface.spec.tsx +114 -67
- package/src/theme/Surface.stories.tsx +8 -25
- package/src/theme/Surface.tsx +87 -43
- package/src/theme/accentFloor.spec.ts +37 -11
- package/src/theme/activateThemeConfig.ts +22 -6
- package/src/theme/animations/animations.spec.ts +62 -0
- package/src/theme/animations/css.ts +9 -35
- package/src/theme/animations/reactNative.ts +98 -25
- package/src/theme/boundaryFloor.spec.ts +57 -0
- package/src/theme/chartPalette.spec.ts +56 -27
- package/src/theme/chartPalette.ts +33 -28
- package/src/theme/colorRules.spec.ts +32 -72
- package/src/theme/cookies.spec.ts +49 -18
- package/src/theme/cookies.ts +36 -7
- package/src/theme/cookiesPersistence.spec.ts +5 -5
- package/src/theme/createDefaultThemeConfig.spec.ts +182 -20
- package/src/theme/createDefaultThemeConfig.ts +34 -28
- package/src/theme/createThemes.ts +169 -218
- package/src/theme/defaults/accent.ts +3 -35
- package/src/theme/defaults/base.ts +14 -53
- package/src/theme/defaults/builderOptions.ts +68 -85
- package/src/theme/defaults/categoryFonts.ts +10 -0
- package/src/theme/defaults/fonts.spec.ts +84 -4
- package/src/theme/defaults/fonts.ts +166 -124
- package/src/theme/defaults/house.ts +48 -0
- package/src/theme/defaults/index.ts +1 -0
- package/src/theme/defaults/neutralRamp.spec.ts +52 -47
- package/src/theme/devtools/ColorLineVisualizer.tsx +6 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.setMarkers.spec.tsx +13 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.tsx +14 -77
- package/src/theme/devtools/animationOptions.spec.ts +23 -37
- package/src/theme/devtools/animationOptions.ts +4 -6
- package/src/theme/devtools/buildSnapshot.ts +0 -3
- package/src/theme/devtools/knobIcons.tsx +4 -24
- package/src/theme/focusState.spec.ts +9 -30
- package/src/theme/index.ts +17 -0
- package/src/theme/intentFill.spec.ts +42 -0
- package/src/theme/knobs.ts +14 -42
- package/src/theme/layoutTokens.ts +15 -1
- package/src/theme/layoutTokensHooks.spec.tsx +8 -4
- package/src/theme/mergedTokens.ts +20 -0
- package/src/theme/motion.spec.ts +84 -0
- package/src/theme/motion.ts +53 -0
- package/src/theme/motionStops.ts +17 -0
- package/src/theme/motionTiming.ts +59 -0
- package/src/theme/mpo13Proof.spec.ts +43 -46
- package/src/theme/mpo19Proof.spec.ts +13 -14
- package/src/theme/namedInkFloor.spec.ts +60 -0
- package/src/theme/overlaySurfaces.tsx +330 -0
- package/src/theme/preset.types.ts +16 -0
- package/src/theme/presetPreview.ts +32 -0
- package/src/theme/presetRoot.spec.tsx +89 -0
- package/src/theme/presetStore.persistence.spec.ts +141 -0
- package/src/theme/presetStore.spec.ts +205 -0
- package/src/theme/presetStore.ts +286 -0
- package/src/theme/presetUserSync.spec.tsx +168 -0
- package/src/theme/presetUserSync.ts +47 -0
- package/src/theme/presets.spec.ts +22 -15
- package/src/theme/presets.ts +22 -10
- package/src/theme/radiusClass.spec.ts +60 -41
- package/src/theme/radiusClass.ts +17 -4
- package/src/theme/radiusClassTable.generated.ts +3 -3
- package/src/theme/readableColor.spec.ts +59 -152
- package/src/theme/readableColor.ts +34 -174
- package/src/theme/recipeInputs.ts +153 -295
- package/src/theme/recipes.ts +58 -18
- package/src/theme/resolveKnobs.spec.ts +528 -588
- package/src/theme/resolveKnobs.ts +250 -325
- package/src/theme/scale/ScaleStyles.native.tsx +19 -0
- package/src/theme/scale/ScaleStyles.tsx +48 -0
- package/src/theme/scale/activeScale.ts +30 -0
- package/src/theme/scale/deriveRecipeInputs.ts +110 -0
- package/src/theme/scale/generateScale.spec.ts +260 -0
- package/src/theme/scale/generateScale.ts +147 -0
- package/src/theme/scale/index.ts +54 -0
- package/src/theme/scale/installScaleConfig.native.spec.ts +77 -0
- package/src/theme/scale/installScaleConfig.native.ts +57 -0
- package/src/theme/scale/installScaleConfig.ts +7 -0
- package/src/theme/scale/scaleAttribute.ts +21 -0
- package/src/theme/scale/scaleCss.probe.spec.tsx +64 -0
- package/src/theme/scale/scaleCss.spec.ts +132 -0
- package/src/theme/scale/scaleCss.ts +95 -0
- package/src/theme/scale/scaleFonts.ts +106 -0
- package/src/theme/scale/scaleInputs.ts +322 -0
- package/src/theme/scale/themeScales.ts +181 -0
- package/src/theme/scale/useActiveScale.spec.tsx +129 -0
- package/src/theme/scale/useActiveScale.ts +95 -0
- package/src/theme/schemeStore.spec.ts +81 -0
- package/src/theme/schemeStore.ts +67 -0
- package/src/theme/shared.spec.ts +7 -6
- package/src/theme/shared.tsx +37 -3
- package/src/theme/sizeLadder.spec.ts +37 -36
- package/src/theme/sizeRecipes.spec.ts +179 -85
- package/src/theme/sizeRecipes.ts +48 -25
- package/src/theme/subsetThemes.spec.ts +9 -3
- package/src/theme/switchThumb.spec.ts +69 -0
- package/src/theme/syntaxFloor.spec.ts +11 -8
- package/src/theme/theme.hydration.native.spec.tsx +51 -0
- package/src/theme/theme.native.tsx +59 -21
- package/src/theme/theme.palette.spec.tsx +76 -0
- package/src/theme/theme.preset.native.spec.tsx +73 -0
- package/src/theme/theme.provider.spec.ts +143 -1
- package/src/theme/theme.tsx +61 -25
- package/src/theme/tileSizes.ts +6 -7
- package/src/theme/transitionProps.native.ts +25 -2
- package/src/theme/transitionProps.spec.ts +34 -0
- package/src/theme/transitionProps.ts +26 -2
- package/src/theme/usePresetSwitch.native.spec.tsx +68 -0
- package/src/theme/usePresetSwitch.native.ts +32 -0
- package/src/theme/usePresetSwitch.spec.tsx +103 -0
- package/src/theme/usePresetSwitch.ts +54 -0
- package/src/theme/useResolvedKnobs.hydration.spec.tsx +2 -2
- package/src/theme/useResolvedKnobs.ts +23 -90
- package/src/theme/useResolvedKnobsBehavior.spec.tsx +25 -30
- package/src/theme/useSchemePreference.native.ts +33 -0
- package/src/theme/useSchemePreference.spec.tsx +51 -0
- package/src/theme/useSchemePreference.ts +32 -0
- package/src/theme/useSchemePreference.web.spec.tsx +32 -0
- package/src/theme/useTheme.native.spec.tsx +68 -0
- package/src/theme/useTheme.native.ts +49 -24
- package/src/theme/useTheme.ts +7 -4
- package/types/audit/constraintAudit.d.ts +6 -5
- package/types/audit/constraintAudit.d.ts.map +1 -1
- package/types/audit/designTokens.d.ts +13 -0
- package/types/audit/designTokens.d.ts.map +1 -0
- package/types/audit/themeMatrix.d.ts +10 -0
- package/types/audit/themeMatrix.d.ts.map +1 -1
- package/types/figma/figmaTokens.d.ts +11 -28
- package/types/figma/figmaTokens.d.ts.map +1 -1
- package/types/fonts/createFontLoader.d.ts.map +1 -1
- package/types/menuRow.d.ts +4 -1
- package/types/menuRow.d.ts.map +1 -1
- package/types/theme/FontKnobStyles.d.ts.map +1 -1
- package/types/theme/Preset.d.ts.map +1 -1
- package/types/theme/PresetContext.d.ts +4 -0
- package/types/theme/PresetContext.d.ts.map +1 -1
- package/types/theme/Surface.d.ts +57 -26
- package/types/theme/Surface.d.ts.map +1 -1
- package/types/theme/activateThemeConfig.d.ts +5 -3
- package/types/theme/activateThemeConfig.d.ts.map +1 -1
- package/types/theme/animations/css.d.ts +27 -34
- package/types/theme/animations/css.d.ts.map +1 -1
- package/types/theme/animations/reactNative.d.ts +196 -45
- package/types/theme/animations/reactNative.d.ts.map +1 -1
- package/types/theme/chartPalette.d.ts +11 -3
- package/types/theme/chartPalette.d.ts.map +1 -1
- package/types/theme/colorRules.d.ts +1 -1
- package/types/theme/cookies.d.ts +12 -1
- package/types/theme/cookies.d.ts.map +1 -1
- package/types/theme/createDefaultThemeConfig.d.ts +13 -13
- package/types/theme/createDefaultThemeConfig.d.ts.map +1 -1
- package/types/theme/createThemes.d.ts +58 -2
- package/types/theme/createThemes.d.ts.map +1 -1
- package/types/theme/defaults/accent.d.ts.map +1 -1
- package/types/theme/defaults/base.d.ts.map +1 -1
- package/types/theme/defaults/builderOptions.d.ts.map +1 -1
- package/types/theme/defaults/categoryFonts.d.ts +10 -0
- package/types/theme/defaults/categoryFonts.d.ts.map +1 -1
- package/types/theme/defaults/fonts.d.ts +330 -30
- package/types/theme/defaults/fonts.d.ts.map +1 -1
- package/types/theme/defaults/house.d.ts +8 -0
- package/types/theme/defaults/house.d.ts.map +1 -0
- package/types/theme/defaults/index.d.ts +1 -0
- package/types/theme/defaults/index.d.ts.map +1 -1
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts +3 -3
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -1
- package/types/theme/devtools/animationOptions.d.ts.map +1 -1
- package/types/theme/devtools/buildSnapshot.d.ts.map +1 -1
- package/types/theme/devtools/knobIcons.d.ts +0 -3
- package/types/theme/devtools/knobIcons.d.ts.map +1 -1
- package/types/theme/index.d.ts +11 -0
- package/types/theme/index.d.ts.map +1 -1
- package/types/theme/knobs.d.ts +8 -36
- package/types/theme/knobs.d.ts.map +1 -1
- package/types/theme/layoutTokens.d.ts +13 -1
- package/types/theme/layoutTokens.d.ts.map +1 -1
- package/types/theme/mergedTokens.d.ts +11 -0
- package/types/theme/mergedTokens.d.ts.map +1 -0
- package/types/theme/motion.d.ts +17 -0
- package/types/theme/motion.d.ts.map +1 -0
- package/types/theme/motionStops.d.ts +3 -0
- package/types/theme/motionStops.d.ts.map +1 -0
- package/types/theme/motionTiming.d.ts +36 -0
- package/types/theme/motionTiming.d.ts.map +1 -0
- package/types/theme/overlaySurfaces.d.ts +70 -0
- package/types/theme/overlaySurfaces.d.ts.map +1 -0
- package/types/theme/preset.types.d.ts +16 -0
- package/types/theme/preset.types.d.ts.map +1 -1
- package/types/theme/presetPreview.d.ts +14 -0
- package/types/theme/presetPreview.d.ts.map +1 -0
- package/types/theme/presetStore.d.ts +40 -0
- package/types/theme/presetStore.d.ts.map +1 -0
- package/types/theme/presetUserSync.d.ts +7 -0
- package/types/theme/presetUserSync.d.ts.map +1 -0
- package/types/theme/presets.d.ts +12 -4
- package/types/theme/presets.d.ts.map +1 -1
- package/types/theme/radiusClass.d.ts +5 -2
- package/types/theme/radiusClass.d.ts.map +1 -1
- package/types/theme/radiusClassTable.generated.d.ts +3 -3
- package/types/theme/readableColor.d.ts +11 -49
- package/types/theme/readableColor.d.ts.map +1 -1
- package/types/theme/recipeInputs.d.ts +39 -70
- package/types/theme/recipeInputs.d.ts.map +1 -1
- package/types/theme/recipes.d.ts +79 -18
- package/types/theme/recipes.d.ts.map +1 -1
- package/types/theme/resolveKnobs.d.ts +64 -32
- package/types/theme/resolveKnobs.d.ts.map +1 -1
- package/types/theme/scale/ScaleStyles.d.ts +18 -0
- package/types/theme/scale/ScaleStyles.d.ts.map +1 -0
- package/types/theme/scale/ScaleStyles.native.d.ts +10 -0
- package/types/theme/scale/ScaleStyles.native.d.ts.map +1 -0
- package/types/theme/scale/activeScale.d.ts +13 -0
- package/types/theme/scale/activeScale.d.ts.map +1 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts +10 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts.map +1 -0
- package/types/theme/scale/generateScale.d.ts +38 -0
- package/types/theme/scale/generateScale.d.ts.map +1 -0
- package/types/theme/scale/index.d.ts +17 -0
- package/types/theme/scale/index.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.d.ts +4 -0
- package/types/theme/scale/installScaleConfig.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.native.d.ts +13 -0
- package/types/theme/scale/installScaleConfig.native.d.ts.map +1 -0
- package/types/theme/scale/scaleAttribute.d.ts +14 -0
- package/types/theme/scale/scaleAttribute.d.ts.map +1 -0
- package/types/theme/scale/scaleCss.d.ts +25 -0
- package/types/theme/scale/scaleCss.d.ts.map +1 -0
- package/types/theme/scale/scaleFonts.d.ts +16 -0
- package/types/theme/scale/scaleFonts.d.ts.map +1 -0
- package/types/theme/scale/scaleInputs.d.ts +66 -0
- package/types/theme/scale/scaleInputs.d.ts.map +1 -0
- package/types/theme/scale/themeScales.d.ts +52 -0
- package/types/theme/scale/themeScales.d.ts.map +1 -0
- package/types/theme/scale/useActiveScale.d.ts +31 -0
- package/types/theme/scale/useActiveScale.d.ts.map +1 -0
- package/types/theme/schemeStore.d.ts +6 -0
- package/types/theme/schemeStore.d.ts.map +1 -0
- package/types/theme/shared.d.ts +18 -0
- package/types/theme/shared.d.ts.map +1 -1
- package/types/theme/sizeRecipes.d.ts +20 -15
- package/types/theme/sizeRecipes.d.ts.map +1 -1
- package/types/theme/theme.d.ts +2 -1
- package/types/theme/theme.d.ts.map +1 -1
- package/types/theme/theme.native.d.ts +10 -1
- package/types/theme/theme.native.d.ts.map +1 -1
- package/types/theme/tileSizes.d.ts +6 -7
- package/types/theme/tileSizes.d.ts.map +1 -1
- package/types/theme/transitionProps.d.ts +3 -1
- package/types/theme/transitionProps.d.ts.map +1 -1
- package/types/theme/transitionProps.native.d.ts +3 -1
- package/types/theme/transitionProps.native.d.ts.map +1 -1
- package/types/theme/usePresetSwitch.d.ts +10 -0
- package/types/theme/usePresetSwitch.d.ts.map +1 -0
- package/types/theme/usePresetSwitch.native.d.ts +10 -0
- package/types/theme/usePresetSwitch.native.d.ts.map +1 -0
- package/types/theme/useResolvedKnobs.d.ts +13 -13
- package/types/theme/useResolvedKnobs.d.ts.map +1 -1
- package/types/theme/useSchemePreference.d.ts +8 -0
- package/types/theme/useSchemePreference.d.ts.map +1 -0
- package/types/theme/useSchemePreference.native.d.ts +8 -0
- package/types/theme/useSchemePreference.native.d.ts.map +1 -0
- package/types/theme/useTheme.d.ts +4 -2
- package/types/theme/useTheme.d.ts.map +1 -1
- package/types/theme/useTheme.native.d.ts +10 -1
- package/types/theme/useTheme.native.d.ts.map +1 -1
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import type { CreateThemesBuilderOptions, GetThemeProps } from "../createThemes";
|
|
2
|
-
import {
|
|
3
|
-
contrastRatio,
|
|
4
|
-
minContrastRatio,
|
|
5
|
-
normalizeToHex,
|
|
6
|
-
pickReadableForeground,
|
|
7
|
-
relativeLuminance,
|
|
8
|
-
} from "../colorRules";
|
|
2
|
+
import { contrastRatio, minContrastRatio, normalizeToHex, relativeLuminance } from "../colorRules";
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
|
-
* DG-A11Y-01 /
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* set `$outlineColor`, so it fell through to Tamagui's stock 20%-alpha ink
|
|
15
|
-
* (measured 1.36:1) — every keyboard user got a near-invisible ring (DF-03).
|
|
16
|
-
*
|
|
17
|
-
* Steps are tried strongest-visible-first; the first that clears 3:1 vs both
|
|
18
|
-
* page steps wins, else the highest-contrast candidate. The ring sits offset
|
|
19
|
-
* outside the control on the page, so page contrast is the governing pair.
|
|
5
|
+
* DG-A11Y-01 / DF-03. The external focus ring must clear 3:1 against both page
|
|
6
|
+
* surfaces in its own scheme. Prefer a solid palette step over a low-alpha
|
|
7
|
+
* outline.
|
|
20
8
|
*/
|
|
21
9
|
function pickFocusRingColorFromSteps(step: (n: number) => string | undefined): string {
|
|
22
10
|
const fallback = step(11) ?? step(12) ?? step(9) ?? "transparent";
|
|
@@ -49,38 +37,38 @@ function pickFocusRingColor(theme: Record<string, string>): string {
|
|
|
49
37
|
return pickFocusRingColorFromSteps((n) => theme[`color${n}`]);
|
|
50
38
|
}
|
|
51
39
|
|
|
40
|
+
// DF-06 / WCAG 1.4.11: control shapes need 3:1 on page, surface1 and control wells.
|
|
41
|
+
function pickBoundaryColor(theme: Record<string, string>): string {
|
|
42
|
+
const fallback = theme.color12 ?? "transparent";
|
|
43
|
+
const surfaces = [theme.background, theme.color3, theme.color5].map((raw) =>
|
|
44
|
+
normalizeToHex(raw ?? ""),
|
|
45
|
+
);
|
|
46
|
+
if (surfaces.some((hex) => !hex)) return fallback;
|
|
47
|
+
const luminances = surfaces.map((hex) => relativeLuminance(hex!));
|
|
48
|
+
let best = fallback;
|
|
49
|
+
let bestMin = 0;
|
|
50
|
+
for (const key of [8, 9, 10, 11, 12]) {
|
|
51
|
+
const raw = theme[`color${key}`];
|
|
52
|
+
const hex = raw ? normalizeToHex(raw) : null;
|
|
53
|
+
if (!raw || !hex) continue;
|
|
54
|
+
const luminance = relativeLuminance(hex);
|
|
55
|
+
const minC = Math.min(...luminances.map((surface) => contrastRatio(luminance, surface)));
|
|
56
|
+
if (minC >= minContrastRatio) return raw;
|
|
57
|
+
if (minC > bestMin) {
|
|
58
|
+
bestMin = minC;
|
|
59
|
+
best = raw;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
function getBaseTheme(theme: Record<string, string>) {
|
|
53
66
|
return {
|
|
54
|
-
// DG-A11Y-01: solid, scheme-aware focus ring (≥3:1 vs page surfaces).
|
|
55
|
-
// Additive — Tamagui's own ring is `rgba(ink, 0.2)`, which measured
|
|
56
|
-
// 1.36:1. This is an improvement and stays (M5).
|
|
57
67
|
outlineColor: pickFocusRingColor(theme),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// `backgroundPress: color5`, `backgroundFocus: color6`,
|
|
64
|
-
// `backgroundActive: color4`, `borderColor: color5`,
|
|
65
|
-
// `borderColorHover: color6`, `borderColorFocus: color7` and
|
|
66
|
-
// `borderColorPress: color7` used to sit in this object, re-anchoring
|
|
67
|
-
// Tamagui's whole interaction ramp one to three steps deeper. The only
|
|
68
|
-
// justification written down was "Normalize interaction ramp across
|
|
69
|
-
// components", and uniformity is an argument for HAVING a ramp, not for
|
|
70
|
-
// these values. The effect was that every mpo border read heavier and
|
|
71
|
-
// every focus fill landed darker than the same component in Tamagui.
|
|
72
|
-
//
|
|
73
|
-
// The theme builder's own templates already derive all seven from the
|
|
74
|
-
// background index, so deleting the pins does not leave a hole: it hands
|
|
75
|
-
// the keys back to the layer that owns them. If uniformity is wanted
|
|
76
|
-
// again, it gets re-derived from the template offsets rather than pinned
|
|
77
|
-
// as literal ramp steps.
|
|
78
|
-
//
|
|
79
|
-
// The semantic aliases below are a different thing and stay: they name
|
|
80
|
-
// slots Tamagui has no token for at all, so they contradict nothing.
|
|
81
|
-
// Project-level semantic aliases for palette slots not covered by Tamagui defaults.
|
|
82
|
-
// textMuted sits on the $color11 tier: the lowest ramp step that keeps
|
|
83
|
-
// secondary text AA-readable on color1/2 surfaces (SB-R contrast sweep).
|
|
68
|
+
boundaryColor: pickBoundaryColor(theme),
|
|
69
|
+
switchThumb: "#ffffff",
|
|
70
|
+
// The v5 makeSurface templates own the interaction ramp and borders.
|
|
71
|
+
// No ramp step is pinned here; only semantic aliases are added.
|
|
84
72
|
textMuted: theme.color11,
|
|
85
73
|
textSubtle: theme.color6,
|
|
86
74
|
borderStrong: theme.color7,
|
|
@@ -96,25 +84,35 @@ function getBaseTheme(theme: Record<string, string>) {
|
|
|
96
84
|
// accent — SB-E-01). The accent theme is solid by palette shape already.
|
|
97
85
|
const solidIntentParents = new Set(["error", "warning", "success"]);
|
|
98
86
|
|
|
87
|
+
function parseHsl(raw: string): [number, number, number] | undefined {
|
|
88
|
+
const match = raw.match(/hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
|
|
89
|
+
if (!match) return undefined;
|
|
90
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function lowerLightness(raw: string, points: number): string {
|
|
94
|
+
const hsl = parseHsl(raw);
|
|
95
|
+
if (!hsl) return raw;
|
|
96
|
+
const [h, s, l] = hsl;
|
|
97
|
+
return `hsl(${h}, ${s}%, ${Math.max(0, l - points)}%)`;
|
|
98
|
+
}
|
|
99
|
+
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* time; bails (returns the original) on unparseable colors.
|
|
101
|
+
* Darken a background until its pale foreground clears WCAG AA (4.5:1).
|
|
102
|
+
* Unparseable colors retain their original background.
|
|
103
103
|
*/
|
|
104
104
|
function clampBgForReadableText(bgRaw: string, fgRaw: string): string {
|
|
105
105
|
const fgHex = normalizeToHex(fgRaw);
|
|
106
106
|
let bgHex = normalizeToHex(bgRaw);
|
|
107
107
|
if (!fgHex || !bgHex) return bgRaw;
|
|
108
108
|
const fgLum = relativeLuminance(fgHex);
|
|
109
|
-
const
|
|
110
|
-
if (!
|
|
111
|
-
const h =
|
|
112
|
-
|
|
113
|
-
let
|
|
114
|
-
const dir = fgLum > relativeLuminance(bgHex) ? -1 : 1;
|
|
115
|
-
for (let i = 0; i < 24; i++) {
|
|
109
|
+
const hsl = parseHsl(bgRaw);
|
|
110
|
+
if (!hsl) return bgRaw;
|
|
111
|
+
const [h, s, initialLightness] = hsl;
|
|
112
|
+
let l = initialLightness;
|
|
113
|
+
for (let i = 0; i < 40; i++) {
|
|
116
114
|
if (contrastRatio(fgLum, relativeLuminance(bgHex)) >= 4.5) break;
|
|
117
|
-
l = Math.
|
|
115
|
+
l = Math.max(0, l - 1.5);
|
|
118
116
|
const next = normalizeToHex(`hsl(${h}, ${s}%, ${l}%)`);
|
|
119
117
|
if (!next) return bgRaw;
|
|
120
118
|
bgHex = next;
|
|
@@ -124,39 +122,28 @@ function clampBgForReadableText(bgRaw: string, fgRaw: string): string {
|
|
|
124
122
|
|
|
125
123
|
export const defaultBuilderOptions: CreateThemesBuilderOptions = {
|
|
126
124
|
getTheme: ({ name, theme, palette }: GetThemeProps): Record<string, string> => {
|
|
127
|
-
//
|
|
128
|
-
// templates -- background/color steps only, NO colorN/border tokens. An
|
|
129
|
-
// element inside one that sets borderColor="$borderColor" resolves
|
|
130
|
-
// nothing and CSS falls back to currentColor: near-black borders on
|
|
131
|
-
// every bordered card/row. Anchor border tokens from the scheme
|
|
132
|
-
// palette (padded shape: [(len-12)/2 alpha anchors] + 12 colors +
|
|
133
|
-
// [anchors]; colorN = colors[N-1]).
|
|
125
|
+
// Surface templates carry their own borders and states, but no colorN ramp.
|
|
134
126
|
if (!theme.color5) {
|
|
135
127
|
if (!palette) return {};
|
|
136
128
|
const colors0 = Math.max(0, Math.floor((palette.length - 12) / 2));
|
|
137
129
|
const step = (i: number): string => palette[colors0 + i] ?? "transparent";
|
|
138
|
-
//
|
|
139
|
-
// Tamagui's stock 20%-alpha `outlineColor`, which silently overrides the
|
|
140
|
-
// base theme's solid ring inside those scopes. Re-derive the solid,
|
|
141
|
-
// scheme-aware ring from this sub-theme's own palette steps.
|
|
130
|
+
// A sub-theme can replace the palette, so its ring must follow that palette.
|
|
142
131
|
const outlineColor = pickFocusRingColorFromSteps((n) => step(n - 1));
|
|
143
132
|
|
|
144
|
-
// Solid semantic-intent Buttons
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// reads better on the resting fill. The sub-theme `color` carries it so
|
|
148
|
-
// labels ($color via useResolvedKnobs), icons (currentColor) and any
|
|
149
|
-
// inherited text all pair correctly. Every interaction fill is then
|
|
150
|
-
// darkened/lightened until it holds that foreground at the 4.5 AA floor.
|
|
133
|
+
// Solid semantic-intent Buttons pair Radix 9 with the hue's pale anchor
|
|
134
|
+
// at a 4.5:1 floor. Hover and press darken that fill in both schemes so
|
|
135
|
+
// inherited labels and icons keep the same readable foreground.
|
|
151
136
|
const parts = name.split("_");
|
|
152
137
|
if (parts[parts.length - 1] === "Button" && solidIntentParents.has(parts[parts.length - 2])) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
138
|
+
const firstHex = normalizeToHex(step(0));
|
|
139
|
+
const lastHex = normalizeToHex(step(11));
|
|
140
|
+
const fg =
|
|
141
|
+
firstHex && lastHex && relativeLuminance(lastHex) > relativeLuminance(firstHex)
|
|
142
|
+
? step(11)
|
|
143
|
+
: step(0);
|
|
144
|
+
const bg = clampBgForReadableText(step(8), fg);
|
|
145
|
+
const hover = lowerLightness(bg, 4);
|
|
146
|
+
const press = lowerLightness(bg, 8);
|
|
160
147
|
return {
|
|
161
148
|
outlineColor,
|
|
162
149
|
background: bg,
|
|
@@ -176,10 +163,6 @@ export const defaultBuilderOptions: CreateThemesBuilderOptions = {
|
|
|
176
163
|
|
|
177
164
|
return {
|
|
178
165
|
outlineColor,
|
|
179
|
-
borderColor: step(4),
|
|
180
|
-
borderColorHover: step(5),
|
|
181
|
-
borderColorFocus: step(6),
|
|
182
|
-
borderColorPress: step(6),
|
|
183
166
|
};
|
|
184
167
|
}
|
|
185
168
|
return getBaseTheme(theme);
|
|
@@ -11,6 +11,16 @@ import { defaultFonts } from "./fonts";
|
|
|
11
11
|
* and another there, which is the same one-source failure MPO-44 was about.
|
|
12
12
|
*/
|
|
13
13
|
export const defaultCategoryFonts = {
|
|
14
|
+
headingSerif: defaultFonts.headingSerif,
|
|
15
|
+
headingMono: defaultFonts.headingMono,
|
|
16
|
+
headingSlab: defaultFonts.headingSlab,
|
|
17
|
+
headingRounded: defaultFonts.headingRounded,
|
|
18
|
+
headingCondensed: defaultFonts.headingCondensed,
|
|
19
|
+
headingCursive: defaultFonts.headingCursive,
|
|
20
|
+
headingHandwriting: defaultFonts.headingHandwriting,
|
|
21
|
+
headingPixel: defaultFonts.headingPixel,
|
|
22
|
+
headingBlackletter: defaultFonts.headingBlackletter,
|
|
23
|
+
headingGeometric: defaultFonts.headingGeometric,
|
|
14
24
|
serif: defaultFonts.serif,
|
|
15
25
|
mono: defaultFonts.mono,
|
|
16
26
|
slab: defaultFonts.slab,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createFontLoader } from "../../fonts/createFontLoader.js";
|
|
4
|
+
import { generateScale } from "../scale/generateScale";
|
|
5
|
+
import { defaultScaleInputs } from "../scale/scaleInputs";
|
|
6
|
+
import { scaleFontSet } from "../scale/scaleFonts";
|
|
7
|
+
import * as fontDefaults from "./fonts";
|
|
2
8
|
import { createDefaultFont } from "../../font";
|
|
3
9
|
import { flooredLineHeight, fontCategoryMetrics } from "../fontCategoryMetrics";
|
|
4
10
|
import { fontCategoryStacks } from "../fontCategoryStacks";
|
|
@@ -78,9 +84,7 @@ describe("default heading/body fonts — tracking-by-size (Axiom 15 OPTICS, LC-1
|
|
|
78
84
|
it("heading H1–H4 land on the expected optical tracking (snapshot)", () => {
|
|
79
85
|
const size = table(defaultHeadingFont, "size");
|
|
80
86
|
const letterSpacing = table(defaultHeadingFont, "letterSpacing");
|
|
81
|
-
//
|
|
82
|
-
// weight 800 rather than 64px at 400. Tracking follows the rendered
|
|
83
|
-
// size, so every value moves with it — that IS the invariant.
|
|
87
|
+
// Tracking follows the rendered size on the shared heading/body ladder.
|
|
84
88
|
expect({
|
|
85
89
|
h1: { size: size["10"], tracking: letterSpacing["10"] },
|
|
86
90
|
h2: { size: size["9"], tracking: letterSpacing["9"] },
|
|
@@ -113,7 +117,7 @@ describe("bodyFont category fonts — one ladder, distinct families (MPO-44)", (
|
|
|
113
117
|
const bodyStops = ["body", "serif", "mono", "rounded"] as const;
|
|
114
118
|
const firstFamily = (stack: string) => stack.split(",")[0]?.replace(/['"]/g, "").trim();
|
|
115
119
|
const categories = Object.keys(defaultFonts).filter(
|
|
116
|
-
(name) => name
|
|
120
|
+
(name) => !name.startsWith("heading") && name !== "body",
|
|
117
121
|
) as FontCategory[];
|
|
118
122
|
const bodySize = table(defaultBodyFont, "size");
|
|
119
123
|
const bodyLineHeight = table(defaultBodyFont, "lineHeight");
|
|
@@ -238,3 +242,79 @@ function fontFamily(font: unknown): string {
|
|
|
238
242
|
if (family && typeof family.val === "string") return family.val;
|
|
239
243
|
return "";
|
|
240
244
|
}
|
|
245
|
+
|
|
246
|
+
describe("heading scope categories", () => {
|
|
247
|
+
it("keeps true at the size-four weight", () => {
|
|
248
|
+
expect(strTable(defaultHeadingFont, "weight").true).toBe("600");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("registers ten heading categories on heading tables with category leading floors", () => {
|
|
252
|
+
const keys = fontDefaults.headingCategoryFontKeys;
|
|
253
|
+
expect(Object.keys(keys ?? {})).toHaveLength(10);
|
|
254
|
+
const headingSizes = table(defaultHeadingFont, "size");
|
|
255
|
+
const headingLeading = table(defaultHeadingFont, "lineHeight");
|
|
256
|
+
for (const [category, key] of Object.entries(keys)) {
|
|
257
|
+
const font = defaultFonts[key];
|
|
258
|
+
expect(fontFamily(font)).toBe(fontCategoryStacks[category as FontCategory]);
|
|
259
|
+
expect(table(font, "size")).toEqual(headingSizes);
|
|
260
|
+
expect(strTable(font, "weight")).toEqual(strTable(defaultHeadingFont, "weight"));
|
|
261
|
+
for (const [step, size] of Object.entries(headingSizes)) {
|
|
262
|
+
expect(table(font, "lineHeight")[step]).toBe(
|
|
263
|
+
flooredLineHeight(
|
|
264
|
+
size,
|
|
265
|
+
headingLeading[step]!,
|
|
266
|
+
fontCategoryMetrics[category as FontCategory].heading,
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
expect(table(font, "letterSpacing")[step]).toBe(0);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("rebuilds heading categories on the target scale and preserves consumer families and weights", () => {
|
|
275
|
+
const from = generateScale(defaultScaleInputs).fonts;
|
|
276
|
+
const to = generateScale({ ...defaultScaleInputs, type: 19 }).fonts;
|
|
277
|
+
const scaled = scaleFontSet(defaultFonts, from, to);
|
|
278
|
+
expect(scaled.headingSerif).toBeDefined();
|
|
279
|
+
for (const [category, key] of Object.entries(fontDefaults.headingCategoryFontKeys)) {
|
|
280
|
+
expect(table(scaled[key], "size")).toEqual(to.headingSizes);
|
|
281
|
+
for (const [step, size] of Object.entries(to.headingSizes)) {
|
|
282
|
+
expect(table(scaled[key], "lineHeight")[step]).toBe(
|
|
283
|
+
flooredLineHeight(
|
|
284
|
+
size,
|
|
285
|
+
to.headingLineHeights[step]!,
|
|
286
|
+
fontCategoryMetrics[category as FontCategory].heading,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
expect(strTable(scaled[key], "weight").true).toBe("600");
|
|
291
|
+
}
|
|
292
|
+
const custom = { ...defaultFonts.heading, family: "Custom", weight: { 4: "500", true: "500" } };
|
|
293
|
+
const overridden = scaleFontSet({ headingSerif: custom }, from, to);
|
|
294
|
+
expect(fontFamily(overridden.headingSerif)).toBe("Custom");
|
|
295
|
+
expect(strTable(overridden.headingSerif, "weight").true).toBe("500");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("loads every native face declared by the purpose fonts", () => {
|
|
299
|
+
const require = createRequire(import.meta.url);
|
|
300
|
+
const original = require.extensions[".otf"];
|
|
301
|
+
require.extensions[".otf"] = (module) => {
|
|
302
|
+
module.exports = 1;
|
|
303
|
+
};
|
|
304
|
+
try {
|
|
305
|
+
const fonts = createFontLoader({ inter: true, fonts: [] }).importFonts();
|
|
306
|
+
expect(Object.keys(fonts).sort()).toEqual(
|
|
307
|
+
["Inter", "InterMedium", "InterSemiBold", "InterBold", "InterExtraBold"].sort(),
|
|
308
|
+
);
|
|
309
|
+
for (const font of [defaultHeadingFont, defaultBodyFont]) {
|
|
310
|
+
expect(font.face[500]?.normal).toBe("InterMedium");
|
|
311
|
+
for (const face of Object.values(font.face)) {
|
|
312
|
+
expect(fonts).toHaveProperty(face.normal!.split(",")[0]!.trim());
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} finally {
|
|
316
|
+
if (original) require.extensions[".otf"] = original;
|
|
317
|
+
else delete require.extensions[".otf"];
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|