@advui/theme 0.1.0 → 0.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/README.md +5 -1
- package/dist/config.d.ts +581 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/material.d.ts +48 -0
- package/dist/material.d.ts.map +1 -0
- package/dist/material.js +222 -0
- package/dist/material.js.map +1 -0
- package/dist/themes.d.ts +2 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +4 -0
- package/dist/themes.js.map +1 -1
- package/dist/tokens.d.ts +26 -9
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +13 -4
- package/dist/tokens.js.map +1 -1
- package/package.json +9 -4
- package/src/config.ts +35 -6
- package/src/index.ts +10 -1
- package/src/material.ts +268 -0
- package/src/themes.ts +6 -0
- package/src/tokens.ts +22 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@advui/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Design tokens, color presets and the Tamagui config factory for Adv UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"types": "./dist/index.d.ts",
|
|
21
21
|
"default": "./dist/index.js"
|
|
22
22
|
},
|
|
23
|
+
"./material": {
|
|
24
|
+
"types": "./dist/material.d.ts",
|
|
25
|
+
"default": "./dist/material.js"
|
|
26
|
+
},
|
|
23
27
|
"./package.json": "./package.json"
|
|
24
28
|
},
|
|
25
29
|
"files": [
|
|
@@ -31,11 +35,12 @@
|
|
|
31
35
|
"access": "public"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
38
|
+
"@advui/utils": "0.2.0",
|
|
39
|
+
"@material/material-color-utilities": "0.3.0",
|
|
34
40
|
"@tamagui/animations-css": "2.7.7",
|
|
35
41
|
"@tamagui/animations-react-native": "2.7.7",
|
|
36
42
|
"@tamagui/colors": "2.7.7",
|
|
37
|
-
"@tamagui/web": "2.7.7"
|
|
38
|
-
"@advui/utils": "0.1.0"
|
|
43
|
+
"@tamagui/web": "2.7.7"
|
|
39
44
|
},
|
|
40
45
|
"peerDependencies": {
|
|
41
46
|
"react": ">=19",
|
|
@@ -43,9 +48,9 @@
|
|
|
43
48
|
"tamagui": "^2.7.7"
|
|
44
49
|
},
|
|
45
50
|
"devDependencies": {
|
|
46
|
-
"@types/react": "~19.2.2",
|
|
47
51
|
"@advui/eslint-config": "0.1.0",
|
|
48
52
|
"@advui/tsconfig": "0.1.0",
|
|
53
|
+
"@types/react": "~19.2.2",
|
|
49
54
|
"react": "19.2.3",
|
|
50
55
|
"react-dom": "19.2.3",
|
|
51
56
|
"react-native": "0.86.3",
|
package/src/config.ts
CHANGED
|
@@ -4,19 +4,44 @@ import { type FontFamilies, type FontScale, createUniversalFonts } from './fonts
|
|
|
4
4
|
import { media, mediaQueryDefaultActive } from './media'
|
|
5
5
|
import { type ThemePresetName, themePresets } from './presets'
|
|
6
6
|
import { shorthands } from './shorthands'
|
|
7
|
-
import { type ThemeColorsInput, createThemeColors } from './themes'
|
|
8
|
-
import { type
|
|
7
|
+
import { type GeneratedThemes, type ThemeColorsInput, createThemeColors } from './themes'
|
|
8
|
+
import { type RadiusInput, createUniversalTokens } from './tokens'
|
|
9
9
|
|
|
10
10
|
export interface UniversalConfigOptions {
|
|
11
11
|
/** Starting color preset. Individual `colors` win over the preset. Default: `indigo`. */
|
|
12
12
|
preset?: ThemePresetName
|
|
13
13
|
/** Brand and intent colors — scale names (`'violet'`) or any color string (`'#6366f1'`). */
|
|
14
14
|
colors?: ThemeColorsInput
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Corner roundness for every component: a scale name or base in px (default
|
|
17
|
+
* `md`, 8px), or exact values per token, e.g. `{ button: 9999 }`.
|
|
18
|
+
*/
|
|
19
|
+
radius?: RadiusInput
|
|
17
20
|
/** Global type scale. Default: `default`. */
|
|
18
21
|
fontScale?: FontScale | number
|
|
19
22
|
fonts?: FontFamilies
|
|
23
|
+
/**
|
|
24
|
+
* Complete light and dark themes, e.g. from `material()` in
|
|
25
|
+
* `@advui/theme/material`. Replaces `preset` and `colors`.
|
|
26
|
+
*/
|
|
27
|
+
themes?: GeneratedThemes
|
|
28
|
+
/**
|
|
29
|
+
* Android only: pressing a button, chip, tab or list row shows the native
|
|
30
|
+
* ripple instead of darkening it. `material()` turns it on. Default: false.
|
|
31
|
+
*/
|
|
32
|
+
androidRipple?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Adv UI settings that are not Tamagui's, such as `androidRipple`. */
|
|
36
|
+
export interface UniversalSettings {
|
|
37
|
+
androidRipple: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const defaultSettings: UniversalSettings = { androidRipple: false }
|
|
41
|
+
|
|
42
|
+
/** The Adv UI settings stored on a config from `createUniversalConfig()`. */
|
|
43
|
+
export function getUniversalSettings(config: object): UniversalSettings {
|
|
44
|
+
return (config as { advui?: UniversalSettings }).advui ?? defaultSettings
|
|
20
45
|
}
|
|
21
46
|
|
|
22
47
|
/**
|
|
@@ -28,9 +53,9 @@ export interface UniversalConfigOptions {
|
|
|
28
53
|
*/
|
|
29
54
|
export function createUniversalConfig(options: UniversalConfigOptions = {}) {
|
|
30
55
|
const { preset = 'indigo', colors, radius = 'md', fontScale = 'default', fonts } = options
|
|
31
|
-
const themes = createThemeColors({ ...themePresets[preset].colors, ...colors })
|
|
56
|
+
const themes = options.themes ?? createThemeColors({ ...themePresets[preset].colors, ...colors })
|
|
32
57
|
|
|
33
|
-
|
|
58
|
+
const config = createTamagui({
|
|
34
59
|
tokens: createUniversalTokens({ radius }),
|
|
35
60
|
themes,
|
|
36
61
|
fonts: createUniversalFonts({ scale: fontScale, families: fonts }),
|
|
@@ -50,6 +75,10 @@ export function createUniversalConfig(options: UniversalConfigOptions = {}) {
|
|
|
50
75
|
styleCompat: 'web',
|
|
51
76
|
},
|
|
52
77
|
})
|
|
78
|
+
// Stored on the config object, which components reach from anywhere with
|
|
79
|
+
// getConfig(), including native portals that do not carry React context.
|
|
80
|
+
const advui: UniversalSettings = { androidRipple: options.androidRipple ?? false }
|
|
81
|
+
return Object.assign(config, { advui })
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
export type UniversalConfig = ReturnType<typeof createUniversalConfig>
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export { animations } from './animations'
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
createUniversalConfig,
|
|
4
|
+
getUniversalSettings,
|
|
5
|
+
type UniversalConfig,
|
|
6
|
+
type UniversalConfigOptions,
|
|
7
|
+
type UniversalSettings,
|
|
8
|
+
} from './config'
|
|
3
9
|
export { createUniversalFonts, fontScales, type FontFamilies, type FontScale } from './fonts'
|
|
4
10
|
export { breakpoints, media, mediaQueryDefaultActive } from './media'
|
|
5
11
|
export {
|
|
@@ -33,5 +39,8 @@ export {
|
|
|
33
39
|
size,
|
|
34
40
|
space,
|
|
35
41
|
zIndex,
|
|
42
|
+
type RadiusInput,
|
|
43
|
+
type RadiusOverrides,
|
|
36
44
|
type RadiusScale,
|
|
45
|
+
type RadiusToken,
|
|
37
46
|
} from './tokens'
|
package/src/material.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Material 3 for Adv UI: color roles generated from one seed color with
|
|
3
|
+
* Google's material-color-utilities (the algorithm behind Android dynamic
|
|
4
|
+
* color), plus Material's shape scale and fonts. It lives in its own entry,
|
|
5
|
+
* `@advui/theme/material`, so apps that do not use it never ship the library.
|
|
6
|
+
*
|
|
7
|
+
* Material roles are mapped onto Adv UI's roles, so every component works
|
|
8
|
+
* unchanged: `primaryContainer` → `$primarySoft`, `secondaryContainer` →
|
|
9
|
+
* `$secondary` (Material's tonal button), `outline` → `$input`, and so on.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
type DynamicScheme,
|
|
13
|
+
Hct,
|
|
14
|
+
SchemeTonalSpot,
|
|
15
|
+
argbFromHex,
|
|
16
|
+
customColor,
|
|
17
|
+
hexFromArgb,
|
|
18
|
+
} from '@material/material-color-utilities'
|
|
19
|
+
import { ensureContrast, parseColor, toHex, withAlpha } from '@advui/utils'
|
|
20
|
+
import type { UniversalConfigOptions } from './config'
|
|
21
|
+
import type { FontFamilies } from './fonts'
|
|
22
|
+
import type { ColorMode, GeneratedThemes, ThemeValues } from './themes'
|
|
23
|
+
import type { RadiusOverrides } from './tokens'
|
|
24
|
+
|
|
25
|
+
/** Material's baseline purple, used when no seed is given. */
|
|
26
|
+
export const MATERIAL_BASELINE_SEED = '#6750A4'
|
|
27
|
+
|
|
28
|
+
export interface MaterialOptions {
|
|
29
|
+
/** Seed color the whole palette is generated from. Default: Material baseline purple. */
|
|
30
|
+
seed?: string
|
|
31
|
+
/**
|
|
32
|
+
* Status colors Material does not define. They are harmonized toward the
|
|
33
|
+
* seed (a slight hue shift) so they sit well with it.
|
|
34
|
+
*/
|
|
35
|
+
colors?: { success?: string; warning?: string; info?: string }
|
|
36
|
+
/** Escape hatch: override any generated value per mode. */
|
|
37
|
+
overrides?: Partial<Record<ColorMode, Partial<ThemeValues>>>
|
|
38
|
+
/** Android: show the native ripple when something is pressed. Default: true. */
|
|
39
|
+
androidRipple?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const defaultStatus = { success: '#2e7d32', warning: '#b26a00', info: '#0061a4' }
|
|
43
|
+
|
|
44
|
+
// Material state-layer opacities: hover 8%, focus and press 10%.
|
|
45
|
+
const HOVER = 0.08
|
|
46
|
+
const PRESS = 0.1
|
|
47
|
+
|
|
48
|
+
/** Paints `overlay` over `base` at `opacity`, like a Material state layer (sRGB). */
|
|
49
|
+
function layer(base: string, overlay: string, opacity: number): string {
|
|
50
|
+
const b = parseColor(base)
|
|
51
|
+
const o = parseColor(overlay)
|
|
52
|
+
if (!b || !o) throw new Error(`Invalid color: ${!b ? base : overlay}`)
|
|
53
|
+
const c = (x: number, y: number) => x * (1 - opacity) + y * opacity
|
|
54
|
+
return toHex({ r: c(b.r, o.r), g: c(b.g, o.g), b: c(b.b, o.b), a: 1 })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Neutral tones behind Tamagui's color1…color12 (lightest to strongest text).
|
|
58
|
+
const NEUTRAL_TONES = {
|
|
59
|
+
light: [99, 98, 96, 94, 92, 90, 87, 80, 60, 50, 30, 10],
|
|
60
|
+
dark: [6, 10, 12, 17, 22, 24, 30, 40, 60, 70, 80, 90],
|
|
61
|
+
} as const
|
|
62
|
+
|
|
63
|
+
function buildMode(
|
|
64
|
+
s: DynamicScheme,
|
|
65
|
+
mode: ColorMode,
|
|
66
|
+
status: Record<'success' | 'warning' | 'info', ReturnType<typeof customColor>>,
|
|
67
|
+
): ThemeValues {
|
|
68
|
+
const hex = hexFromArgb
|
|
69
|
+
const isDark = mode === 'dark'
|
|
70
|
+
const surface = hex(s.surface)
|
|
71
|
+
const onSurface = hex(s.onSurface)
|
|
72
|
+
const popover = hex(s.surfaceContainerHigh)
|
|
73
|
+
const muted = hex(s.surfaceContainerHigh)
|
|
74
|
+
const tone = (step: number) => hex(s.neutralPalette.tone(NEUTRAL_TONES[mode][step]!))
|
|
75
|
+
|
|
76
|
+
const primary = hex(s.primary)
|
|
77
|
+
const onPrimary = hex(s.onPrimary)
|
|
78
|
+
const primaryContainer = hex(s.primaryContainer)
|
|
79
|
+
const onPrimaryContainer = hex(s.onPrimaryContainer)
|
|
80
|
+
const secondaryContainer = hex(s.secondaryContainer)
|
|
81
|
+
const onSecondaryContainer = hex(s.onSecondaryContainer)
|
|
82
|
+
const error = hex(s.error)
|
|
83
|
+
const onError = hex(s.onError)
|
|
84
|
+
const errorContainer = hex(s.errorContainer)
|
|
85
|
+
const onErrorContainer = hex(s.onErrorContainer)
|
|
86
|
+
const outline = hex(s.outline)
|
|
87
|
+
const outlineVariant = hex(s.outlineVariant)
|
|
88
|
+
|
|
89
|
+
const intent = (group: ReturnType<typeof customColor>) => {
|
|
90
|
+
const g = isDark ? group.dark : group.light
|
|
91
|
+
return {
|
|
92
|
+
solid: hex(g.color),
|
|
93
|
+
foreground: hex(g.onColor),
|
|
94
|
+
soft: hex(g.colorContainer),
|
|
95
|
+
softForeground: ensureContrast(hex(g.onColorContainer), hex(g.colorContainer)),
|
|
96
|
+
border: layer(hex(g.colorContainer), hex(g.color), 0.35),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const success = intent(status.success)
|
|
100
|
+
const warning = intent(status.warning)
|
|
101
|
+
const info = intent(status.info)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
background: surface,
|
|
105
|
+
backgroundHover: layer(surface, onSurface, HOVER),
|
|
106
|
+
backgroundPress: layer(surface, onSurface, PRESS),
|
|
107
|
+
backgroundFocus: layer(surface, onSurface, PRESS),
|
|
108
|
+
backgroundStrong: hex(s.surfaceContainer),
|
|
109
|
+
backgroundTransparent: withAlpha(surface, 0),
|
|
110
|
+
color: onSurface,
|
|
111
|
+
colorHover: onSurface,
|
|
112
|
+
colorPress: hex(s.onSurfaceVariant),
|
|
113
|
+
colorFocus: onSurface,
|
|
114
|
+
colorTransparent: withAlpha(onSurface, 0),
|
|
115
|
+
borderColor: outlineVariant,
|
|
116
|
+
borderColorHover: outline,
|
|
117
|
+
borderColorPress: outline,
|
|
118
|
+
borderColorFocus: primary,
|
|
119
|
+
placeholderColor: hex(s.onSurfaceVariant),
|
|
120
|
+
outlineColor: primary,
|
|
121
|
+
shadowColor: isDark ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.12)',
|
|
122
|
+
shadowColorStrong: isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.24)',
|
|
123
|
+
color1: tone(0),
|
|
124
|
+
color2: tone(1),
|
|
125
|
+
color3: tone(2),
|
|
126
|
+
color4: tone(3),
|
|
127
|
+
color5: tone(4),
|
|
128
|
+
color6: tone(5),
|
|
129
|
+
color7: tone(6),
|
|
130
|
+
color8: tone(7),
|
|
131
|
+
color9: tone(8),
|
|
132
|
+
color10: tone(9),
|
|
133
|
+
color11: tone(10),
|
|
134
|
+
color12: tone(11),
|
|
135
|
+
|
|
136
|
+
foreground: onSurface,
|
|
137
|
+
muted,
|
|
138
|
+
mutedForeground: ensureContrast(hex(s.onSurfaceVariant), muted),
|
|
139
|
+
card: hex(s.surfaceContainerLow),
|
|
140
|
+
cardForeground: onSurface,
|
|
141
|
+
popover,
|
|
142
|
+
popoverForeground: onSurface,
|
|
143
|
+
overlay: withAlpha(hex(s.scrim), 0.32),
|
|
144
|
+
|
|
145
|
+
border: outlineVariant,
|
|
146
|
+
borderStrong: outline,
|
|
147
|
+
input: outline,
|
|
148
|
+
ring: primary,
|
|
149
|
+
|
|
150
|
+
primary,
|
|
151
|
+
primaryHover: layer(primary, onPrimary, HOVER),
|
|
152
|
+
primaryPress: layer(primary, onPrimary, PRESS),
|
|
153
|
+
primaryForeground: onPrimary,
|
|
154
|
+
primaryText: ensureContrast(primary, surface),
|
|
155
|
+
inversePrimary: ensureContrast(hex(s.inversePrimary), onSurface),
|
|
156
|
+
primarySoft: primaryContainer,
|
|
157
|
+
primarySoftHover: layer(primaryContainer, onPrimaryContainer, HOVER),
|
|
158
|
+
primarySoftForeground: onPrimaryContainer,
|
|
159
|
+
// Material's tonal button.
|
|
160
|
+
secondary: secondaryContainer,
|
|
161
|
+
secondaryHover: layer(secondaryContainer, onSecondaryContainer, HOVER),
|
|
162
|
+
secondaryPress: layer(secondaryContainer, onSecondaryContainer, PRESS),
|
|
163
|
+
secondaryForeground: onSecondaryContainer,
|
|
164
|
+
// Hover highlight for menu items and ghost buttons: a state layer over the
|
|
165
|
+
// menu surface, so it reads on both the page and the menu.
|
|
166
|
+
accent: layer(popover, onSurface, HOVER),
|
|
167
|
+
accentHover: layer(popover, onSurface, 0.12),
|
|
168
|
+
accentForeground: onSurface,
|
|
169
|
+
|
|
170
|
+
destructive: error,
|
|
171
|
+
destructiveHover: layer(error, onError, HOVER),
|
|
172
|
+
destructivePress: layer(error, onError, PRESS),
|
|
173
|
+
destructiveForeground: onError,
|
|
174
|
+
destructiveSoft: errorContainer,
|
|
175
|
+
destructiveSoftForeground: onErrorContainer,
|
|
176
|
+
success: success.solid,
|
|
177
|
+
successForeground: success.foreground,
|
|
178
|
+
successSoft: success.soft,
|
|
179
|
+
successSoftForeground: success.softForeground,
|
|
180
|
+
successBorder: success.border,
|
|
181
|
+
warning: warning.solid,
|
|
182
|
+
warningForeground: warning.foreground,
|
|
183
|
+
warningSoft: warning.soft,
|
|
184
|
+
warningSoftForeground: warning.softForeground,
|
|
185
|
+
warningBorder: warning.border,
|
|
186
|
+
error,
|
|
187
|
+
errorForeground: onError,
|
|
188
|
+
errorSoft: errorContainer,
|
|
189
|
+
errorSoftForeground: onErrorContainer,
|
|
190
|
+
errorBorder: layer(errorContainer, error, 0.35),
|
|
191
|
+
info: info.solid,
|
|
192
|
+
infoForeground: info.foreground,
|
|
193
|
+
infoSoft: info.soft,
|
|
194
|
+
infoSoftForeground: info.softForeground,
|
|
195
|
+
infoBorder: info.border,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Light and dark themes with Material 3 color roles (the "tonal spot" scheme
|
|
201
|
+
* Android uses for dynamic color), generated from `seed`.
|
|
202
|
+
*/
|
|
203
|
+
export function createMaterialThemes(options: MaterialOptions = {}): GeneratedThemes {
|
|
204
|
+
const seed = options.seed ?? MATERIAL_BASELINE_SEED
|
|
205
|
+
const source = argbFromHex(toHex(seed))
|
|
206
|
+
const colors = { ...defaultStatus, ...options.colors }
|
|
207
|
+
const status = {
|
|
208
|
+
success: customColor(source, {
|
|
209
|
+
name: 'success',
|
|
210
|
+
value: argbFromHex(colors.success),
|
|
211
|
+
blend: true,
|
|
212
|
+
}),
|
|
213
|
+
warning: customColor(source, {
|
|
214
|
+
name: 'warning',
|
|
215
|
+
value: argbFromHex(colors.warning),
|
|
216
|
+
blend: true,
|
|
217
|
+
}),
|
|
218
|
+
info: customColor(source, { name: 'info', value: argbFromHex(colors.info), blend: true }),
|
|
219
|
+
}
|
|
220
|
+
const scheme = (isDark: boolean) => new SchemeTonalSpot(Hct.fromInt(source), isDark, 0)
|
|
221
|
+
return {
|
|
222
|
+
light: { ...buildMode(scheme(false), 'light', status), ...options.overrides?.light },
|
|
223
|
+
dark: { ...buildMode(scheme(true), 'dark', status), ...options.overrides?.dark },
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Material 3 shape scale on Adv UI's radius tokens: pill buttons, 4px fields,
|
|
229
|
+
* 12px cards and 28px dialogs and sheets.
|
|
230
|
+
*/
|
|
231
|
+
export const materialShape: RadiusOverrides = {
|
|
232
|
+
xs: 2,
|
|
233
|
+
sm: 4,
|
|
234
|
+
md: 4,
|
|
235
|
+
lg: 8,
|
|
236
|
+
xl: 12,
|
|
237
|
+
'2xl': 16,
|
|
238
|
+
'3xl': 28,
|
|
239
|
+
button: 9999,
|
|
240
|
+
buttonLg: 9999,
|
|
241
|
+
dialog: 28,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const roboto =
|
|
245
|
+
'Roboto, "Roboto Flex", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif'
|
|
246
|
+
|
|
247
|
+
/** Roboto on web (load it in your app); the platform font on iOS and Android. */
|
|
248
|
+
export const materialFonts: FontFamilies = { body: roboto, heading: roboto }
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Options for `createUniversalConfig` that give every component the Material 3
|
|
252
|
+
* look: color roles from `seed`, Material's shapes, Roboto and, on Android,
|
|
253
|
+
* the press ripple.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* import { createUniversalConfig } from '@advui/theme'
|
|
257
|
+
* import { material } from '@advui/theme/material'
|
|
258
|
+
*
|
|
259
|
+
* export const config = createUniversalConfig(material({ seed: '#6750A4' }))
|
|
260
|
+
*/
|
|
261
|
+
export function material(options: MaterialOptions = {}): UniversalConfigOptions {
|
|
262
|
+
return {
|
|
263
|
+
themes: createMaterialThemes(options),
|
|
264
|
+
radius: materialShape,
|
|
265
|
+
fonts: materialFonts,
|
|
266
|
+
androidRipple: options.androidRipple ?? true,
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/themes.ts
CHANGED
|
@@ -65,6 +65,8 @@ export type ThemeValues = {
|
|
|
65
65
|
primaryForeground: string
|
|
66
66
|
/** Primary-colored text/links on the page background (≥4.5:1). `primary` is a fill color. */
|
|
67
67
|
primaryText: string
|
|
68
|
+
/** Primary-colored text on inverse surfaces (`$foreground` as a background), e.g. a snackbar action. */
|
|
69
|
+
inversePrimary: string
|
|
68
70
|
primarySoft: string
|
|
69
71
|
primarySoftHover: string
|
|
70
72
|
primarySoftForeground: string
|
|
@@ -240,6 +242,10 @@ function buildMode(mode: ColorMode, input: Required<Omit<ThemeColorsInput, 'over
|
|
|
240
242
|
primaryPress: primary.press,
|
|
241
243
|
primaryForeground: primary.foreground,
|
|
242
244
|
primaryText: input.monochrome ? n[11] : ensureContrast(primaryScale[10], background),
|
|
245
|
+
// The other mode's text step reads on the inverted surface.
|
|
246
|
+
inversePrimary: input.monochrome
|
|
247
|
+
? background
|
|
248
|
+
: ensureContrast(resolveScale(input.primary)[isDark ? 'light' : 'dark'][10], n[11]),
|
|
243
249
|
primarySoft: primaryScale[2],
|
|
244
250
|
primarySoftHover: primaryScale[3],
|
|
245
251
|
primarySoftForeground: ensureContrast(primaryScale[10], primaryScale[3]),
|
package/src/tokens.ts
CHANGED
|
@@ -71,15 +71,28 @@ export const radiusScales = {
|
|
|
71
71
|
|
|
72
72
|
export type RadiusScale = keyof typeof radiusScales
|
|
73
73
|
|
|
74
|
+
export type RadiusToken =
|
|
75
|
+
'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'button' | 'buttonLg' | 'dialog'
|
|
76
|
+
|
|
77
|
+
/** Exact values for some radius tokens, on top of a base (`md` if omitted). */
|
|
78
|
+
export type RadiusOverrides = { base?: RadiusScale | number } & Partial<Record<RadiusToken, number>>
|
|
79
|
+
|
|
80
|
+
export type RadiusInput = RadiusScale | number | RadiusOverrides
|
|
81
|
+
|
|
74
82
|
/**
|
|
75
83
|
* Radius tokens derived from a single base value so the whole library can be
|
|
76
84
|
* made sharper or rounder at once. Components pick semantic steps:
|
|
77
|
-
* controls `$md`, menus/popovers `$lg`, cards
|
|
85
|
+
* controls `$md`, menus/popovers `$lg`, cards `$xl`, pills `$full`, and role
|
|
86
|
+
* tokens a design system can reshape on their own: `$button` / `$buttonLg`
|
|
87
|
+
* (buttons, icon buttons, toggles) and `$dialog` (dialogs and sheets).
|
|
78
88
|
*/
|
|
79
|
-
export function createRadius(
|
|
89
|
+
export function createRadius(input: RadiusInput = 'md') {
|
|
90
|
+
const overrides = typeof input === 'object' ? input : {}
|
|
91
|
+
const scale = typeof input === 'object' ? (input.base ?? 'md') : input
|
|
80
92
|
const base = typeof scale === 'number' ? scale : radiusScales[scale]
|
|
81
93
|
const r = (factor: number) => Math.round(base * factor)
|
|
82
|
-
|
|
94
|
+
const { base: _base, ...exact } = overrides
|
|
95
|
+
const tokens = {
|
|
83
96
|
0: 0,
|
|
84
97
|
xs: r(0.25),
|
|
85
98
|
sm: r(0.5),
|
|
@@ -89,8 +102,12 @@ export function createRadius(scale: RadiusScale | number = 'md') {
|
|
|
89
102
|
'2xl': r(2),
|
|
90
103
|
'3xl': r(3),
|
|
91
104
|
full: 9999,
|
|
92
|
-
|
|
105
|
+
button: r(0.75),
|
|
106
|
+
buttonLg: r(1),
|
|
107
|
+
dialog: r(1.5),
|
|
108
|
+
...exact,
|
|
93
109
|
}
|
|
110
|
+
return { ...tokens, true: tokens.md }
|
|
94
111
|
}
|
|
95
112
|
|
|
96
113
|
export const zIndex = {
|
|
@@ -112,7 +129,7 @@ export const colorTokens = {
|
|
|
112
129
|
transparent: 'rgba(0, 0, 0, 0)',
|
|
113
130
|
} as const
|
|
114
131
|
|
|
115
|
-
export function createUniversalTokens({ radius = 'md' }: { radius?:
|
|
132
|
+
export function createUniversalTokens({ radius = 'md' }: { radius?: RadiusInput } = {}) {
|
|
116
133
|
return createTokens({
|
|
117
134
|
space,
|
|
118
135
|
size,
|