@multiplatform.one/theme 6.0.4 → 6.3.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/README.md +73 -0
- package/package.json +22 -9
- package/src/dates.spec.ts +75 -0
- package/src/dates.ts +130 -0
- package/src/devWarn.spec.ts +144 -0
- package/src/devWarn.ts +235 -0
- package/src/figma/figmaTokens.spec.ts +244 -0
- package/src/figma/figmaTokens.ts +503 -0
- package/src/font.ts +6 -8
- package/src/fonts/createFontLoader.web.ts +15 -4
- package/src/index.ts +7 -0
- package/src/keyboardFocusRing.ts +45 -0
- package/src/menuRow.spec.ts +32 -0
- package/src/menuRow.ts +31 -0
- package/src/theme/FontKnobStyles.native.tsx +24 -0
- package/src/theme/FontKnobStyles.tsx +256 -0
- package/src/theme/animations/reactNative.ts +6 -1
- package/src/theme/chartPalette.spec.ts +159 -0
- package/src/theme/chartPalette.ts +170 -0
- package/src/theme/colorRules.ts +33 -1
- package/src/theme/cookies.spec.ts +32 -0
- package/src/theme/cookiesPersistence.spec.ts +86 -0
- package/src/theme/cornerSmoothing.native.ts +16 -0
- package/src/theme/cornerSmoothing.spec.ts +27 -0
- package/src/theme/cornerSmoothing.ts +46 -0
- package/src/theme/createDefaultThemeConfig.ts +32 -8
- package/src/theme/createThemeConfig.spec.ts +137 -0
- package/src/theme/createThemes.ts +77 -2
- package/src/theme/defaults/accent.ts +39 -0
- package/src/theme/defaults/base.ts +63 -0
- package/src/theme/defaults/builderOptions.ts +174 -0
- package/src/theme/defaults/fonts.spec.ts +87 -0
- package/src/theme/defaults/fonts.ts +89 -0
- package/src/theme/defaults/index.ts +4 -0
- package/src/theme/devtools/ThemeDevtoolsPanel.tsx +196 -2
- package/src/theme/devtools/buildSnapshot.ts +11 -0
- package/src/theme/devtools/knobIcons.tsx +141 -0
- package/src/theme/devtools/vizConstants.ts +1 -1
- package/src/theme/focusState.spec.ts +174 -0
- package/src/theme/focusState.ts +112 -0
- package/src/theme/fontKnobStyles.spec.ts +98 -0
- package/src/theme/hairline.native.ts +43 -0
- package/src/theme/hairline.spec.ts +63 -0
- package/src/theme/hairline.ts +101 -0
- package/src/theme/index.ts +8 -0
- package/src/theme/intent.spec.tsx +2 -2
- package/src/theme/intents.ts +6 -2
- package/src/theme/knobs.ts +134 -2
- package/src/theme/layoutTokens.spec.ts +75 -0
- package/src/theme/layoutTokens.ts +213 -0
- package/src/theme/layoutTokensHooks.spec.tsx +157 -0
- package/src/theme/presets.spec.ts +17 -0
- package/src/theme/presets.ts +22 -2
- package/src/theme/readableColor.ts +101 -0
- package/src/theme/recipes.ts +130 -7
- package/src/theme/resolveKnobs.spec.ts +560 -21
- package/src/theme/resolveKnobs.ts +267 -23
- package/src/theme/shared.tsx +26 -2
- package/src/theme/useResolvedKnobs.ts +67 -26
- package/src/theme/useResolvedKnobsBehavior.spec.tsx +166 -0
- package/types/audit/constraintAudit.d.ts +83 -0
- package/types/audit/constraintAudit.d.ts.map +1 -0
- package/types/audit/diffReport.d.ts +45 -0
- package/types/audit/diffReport.d.ts.map +1 -0
- package/types/audit/index.d.ts +5 -0
- package/types/audit/index.d.ts.map +1 -0
- package/types/dates.d.ts +52 -0
- package/types/dates.d.ts.map +1 -0
- package/types/devWarn.d.ts +104 -0
- package/types/devWarn.d.ts.map +1 -0
- package/types/figma/figmaTokens.d.ts +168 -0
- package/types/figma/figmaTokens.d.ts.map +1 -0
- package/types/font.d.ts +5 -0
- package/types/font.d.ts.map +1 -0
- package/types/fonts/createFontLoader.d.ts +3 -0
- package/types/fonts/createFontLoader.d.ts.map +1 -0
- package/types/fonts/createFontLoader.web.d.ts +3 -0
- package/types/fonts/createFontLoader.web.d.ts.map +1 -0
- package/types/fonts/index.d.ts +3 -0
- package/types/fonts/index.d.ts.map +1 -0
- package/types/fonts/types.d.ts +12 -0
- package/types/fonts/types.d.ts.map +1 -0
- package/types/index.d.ts +9 -0
- package/types/index.d.ts.map +1 -0
- package/types/keyboardFocusRing.d.ts +32 -0
- package/types/keyboardFocusRing.d.ts.map +1 -0
- package/types/menuRow.d.ts +32 -0
- package/types/menuRow.d.ts.map +1 -0
- package/types/theme/FontKnobStyles.d.ts +49 -0
- package/types/theme/FontKnobStyles.d.ts.map +1 -0
- package/types/theme/FontKnobStyles.native.d.ts +12 -0
- package/types/theme/FontKnobStyles.native.d.ts.map +1 -0
- package/types/theme/Intent.d.ts +20 -0
- package/types/theme/Intent.d.ts.map +1 -0
- package/types/theme/Preset.d.ts +15 -0
- package/types/theme/Preset.d.ts.map +1 -0
- package/types/theme/PresetContext.d.ts +18 -0
- package/types/theme/PresetContext.d.ts.map +1 -0
- package/types/theme/Tint.d.ts +19 -0
- package/types/theme/Tint.d.ts.map +1 -0
- package/types/theme/animations/css.d.ts +24 -0
- package/types/theme/animations/css.d.ts.map +1 -0
- package/types/theme/animations/index.d.ts +2 -0
- package/types/theme/animations/index.d.ts.map +1 -0
- package/types/theme/animations/index.web.d.ts +2 -0
- package/types/theme/animations/index.web.d.ts.map +1 -0
- package/types/theme/animations/reactNative.d.ts +104 -0
- package/types/theme/animations/reactNative.d.ts.map +1 -0
- package/types/theme/chartPalette.d.ts +62 -0
- package/types/theme/chartPalette.d.ts.map +1 -0
- package/types/theme/colorRules.d.ts +69 -0
- package/types/theme/colorRules.d.ts.map +1 -0
- package/types/theme/cookies.d.ts +76 -0
- package/types/theme/cookies.d.ts.map +1 -0
- package/types/theme/cornerSmoothing.d.ts +31 -0
- package/types/theme/cornerSmoothing.d.ts.map +1 -0
- package/types/theme/cornerSmoothing.native.d.ts +14 -0
- package/types/theme/cornerSmoothing.native.d.ts.map +1 -0
- package/types/theme/createDefaultThemeConfig.d.ts +37 -0
- package/types/theme/createDefaultThemeConfig.d.ts.map +1 -0
- package/types/theme/createThemes.d.ts +38 -0
- package/types/theme/createThemes.d.ts.map +1 -0
- package/types/theme/defaults/accent.d.ts +3 -0
- package/types/theme/defaults/accent.d.ts.map +1 -0
- package/types/theme/defaults/base.d.ts +3 -0
- package/types/theme/defaults/base.d.ts.map +1 -0
- package/types/theme/defaults/builderOptions.d.ts +3 -0
- package/types/theme/defaults/builderOptions.d.ts.map +1 -0
- package/types/theme/defaults/fonts.d.ts +7 -0
- package/types/theme/defaults/fonts.d.ts.map +1 -0
- package/types/theme/defaults/index.d.ts +5 -0
- package/types/theme/defaults/index.d.ts.map +1 -0
- package/types/theme/devtools/ColorLineVisualizer.d.ts +17 -0
- package/types/theme/devtools/ColorLineVisualizer.d.ts.map +1 -0
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts +87 -0
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -0
- package/types/theme/devtools/buildSnapshot.d.ts +12 -0
- package/types/theme/devtools/buildSnapshot.d.ts.map +1 -0
- package/types/theme/devtools/knobIcons.d.ts +33 -0
- package/types/theme/devtools/knobIcons.d.ts.map +1 -0
- package/types/theme/devtools/vizConstants.d.ts +5 -0
- package/types/theme/devtools/vizConstants.d.ts.map +1 -0
- package/types/theme/focusState.d.ts +42 -0
- package/types/theme/focusState.d.ts.map +1 -0
- package/types/theme/hairline.d.ts +88 -0
- package/types/theme/hairline.d.ts.map +1 -0
- package/types/theme/hairline.native.d.ts +42 -0
- package/types/theme/hairline.native.d.ts.map +1 -0
- package/types/theme/index.d.ts +29 -0
- package/types/theme/index.d.ts.map +1 -0
- package/types/theme/intents.d.ts +3 -0
- package/types/theme/intents.d.ts.map +1 -0
- package/types/theme/knobs.d.ts +236 -0
- package/types/theme/knobs.d.ts.map +1 -0
- package/types/theme/layoutTokens.d.ts +104 -0
- package/types/theme/layoutTokens.d.ts.map +1 -0
- package/types/theme/preset.types.d.ts +21 -0
- package/types/theme/preset.types.d.ts.map +1 -0
- package/types/theme/presets.d.ts +4 -0
- package/types/theme/presets.d.ts.map +1 -0
- package/types/theme/readableColor.d.ts +17 -0
- package/types/theme/readableColor.d.ts.map +1 -0
- package/types/theme/recipes.d.ts +200 -0
- package/types/theme/recipes.d.ts.map +1 -0
- package/types/theme/resolveKnobs.d.ts +22 -0
- package/types/theme/resolveKnobs.d.ts.map +1 -0
- package/types/theme/shared.d.ts +40 -0
- package/types/theme/shared.d.ts.map +1 -0
- package/types/theme/theme.d.ts +19 -0
- package/types/theme/theme.d.ts.map +1 -0
- package/types/theme/theme.native.d.ts +10 -0
- package/types/theme/theme.native.d.ts.map +1 -0
- package/types/theme/useColorScale.d.ts +7 -0
- package/types/theme/useColorScale.d.ts.map +1 -0
- package/types/theme/useColorScale.native.d.ts +6 -0
- package/types/theme/useColorScale.native.d.ts.map +1 -0
- package/types/theme/useResolvedKnobs.d.ts +32 -0
- package/types/theme/useResolvedKnobs.d.ts.map +1 -0
- package/types/theme/useTheme.d.ts +31 -0
- package/types/theme/useTheme.d.ts.map +1 -0
- package/types/theme/useTheme.native.d.ts +10 -0
- package/types/theme/useTheme.native.d.ts.map +1 -0
- package/CHANGELOG.md +0 -46
- package/src/theme/__snapshots__/resolveKnobs.spec.ts.snap +0 -69
- package/src/theme/pokemonScreen.spec.ts +0 -109
- package/tsconfig.json +0 -11
- package/vitest.config.mjs +0 -9
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { CreateThemesBuilderOptions, GetThemeProps } from "../createThemes";
|
|
2
|
+
import {
|
|
3
|
+
contrastRatio,
|
|
4
|
+
minContrastRatio,
|
|
5
|
+
normalizeToHex,
|
|
6
|
+
pickReadableForeground,
|
|
7
|
+
relativeLuminance,
|
|
8
|
+
} from "../colorRules";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DG-A11Y-01 / Axiom 12 — resolve a solid `$outlineColor` for the keyboard
|
|
12
|
+
* focus-visible ring that clears the ≥3:1 contrast floor against BOTH page
|
|
13
|
+
* surfaces (color1/color2) in this scheme's own ramp. The house themes never
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
function pickFocusRingColorFromSteps(step: (n: number) => string | undefined): string {
|
|
22
|
+
const fallback = step(11) ?? step(12) ?? step(9) ?? "transparent";
|
|
23
|
+
const bg1 = normalizeToHex(step(1) ?? "");
|
|
24
|
+
const bg2 = normalizeToHex(step(2) ?? "");
|
|
25
|
+
if (!bg1 || !bg2) return fallback;
|
|
26
|
+
const l1 = relativeLuminance(bg1);
|
|
27
|
+
const l2 = relativeLuminance(bg2);
|
|
28
|
+
// Prefer a strong-but-not-ink neutral so the ring is clearly visible in both
|
|
29
|
+
// schemes without reading as heavy black/white text.
|
|
30
|
+
const candidates = [11, 10, 9, 12, 8];
|
|
31
|
+
let best = fallback;
|
|
32
|
+
let bestMin = 0;
|
|
33
|
+
for (const key of candidates) {
|
|
34
|
+
const raw = step(key);
|
|
35
|
+
const hex = raw ? normalizeToHex(raw) : null;
|
|
36
|
+
if (!raw || !hex) continue;
|
|
37
|
+
const l = relativeLuminance(hex);
|
|
38
|
+
const minC = Math.min(contrastRatio(l, l1), contrastRatio(l, l2));
|
|
39
|
+
if (minC >= minContrastRatio) return raw;
|
|
40
|
+
if (minC > bestMin) {
|
|
41
|
+
bestMin = minC;
|
|
42
|
+
best = raw;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return best;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pickFocusRingColor(theme: Record<string, string>): string {
|
|
49
|
+
return pickFocusRingColorFromSteps((n) => theme[`color${n}`]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getBaseTheme(theme: Record<string, string>) {
|
|
53
|
+
return {
|
|
54
|
+
// DG-A11Y-01: solid, scheme-aware focus ring (≥3:1 vs page surfaces).
|
|
55
|
+
outlineColor: pickFocusRingColor(theme),
|
|
56
|
+
// Normalize interaction ramp across components:
|
|
57
|
+
// background: 5 -> 6 -> 4, border: 5 -> 6 -> 7.
|
|
58
|
+
backgroundPress: theme.color5,
|
|
59
|
+
backgroundFocus: theme.color6,
|
|
60
|
+
backgroundActive: theme.color4,
|
|
61
|
+
// Align placeholder/disabled text with the dedicated muted slot.
|
|
62
|
+
placeholderColor: theme.color10,
|
|
63
|
+
// Subtle default borders; focus/press one step deeper than hover.
|
|
64
|
+
borderColor: theme.color5,
|
|
65
|
+
borderColorHover: theme.color6,
|
|
66
|
+
borderColorFocus: theme.color7,
|
|
67
|
+
borderColorPress: theme.color7,
|
|
68
|
+
// Project-level semantic aliases for palette slots not covered by Tamagui defaults.
|
|
69
|
+
// textMuted sits on the $color11 tier: the lowest ramp step that keeps
|
|
70
|
+
// secondary text AA-readable on color1/2 surfaces (SB-R contrast sweep).
|
|
71
|
+
textMuted: theme.color11,
|
|
72
|
+
textSubtle: theme.color6,
|
|
73
|
+
borderStrong: theme.color7,
|
|
74
|
+
borderStrongHover: theme.color8,
|
|
75
|
+
borderStrongPress: theme.color6,
|
|
76
|
+
surfaceStrong: theme.color7,
|
|
77
|
+
surfaceMuted: theme.color10,
|
|
78
|
+
surfacePressStrong: theme.color6,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Semantic intents whose Buttons render SOLID (one emphasis system with
|
|
83
|
+
// accent — SB-E-01). The accent theme is solid by palette shape already.
|
|
84
|
+
const solidIntentParents = new Set(["error", "warning", "success"]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Adjust a background's lightness until it clears WCAG AA (4.5:1) against
|
|
88
|
+
* the given foreground. Steps away from the foreground's lightness 1.5% at a
|
|
89
|
+
* time; bails (returns the original) on unparseable colors.
|
|
90
|
+
*/
|
|
91
|
+
function clampBgForReadableText(bgRaw: string, fgRaw: string): string {
|
|
92
|
+
const fgHex = normalizeToHex(fgRaw);
|
|
93
|
+
let bgHex = normalizeToHex(bgRaw);
|
|
94
|
+
if (!fgHex || !bgHex) return bgRaw;
|
|
95
|
+
const fgLum = relativeLuminance(fgHex);
|
|
96
|
+
const hslMatch = bgRaw.match(/hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/);
|
|
97
|
+
if (!hslMatch) return bgRaw;
|
|
98
|
+
const h = Number(hslMatch[1]);
|
|
99
|
+
const s = Number(hslMatch[2]);
|
|
100
|
+
let l = Number(hslMatch[3]);
|
|
101
|
+
const dir = fgLum > relativeLuminance(bgHex) ? -1 : 1;
|
|
102
|
+
for (let i = 0; i < 24; i++) {
|
|
103
|
+
if (contrastRatio(fgLum, relativeLuminance(bgHex)) >= 4.5) break;
|
|
104
|
+
l = Math.min(100, Math.max(0, l + dir * 1.5));
|
|
105
|
+
const next = normalizeToHex(`hsl(${h}, ${s}%, ${l}%)`);
|
|
106
|
+
if (!next) return bgRaw;
|
|
107
|
+
bgHex = next;
|
|
108
|
+
}
|
|
109
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const defaultBuilderOptions: CreateThemesBuilderOptions = {
|
|
113
|
+
getTheme: ({ name, theme, palette }: GetThemeProps): Record<string, string> => {
|
|
114
|
+
// Component sub-themes (t_Card, t_ListItem, t_Button, ...) are narrow
|
|
115
|
+
// templates -- background/color steps only, NO colorN/border tokens. An
|
|
116
|
+
// element inside one that sets borderColor="$borderColor" resolves
|
|
117
|
+
// nothing and CSS falls back to currentColor: near-black borders on
|
|
118
|
+
// every bordered card/row. Anchor border tokens from the scheme
|
|
119
|
+
// palette (padded shape: [(len-12)/2 alpha anchors] + 12 colors +
|
|
120
|
+
// [anchors]; colorN = colors[N-1]).
|
|
121
|
+
if (!theme.color5) {
|
|
122
|
+
if (!palette) return {};
|
|
123
|
+
const colors0 = Math.max(0, Math.floor((palette.length - 12) / 2));
|
|
124
|
+
const step = (i: number): string => palette[colors0 + i] ?? "transparent";
|
|
125
|
+
// DF-03: component sub-theme templates (t_Input, t_Button, ...) carry
|
|
126
|
+
// Tamagui's stock 20%-alpha `outlineColor`, which silently overrides the
|
|
127
|
+
// base theme's solid ring inside those scopes. Re-derive the solid,
|
|
128
|
+
// scheme-aware ring from this sub-theme's own palette steps.
|
|
129
|
+
const outlineColor = pickFocusRingColorFromSteps((n) => step(n - 1));
|
|
130
|
+
|
|
131
|
+
// Solid semantic-intent Buttons (light_error_Button, dark_warning_Button,
|
|
132
|
+
// ...): surface on the hue's step 11 with a LUMINANCE-COMPUTED foreground
|
|
133
|
+
// (Axiom 15 / LC-05) — the hue's step-1 or step-12 anchor, whichever
|
|
134
|
+
// reads better on the resting fill. The sub-theme `color` carries it so
|
|
135
|
+
// labels ($color via useResolvedKnobs), icons (currentColor) and any
|
|
136
|
+
// inherited text all pair correctly. Every interaction fill is then
|
|
137
|
+
// darkened/lightened until it holds that foreground at the 4.5 AA floor.
|
|
138
|
+
const parts = name.split("_");
|
|
139
|
+
if (parts[parts.length - 1] === "Button" && solidIntentParents.has(parts[parts.length - 2])) {
|
|
140
|
+
const scheme = parts[0];
|
|
141
|
+
// Luminance-computed on-fill foreground (Axiom 15): the hue's step-1
|
|
142
|
+
// (pale) or step-12 (deep) anchor, whichever reads better on the fill.
|
|
143
|
+
const fg = pickReadableForeground(step(10), step(0), step(11));
|
|
144
|
+
const bg = clampBgForReadableText(step(10), fg);
|
|
145
|
+
const hover = clampBgForReadableText(scheme === "light" ? step(9) : step(11), fg);
|
|
146
|
+
const press = clampBgForReadableText(scheme === "light" ? step(11) : step(9), fg);
|
|
147
|
+
return {
|
|
148
|
+
outlineColor,
|
|
149
|
+
background: bg,
|
|
150
|
+
backgroundHover: hover,
|
|
151
|
+
backgroundPress: press,
|
|
152
|
+
backgroundFocus: hover,
|
|
153
|
+
color: fg,
|
|
154
|
+
colorHover: fg,
|
|
155
|
+
colorPress: fg,
|
|
156
|
+
colorFocus: fg,
|
|
157
|
+
borderColor: bg,
|
|
158
|
+
borderColorHover: hover,
|
|
159
|
+
borderColorFocus: hover,
|
|
160
|
+
borderColorPress: press,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
outlineColor,
|
|
166
|
+
borderColor: step(4),
|
|
167
|
+
borderColorHover: step(5),
|
|
168
|
+
borderColorFocus: step(6),
|
|
169
|
+
borderColorPress: step(6),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return getBaseTheme(theme);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { defaultBodyFont, defaultHeadingFont, interTrackingPx } from "./fonts";
|
|
3
|
+
|
|
4
|
+
type NumTable = Record<string, number>;
|
|
5
|
+
|
|
6
|
+
/** Unwrap a font table whose values may be tamagui Variables. */
|
|
7
|
+
function table(font: unknown, key: "size" | "lineHeight" | "letterSpacing"): NumTable {
|
|
8
|
+
const raw = (font as Record<string, Record<string, number | { val: number }>>)[key] ?? {};
|
|
9
|
+
const out: NumTable = {};
|
|
10
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
11
|
+
const num = typeof v === "number" ? v : v && typeof v.val === "number" ? v.val : undefined;
|
|
12
|
+
if (num !== undefined) out[k.replace(/^\$/, "")] = num;
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("default heading/body fonts — tracking-by-size (Axiom 15 OPTICS, LC-11)", () => {
|
|
18
|
+
for (const [name, font] of [
|
|
19
|
+
["heading", defaultHeadingFont],
|
|
20
|
+
["body", defaultBodyFont],
|
|
21
|
+
] as const) {
|
|
22
|
+
describe(`${name} font`, () => {
|
|
23
|
+
const size = table(font, "size");
|
|
24
|
+
const letterSpacing = table(font, "letterSpacing");
|
|
25
|
+
|
|
26
|
+
it("defines tracking for every size key, computed from the rendered size", () => {
|
|
27
|
+
expect(Object.keys(size).length).toBeGreaterThan(0);
|
|
28
|
+
for (const [key, px] of Object.entries(size)) {
|
|
29
|
+
expect(letterSpacing[key], `letterSpacing[${key}]`).toBe(interTrackingPx(px));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is looser at caption sizes and tighter at display sizes", () => {
|
|
34
|
+
// caption end (≤12px rendered) is neutral-to-loose, never tighter
|
|
35
|
+
for (const [key, px] of Object.entries(size)) {
|
|
36
|
+
if (px <= 12) expect(letterSpacing[key]).toBeGreaterThanOrEqual(0);
|
|
37
|
+
if (px >= 28) expect(letterSpacing[key]).toBeLessThanOrEqual(-0.55);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("tracking decreases monotonically as size grows", () => {
|
|
42
|
+
const bySize = Object.entries(size)
|
|
43
|
+
.filter(([key]) => key !== "true")
|
|
44
|
+
.sort((a, b) => a[1] - b[1]);
|
|
45
|
+
for (let i = 1; i < bySize.length; i++) {
|
|
46
|
+
const [prevKey, prevPx] = bySize[i - 1] as [string, number];
|
|
47
|
+
const [key, px] = bySize[i] as [string, number];
|
|
48
|
+
if (px === prevPx) continue;
|
|
49
|
+
expect(letterSpacing[key], `letterSpacing[${key}]`).toBeLessThan(
|
|
50
|
+
letterSpacing[prevKey] as number,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
it("heading H1–H4 land on the expected optical tracking (snapshot)", () => {
|
|
58
|
+
const size = table(defaultHeadingFont, "size");
|
|
59
|
+
const letterSpacing = table(defaultHeadingFont, "letterSpacing");
|
|
60
|
+
// H1 $10=64px, H2 $9=42px, H3 $8=32px, H4 $7=28px on the 1.4x scale
|
|
61
|
+
expect({
|
|
62
|
+
h1: { size: size["10"], tracking: letterSpacing["10"] },
|
|
63
|
+
h2: { size: size["9"], tracking: letterSpacing["9"] },
|
|
64
|
+
h3: { size: size["8"], tracking: letterSpacing["8"] },
|
|
65
|
+
h4: { size: size["7"], tracking: letterSpacing["7"] },
|
|
66
|
+
}).toMatchInlineSnapshot(`
|
|
67
|
+
{
|
|
68
|
+
"h1": {
|
|
69
|
+
"size": 64,
|
|
70
|
+
"tracking": -1.43,
|
|
71
|
+
},
|
|
72
|
+
"h2": {
|
|
73
|
+
"size": 42,
|
|
74
|
+
"tracking": -0.93,
|
|
75
|
+
},
|
|
76
|
+
"h3": {
|
|
77
|
+
"size": 32,
|
|
78
|
+
"tracking": -0.69,
|
|
79
|
+
},
|
|
80
|
+
"h4": {
|
|
81
|
+
"size": 28,
|
|
82
|
+
"tracking": -0.59,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
`);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createInterFont } from "@tamagui/font-inter";
|
|
2
|
+
|
|
3
|
+
// Default purpose fonts for components that reference $heading / $body
|
|
4
|
+
// directly, both backed by Inter.
|
|
5
|
+
// Tables match @tamagui/config/v4 (the scale Bento is authored against):
|
|
6
|
+
// headings 1.4x the base size scale (H1 $10 = 64px, H2 $9 = 42, H3 $8 = 32,
|
|
7
|
+
// H4 $7 = 28) at default weight with zero tracking — large and light, not
|
|
8
|
+
// small and bold — and body at the unscaled 14px/$true with lineHeight
|
|
9
|
+
// size+10. No uppercase transforms.
|
|
10
|
+
//
|
|
11
|
+
// Tracking-by-size (Axiom 15 OPTICS + LC-11): letter-spacing is a function
|
|
12
|
+
// of the rendered size, not a constant — slightly loose at caption sizes,
|
|
13
|
+
// increasingly tight at display sizes (the same optical behavior Apple's SF
|
|
14
|
+
// implements per point size). The curve is Inter's own dynamic-metrics
|
|
15
|
+
// model (rsms.me/inter/dynmetrics): tracking(em) = a + b·e^(c·size).
|
|
16
|
+
// These values are Inter-specific: when the headingFont/bodyFont knob picks
|
|
17
|
+
// another family, FontKnobStyles' per-category metrics (LC-11) neutralize
|
|
18
|
+
// this table for fixed-pitch and connected-script categories.
|
|
19
|
+
|
|
20
|
+
const TRACKING_A = -0.0223;
|
|
21
|
+
const TRACKING_B = 0.185;
|
|
22
|
+
const TRACKING_C = -0.1745;
|
|
23
|
+
|
|
24
|
+
/** Inter dynamic tracking for a rendered `sizePx`, in em. */
|
|
25
|
+
export function interTrackingEm(sizePx: number): number {
|
|
26
|
+
return TRACKING_A + TRACKING_B * Math.exp(TRACKING_C * sizePx);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Inter dynamic tracking for a rendered `sizePx`, in px (2-decimal). */
|
|
30
|
+
export function interTrackingPx(sizePx: number): number {
|
|
31
|
+
return Math.round(interTrackingEm(sizePx) * sizePx * 100) / 100;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Mirrors @tamagui/font-inter's default size table (pinned 2.0.0-rc.41) so
|
|
35
|
+
// tracking can be computed per size key. The fonts.spec.ts invariant test
|
|
36
|
+
// (letterSpacing[k] === interTrackingPx(size[k]) on the BUILT font) catches
|
|
37
|
+
// any upstream drift.
|
|
38
|
+
const interBaseSizes: Record<string, number> = {
|
|
39
|
+
1: 11,
|
|
40
|
+
2: 12,
|
|
41
|
+
3: 13,
|
|
42
|
+
4: 14,
|
|
43
|
+
true: 14,
|
|
44
|
+
5: 16,
|
|
45
|
+
6: 18,
|
|
46
|
+
7: 20,
|
|
47
|
+
8: 23,
|
|
48
|
+
9: 30,
|
|
49
|
+
10: 46,
|
|
50
|
+
11: 55,
|
|
51
|
+
12: 62,
|
|
52
|
+
13: 72,
|
|
53
|
+
14: 92,
|
|
54
|
+
15: 114,
|
|
55
|
+
16: 134,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function trackingTable(scaleSize: (size: number) => number): Record<string, number> {
|
|
59
|
+
return Object.fromEntries(
|
|
60
|
+
Object.entries(interBaseSizes).map(([key, size]) => [key, interTrackingPx(scaleSize(size))]),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const headingSizeOf = (size: number) => Math.round(size * 1.4);
|
|
65
|
+
|
|
66
|
+
export const defaultHeadingFont = createInterFont(
|
|
67
|
+
{
|
|
68
|
+
face: {
|
|
69
|
+
700: { normal: "InterBold" },
|
|
70
|
+
},
|
|
71
|
+
letterSpacing: trackingTable(headingSizeOf),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
sizeSize: headingSizeOf,
|
|
75
|
+
sizeLineHeight: (size) => size + 10,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export const defaultBodyFont = createInterFont(
|
|
80
|
+
{
|
|
81
|
+
face: {
|
|
82
|
+
700: { normal: "InterBold" },
|
|
83
|
+
},
|
|
84
|
+
letterSpacing: trackingTable((size) => size),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
sizeLineHeight: (size) => size + 10,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
* Controls:
|
|
7
7
|
* - Color scheme (system / light / dark) via @vxrn/color-scheme
|
|
8
8
|
* - Preset picker (using registered presets)
|
|
9
|
-
* - All knobs: fillStyle, borderRadius,
|
|
10
|
-
* textAccent, headingFont, bodyFont,
|
|
9
|
+
* - All knobs: fillStyle, borderRadius, cornerSmoothing, borderWidth,
|
|
10
|
+
* elevation, space, size, density, textAccent, headingFont, bodyFont,
|
|
11
|
+
* fontWeight, animation,
|
|
12
|
+
* plus house decisions (fieldLabelPlacement, requiredMarking, tableZebra,
|
|
13
|
+
* bulkBarPlacement, selectAllScope, timestampStyle, disabledStyle,
|
|
14
|
+
* formAutofocus)
|
|
11
15
|
*
|
|
12
16
|
* This panel is rendered *inside* the TanStack Devtools shell so it shares
|
|
13
17
|
* the same React tree and can call hooks like `useTheme` / `useUserScheme`.
|
|
@@ -48,14 +52,24 @@ import {
|
|
|
48
52
|
defaultKnobs,
|
|
49
53
|
FillStyle,
|
|
50
54
|
BorderRadius,
|
|
55
|
+
CornerSmoothing,
|
|
51
56
|
BorderWidth,
|
|
52
57
|
Elevation,
|
|
53
58
|
Space,
|
|
54
59
|
Size,
|
|
60
|
+
Density,
|
|
55
61
|
TextAccent,
|
|
56
62
|
HeadingFont,
|
|
57
63
|
BodyFont,
|
|
58
64
|
FontWeight,
|
|
65
|
+
FieldLabelPlacement,
|
|
66
|
+
RequiredMarking,
|
|
67
|
+
TableZebra,
|
|
68
|
+
BulkBarPlacement,
|
|
69
|
+
SelectAllScope,
|
|
70
|
+
TimestampStyle,
|
|
71
|
+
DisabledStyle,
|
|
72
|
+
FormAutofocus,
|
|
59
73
|
interactionStates,
|
|
60
74
|
} from "../knobs";
|
|
61
75
|
import { animationNames } from "../animations/index";
|
|
@@ -66,7 +80,9 @@ import {
|
|
|
66
80
|
fillIcon as _fillIcon,
|
|
67
81
|
spaceIcon as _spaceIcon,
|
|
68
82
|
sizeIcon as _sizeIcon,
|
|
83
|
+
densityIcon as _densityIcon,
|
|
69
84
|
borderRadiusIcon as _borderRadiusIcon,
|
|
85
|
+
cornerSmoothingIcon as _cornerSmoothingIcon,
|
|
70
86
|
borderWidthIcon as _borderWidthIcon,
|
|
71
87
|
elevationIcon as _elevationIcon,
|
|
72
88
|
textAccentIcon as _textAccentIcon,
|
|
@@ -74,6 +90,14 @@ import {
|
|
|
74
90
|
bodyFontIcon as _bodyFontIcon,
|
|
75
91
|
fontWeightIcon as _fontWeightIcon,
|
|
76
92
|
modeIcon as _modeIcon,
|
|
93
|
+
fieldLabelPlacementIcon as _fieldLabelPlacementIcon,
|
|
94
|
+
requiredMarkingIcon as _requiredMarkingIcon,
|
|
95
|
+
tableZebraIcon as _tableZebraIcon,
|
|
96
|
+
bulkBarPlacementIcon as _bulkBarPlacementIcon,
|
|
97
|
+
selectAllScopeIcon as _selectAllScopeIcon,
|
|
98
|
+
timestampStyleIcon as _timestampStyleIcon,
|
|
99
|
+
disabledStyleIcon as _disabledStyleIcon,
|
|
100
|
+
formAutofocusIcon as _formAutofocusIcon,
|
|
77
101
|
} from "./knobIcons";
|
|
78
102
|
|
|
79
103
|
function extractAnimationNames(tamaguiConfig: unknown): string[] {
|
|
@@ -111,15 +135,25 @@ const stateDisplayLabels: Record<InteractionState, string> = {
|
|
|
111
135
|
const knobKeys: (keyof Knobs)[] = [
|
|
112
136
|
"fillStyle",
|
|
113
137
|
"borderRadius",
|
|
138
|
+
"cornerSmoothing",
|
|
114
139
|
"borderWidth",
|
|
115
140
|
"elevation",
|
|
116
141
|
"space",
|
|
117
142
|
"size",
|
|
143
|
+
"density",
|
|
118
144
|
"textAccent",
|
|
119
145
|
"headingFont",
|
|
120
146
|
"bodyFont",
|
|
121
147
|
"fontWeight",
|
|
122
148
|
"animation",
|
|
149
|
+
"fieldLabelPlacement",
|
|
150
|
+
"requiredMarking",
|
|
151
|
+
"tableZebra",
|
|
152
|
+
"bulkBarPlacement",
|
|
153
|
+
"selectAllScope",
|
|
154
|
+
"timestampStyle",
|
|
155
|
+
"disabledStyle",
|
|
156
|
+
"formAutofocus",
|
|
123
157
|
];
|
|
124
158
|
|
|
125
159
|
/** Knob keys that support per-state overrides. */
|
|
@@ -685,10 +719,18 @@ function sizeIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
|
685
719
|
return _sizeIcon(v, ic(active, dark));
|
|
686
720
|
}
|
|
687
721
|
|
|
722
|
+
function densityIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
723
|
+
return _densityIcon(v, ic(active, dark));
|
|
724
|
+
}
|
|
725
|
+
|
|
688
726
|
function borderRadiusIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
689
727
|
return _borderRadiusIcon(v, ic(active, dark));
|
|
690
728
|
}
|
|
691
729
|
|
|
730
|
+
function cornerSmoothingIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
731
|
+
return _cornerSmoothingIcon(v, ic(active, dark));
|
|
732
|
+
}
|
|
733
|
+
|
|
692
734
|
function borderWidthIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
693
735
|
return _borderWidthIcon(v, ic(active, dark));
|
|
694
736
|
}
|
|
@@ -713,6 +755,38 @@ function fontWeightIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
|
713
755
|
return _fontWeightIcon(v, ic(active, dark));
|
|
714
756
|
}
|
|
715
757
|
|
|
758
|
+
function fieldLabelPlacementIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
759
|
+
return _fieldLabelPlacementIcon(v, ic(active, dark));
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function requiredMarkingIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
763
|
+
return _requiredMarkingIcon(v, ic(active, dark));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function tableZebraIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
767
|
+
return _tableZebraIcon(v, ic(active, dark));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function bulkBarPlacementIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
771
|
+
return _bulkBarPlacementIcon(v, ic(active, dark));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function selectAllScopeIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
775
|
+
return _selectAllScopeIcon(v, ic(active, dark));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function timestampStyleIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
779
|
+
return _timestampStyleIcon(v, ic(active, dark));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function disabledStyleIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
783
|
+
return _disabledStyleIcon(v, ic(active, dark));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function formAutofocusIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
787
|
+
return _formAutofocusIcon(v, ic(active, dark));
|
|
788
|
+
}
|
|
789
|
+
|
|
716
790
|
// ---------------------------------------------------------------------------
|
|
717
791
|
// Main panel
|
|
718
792
|
// ---------------------------------------------------------------------------
|
|
@@ -1365,6 +1439,18 @@ export function ThemeDevtoolsPanel({
|
|
|
1365
1439
|
>
|
|
1366
1440
|
{stateRows("space", Object.values(Space), spaceIcon)}
|
|
1367
1441
|
</IconGroup>
|
|
1442
|
+
<IconGroup
|
|
1443
|
+
label="Density"
|
|
1444
|
+
options={Object.values(Density)}
|
|
1445
|
+
value={getOverride("density")}
|
|
1446
|
+
onChange={(v) => set("density", v)}
|
|
1447
|
+
icon={densityIcon}
|
|
1448
|
+
styles={styles}
|
|
1449
|
+
isDark={isDark}
|
|
1450
|
+
pinned={pinnedKnobs.has("density")}
|
|
1451
|
+
onTogglePin={() => togglePin("density")}
|
|
1452
|
+
onUnset={() => unset("density")}
|
|
1453
|
+
/>
|
|
1368
1454
|
<IconGroup
|
|
1369
1455
|
label="Radius"
|
|
1370
1456
|
options={Object.values(BorderRadius)}
|
|
@@ -1381,6 +1467,18 @@ export function ThemeDevtoolsPanel({
|
|
|
1381
1467
|
>
|
|
1382
1468
|
{stateRows("borderRadius", Object.values(BorderRadius), borderRadiusIcon)}
|
|
1383
1469
|
</IconGroup>
|
|
1470
|
+
<IconGroup
|
|
1471
|
+
label="Smoothing"
|
|
1472
|
+
options={Object.values(CornerSmoothing)}
|
|
1473
|
+
value={getOverride("cornerSmoothing")}
|
|
1474
|
+
onChange={(v) => set("cornerSmoothing", v)}
|
|
1475
|
+
icon={cornerSmoothingIcon}
|
|
1476
|
+
styles={styles}
|
|
1477
|
+
isDark={isDark}
|
|
1478
|
+
pinned={pinnedKnobs.has("cornerSmoothing")}
|
|
1479
|
+
onTogglePin={() => togglePin("cornerSmoothing")}
|
|
1480
|
+
onUnset={() => unset("cornerSmoothing")}
|
|
1481
|
+
/>
|
|
1384
1482
|
<IconGroup
|
|
1385
1483
|
label="Border"
|
|
1386
1484
|
options={Object.values(BorderWidth)}
|
|
@@ -1488,6 +1586,102 @@ export function ThemeDevtoolsPanel({
|
|
|
1488
1586
|
onTogglePin={() => togglePin("animation")}
|
|
1489
1587
|
isDark={isDark}
|
|
1490
1588
|
/>
|
|
1589
|
+
<IconGroup
|
|
1590
|
+
label="Label place"
|
|
1591
|
+
options={Object.values(FieldLabelPlacement)}
|
|
1592
|
+
value={getOverride("fieldLabelPlacement")}
|
|
1593
|
+
onChange={(v) => set("fieldLabelPlacement", v)}
|
|
1594
|
+
icon={fieldLabelPlacementIcon}
|
|
1595
|
+
styles={styles}
|
|
1596
|
+
isDark={isDark}
|
|
1597
|
+
pinned={pinnedKnobs.has("fieldLabelPlacement")}
|
|
1598
|
+
onTogglePin={() => togglePin("fieldLabelPlacement")}
|
|
1599
|
+
onUnset={() => unset("fieldLabelPlacement")}
|
|
1600
|
+
/>
|
|
1601
|
+
<IconGroup
|
|
1602
|
+
label="Required mark"
|
|
1603
|
+
options={Object.values(RequiredMarking)}
|
|
1604
|
+
value={getOverride("requiredMarking")}
|
|
1605
|
+
onChange={(v) => set("requiredMarking", v)}
|
|
1606
|
+
icon={requiredMarkingIcon}
|
|
1607
|
+
styles={styles}
|
|
1608
|
+
isDark={isDark}
|
|
1609
|
+
pinned={pinnedKnobs.has("requiredMarking")}
|
|
1610
|
+
onTogglePin={() => togglePin("requiredMarking")}
|
|
1611
|
+
onUnset={() => unset("requiredMarking")}
|
|
1612
|
+
/>
|
|
1613
|
+
<IconGroup
|
|
1614
|
+
label="Zebra"
|
|
1615
|
+
options={Object.values(TableZebra)}
|
|
1616
|
+
value={getOverride("tableZebra")}
|
|
1617
|
+
onChange={(v) => set("tableZebra", v)}
|
|
1618
|
+
icon={tableZebraIcon}
|
|
1619
|
+
styles={styles}
|
|
1620
|
+
isDark={isDark}
|
|
1621
|
+
pinned={pinnedKnobs.has("tableZebra")}
|
|
1622
|
+
onTogglePin={() => togglePin("tableZebra")}
|
|
1623
|
+
onUnset={() => unset("tableZebra")}
|
|
1624
|
+
/>
|
|
1625
|
+
<IconGroup
|
|
1626
|
+
label="Bulk bar"
|
|
1627
|
+
options={Object.values(BulkBarPlacement)}
|
|
1628
|
+
value={getOverride("bulkBarPlacement")}
|
|
1629
|
+
onChange={(v) => set("bulkBarPlacement", v)}
|
|
1630
|
+
icon={bulkBarPlacementIcon}
|
|
1631
|
+
styles={styles}
|
|
1632
|
+
isDark={isDark}
|
|
1633
|
+
pinned={pinnedKnobs.has("bulkBarPlacement")}
|
|
1634
|
+
onTogglePin={() => togglePin("bulkBarPlacement")}
|
|
1635
|
+
onUnset={() => unset("bulkBarPlacement")}
|
|
1636
|
+
/>
|
|
1637
|
+
<IconGroup
|
|
1638
|
+
label="Select all"
|
|
1639
|
+
options={Object.values(SelectAllScope)}
|
|
1640
|
+
value={getOverride("selectAllScope")}
|
|
1641
|
+
onChange={(v) => set("selectAllScope", v)}
|
|
1642
|
+
icon={selectAllScopeIcon}
|
|
1643
|
+
styles={styles}
|
|
1644
|
+
isDark={isDark}
|
|
1645
|
+
pinned={pinnedKnobs.has("selectAllScope")}
|
|
1646
|
+
onTogglePin={() => togglePin("selectAllScope")}
|
|
1647
|
+
onUnset={() => unset("selectAllScope")}
|
|
1648
|
+
/>
|
|
1649
|
+
<IconGroup
|
|
1650
|
+
label="Timestamp"
|
|
1651
|
+
options={Object.values(TimestampStyle)}
|
|
1652
|
+
value={getOverride("timestampStyle")}
|
|
1653
|
+
onChange={(v) => set("timestampStyle", v)}
|
|
1654
|
+
icon={timestampStyleIcon}
|
|
1655
|
+
styles={styles}
|
|
1656
|
+
isDark={isDark}
|
|
1657
|
+
pinned={pinnedKnobs.has("timestampStyle")}
|
|
1658
|
+
onTogglePin={() => togglePin("timestampStyle")}
|
|
1659
|
+
onUnset={() => unset("timestampStyle")}
|
|
1660
|
+
/>
|
|
1661
|
+
<IconGroup
|
|
1662
|
+
label="Disabled"
|
|
1663
|
+
options={Object.values(DisabledStyle)}
|
|
1664
|
+
value={getOverride("disabledStyle")}
|
|
1665
|
+
onChange={(v) => set("disabledStyle", v)}
|
|
1666
|
+
icon={disabledStyleIcon}
|
|
1667
|
+
styles={styles}
|
|
1668
|
+
isDark={isDark}
|
|
1669
|
+
pinned={pinnedKnobs.has("disabledStyle")}
|
|
1670
|
+
onTogglePin={() => togglePin("disabledStyle")}
|
|
1671
|
+
onUnset={() => unset("disabledStyle")}
|
|
1672
|
+
/>
|
|
1673
|
+
<IconGroup
|
|
1674
|
+
label="Autofocus"
|
|
1675
|
+
options={Object.values(FormAutofocus)}
|
|
1676
|
+
value={getOverride("formAutofocus")}
|
|
1677
|
+
onChange={(v) => set("formAutofocus", v)}
|
|
1678
|
+
icon={formAutofocusIcon}
|
|
1679
|
+
styles={styles}
|
|
1680
|
+
isDark={isDark}
|
|
1681
|
+
pinned={pinnedKnobs.has("formAutofocus")}
|
|
1682
|
+
onTogglePin={() => togglePin("formAutofocus")}
|
|
1683
|
+
onUnset={() => unset("formAutofocus")}
|
|
1684
|
+
/>
|
|
1491
1685
|
<div
|
|
1492
1686
|
style={{
|
|
1493
1687
|
...styles.card,
|
|
@@ -3,14 +3,25 @@ import { semanticGroups } from "../colorRules";
|
|
|
3
3
|
const knobKeys = [
|
|
4
4
|
"fillStyle",
|
|
5
5
|
"borderRadius",
|
|
6
|
+
"cornerSmoothing",
|
|
6
7
|
"borderWidth",
|
|
7
8
|
"elevation",
|
|
8
9
|
"space",
|
|
10
|
+
"size",
|
|
11
|
+
"density",
|
|
9
12
|
"textAccent",
|
|
10
13
|
"headingFont",
|
|
11
14
|
"bodyFont",
|
|
12
15
|
"fontWeight",
|
|
13
16
|
"animation",
|
|
17
|
+
"fieldLabelPlacement",
|
|
18
|
+
"requiredMarking",
|
|
19
|
+
"tableZebra",
|
|
20
|
+
"bulkBarPlacement",
|
|
21
|
+
"selectAllScope",
|
|
22
|
+
"timestampStyle",
|
|
23
|
+
"disabledStyle",
|
|
24
|
+
"formAutofocus",
|
|
14
25
|
];
|
|
15
26
|
|
|
16
27
|
const stateKnobKeys = ["elevation", "borderWidth", "borderRadius"] as const;
|