@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/fonts.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { createFont, isWeb } from 'tamagui'
2
+
3
+ export const fontScales = {
4
+ compact: 0.9375,
5
+ default: 1,
6
+ large: 1.125,
7
+ } as const
8
+
9
+ export type FontScale = keyof typeof fontScales
10
+
11
+ /** Type scale in px: `$1` xs(12) · `$2` sm(14) · `$3` base(16) · … · `$10` 6xl(60). */
12
+ const baseSizes = {
13
+ 1: 12,
14
+ 2: 14,
15
+ 3: 16,
16
+ 4: 18,
17
+ 5: 20,
18
+ 6: 24,
19
+ 7: 30,
20
+ 8: 36,
21
+ 9: 48,
22
+ 10: 60,
23
+ true: 16,
24
+ } as const
25
+
26
+ type SizeKey = keyof typeof baseSizes
27
+
28
+ const systemStack =
29
+ '-apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
30
+ const monoStack =
31
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace'
32
+
33
+ function mapSizes(fn: (px: number) => number) {
34
+ return Object.fromEntries(Object.entries(baseSizes).map(([key, px]) => [key, fn(px)])) as Record<
35
+ SizeKey,
36
+ number
37
+ >
38
+ }
39
+
40
+ export interface FontFamilies {
41
+ /** CSS font stack on web. */
42
+ body?: string
43
+ heading?: string
44
+ mono?: string
45
+ /** Loaded font family names on iOS/Android (defaults to the platform system font). */
46
+ native?: { body?: string; heading?: string; mono?: string }
47
+ }
48
+
49
+ export function createUniversalFonts({
50
+ scale = 'default',
51
+ families = {},
52
+ }: { scale?: FontScale | number; families?: FontFamilies } = {}) {
53
+ const factor = typeof scale === 'number' ? scale : fontScales[scale]
54
+ const size = mapSizes((px) => Math.round(px * factor))
55
+ const pick = (web: string, native: string | undefined, fallback: string) =>
56
+ isWeb ? web : (native ?? fallback)
57
+
58
+ const weight = { 1: '400', 4: '400', 5: '500', 6: '600', 7: '700', true: '400' } as const
59
+
60
+ const body = createFont({
61
+ family: pick(families.body ?? systemStack, families.native?.body, 'System'),
62
+ size,
63
+ lineHeight: mapSizes((px) => Math.round(px * factor * (px <= 20 ? 1.5 : 1.3))),
64
+ weight,
65
+ letterSpacing: { 1: 0, true: 0 },
66
+ })
67
+
68
+ const heading = createFont({
69
+ family: pick(
70
+ families.heading ?? families.body ?? systemStack,
71
+ families.native?.heading,
72
+ 'System',
73
+ ),
74
+ size,
75
+ lineHeight: mapSizes((px) => Math.round(px * factor * (px <= 20 ? 1.4 : 1.2))),
76
+ weight: { ...weight, true: '600' },
77
+ letterSpacing: { 1: 0, 6: -0.3, 7: -0.5, 8: -0.7, 9: -1, 10: -1.2, true: 0 },
78
+ })
79
+
80
+ const mono = createFont({
81
+ family: pick(families.mono ?? monoStack, families.native?.mono, 'monospace'),
82
+ size: mapSizes((px) => Math.round(px * factor * 0.92)),
83
+ lineHeight: mapSizes((px) => Math.round(px * factor * 1.6)),
84
+ weight,
85
+ letterSpacing: { 1: 0, true: 0 },
86
+ })
87
+
88
+ return { body, heading, mono }
89
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ export { animations } from './animations'
2
+ export { createUniversalConfig, type UniversalConfig, type UniversalConfigOptions } from './config'
3
+ export { createUniversalFonts, fontScales, type FontFamilies, type FontScale } from './fonts'
4
+ export { breakpoints, media, mediaQueryDefaultActive } from './media'
5
+ export {
6
+ generateScale,
7
+ neutralScaleNames,
8
+ resolveScale,
9
+ scales,
10
+ type ColorScale,
11
+ type ColorSource,
12
+ type NeutralScaleName,
13
+ type ScaleName,
14
+ type ScaleSteps,
15
+ } from './palettes'
16
+ export { themePresetNames, themePresets, type ThemePreset, type ThemePresetName } from './presets'
17
+ export { shadows, type ShadowName } from './shadows'
18
+ export { shorthands } from './shorthands'
19
+ export {
20
+ createThemeColors,
21
+ defaultThemeColors,
22
+ type ColorMode,
23
+ type GeneratedThemes,
24
+ type ThemeColorsInput,
25
+ type ThemeKey,
26
+ type ThemeValues,
27
+ } from './themes'
28
+ export {
29
+ colorTokens,
30
+ createRadius,
31
+ createUniversalTokens,
32
+ radiusScales,
33
+ size,
34
+ space,
35
+ zIndex,
36
+ type RadiusScale,
37
+ } from './tokens'
package/src/media.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { isWeb } from 'tamagui'
2
+
3
+ /** Mobile-first breakpoints (px). `xxl` is the "2xl" breakpoint. */
4
+ export const breakpoints = {
5
+ xs: 460,
6
+ sm: 640,
7
+ md: 768,
8
+ lg: 1024,
9
+ xl: 1280,
10
+ xxl: 1536,
11
+ } as const
12
+
13
+ type MediaQuery = { [key: string]: string | number }
14
+
15
+ const below = (px: number): MediaQuery => ({ maxWidth: px - (isWeb ? 0.02 : 1) })
16
+
17
+ // Order matters: later keys win when several queries match.
18
+ export const media = {
19
+ // always true on native (touch screens), pointer-based on web
20
+ touchable: (isWeb ? { pointer: 'coarse' } : { minWidth: 0 }) as MediaQuery,
21
+ // never true on native: hover-only UI must have a touch alternative
22
+ hoverable: (isWeb ? { hover: 'hover' } : { maxWidth: 0 }) as MediaQuery,
23
+
24
+ 'max-xxl': below(breakpoints.xxl),
25
+ 'max-xl': below(breakpoints.xl),
26
+ 'max-lg': below(breakpoints.lg),
27
+ 'max-md': below(breakpoints.md),
28
+ 'max-sm': below(breakpoints.sm),
29
+ 'max-xs': below(breakpoints.xs),
30
+
31
+ xs: { minWidth: breakpoints.xs },
32
+ sm: { minWidth: breakpoints.sm },
33
+ md: { minWidth: breakpoints.md },
34
+ lg: { minWidth: breakpoints.lg },
35
+ xl: { minWidth: breakpoints.xl },
36
+ xxl: { minWidth: breakpoints.xxl },
37
+ } as const
38
+
39
+ /** Assumed active queries during SSR / first render (a phone-sized viewport). */
40
+ export const mediaQueryDefaultActive = {
41
+ touchable: !isWeb,
42
+ hoverable: isWeb,
43
+ 'max-xxl': true,
44
+ 'max-xl': true,
45
+ 'max-lg': true,
46
+ 'max-md': true,
47
+ 'max-sm': true,
48
+ 'max-xs': false,
49
+ xs: true,
50
+ sm: false,
51
+ md: false,
52
+ lg: false,
53
+ xl: false,
54
+ xxl: false,
55
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 12-step color scales. Built-in scales come from Radix Colors (MIT, shipped
3
+ * via `@tamagui/colors`), which are hand-tuned separately for light and dark
4
+ * mode — dark mode is designed, not inverted. Custom brand colors are turned
5
+ * into scales with `generateScale`.
6
+ *
7
+ * Step semantics (same as Radix):
8
+ * 1-2 app / subtle backgrounds 3-5 component backgrounds (rest/hover/active)
9
+ * 6-8 borders (subtle/default/hover) 9-10 solid fills (rest/hover)
10
+ * 11 low-contrast text 12 high-contrast text
11
+ */
12
+ import {
13
+ amber,
14
+ amberDark,
15
+ blue,
16
+ blueDark,
17
+ crimson,
18
+ crimsonDark,
19
+ gray,
20
+ grayDark,
21
+ green,
22
+ greenDark,
23
+ indigo,
24
+ indigoDark,
25
+ mauve,
26
+ mauveDark,
27
+ orange,
28
+ orangeDark,
29
+ red,
30
+ redDark,
31
+ sage,
32
+ sageDark,
33
+ sand,
34
+ sandDark,
35
+ slate,
36
+ slateDark,
37
+ teal,
38
+ tealDark,
39
+ violet,
40
+ violetDark,
41
+ } from '@tamagui/colors'
42
+ import { fromOklch, isValidColor, toOklch } from '@advui/utils'
43
+
44
+ export type ScaleSteps = readonly [
45
+ string,
46
+ string,
47
+ string,
48
+ string,
49
+ string,
50
+ string,
51
+ string,
52
+ string,
53
+ string,
54
+ string,
55
+ string,
56
+ string,
57
+ ]
58
+
59
+ export interface ColorScale {
60
+ light: ScaleSteps
61
+ dark: ScaleSteps
62
+ }
63
+
64
+ const steps = (record: Record<string, string>) => Object.values(record) as unknown as ScaleSteps
65
+ const scale = (light: Record<string, string>, dark: Record<string, string>): ColorScale => ({
66
+ light: steps(light),
67
+ dark: steps(dark),
68
+ })
69
+
70
+ export const scales = {
71
+ indigo: scale(indigo, indigoDark),
72
+ blue: scale(blue, blueDark),
73
+ violet: scale(violet, violetDark),
74
+ green: scale(green, greenDark),
75
+ teal: scale(teal, tealDark),
76
+ orange: scale(orange, orangeDark),
77
+ amber: scale(amber, amberDark),
78
+ crimson: scale(crimson, crimsonDark),
79
+ red: scale(red, redDark),
80
+ gray: scale(gray, grayDark),
81
+ slate: scale(slate, slateDark),
82
+ mauve: scale(mauve, mauveDark),
83
+ sage: scale(sage, sageDark),
84
+ sand: scale(sand, sandDark),
85
+ } as const
86
+
87
+ export type ScaleName = keyof typeof scales
88
+
89
+ export const neutralScaleNames = ['gray', 'slate', 'mauve', 'sage', 'sand'] as const
90
+ export type NeutralScaleName = (typeof neutralScaleNames)[number]
91
+
92
+ /** A named built-in scale, a hex/rgb/hsl color string, or a complete custom scale. */
93
+ export type ColorSource = ScaleName | (string & {}) | ColorScale
94
+
95
+ // Target OKLCH lightness per step, and chroma as a fraction of the base chroma.
96
+ const LIGHT_L = [0.993, 0.982, 0.956, 0.93, 0.9, 0.865, 0.815, 0.745, 0, 0, 0.5, 0.29]
97
+ const DARK_L = [0.175, 0.205, 0.255, 0.295, 0.335, 0.38, 0.44, 0.52, 0, 0, 0.8, 0.93]
98
+ const CHROMA = [0.02, 0.05, 0.12, 0.2, 0.28, 0.36, 0.46, 0.62, 1, 1, 0.85, 0.45]
99
+
100
+ /**
101
+ * Generates a light + dark 12-step scale around a single brand color. Step 9
102
+ * is the exact input color in both modes, mirroring how Radix scales work.
103
+ */
104
+ export function generateScale(color: string): ColorScale {
105
+ const base = toOklch(color)
106
+ const build = (targets: number[], hoverShift: number) =>
107
+ targets.map((l, i) => {
108
+ if (i === 8) return fromOklch({ ...base, a: 1 })
109
+ if (i === 9) return fromOklch({ ...base, l: base.l + hoverShift, a: 1 })
110
+ return fromOklch({ l, c: base.c * CHROMA[i]!, h: base.h })
111
+ }) as unknown as ScaleSteps
112
+
113
+ return { light: build(LIGHT_L, -0.05), dark: build(DARK_L, 0.05) }
114
+ }
115
+
116
+ export function resolveScale(source: ColorSource): ColorScale {
117
+ if (typeof source === 'object') return source
118
+ if (source in scales) return scales[source as ScaleName]
119
+ if (isValidColor(source)) return generateScale(source)
120
+ throw new Error(
121
+ `[advui] Unknown color "${source}". Use a scale name (${Object.keys(scales).join(', ')}) or a color string.`,
122
+ )
123
+ }
package/src/presets.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { ThemeColorsInput } from './themes'
2
+
3
+ export interface ThemePreset {
4
+ name: string
5
+ label: string
6
+ colors: ThemeColorsInput
7
+ }
8
+
9
+ export const themePresets = {
10
+ indigo: {
11
+ name: 'indigo',
12
+ label: 'Indigo',
13
+ colors: { primary: 'indigo', neutral: 'slate' },
14
+ },
15
+ blue: {
16
+ name: 'blue',
17
+ label: 'Blue',
18
+ colors: { primary: 'blue', neutral: 'slate' },
19
+ },
20
+ violet: {
21
+ name: 'violet',
22
+ label: 'Violet',
23
+ colors: { primary: 'violet', neutral: 'mauve' },
24
+ },
25
+ green: {
26
+ name: 'green',
27
+ label: 'Green',
28
+ colors: { primary: 'green', neutral: 'sage' },
29
+ },
30
+ emerald: {
31
+ name: 'emerald',
32
+ label: 'Emerald',
33
+ colors: { primary: 'teal', neutral: 'sage' },
34
+ },
35
+ orange: {
36
+ name: 'orange',
37
+ label: 'Orange',
38
+ colors: { primary: 'orange', neutral: 'sand' },
39
+ },
40
+ rose: {
41
+ name: 'rose',
42
+ label: 'Rose',
43
+ colors: { primary: 'crimson', neutral: 'mauve' },
44
+ },
45
+ red: {
46
+ name: 'red',
47
+ label: 'Red',
48
+ colors: { primary: 'red', neutral: 'gray' },
49
+ },
50
+ slate: {
51
+ name: 'slate',
52
+ label: 'Slate',
53
+ colors: { neutral: 'slate', monochrome: true },
54
+ },
55
+ neutral: {
56
+ name: 'neutral',
57
+ label: 'Neutral',
58
+ colors: { neutral: 'gray', monochrome: true },
59
+ },
60
+ } as const satisfies Record<string, ThemePreset>
61
+
62
+ export type ThemePresetName = keyof typeof themePresets
63
+
64
+ export const themePresetNames = Object.keys(themePresets) as ThemePresetName[]
package/src/shadows.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Elevation presets. Spread them into `styled()` definitions:
3
+ * `styled(View, { ...shadows.md })`. Colors come from the theme
4
+ * (`$shadowColor`, `$shadowColorStrong`) so dark mode gets deeper shadows.
5
+ * `elevation` is the Android equivalent.
6
+ */
7
+ export const shadows = {
8
+ none: {
9
+ shadowColor: 'transparent',
10
+ shadowOffset: { width: 0, height: 0 },
11
+ shadowOpacity: 0,
12
+ shadowRadius: 0,
13
+ elevation: 0,
14
+ },
15
+ xs: {
16
+ shadowColor: '$shadowColor',
17
+ shadowOffset: { width: 0, height: 1 },
18
+ shadowOpacity: 1,
19
+ shadowRadius: 2,
20
+ elevation: 1,
21
+ },
22
+ sm: {
23
+ shadowColor: '$shadowColor',
24
+ shadowOffset: { width: 0, height: 2 },
25
+ shadowOpacity: 1,
26
+ shadowRadius: 6,
27
+ elevation: 2,
28
+ },
29
+ md: {
30
+ shadowColor: '$shadowColorStrong',
31
+ shadowOffset: { width: 0, height: 6 },
32
+ shadowOpacity: 1,
33
+ shadowRadius: 16,
34
+ elevation: 6,
35
+ },
36
+ lg: {
37
+ shadowColor: '$shadowColorStrong',
38
+ shadowOffset: { width: 0, height: 12 },
39
+ shadowOpacity: 1,
40
+ shadowRadius: 32,
41
+ elevation: 12,
42
+ },
43
+ } as const
44
+
45
+ export type ShadowName = keyof typeof shadows
@@ -0,0 +1,36 @@
1
+ /** Opt-in shorthands (`px`, `bg`, `rounded`…). Library source uses longhand props for readability. */
2
+ export const shorthands = {
3
+ text: 'textAlign',
4
+ b: 'bottom',
5
+ bg: 'backgroundColor',
6
+ content: 'alignContent',
7
+ grow: 'flexGrow',
8
+ items: 'alignItems',
9
+ justify: 'justifyContent',
10
+ l: 'left',
11
+ m: 'margin',
12
+ maxH: 'maxHeight',
13
+ maxW: 'maxWidth',
14
+ mb: 'marginBottom',
15
+ minH: 'minHeight',
16
+ minW: 'minWidth',
17
+ ml: 'marginLeft',
18
+ mr: 'marginRight',
19
+ mt: 'marginTop',
20
+ mx: 'marginHorizontal',
21
+ my: 'marginVertical',
22
+ p: 'padding',
23
+ pb: 'paddingBottom',
24
+ pl: 'paddingLeft',
25
+ pr: 'paddingRight',
26
+ pt: 'paddingTop',
27
+ px: 'paddingHorizontal',
28
+ py: 'paddingVertical',
29
+ r: 'right',
30
+ rounded: 'borderRadius',
31
+ select: 'userSelect',
32
+ self: 'alignSelf',
33
+ shrink: 'flexShrink',
34
+ t: 'top',
35
+ z: 'zIndex',
36
+ } as const