@advui/theme 0.1.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.
Files changed (63) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +23 -0
  3. package/dist/animations.d.ts +18 -0
  4. package/dist/animations.d.ts.map +1 -0
  5. package/dist/animations.js +25 -0
  6. package/dist/animations.js.map +1 -0
  7. package/dist/animations.native.d.ts +75 -0
  8. package/dist/animations.native.d.ts.map +1 -0
  9. package/dist/animations.native.js +21 -0
  10. package/dist/animations.native.js.map +1 -0
  11. package/dist/config.d.ts +427 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +39 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/fonts.d.ts +80 -0
  16. package/dist/fonts.d.ts.map +1 -0
  17. package/dist/fonts.js +54 -0
  18. package/dist/fonts.js.map +1 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +11 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/media.d.ts +59 -0
  24. package/dist/media.d.ts.map +1 -0
  25. package/dist/media.js +48 -0
  26. package/dist/media.js.map +1 -0
  27. package/dist/palettes.d.ts +46 -0
  28. package/dist/palettes.d.ts.map +1 -0
  29. package/dist/palettes.js +64 -0
  30. package/dist/palettes.js.map +1 -0
  31. package/dist/presets.d.ts +91 -0
  32. package/dist/presets.d.ts.map +1 -0
  33. package/dist/presets.js +54 -0
  34. package/dist/presets.js.map +1 -0
  35. package/dist/shadows.d.ts +60 -0
  36. package/dist/shadows.d.ts.map +1 -0
  37. package/dist/shadows.js +44 -0
  38. package/dist/shadows.js.map +1 -0
  39. package/dist/shorthands.d.ts +37 -0
  40. package/dist/shorthands.d.ts.map +1 -0
  41. package/dist/shorthands.js +37 -0
  42. package/dist/shorthands.js.map +1 -0
  43. package/dist/themes.d.ts +129 -0
  44. package/dist/themes.d.ts.map +1 -0
  45. package/dist/themes.js +169 -0
  46. package/dist/themes.js.map +1 -0
  47. package/dist/tokens.d.ts +359 -0
  48. package/dist/tokens.d.ts.map +1 -0
  49. package/dist/tokens.js +109 -0
  50. package/dist/tokens.js.map +1 -0
  51. package/package.json +63 -0
  52. package/src/animations.native.ts +21 -0
  53. package/src/animations.ts +26 -0
  54. package/src/config.ts +55 -0
  55. package/src/fonts.ts +89 -0
  56. package/src/index.ts +37 -0
  57. package/src/media.ts +55 -0
  58. package/src/palettes.ts +123 -0
  59. package/src/presets.ts +64 -0
  60. package/src/shadows.ts +45 -0
  61. package/src/shorthands.ts +36 -0
  62. package/src/themes.ts +324 -0
  63. package/src/tokens.ts +123 -0
package/src/themes.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { accessibleSolid, adjustLightness, ensureContrast, withAlpha } from '@advui/utils'
2
+ import { type ColorScale, type ColorSource, type ScaleSteps, resolveScale } from './palettes'
3
+
4
+ export type ColorMode = 'light' | 'dark'
5
+
6
+ /**
7
+ * Every value a Adv UI theme defines. The first block is Tamagui's
8
+ * standard theme contract (used by Tamagui primitives); the rest are the
9
+ * semantic tokens our components use (`$primary`, `$mutedForeground`, …).
10
+ */
11
+ export type ThemeValues = {
12
+ // Tamagui standard keys
13
+ background: string
14
+ backgroundHover: string
15
+ backgroundPress: string
16
+ backgroundFocus: string
17
+ backgroundStrong: string
18
+ backgroundTransparent: string
19
+ color: string
20
+ colorHover: string
21
+ colorPress: string
22
+ colorFocus: string
23
+ colorTransparent: string
24
+ borderColor: string
25
+ borderColorHover: string
26
+ borderColorPress: string
27
+ borderColorFocus: string
28
+ placeholderColor: string
29
+ outlineColor: string
30
+ shadowColor: string
31
+ shadowColorStrong: string
32
+ color1: string
33
+ color2: string
34
+ color3: string
35
+ color4: string
36
+ color5: string
37
+ color6: string
38
+ color7: string
39
+ color8: string
40
+ color9: string
41
+ color10: string
42
+ color11: string
43
+ color12: string
44
+
45
+ // Surfaces
46
+ foreground: string
47
+ muted: string
48
+ mutedForeground: string
49
+ card: string
50
+ cardForeground: string
51
+ popover: string
52
+ popoverForeground: string
53
+ overlay: string
54
+
55
+ // Lines
56
+ border: string
57
+ borderStrong: string
58
+ input: string
59
+ ring: string
60
+
61
+ // Brand
62
+ primary: string
63
+ primaryHover: string
64
+ primaryPress: string
65
+ primaryForeground: string
66
+ /** Primary-colored text/links on the page background (≥4.5:1). `primary` is a fill color. */
67
+ primaryText: string
68
+ primarySoft: string
69
+ primarySoftHover: string
70
+ primarySoftForeground: string
71
+ secondary: string
72
+ secondaryHover: string
73
+ secondaryPress: string
74
+ secondaryForeground: string
75
+ accent: string
76
+ accentHover: string
77
+ accentForeground: string
78
+
79
+ // Intent
80
+ destructive: string
81
+ destructiveHover: string
82
+ destructivePress: string
83
+ destructiveForeground: string
84
+ destructiveSoft: string
85
+ destructiveSoftForeground: string
86
+ success: string
87
+ successForeground: string
88
+ successSoft: string
89
+ successSoftForeground: string
90
+ successBorder: string
91
+ warning: string
92
+ warningForeground: string
93
+ warningSoft: string
94
+ warningSoftForeground: string
95
+ warningBorder: string
96
+ error: string
97
+ errorForeground: string
98
+ errorSoft: string
99
+ errorSoftForeground: string
100
+ errorBorder: string
101
+ info: string
102
+ infoForeground: string
103
+ infoSoft: string
104
+ infoSoftForeground: string
105
+ infoBorder: string
106
+ }
107
+
108
+ export type ThemeKey = keyof ThemeValues
109
+
110
+ export interface ThemeColorsInput {
111
+ /** Brand color used for primary actions, focus rings and selection. */
112
+ primary?: ColorSource
113
+ /** Soft surface for secondary actions. Defaults to the neutral scale. */
114
+ secondary?: ColorSource
115
+ /** Highlight surface for hovered menu items, ghost buttons, etc. Defaults to neutral. */
116
+ accent?: ColorSource
117
+ /** Gray scale for backgrounds, borders and text. */
118
+ neutral?: ColorSource
119
+ destructive?: ColorSource
120
+ success?: ColorSource
121
+ warning?: ColorSource
122
+ error?: ColorSource
123
+ info?: ColorSource
124
+ /**
125
+ * `true` makes the primary color the neutral's high-contrast step
126
+ * (black in light mode, white in dark mode) — the "Slate"/"Neutral" look.
127
+ */
128
+ monochrome?: boolean
129
+ /** Escape hatch: override any generated value per mode. */
130
+ overrides?: Partial<Record<ColorMode, Partial<ThemeValues>>>
131
+ }
132
+
133
+ export type GeneratedThemes = {
134
+ light: ThemeValues
135
+ dark: ThemeValues
136
+ }
137
+
138
+ const WHITE = '#ffffff'
139
+
140
+ type Solid = { base: string; hover: string; press: string; foreground: string }
141
+
142
+ function solid(background: string, darkText: string): Solid {
143
+ const pair = accessibleSolid(background, { dark: darkText })
144
+ return {
145
+ base: pair.background,
146
+ hover: adjustLightness(pair.background, -0.05),
147
+ press: adjustLightness(pair.background, -0.09),
148
+ foreground: pair.foreground,
149
+ }
150
+ }
151
+
152
+ function status(scale: ScaleSteps, darkText: string) {
153
+ const s = solid(scale[8], darkText)
154
+ return {
155
+ solid: s.base,
156
+ foreground: s.foreground,
157
+ soft: scale[2],
158
+ // Radix step 11 targets APCA; nudge it until it also meets WCAG AA on step 3.
159
+ softForeground: ensureContrast(scale[10], scale[2]),
160
+ border: scale[5],
161
+ }
162
+ }
163
+
164
+ function buildMode(mode: ColorMode, input: Required<Omit<ThemeColorsInput, 'overrides'>>) {
165
+ const pick = (source: ColorSource): ScaleSteps => resolveScale(source)[mode]
166
+ const n = pick(input.neutral)
167
+ const isDark = mode === 'dark'
168
+ const darkText = isDark ? n[0] : n[11]
169
+
170
+ const primary = input.monochrome
171
+ ? {
172
+ base: n[11],
173
+ hover: isDark ? n[10] : adjustLightness(n[11], 0.1),
174
+ press: isDark ? adjustLightness(n[10], -0.06) : adjustLightness(n[11], 0.16),
175
+ foreground: isDark ? n[0] : WHITE,
176
+ }
177
+ : solid(pick(input.primary)[8], darkText)
178
+ const primaryScale = input.monochrome ? n : pick(input.primary)
179
+ const secondary = pick(input.secondary)
180
+ const accent = pick(input.accent)
181
+ const destructive = solid(pick(input.destructive)[8], darkText)
182
+ const destructiveScale = pick(input.destructive)
183
+ const success = status(pick(input.success), darkText)
184
+ const warning = status(pick(input.warning), darkText)
185
+ const error = status(pick(input.error), darkText)
186
+ const info = status(pick(input.info), darkText)
187
+
188
+ const background = isDark ? n[0] : WHITE
189
+ const surface = isDark ? n[1] : WHITE
190
+
191
+ const values: ThemeValues = {
192
+ background,
193
+ backgroundHover: isDark ? n[2] : n[1],
194
+ backgroundPress: isDark ? n[3] : n[2],
195
+ backgroundFocus: isDark ? n[2] : n[1],
196
+ backgroundStrong: isDark ? n[1] : n[1],
197
+ backgroundTransparent: withAlpha(background, 0),
198
+ color: n[11],
199
+ colorHover: n[11],
200
+ colorPress: n[10],
201
+ colorFocus: n[11],
202
+ colorTransparent: withAlpha(n[11], 0),
203
+ borderColor: n[5],
204
+ borderColorHover: n[7],
205
+ borderColorPress: n[7],
206
+ borderColorFocus: primary.base,
207
+ placeholderColor: n[9],
208
+ outlineColor: primary.base,
209
+ shadowColor: isDark ? 'rgba(0, 0, 0, 0.45)' : 'rgba(16, 24, 40, 0.08)',
210
+ shadowColorStrong: isDark ? 'rgba(0, 0, 0, 0.65)' : 'rgba(16, 24, 40, 0.18)',
211
+ color1: n[0],
212
+ color2: n[1],
213
+ color3: n[2],
214
+ color4: n[3],
215
+ color5: n[4],
216
+ color6: n[5],
217
+ color7: n[6],
218
+ color8: n[7],
219
+ color9: n[8],
220
+ color10: n[9],
221
+ color11: n[10],
222
+ color12: n[11],
223
+
224
+ foreground: n[11],
225
+ muted: isDark ? n[2] : n[1],
226
+ mutedForeground: ensureContrast(n[10], isDark ? n[1] : n[1]),
227
+ card: surface,
228
+ cardForeground: n[11],
229
+ popover: surface,
230
+ popoverForeground: n[11],
231
+ overlay: isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(9, 9, 11, 0.5)',
232
+
233
+ border: n[5],
234
+ borderStrong: n[6],
235
+ input: n[6],
236
+ ring: primary.base,
237
+
238
+ primary: primary.base,
239
+ primaryHover: primary.hover,
240
+ primaryPress: primary.press,
241
+ primaryForeground: primary.foreground,
242
+ primaryText: input.monochrome ? n[11] : ensureContrast(primaryScale[10], background),
243
+ primarySoft: primaryScale[2],
244
+ primarySoftHover: primaryScale[3],
245
+ primarySoftForeground: ensureContrast(primaryScale[10], primaryScale[3]),
246
+ secondary: secondary[2],
247
+ secondaryHover: secondary[3],
248
+ secondaryPress: secondary[4],
249
+ secondaryForeground: ensureContrast(secondary[11], secondary[4]),
250
+ accent: accent[2],
251
+ accentHover: accent[3],
252
+ accentForeground: ensureContrast(accent[11], accent[3]),
253
+
254
+ destructive: destructive.base,
255
+ destructiveHover: destructive.hover,
256
+ destructivePress: destructive.press,
257
+ destructiveForeground: destructive.foreground,
258
+ destructiveSoft: destructiveScale[2],
259
+ destructiveSoftForeground: ensureContrast(destructiveScale[10], destructiveScale[3]),
260
+ success: success.solid,
261
+ successForeground: success.foreground,
262
+ successSoft: success.soft,
263
+ successSoftForeground: success.softForeground,
264
+ successBorder: success.border,
265
+ warning: warning.solid,
266
+ warningForeground: warning.foreground,
267
+ warningSoft: warning.soft,
268
+ warningSoftForeground: warning.softForeground,
269
+ warningBorder: warning.border,
270
+ error: error.solid,
271
+ errorForeground: error.foreground,
272
+ errorSoft: error.soft,
273
+ errorSoftForeground: error.softForeground,
274
+ errorBorder: error.border,
275
+ info: info.solid,
276
+ infoForeground: info.foreground,
277
+ infoSoft: info.soft,
278
+ infoSoftForeground: info.softForeground,
279
+ infoBorder: info.border,
280
+ }
281
+
282
+ return values
283
+ }
284
+
285
+ export const defaultThemeColors: Required<Omit<ThemeColorsInput, 'overrides'>> = {
286
+ primary: 'indigo',
287
+ secondary: 'slate',
288
+ accent: 'slate',
289
+ neutral: 'slate',
290
+ destructive: 'red',
291
+ success: 'green',
292
+ warning: 'amber',
293
+ error: 'red',
294
+ info: 'blue',
295
+ monochrome: false,
296
+ }
297
+
298
+ /**
299
+ * Generates the light and dark theme objects. Missing `secondary`/`accent`
300
+ * fall back to the neutral scale so a single `primary` is enough to brand an app.
301
+ */
302
+ export function createThemeColors(input: ThemeColorsInput = {}): GeneratedThemes {
303
+ const neutral = input.neutral ?? defaultThemeColors.neutral
304
+ const resolved = {
305
+ ...defaultThemeColors,
306
+ secondary: neutral,
307
+ accent: neutral,
308
+ ...stripUndefined(input),
309
+ neutral,
310
+ } as Required<Omit<ThemeColorsInput, 'overrides'>>
311
+
312
+ return {
313
+ light: { ...buildMode('light', resolved), ...input.overrides?.light },
314
+ dark: { ...buildMode('dark', resolved), ...input.overrides?.dark },
315
+ }
316
+ }
317
+
318
+ function stripUndefined<T extends object>(value: T): Partial<T> {
319
+ return Object.fromEntries(
320
+ Object.entries(value).filter(([key, v]) => v !== undefined && key !== 'overrides'),
321
+ ) as Partial<T>
322
+ }
323
+
324
+ export type { ColorScale }
package/src/tokens.ts ADDED
@@ -0,0 +1,123 @@
1
+ import { createTokens } from 'tamagui'
2
+
3
+ /**
4
+ * 4px-based spacing scale: `$1` = 4px, `$4` = 16px, `$10` = 40px.
5
+ * Negative keys (`$-2`) exist for margins.
6
+ */
7
+ const spaceScale = {
8
+ 0: 0,
9
+ 0.5: 2,
10
+ 1: 4,
11
+ 1.5: 6,
12
+ 2: 8,
13
+ 2.5: 10,
14
+ 3: 12,
15
+ 3.5: 14,
16
+ 4: 16,
17
+ 5: 20,
18
+ 6: 24,
19
+ 7: 28,
20
+ 8: 32,
21
+ 9: 36,
22
+ 10: 40,
23
+ 11: 44,
24
+ 12: 48,
25
+ 14: 56,
26
+ 16: 64,
27
+ 20: 80,
28
+ 24: 96,
29
+ 32: 128,
30
+ } as const
31
+
32
+ type SpaceKey = keyof typeof spaceScale
33
+ type NegativeSpace = { [K in Exclude<SpaceKey, 0> as `-${K}`]: number }
34
+
35
+ export const space = {
36
+ ...spaceScale,
37
+ true: 16,
38
+ ...(Object.fromEntries(
39
+ Object.entries(spaceScale)
40
+ .filter(([key]) => key !== '0')
41
+ .map(([key, value]) => [`-${key}`, -value]),
42
+ ) as NegativeSpace),
43
+ }
44
+
45
+ /** Sizes share the spacing scale and extend it for larger layout boxes. */
46
+ export const size = {
47
+ ...spaceScale,
48
+ 40: 160,
49
+ 48: 192,
50
+ 56: 224,
51
+ 64: 256,
52
+ 72: 288,
53
+ 80: 320,
54
+ 96: 384,
55
+ 112: 448,
56
+ 128: 512,
57
+ 144: 576,
58
+ 168: 672,
59
+ 192: 768,
60
+ 224: 896,
61
+ true: 40,
62
+ } as const
63
+
64
+ export const radiusScales = {
65
+ none: 0,
66
+ sm: 4,
67
+ md: 8,
68
+ lg: 12,
69
+ xl: 16,
70
+ } as const
71
+
72
+ export type RadiusScale = keyof typeof radiusScales
73
+
74
+ /**
75
+ * Radius tokens derived from a single base value so the whole library can be
76
+ * made sharper or rounder at once. Components pick semantic steps:
77
+ * controls `$md`, menus/popovers `$lg`, cards/dialogs `$xl`, pills `$full`.
78
+ */
79
+ export function createRadius(scale: RadiusScale | number = 'md') {
80
+ const base = typeof scale === 'number' ? scale : radiusScales[scale]
81
+ const r = (factor: number) => Math.round(base * factor)
82
+ return {
83
+ 0: 0,
84
+ xs: r(0.25),
85
+ sm: r(0.5),
86
+ md: r(0.75),
87
+ lg: r(1),
88
+ xl: r(1.5),
89
+ '2xl': r(2),
90
+ '3xl': r(3),
91
+ full: 9999,
92
+ true: r(0.75),
93
+ }
94
+ }
95
+
96
+ export const zIndex = {
97
+ 0: 0,
98
+ 1: 1,
99
+ base: 0,
100
+ dropdown: 1000,
101
+ sticky: 1100,
102
+ overlay: 1200,
103
+ modal: 1300,
104
+ popover: 1400,
105
+ toast: 1500,
106
+ tooltip: 1600,
107
+ } as const
108
+
109
+ export const colorTokens = {
110
+ white: '#ffffff',
111
+ black: '#000000',
112
+ transparent: 'rgba(0, 0, 0, 0)',
113
+ } as const
114
+
115
+ export function createUniversalTokens({ radius = 'md' }: { radius?: RadiusScale | number } = {}) {
116
+ return createTokens({
117
+ space,
118
+ size,
119
+ radius: createRadius(radius),
120
+ zIndex,
121
+ color: colorTokens,
122
+ })
123
+ }