@tamagui/theme-builder 1.121.12 → 1.122.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 (84) hide show
  1. package/dist/cjs/ThemeBuilder.cjs +3 -1
  2. package/dist/cjs/ThemeBuilder.js +5 -1
  3. package/dist/cjs/ThemeBuilder.js.map +1 -1
  4. package/dist/cjs/ThemeBuilder.native.js +3 -1
  5. package/dist/cjs/ThemeBuilder.native.js.map +1 -1
  6. package/dist/cjs/createThemes.cjs +240 -0
  7. package/dist/cjs/createThemes.js +251 -0
  8. package/dist/cjs/createThemes.js.map +6 -0
  9. package/dist/cjs/createThemes.native.js +270 -0
  10. package/dist/cjs/createThemes.native.js.map +6 -0
  11. package/dist/cjs/defaultComponentThemes.cjs +81 -0
  12. package/dist/cjs/defaultComponentThemes.js +40 -0
  13. package/dist/cjs/defaultComponentThemes.js.map +6 -0
  14. package/dist/cjs/defaultComponentThemes.native.js +81 -0
  15. package/dist/cjs/defaultComponentThemes.native.js.map +6 -0
  16. package/dist/cjs/defaultTemplates.cjs +141 -0
  17. package/dist/cjs/defaultTemplates.js +132 -0
  18. package/dist/cjs/defaultTemplates.js.map +6 -0
  19. package/dist/cjs/defaultTemplates.native.js +142 -0
  20. package/dist/cjs/defaultTemplates.native.js.map +6 -0
  21. package/dist/cjs/getThemeSuitePalettes.cjs +81 -0
  22. package/dist/cjs/getThemeSuitePalettes.js +64 -0
  23. package/dist/cjs/getThemeSuitePalettes.js.map +6 -0
  24. package/dist/cjs/getThemeSuitePalettes.native.js +95 -0
  25. package/dist/cjs/getThemeSuitePalettes.native.js.map +6 -0
  26. package/dist/cjs/index.cjs +12 -1
  27. package/dist/cjs/index.js +8 -1
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/index.native.js +15 -1
  30. package/dist/cjs/index.native.js.map +1 -1
  31. package/dist/esm/ThemeBuilder.js +5 -1
  32. package/dist/esm/ThemeBuilder.js.map +1 -1
  33. package/dist/esm/ThemeBuilder.mjs +3 -1
  34. package/dist/esm/ThemeBuilder.mjs.map +1 -1
  35. package/dist/esm/ThemeBuilder.native.js +3 -1
  36. package/dist/esm/ThemeBuilder.native.js.map +1 -1
  37. package/dist/esm/createThemes.js +244 -0
  38. package/dist/esm/createThemes.js.map +6 -0
  39. package/dist/esm/createThemes.mjs +209 -0
  40. package/dist/esm/createThemes.mjs.map +1 -0
  41. package/dist/esm/createThemes.native.js +247 -0
  42. package/dist/esm/createThemes.native.js.map +6 -0
  43. package/dist/esm/defaultComponentThemes.js +24 -0
  44. package/dist/esm/defaultComponentThemes.js.map +6 -0
  45. package/dist/esm/defaultComponentThemes.mjs +58 -0
  46. package/dist/esm/defaultComponentThemes.mjs.map +1 -0
  47. package/dist/esm/defaultComponentThemes.native.js +60 -0
  48. package/dist/esm/defaultComponentThemes.native.js.map +6 -0
  49. package/dist/esm/defaultTemplates.js +116 -0
  50. package/dist/esm/defaultTemplates.js.map +6 -0
  51. package/dist/esm/defaultTemplates.mjs +118 -0
  52. package/dist/esm/defaultTemplates.mjs.map +1 -0
  53. package/dist/esm/defaultTemplates.native.js +122 -0
  54. package/dist/esm/defaultTemplates.native.js.map +6 -0
  55. package/dist/esm/getThemeSuitePalettes.js +48 -0
  56. package/dist/esm/getThemeSuitePalettes.js.map +6 -0
  57. package/dist/esm/getThemeSuitePalettes.mjs +57 -0
  58. package/dist/esm/getThemeSuitePalettes.mjs.map +1 -0
  59. package/dist/esm/getThemeSuitePalettes.native.js +74 -0
  60. package/dist/esm/getThemeSuitePalettes.native.js.map +6 -0
  61. package/dist/esm/index.js +16 -0
  62. package/dist/esm/index.js.map +1 -1
  63. package/dist/esm/index.mjs +5 -1
  64. package/dist/esm/index.mjs.map +1 -1
  65. package/dist/esm/index.native.js +11 -0
  66. package/dist/esm/index.native.js.map +2 -2
  67. package/package.json +3 -3
  68. package/src/ThemeBuilder.ts +3 -1
  69. package/src/createThemes.ts +438 -0
  70. package/src/defaultComponentThemes.ts +22 -0
  71. package/src/defaultTemplates.ts +144 -0
  72. package/src/getThemeSuitePalettes.ts +95 -0
  73. package/src/index.ts +13 -0
  74. package/types/ThemeBuilder.d.ts.map +1 -1
  75. package/types/createThemes.d.ts +86 -0
  76. package/types/createThemes.d.ts.map +1 -0
  77. package/types/defaultComponentThemes.d.ts +57 -0
  78. package/types/defaultComponentThemes.d.ts.map +1 -0
  79. package/types/defaultTemplates.d.ts +2 -0
  80. package/types/defaultTemplates.d.ts.map +1 -0
  81. package/types/getThemeSuitePalettes.d.ts +7 -0
  82. package/types/getThemeSuitePalettes.d.ts.map +1 -0
  83. package/types/index.d.ts +4 -0
  84. package/types/index.d.ts.map +1 -1
@@ -0,0 +1,438 @@
1
+ import {
2
+ createThemeBuilder,
3
+ PALETTE_BACKGROUND_OFFSET,
4
+ type ThemeBuilder,
5
+ } from '@tamagui/theme-builder'
6
+ import type { BuildPalettes, BuildTemplates, BuildThemeSuiteProps } from '@tamagui/themes'
7
+ import { parseToHsla } from 'color2k'
8
+ import { defaultTemplates } from './defaultTemplates'
9
+ import { getThemeSuitePalettes } from './getThemeSuitePalettes'
10
+ import { defaultComponentThemes } from './defaultComponentThemes'
11
+
12
+ export { defaultTemplates } from './defaultTemplates'
13
+ export { getThemeSuitePalettes, PALETTE_BACKGROUND_OFFSET } from './getThemeSuitePalettes'
14
+
15
+ // for studio
16
+ // allows more detailed configuration, used by studio
17
+ // eventually we should merge this down into simple and have it handle what we need
18
+ export function createStudioThemes(props: BuildThemeSuiteProps) {
19
+ const palettes = createPalettes(props.palettes)
20
+ return createSimpleThemeBuilder({
21
+ palettes,
22
+ templates: defaultTemplates,
23
+ componentThemes: defaultComponentThemes,
24
+ accentTheme: !!props.palettes.accent,
25
+ })
26
+ }
27
+
28
+ /**
29
+ * TODO
30
+ *
31
+ * - we avoidNestingWithin accent, but sometimes want it eg v4-tamagui grandChildren
32
+ * a good default would be to IF palette is set, dont nest, IF only template, nest
33
+ * needs to update both runtime logic and types
34
+ */
35
+
36
+ type ExtraThemeValues = Record<string, string>
37
+ type ExtraThemeValuesByScheme<Values extends ExtraThemeValues = ExtraThemeValues> = {
38
+ dark: Values
39
+ light: Values
40
+ }
41
+
42
+ type SimpleThemeDefinition = { palette?: Palette; template?: string }
43
+ type BaseThemeDefinition<Extra extends ExtraThemeValuesByScheme> = {
44
+ palette: Palette
45
+ template?: string
46
+ extra?: Extra
47
+ }
48
+
49
+ export type SimpleThemesDefinition = Record<string, SimpleThemeDefinition>
50
+ type SimplePaletteDefinitions = Record<string, string[]>
51
+
52
+ type SinglePalette = string[]
53
+ type SchemePalette = { light: SinglePalette; dark: SinglePalette }
54
+ type Palette = SinglePalette | SchemePalette
55
+
56
+ export type CreateThemesProps<
57
+ Accent extends BaseThemeDefinition<Extra> | undefined = undefined,
58
+ GrandChildrenThemes extends SimpleThemesDefinition | undefined = undefined,
59
+ Extra extends ExtraThemeValuesByScheme = ExtraThemeValuesByScheme,
60
+ ChildrenThemes extends SimpleThemesDefinition = SimpleThemesDefinition,
61
+ ComponentThemes extends SimpleThemesDefinition = SimpleThemesDefinition,
62
+ Templates extends BuildTemplates = typeof defaultTemplates,
63
+ > = {
64
+ base: BaseThemeDefinition<Extra>
65
+ accent?: Accent
66
+ childrenThemes?: ChildrenThemes
67
+ grandChildrenThemes?: GrandChildrenThemes
68
+ templates?: Templates
69
+ componentThemes?: ComponentThemes
70
+ colorsToTheme?: (props: {
71
+ colors: string[]
72
+ name: string
73
+ scheme?: 'light' | 'dark'
74
+ }) => Record<string, string>
75
+ }
76
+
77
+ // TODO we moved studio over to mostly just control palette, so the need for this can basically go away
78
+ export function createThemes<
79
+ Extra extends ExtraThemeValuesByScheme,
80
+ SubThemes extends SimpleThemesDefinition,
81
+ ComponentThemes extends SimpleThemesDefinition,
82
+ GrandChildrenThemes extends SimpleThemesDefinition | undefined = undefined,
83
+ Accent extends BaseThemeDefinition<Extra> | undefined = undefined,
84
+ >(
85
+ props: CreateThemesProps<Accent, GrandChildrenThemes, Extra, SubThemes, ComponentThemes>
86
+ ) {
87
+ const {
88
+ accent,
89
+ childrenThemes,
90
+ grandChildrenThemes,
91
+ templates = defaultTemplates,
92
+ componentThemes,
93
+ } = props
94
+
95
+ const builder = createSimpleThemeBuilder({
96
+ extra: props.base.extra,
97
+ componentThemes,
98
+ palettes: createPalettes(getThemesPalettes(props)),
99
+ templates: templates as typeof defaultTemplates,
100
+ accentTheme: !!accent as Accent extends undefined ? false : true,
101
+ childrenThemes: normalizeSubThemes(childrenThemes),
102
+ grandChildrenThemes: (grandChildrenThemes
103
+ ? normalizeSubThemes(grandChildrenThemes)
104
+ : undefined) as GrandChildrenThemes extends undefined
105
+ ? undefined
106
+ : Record<keyof GrandChildrenThemes, any>,
107
+ })
108
+
109
+ lastBuilder = builder.themeBuilder
110
+
111
+ return builder.themes
112
+ }
113
+
114
+ let lastBuilder: ThemeBuilder | null = null
115
+
116
+ export const getLastBuilder = () => lastBuilder
117
+
118
+ function normalizeSubThemes<A extends SimpleThemesDefinition>(defs?: A) {
119
+ return Object.fromEntries(
120
+ Object.entries(defs || {}).map(([name, value]) => {
121
+ return [
122
+ name,
123
+ {
124
+ palette: name,
125
+ template: value.template || 'base',
126
+ },
127
+ ]
128
+ })
129
+ ) as Record<keyof A, any>
130
+ }
131
+
132
+ const defaultPalettes: SimplePaletteDefinitions = createPalettes(
133
+ getThemesPalettes({
134
+ base: {
135
+ palette: ['#fff', '#000'],
136
+ },
137
+ accent: {
138
+ palette: ['#ff0000', '#ff9999'],
139
+ },
140
+ })
141
+ )
142
+
143
+ type NamesWithChildrenNames<ParentNames extends string, ChildNames> =
144
+ | ParentNames
145
+ | (ChildNames extends string ? `${ParentNames}_${ChildNames}` : never)
146
+
147
+ // a simpler API surface
148
+ export function createSimpleThemeBuilder<
149
+ Extra extends ExtraThemeValuesByScheme,
150
+ Templates extends BuildTemplates,
151
+ Palettes extends SimplePaletteDefinitions,
152
+ ChildrenThemes extends Record<
153
+ string,
154
+ {
155
+ template: keyof Templates extends string ? keyof Templates : never
156
+ palette?: string
157
+ }
158
+ >,
159
+ GrandChildrenThemes extends
160
+ | undefined
161
+ | Record<
162
+ string,
163
+ {
164
+ template: keyof Templates extends string ? keyof Templates : never
165
+ palette?: string
166
+ }
167
+ >,
168
+ HasAccent extends boolean = false,
169
+ ComponentThemes extends SimpleThemesDefinition | false = false,
170
+ FullTheme = {
171
+ [ThemeKey in
172
+ | keyof Templates['light_base']
173
+ | keyof Extra['dark']
174
+ | (HasAccent extends true
175
+ ? `accent${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}`
176
+ : never)]: string
177
+ },
178
+ ThemeNames extends string =
179
+ | 'light'
180
+ | 'dark'
181
+ | (HasAccent extends true ? 'light_accent' | 'dark_accent' : never)
182
+ | (keyof ChildrenThemes extends string
183
+ ? `${'light' | 'dark'}_${GrandChildrenThemes extends undefined
184
+ ? keyof ChildrenThemes
185
+ : NamesWithChildrenNames<keyof ChildrenThemes, keyof GrandChildrenThemes>}`
186
+ : never),
187
+ >(props: {
188
+ palettes?: Palettes
189
+ accentTheme?: HasAccent
190
+ templates?: Templates
191
+ childrenThemes?: ChildrenThemes
192
+ grandChildrenThemes?: GrandChildrenThemes
193
+ componentThemes?: ComponentThemes
194
+ extra?: Extra
195
+ }): {
196
+ themeBuilder: ThemeBuilder<any>
197
+ themes: Record<ThemeNames, FullTheme>
198
+ } {
199
+ const {
200
+ extra,
201
+ childrenThemes = null as unknown as ChildrenThemes,
202
+ grandChildrenThemes = null as unknown as GrandChildrenThemes,
203
+ templates = defaultTemplates as unknown as Templates,
204
+ palettes = defaultPalettes as unknown as Palettes,
205
+ accentTheme,
206
+ componentThemes = templates === (defaultTemplates as any)
207
+ ? (defaultComponentThemes as unknown as ComponentThemes)
208
+ : undefined,
209
+ } = props
210
+
211
+ // start theme-builder
212
+ let themeBuilder = createThemeBuilder()
213
+ .addPalettes(palettes)
214
+ .addTemplates(templates)
215
+ .addThemes({
216
+ light: {
217
+ template: 'base',
218
+ palette: 'light',
219
+ nonInheritedValues: {
220
+ ...extra?.light,
221
+ ...(accentTheme &&
222
+ palettes.light_accent && {
223
+ accent1: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 0],
224
+ accent2: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 1],
225
+ accent3: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 2],
226
+ accent4: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 3],
227
+ accent5: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 4],
228
+ accent6: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 5],
229
+ accent7: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 6],
230
+ accent8: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 7],
231
+ accent9: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 8],
232
+ accent10: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 9],
233
+ accent11: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 10],
234
+ accent12: palettes.light_accent[PALETTE_BACKGROUND_OFFSET + 11],
235
+ }),
236
+ },
237
+ },
238
+ dark: {
239
+ template: 'base',
240
+ palette: 'dark',
241
+ nonInheritedValues: {
242
+ ...extra?.dark,
243
+ ...(accentTheme &&
244
+ palettes.dark_accent && {
245
+ accent1: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 0],
246
+ accent2: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 1],
247
+ accent3: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 2],
248
+ accent4: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 3],
249
+ accent5: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 4],
250
+ accent6: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 5],
251
+ accent7: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 6],
252
+ accent8: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 7],
253
+ accent9: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 8],
254
+ accent10: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 9],
255
+ accent11: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 10],
256
+ accent12: palettes.dark_accent[PALETTE_BACKGROUND_OFFSET + 11],
257
+ }),
258
+ },
259
+ },
260
+ })
261
+
262
+ if (palettes.light_accent) {
263
+ themeBuilder = themeBuilder.addChildThemes({
264
+ accent: [
265
+ {
266
+ parent: 'light',
267
+ template: 'base',
268
+ palette: 'light_accent',
269
+ },
270
+ {
271
+ parent: 'dark',
272
+ template: 'base',
273
+ palette: 'dark_accent',
274
+ },
275
+ ],
276
+ })
277
+ }
278
+
279
+ if (childrenThemes) {
280
+ themeBuilder = themeBuilder.addChildThemes(childrenThemes, {
281
+ avoidNestingWithin: ['accent'],
282
+ }) as any
283
+ }
284
+
285
+ if (grandChildrenThemes) {
286
+ themeBuilder = themeBuilder.addChildThemes(grandChildrenThemes, {
287
+ avoidNestingWithin: ['accent'],
288
+ }) as any
289
+ }
290
+
291
+ if (componentThemes) {
292
+ themeBuilder = themeBuilder.addComponentThemes(getComponentThemes(componentThemes), {
293
+ avoidNestingWithin: [
294
+ 'accent',
295
+ // ...Object.keys(childrenThemes || {}),
296
+ ...Object.keys(grandChildrenThemes || {}),
297
+ ],
298
+ })
299
+ }
300
+
301
+ return {
302
+ themeBuilder,
303
+ themes: themeBuilder.build() as any,
304
+ }
305
+ }
306
+
307
+ function getSchemePalette(colors: SinglePalette): SchemePalette {
308
+ return {
309
+ light: colors,
310
+ dark: colors.toReversed(),
311
+ }
312
+ }
313
+
314
+ function getAnchors(palette: SchemePalette) {
315
+ const maxIndex = 11
316
+ const numItems = palette.light.length
317
+
318
+ const anchors = palette.light.map((lcolor, index) => {
319
+ const dcolor = palette.dark[index]
320
+ const [lhue, lsat, llum] = parseToHsla(lcolor)
321
+ const [dhue, dsat, dlum] = parseToHsla(dcolor)
322
+ return {
323
+ index: spreadIndex(maxIndex, numItems, index),
324
+ hue: { light: lhue, dark: dhue },
325
+ sat: { light: lsat, dark: dsat },
326
+ lum: { light: llum, dark: dlum },
327
+ } as const
328
+ })
329
+
330
+ return anchors
331
+ }
332
+
333
+ function spreadIndex(maxIndex: number, numItems: number, index: number) {
334
+ return Math.round((index / (numItems - 1)) * maxIndex)
335
+ }
336
+
337
+ function coerceSimplePaletteToSchemePalette(def: Palette) {
338
+ return Array.isArray(def) ? getSchemePalette(def) : def
339
+ }
340
+
341
+ function getThemesPalettes(props: CreateThemesProps<any, any>): BuildPalettes {
342
+ const base = coerceSimplePaletteToSchemePalette(props.base.palette)
343
+ const accent = props.accent
344
+ ? coerceSimplePaletteToSchemePalette(props.accent.palette)
345
+ : null
346
+
347
+ const baseAnchors = getAnchors(base)
348
+
349
+ function getSubThemesPalettes(defs: SimpleThemesDefinition) {
350
+ return Object.fromEntries(
351
+ Object.entries(defs).map(([key, value]) => {
352
+ return [
353
+ key,
354
+ {
355
+ name: key,
356
+ anchors: value.palette
357
+ ? getAnchors(coerceSimplePaletteToSchemePalette(value.palette))
358
+ : baseAnchors,
359
+ },
360
+ ]
361
+ })
362
+ )
363
+ }
364
+
365
+ return {
366
+ base: {
367
+ name: 'base',
368
+ anchors: baseAnchors,
369
+ },
370
+ ...(accent && {
371
+ accent: {
372
+ name: 'accent',
373
+ anchors: getAnchors(accent),
374
+ },
375
+ }),
376
+ ...(props.childrenThemes && getSubThemesPalettes(props.childrenThemes)),
377
+ ...(props.grandChildrenThemes && getSubThemesPalettes(props.grandChildrenThemes)),
378
+ }
379
+ }
380
+
381
+ export const getComponentThemes = (components: SimpleThemesDefinition) => {
382
+ return Object.fromEntries(
383
+ Object.entries(components).map(([componentName, { template }]) => {
384
+ return [
385
+ componentName,
386
+ {
387
+ parent: '',
388
+ template: template || 'base',
389
+ },
390
+ ]
391
+ })
392
+ )
393
+ }
394
+
395
+ export function createPalettes(palettes: BuildPalettes): SimplePaletteDefinitions {
396
+ const accentPalettes = palettes.accent ? getThemeSuitePalettes(palettes.accent) : null
397
+ const basePalettes = getThemeSuitePalettes(palettes.base)
398
+
399
+ const next = Object.fromEntries(
400
+ Object.entries(palettes).flatMap(([name, palette]) => {
401
+ const palettes = getThemeSuitePalettes(palette)
402
+ const isAccent = name.startsWith('accent')
403
+ const oppositePalettes = isAccent ? basePalettes : accentPalettes || basePalettes
404
+
405
+ if (!oppositePalettes) {
406
+ return []
407
+ }
408
+
409
+ const oppositeLight = oppositePalettes!.light
410
+ const oppositeDark = oppositePalettes!.dark
411
+
412
+ const bgOffset = 7
413
+
414
+ const out = [
415
+ [
416
+ name === 'base' ? 'light' : `light_${name}`,
417
+ [
418
+ oppositeLight[bgOffset],
419
+ ...palettes.light,
420
+ oppositeLight[oppositeLight.length - bgOffset - 1],
421
+ ],
422
+ ],
423
+ [
424
+ name === 'base' ? 'dark' : `dark_${name}`,
425
+ [
426
+ oppositeDark[oppositeDark.length - bgOffset - 1],
427
+ ...palettes.dark,
428
+ oppositeDark[bgOffset],
429
+ ],
430
+ ],
431
+ ] as const
432
+
433
+ return out
434
+ })
435
+ )
436
+
437
+ return next as any
438
+ }
@@ -0,0 +1,22 @@
1
+ import type { SimpleThemesDefinition } from './createThemes'
2
+
3
+ export const defaultComponentThemes = {
4
+ ListItem: { template: 'surface1' },
5
+ SelectTrigger: { template: 'surface1' },
6
+ Card: { template: 'surface1' },
7
+ Button: { template: 'surface3' },
8
+ Checkbox: { template: 'surface2' },
9
+ Switch: { template: 'surface2' },
10
+ SwitchThumb: { template: 'inverse' },
11
+ TooltipContent: { template: 'surface2' },
12
+ Progress: { template: 'surface1' },
13
+ RadioGroupItem: { template: 'surface2' },
14
+ TooltipArrow: { template: 'surface1' },
15
+ SliderTrackActive: { template: 'surface3' },
16
+ SliderTrack: { template: 'surface1' },
17
+ SliderThumb: { template: 'inverse' },
18
+ Tooltip: { template: 'inverse' },
19
+ ProgressIndicator: { template: 'inverse' },
20
+ Input: { template: 'surface1' },
21
+ TextArea: { template: 'surface1' },
22
+ } satisfies SimpleThemesDefinition
@@ -0,0 +1,144 @@
1
+ import { objectFromEntries, objectKeys, type BuildTemplates } from '@tamagui/themes'
2
+
3
+ const getTemplates = () => {
4
+ const lightTemplates = getBaseTemplates('light')
5
+ const darkTemplates = getBaseTemplates('dark')
6
+ const templates = {
7
+ ...objectFromEntries(
8
+ objectKeys(lightTemplates).map(
9
+ (name) => [`light_${name}`, lightTemplates[name]] as const
10
+ )
11
+ ),
12
+ ...objectFromEntries(
13
+ objectKeys(darkTemplates).map(
14
+ (name) => [`dark_${name}`, darkTemplates[name]] as const
15
+ )
16
+ ),
17
+ }
18
+ return templates as Record<keyof typeof templates, typeof lightTemplates.base>
19
+ }
20
+
21
+ const getBaseTemplates = (scheme: 'dark' | 'light') => {
22
+ const isLight = scheme === 'light'
23
+
24
+ // our palettes have 4 things padding each end until you get to bg/color:
25
+ // [accentBg, transparent1, transparent2, transparent3, transparent4, background, ...]
26
+ const bgIndex = 6
27
+ const lighten = isLight ? -1 : 1
28
+ const darken = -lighten
29
+ const borderColor = bgIndex + 3
30
+
31
+ // templates use the palette and specify index
32
+ // negative goes backwards from end so -1 is the last item
33
+ const base = {
34
+ accentBackground: 0,
35
+ accentColor: -0,
36
+
37
+ background0: 1,
38
+ background02: 2,
39
+ background04: 3,
40
+ background06: 4,
41
+ background08: 5,
42
+ color1: bgIndex,
43
+ color2: bgIndex + 1,
44
+ color3: bgIndex + 2,
45
+ color4: bgIndex + 3,
46
+ color5: bgIndex + 4,
47
+ color6: bgIndex + 5,
48
+ color7: bgIndex + 6,
49
+ color8: bgIndex + 7,
50
+ color9: bgIndex + 8,
51
+ color10: bgIndex + 9,
52
+ color11: bgIndex + 10,
53
+ color12: bgIndex + 11,
54
+ color0: -1,
55
+ color02: -2,
56
+ color04: -3,
57
+ color06: -4,
58
+ color08: -5,
59
+ // the background, color, etc keys here work like generics - they make it so you
60
+ // can publish components for others to use without mandating a specific color scale
61
+ // the @tamagui/button Button component looks for `$background`, so you set the
62
+ // dark_red_Button theme to have a stronger background than the dark_red theme.
63
+ background: bgIndex,
64
+ backgroundHover: bgIndex + lighten, // always lighten on hover no matter the scheme
65
+ backgroundPress: bgIndex + darken, // always darken on press no matter the theme
66
+ backgroundFocus: bgIndex + darken,
67
+ borderColor,
68
+ borderColorHover: borderColor + lighten,
69
+ borderColorPress: borderColor + darken,
70
+ borderColorFocus: borderColor,
71
+ color: -bgIndex,
72
+ colorHover: -bgIndex - 1,
73
+ colorPress: -bgIndex,
74
+ colorFocus: -bgIndex - 1,
75
+ colorTransparent: -1,
76
+ placeholderColor: -bgIndex - 3,
77
+ outlineColor: -2,
78
+ }
79
+
80
+ const surface1 = {
81
+ background: base.background + 1,
82
+ backgroundHover: base.backgroundHover + 1,
83
+ backgroundPress: base.backgroundPress + 1,
84
+ backgroundFocus: base.backgroundFocus + 1,
85
+ borderColor: base.borderColor + 1,
86
+ borderColorHover: base.borderColorHover + 1,
87
+ borderColorFocus: base.borderColorFocus + 1,
88
+ borderColorPress: base.borderColorPress + 1,
89
+ }
90
+
91
+ const surface2 = {
92
+ background: base.background + 2,
93
+ backgroundHover: base.backgroundHover + 2,
94
+ backgroundPress: base.backgroundPress + 2,
95
+ backgroundFocus: base.backgroundFocus + 2,
96
+ borderColor: base.borderColor + 2,
97
+ borderColorHover: base.borderColorHover + 2,
98
+ borderColorFocus: base.borderColorFocus + 2,
99
+ borderColorPress: base.borderColorPress + 2,
100
+ }
101
+
102
+ const surface3 = {
103
+ background: base.background + 3,
104
+ backgroundHover: base.backgroundHover + 3,
105
+ backgroundPress: base.backgroundPress + 3,
106
+ backgroundFocus: base.backgroundFocus + 3,
107
+ borderColor: base.borderColor + 3,
108
+ borderColorHover: base.borderColorHover + 3,
109
+ borderColorFocus: base.borderColorFocus + 3,
110
+ borderColorPress: base.borderColorPress + 3,
111
+ }
112
+
113
+ const alt1 = {
114
+ color: base.color - 1,
115
+ colorHover: base.colorHover - 1,
116
+ colorPress: base.colorPress - 1,
117
+ colorFocus: base.colorFocus - 1,
118
+ }
119
+
120
+ const alt2 = {
121
+ color: base.color - 2,
122
+ colorHover: base.colorHover - 2,
123
+ colorPress: base.colorPress - 2,
124
+ colorFocus: base.colorFocus - 2,
125
+ }
126
+
127
+ const inverse = Object.fromEntries(
128
+ Object.entries(base).map(([key, index]) => {
129
+ return [key, -index]
130
+ })
131
+ )
132
+
133
+ return {
134
+ base,
135
+ surface1,
136
+ surface2,
137
+ surface3,
138
+ alt1,
139
+ alt2,
140
+ inverse,
141
+ } satisfies BuildTemplates
142
+ }
143
+
144
+ export const defaultTemplates = getTemplates()
@@ -0,0 +1,95 @@
1
+ import { hsla, parseToHsla } from 'color2k'
2
+ import type { BuildPalette } from '@tamagui/themes'
3
+
4
+ /**
5
+ * palette generally is:
6
+ *
7
+ * [constrastBackground, accent, backgroundTransparent, ...background, ...foreground, foregroundTransparent, accentForeground]
8
+ */
9
+
10
+ const paletteSize = 12
11
+
12
+ // how many things come before the actual bg color (transparencies etc)
13
+ export const PALETTE_BACKGROUND_OFFSET = 6
14
+
15
+ const generateColorPalette = ({
16
+ palette: buildPalette,
17
+ scheme,
18
+ }: {
19
+ palette: BuildPalette
20
+ scheme: 'light' | 'dark'
21
+ }) => {
22
+ if (!buildPalette) {
23
+ return [] as string[]
24
+ }
25
+
26
+ const { anchors } = buildPalette
27
+
28
+ let palette: string[] = []
29
+
30
+ const add = (h: number, s: number, l: number) => {
31
+ palette.push(hsla(h, s, l, 1))
32
+ }
33
+
34
+ const numAnchors = Object.keys(anchors).length
35
+
36
+ for (const [anchorIndex, anchor] of anchors.entries()) {
37
+ const [h, s, l] = [anchor.hue[scheme], anchor.sat[scheme], anchor.lum[scheme]]
38
+
39
+ if (anchorIndex !== 0) {
40
+ const lastAnchor = anchors[anchorIndex - 1]
41
+ const steps = anchor.index - lastAnchor.index
42
+
43
+ const lastHue = lastAnchor.hue[scheme]
44
+ const lastSat = lastAnchor.sat[scheme]
45
+ const lastLum = lastAnchor.lum[scheme]
46
+
47
+ const stepHue = (lastHue - h) / steps
48
+ const stepSat = (lastSat - s) / steps
49
+ const stepLum = (lastLum - l) / steps
50
+
51
+ // backfill:
52
+ for (let step = lastAnchor.index + 1; step < anchor.index; step++) {
53
+ const str = anchor.index - step
54
+ add(h + stepHue * str, s + stepSat * str, l + stepLum * str)
55
+ }
56
+ }
57
+
58
+ add(h, s, l)
59
+
60
+ const isLastAnchor = anchorIndex === numAnchors - 1
61
+ if (isLastAnchor && palette.length < paletteSize) {
62
+ // forwardfill:
63
+ for (let step = anchor.index + 1; step < paletteSize; step++) {
64
+ add(h, s, l)
65
+ }
66
+ }
67
+ }
68
+
69
+ // add transparent values
70
+ const background = palette[3]
71
+ const foreground = palette[palette.length - 3]
72
+
73
+ const transparentValues = [background, foreground].map((color) => {
74
+ const [h, s, l] = parseToHsla(color)
75
+ // fully transparent to partially
76
+ return [
77
+ hsla(h, s, l, 0),
78
+ hsla(h, s, l, 0.2),
79
+ hsla(h, s, l, 0.4),
80
+ hsla(h, s, l, 0.6),
81
+ hsla(h, s, l, 0.8),
82
+ ] as const
83
+ })
84
+ const reverseForeground = [...transparentValues[1]].reverse()
85
+ palette = [...transparentValues[0], ...palette, ...reverseForeground]
86
+
87
+ return palette
88
+ }
89
+
90
+ export function getThemeSuitePalettes(palette: BuildPalette) {
91
+ return {
92
+ light: generateColorPalette({ palette, scheme: 'light' }),
93
+ dark: generateColorPalette({ palette, scheme: 'dark' }),
94
+ }
95
+ }