@react-native-rethemed/core 0.1.0 → 0.1.1

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 (104) hide show
  1. package/README.md +2 -0
  2. package/dist/color-mode-store.cjs +57 -0
  3. package/dist/color-mode-store.d.cts +11 -0
  4. package/dist/color-mode-store.d.mts +11 -0
  5. package/dist/color-mode-store.mjs +56 -0
  6. package/dist/config.cjs +26 -0
  7. package/dist/config.d.cts +8 -0
  8. package/dist/config.d.mts +8 -0
  9. package/dist/config.mjs +7 -0
  10. package/dist/create-themed-styles.cjs +31 -0
  11. package/dist/{esm/create-themed-styles.d.ts → create-themed-styles.d.cts} +4 -2
  12. package/dist/{cjs/create-themed-styles.d.ts → create-themed-styles.d.mts} +4 -2
  13. package/dist/create-themed-styles.mjs +31 -0
  14. package/dist/create-themed.cjs +93 -0
  15. package/dist/create-themed.d.cts +48 -0
  16. package/dist/create-themed.d.mts +48 -0
  17. package/dist/create-themed.mjs +93 -0
  18. package/dist/define-theme.cjs +15 -0
  19. package/dist/{esm/define-theme.d.ts → define-theme.d.cts} +3 -1
  20. package/dist/{cjs/define-theme.d.ts → define-theme.d.mts} +3 -1
  21. package/dist/define-theme.mjs +15 -0
  22. package/dist/extend-theme.cjs +109 -0
  23. package/dist/{esm/extend-theme.d.ts → extend-theme.d.cts} +3 -1
  24. package/dist/{cjs/extend-theme.d.ts → extend-theme.d.mts} +3 -1
  25. package/dist/extend-theme.mjs +109 -0
  26. package/dist/index.cjs +30 -0
  27. package/dist/index.d.cts +13 -0
  28. package/dist/index.d.mts +13 -0
  29. package/dist/index.mjs +9 -0
  30. package/dist/resolvers/color-resolver.cjs +23 -0
  31. package/dist/resolvers/color-resolver.d.cts +4 -0
  32. package/dist/resolvers/color-resolver.d.mts +4 -0
  33. package/dist/resolvers/color-resolver.mjs +23 -0
  34. package/dist/resolvers/line-height-resolver.cjs +17 -0
  35. package/dist/{esm/resolvers/line-height-resolver.d.ts → resolvers/line-height-resolver.d.cts} +3 -1
  36. package/dist/{cjs/resolvers/line-height-resolver.d.ts → resolvers/line-height-resolver.d.mts} +3 -1
  37. package/dist/resolvers/line-height-resolver.mjs +16 -0
  38. package/dist/resolvers/style-resolver.cjs +86 -0
  39. package/dist/resolvers/style-resolver.mjs +86 -0
  40. package/dist/semantic-colors.cjs +29 -0
  41. package/dist/{esm/semantic-colors.d.ts → semantic-colors.d.cts} +3 -1
  42. package/dist/{cjs/semantic-colors.d.ts → semantic-colors.d.mts} +3 -1
  43. package/dist/semantic-colors.mjs +28 -0
  44. package/dist/style-props.cjs +103 -0
  45. package/dist/{cjs/style-props.d.ts → style-props.d.cts} +2 -1
  46. package/dist/{esm/style-props.d.ts → style-props.d.mts} +2 -1
  47. package/dist/style-props.mjs +95 -0
  48. package/dist/text-tree.cjs +70 -0
  49. package/dist/{cjs/text-tree.d.ts → text-tree.d.cts} +4 -8
  50. package/dist/{esm/text-tree.d.ts → text-tree.d.mts} +4 -8
  51. package/dist/text-tree.mjs +66 -0
  52. package/dist/text-variants.cjs +25 -0
  53. package/dist/text-variants.mjs +25 -0
  54. package/dist/types.d.cts +280 -0
  55. package/dist/types.d.mts +280 -0
  56. package/package.json +16 -24
  57. package/dist/cjs/color-mode-store.d.ts +0 -32
  58. package/dist/cjs/color-mode-store.js +0 -64
  59. package/dist/cjs/config.d.ts +0 -14
  60. package/dist/cjs/config.js +0 -35
  61. package/dist/cjs/create-themed-styles.js +0 -37
  62. package/dist/cjs/create-themed.d.ts +0 -46
  63. package/dist/cjs/create-themed.js +0 -118
  64. package/dist/cjs/define-theme.js +0 -15
  65. package/dist/cjs/extend-theme.js +0 -118
  66. package/dist/cjs/index.d.ts +0 -4
  67. package/dist/cjs/index.js +0 -22
  68. package/dist/cjs/package.json +0 -1
  69. package/dist/cjs/resolvers/color-resolver.d.ts +0 -10
  70. package/dist/cjs/resolvers/color-resolver.js +0 -26
  71. package/dist/cjs/resolvers/line-height-resolver.js +0 -20
  72. package/dist/cjs/resolvers/style-resolver.d.ts +0 -21
  73. package/dist/cjs/resolvers/style-resolver.js +0 -104
  74. package/dist/cjs/semantic-colors.js +0 -32
  75. package/dist/cjs/style-props.js +0 -95
  76. package/dist/cjs/text-tree.js +0 -79
  77. package/dist/cjs/text-variants.d.ts +0 -22
  78. package/dist/cjs/text-variants.js +0 -29
  79. package/dist/cjs/types.d.ts +0 -281
  80. package/dist/cjs/types.js +0 -2
  81. package/dist/esm/color-mode-store.d.ts +0 -32
  82. package/dist/esm/color-mode-store.js +0 -59
  83. package/dist/esm/config.d.ts +0 -14
  84. package/dist/esm/config.js +0 -13
  85. package/dist/esm/create-themed-styles.js +0 -34
  86. package/dist/esm/create-themed.d.ts +0 -46
  87. package/dist/esm/create-themed.js +0 -115
  88. package/dist/esm/define-theme.js +0 -12
  89. package/dist/esm/extend-theme.js +0 -115
  90. package/dist/esm/index.d.ts +0 -4
  91. package/dist/esm/index.js +0 -3
  92. package/dist/esm/package.json +0 -1
  93. package/dist/esm/resolvers/color-resolver.d.ts +0 -10
  94. package/dist/esm/resolvers/color-resolver.js +0 -23
  95. package/dist/esm/resolvers/line-height-resolver.js +0 -16
  96. package/dist/esm/resolvers/style-resolver.d.ts +0 -21
  97. package/dist/esm/resolvers/style-resolver.js +0 -101
  98. package/dist/esm/semantic-colors.js +0 -28
  99. package/dist/esm/style-props.js +0 -92
  100. package/dist/esm/text-tree.js +0 -72
  101. package/dist/esm/text-variants.d.ts +0 -22
  102. package/dist/esm/text-variants.js +0 -26
  103. package/dist/esm/types.d.ts +0 -281
  104. package/dist/esm/types.js +0 -1
@@ -0,0 +1,280 @@
1
+ import { ColorKeys, FontSizeKeys, FontWeightKeys, LetterSpacingKeys, LineHeightKeys, RadiusKeys, SpacingKeys, ZIndexKeys } from "./style-props.cjs";
2
+ import { ImageStyle, TextStyle, ViewStyle } from "react-native";
3
+ //#region src/types.d.ts
4
+ /** One shadow preset — expands into these real RN style props. */
5
+ export type ShadowToken = Pick<ViewStyle, 'shadowColor' | 'shadowOffset' | 'shadowOpacity' | 'shadowRadius' | 'elevation'>;
6
+ /**
7
+ * One `role`/`size` entry in `semanticTokens.text` (Material Design 3
8
+ * type-scale shape). Each field is either a key of the matching primitive
9
+ * scale in `tokens` (e.g. `fontSize: 'lg'`) or a raw value. References are
10
+ * validated by `@react-native-rethemed/cli codegen` against the final config,
11
+ * since a partial theme may reference keys supplied by another theme it is
12
+ * later merged with.
13
+ *
14
+ * `color` is optional: type-scale packages (Material Design) leave it out,
15
+ * while an app's own presets (`caption`, `link`, …) may set it.
16
+ */
17
+ export type TextToken = {
18
+ /** A `tokens.fontSizes` key (`'lg'`) or a raw size (`18`). */
19
+ fontSize?: string | number;
20
+ /**
21
+ * A `tokens.lineHeights` key (`'short'` — a ratio of `fontSize`) or a raw,
22
+ * absolute line height (`24`).
23
+ */
24
+ lineHeight?: string | number;
25
+ /** A `tokens.letterSpacings` key (`'wide'`) or a raw value (`0.15`). */
26
+ letterSpacing?: string | number;
27
+ /** A `tokens.fontWeights` key (`'semibold'`) or a raw RN weight (`'600'`). */
28
+ fontWeight?: string | TextStyle['fontWeight'];
29
+ /**
30
+ * Optional case transform, passed through as-is (no token scale), e.g.
31
+ * Material UI's `button` and `overline` styles.
32
+ *
33
+ * @example
34
+ * button: { fontSize: 14, fontWeight: '500', textTransform: 'uppercase' }
35
+ */
36
+ textTransform?: TextStyle['textTransform'];
37
+ /**
38
+ * A raw color, the same in light and dark (`colors['gray.500']`), or
39
+ * one per scheme. A scheme left out gets no color from the preset, so the
40
+ * surrounding style (or React Native's default) applies there.
41
+ *
42
+ * @example
43
+ * color: { light: colors['gray.950'], dark: colors.white }
44
+ * color: { dark: colors.white } // light: no color from the preset
45
+ */
46
+ color?: TextColor;
47
+ };
48
+ /** One semantic color: its value in each scheme. */
49
+ export type SchemeColor = {
50
+ light: string;
51
+ dark: string;
52
+ };
53
+ /**
54
+ * `semanticTokens.colors`: groups of colors (`bg.default`) and top-level
55
+ * colors (`primary`), in any mix. See `isSchemeColor` for how they are told
56
+ * apart.
57
+ */
58
+ export type SemanticColors = Record<string, SchemeColor | Record<string, SchemeColor>>;
59
+ /** `TextToken['color']`: one raw color, or one per scheme. */
60
+ export type TextColor = string | {
61
+ light?: string;
62
+ dark?: string;
63
+ };
64
+ /**
65
+ * `semanticTokens.text`: groups of any depth whose leaves are presets.
66
+ * See `isTextPreset` for how leaves and groups are told apart.
67
+ */
68
+ export type TextTokenTree = {
69
+ [key: string]: TextToken | TextTokenTree;
70
+ };
71
+ /**
72
+ * The shape a theme package (or an app's local override) must satisfy.
73
+ * `tokens` holds primitive values (the smallest design-system units);
74
+ * `semanticTokens` holds role-named values built on top of them — the
75
+ * scheme-dependent `colors` and the `text` type scale — mirroring Chakra
76
+ * UI's `tokens` vs `semanticTokens` distinction.
77
+ */
78
+ export type ThemeConfig = {
79
+ /**
80
+ * Primitive, scheme-independent values. Keys are the token names used in
81
+ * styles (`themed.view({ padding: 4 })`).
82
+ */
83
+ tokens?: {
84
+ /**
85
+ * Fixed colors, the same in light and dark. Color style props accept
86
+ * them by name (`themed.view({ backgroundColor: 'red.500' })`) alongside
87
+ * `semanticTokens.colors`; when both define a name, the semantic color
88
+ * wins. Prefer semantic colors for anything that should follow the color
89
+ * scheme. Also readable via `useThemed().tokens.colors`.
90
+ *
91
+ * @example
92
+ * colors: { white: '#ffffff', 'gray.50': '#fafafa', 'gray.950': '#111111' }
93
+ */
94
+ colors?: Record<string, string>;
95
+ /**
96
+ * For `borderRadius` and every corner-radius variant.
97
+ *
98
+ * @example
99
+ * radii: { none: 0, sm: 4, md: 6, lg: 8, full: 9999 }
100
+ */
101
+ radii?: Record<string, number>;
102
+ /**
103
+ * For `padding*`, `margin*`, `gap`, `rowGap` and `columnGap`. Numeric
104
+ * keys are used as numbers: `themed.view({ padding: 4 })`.
105
+ *
106
+ * @example
107
+ * spacing: { px: 1, 0: 0, 0.5: 2, 1: 4, 2: 8, 4: 16 }
108
+ */
109
+ spacing?: Record<string, number>;
110
+ /**
111
+ * For `fontSize`.
112
+ *
113
+ * @example
114
+ * fontSizes: { sm: 14, md: 16, lg: 18, xl: 20 }
115
+ */
116
+ fontSizes?: Record<string, number>;
117
+ /**
118
+ * For `fontWeight`.
119
+ *
120
+ * @example
121
+ * fontWeights: { normal: '400', medium: '500', semibold: '600', bold: '700' }
122
+ */
123
+ fontWeights?: Record<string, TextStyle['fontWeight']>;
124
+ /**
125
+ * For `lineHeight`, as **ratios of `fontSize`** (like CSS unitless
126
+ * line-height). A token resolves to `fontSize × ratio`, using the
127
+ * `fontSize` in the same style or else `defaults.fontSize`. A raw number
128
+ * passed to `lineHeight` stays an absolute value, as in React Native.
129
+ *
130
+ * @example
131
+ * lineHeights: { shorter: 1.25, short: 1.375, moderate: 1.5, tall: 1.625 }
132
+ * // themed.text({ fontSize: 'lg', lineHeight: 'short' })
133
+ * // → { fontSize: 18, lineHeight: 24.75 }
134
+ */
135
+ lineHeights?: Record<string, number>;
136
+ /**
137
+ * For `letterSpacing`, in points.
138
+ *
139
+ * @example
140
+ * letterSpacings: { tight: -0.4, normal: 0, wide: 0.4 }
141
+ */
142
+ letterSpacings?: Record<string, number>;
143
+ /**
144
+ * For `zIndex`, which also accepts a raw number.
145
+ *
146
+ * @example
147
+ * zIndices: { base: 0, dropdown: 1000, modal: 1400, toast: 1700 }
148
+ * // themed.view({ zIndex: 'modal' }) → { zIndex: 1400 }
149
+ */
150
+ zIndices?: Record<string, number>;
151
+ /**
152
+ * Presets for the virtual `shadow` prop, which expands to these RN props.
153
+ *
154
+ * @example
155
+ * shadows: {
156
+ * sm: {
157
+ * shadowColor: '#000000',
158
+ * shadowOffset: { width: 0, height: 1 },
159
+ * shadowOpacity: 0.1,
160
+ * shadowRadius: 2,
161
+ * elevation: 2,
162
+ * },
163
+ * }
164
+ */
165
+ shadows?: Record<string, ShadowToken>;
166
+ };
167
+ /** Role-named values built on top of `tokens`. */
168
+ semanticTokens?: {
169
+ /**
170
+ * group -> token -> scheme, used in styles as `'group.token'`
171
+ * (`themed.view({ backgroundColor: 'bg.subtle' })`), or token -> scheme
172
+ * at the top level, used by its bare name (`'primary-foreground'`, as in
173
+ * shadcn/ui). Switched with the current light/dark scheme. Takes
174
+ * precedence over a `tokens.colors` entry with the same name.
175
+ *
176
+ * @example
177
+ * colors: {
178
+ * bg: {
179
+ * default: { light: '#ffffff', dark: '#111111' },
180
+ * subtle: { light: '#fafafa', dark: '#18181b' },
181
+ * },
182
+ * fg: {
183
+ * default: { light: '#111111', dark: '#fafafa' },
184
+ * },
185
+ * // top level: themed.view({ backgroundColor: 'primary' })
186
+ * primary: { light: '#171717', dark: '#e5e5e5' },
187
+ * }
188
+ */
189
+ colors?: SemanticColors;
190
+ /**
191
+ * Typography presets, as a tree of any depth. A node whose values are
192
+ * all primitives is a **preset** (`TextToken`); a node of objects is a
193
+ * **group**. Each preset is called by its path:
194
+ * `themed.text.<path>(override?)`. Fields reference `tokens` keys or
195
+ * take raw values (see `TextToken`).
196
+ *
197
+ * @example
198
+ * text: {
199
+ * // role -> size (Material Design 3 style)
200
+ * title: {
201
+ * md: { fontSize: 'md', lineHeight: 'moderate', fontWeight: 'semibold' },
202
+ * sm: { fontSize: 14, lineHeight: 20, fontWeight: '500' },
203
+ * },
204
+ * // flat: themed.text.caption()
205
+ * caption: { fontSize: 'xs', lineHeight: 'short' },
206
+ * // deeper: themed.text.heading.display.lg()
207
+ * heading: { display: { lg: { fontSize: '4xl', fontWeight: 'bold' } } },
208
+ * }
209
+ */
210
+ text?: TextTokenTree;
211
+ };
212
+ /** Theme-wide defaults. */
213
+ defaults?: {
214
+ /**
215
+ * The base font size: a `tokens.fontSizes` key or a raw size. Used to
216
+ * resolve a `lineHeight` token when the style has no `fontSize` of its
217
+ * own. Falls back to 14 (React Native's default) when unset.
218
+ *
219
+ * @example
220
+ * defaults: { fontSize: 'md' }
221
+ * // with fontSizes.md = 16:
222
+ * // themed.text({ lineHeight: 'short' }) → { lineHeight: 22 } (16 × 1.375)
223
+ */
224
+ fontSize?: string | number;
225
+ };
226
+ };
227
+ /**
228
+ * Exact token types for one theme. Written by
229
+ * `@react-native-rethemed/cli codegen` from the evaluated config, never by
230
+ * hand, so core never has to infer token names from literal types.
231
+ */
232
+ export type ThemedSchema = {
233
+ /**
234
+ * Every token-aware style prop (plus the virtual `shadow`). Each primitive
235
+ * only picks the props its RN style type actually has — see `TokenizeStyle`.
236
+ */
237
+ style: object;
238
+ /** `themed.text.<role>.<size>(override?)` */
239
+ textVariants: object;
240
+ /** `useThemed().tokens` */
241
+ tokens: object;
242
+ /** `useThemed().semanticTokens`, colors resolved for the current scheme. */
243
+ semanticTokens: object;
244
+ };
245
+ /**
246
+ * Swaps `Base`'s token-bearing props for the token-aware ones in `P`.
247
+ * `Omit` + `Pick` only — one level, no recursion. `Pick` is homomorphic, so
248
+ * the generated JSDoc (token tables) survives into editor hovers.
249
+ *
250
+ * Only props `Base` actually has are picked (e.g. no `color` on `View`); the
251
+ * virtual `shadow` prop is kept when `Base` has real shadow props.
252
+ */
253
+ export type TokenizeStyle<Base, P> = Omit<Base, keyof P> & Pick<P, Extract<keyof P, keyof Base | ('shadowColor' extends keyof Base ? 'shadow' : never)>>;
254
+ export type ThemedStyles<S extends ThemedSchema> = {
255
+ /** Resolves token values in a `View` style. */
256
+ view: (style: TokenizeStyle<ViewStyle, S['style']>) => ViewStyle;
257
+ /** Resolves token values in an `Image` style. */
258
+ image: (style: TokenizeStyle<ImageStyle, S['style']>) => ImageStyle;
259
+ /**
260
+ * Resolves token values in a `Text` style. Typography presets are
261
+ * available as `themed.text.<role>.<size>(override?)`.
262
+ */
263
+ text: ((style?: TokenizeStyle<TextStyle, S['style']>) => TextStyle) & S['textVariants'];
264
+ };
265
+ type LooseTokenKeys = ColorKeys | RadiusKeys | SpacingKeys | FontSizeKeys | FontWeightKeys | LineHeightKeys | LetterSpacingKeys | ZIndexKeys | 'shadow';
266
+ type LooseStyle = { [K in LooseTokenKeys]?: string | number; };
267
+ /** Any path under `themed.text`, callable at the leaves. */
268
+ type LooseTextVariants = {
269
+ [key: string]: ((override?: TokenizeStyle<TextStyle, LooseStyle>) => TextStyle) & LooseTextVariants;
270
+ };
271
+ export type LooseSchema = {
272
+ style: LooseStyle;
273
+ textVariants: LooseTextVariants;
274
+ tokens: NonNullable<ThemeConfig['tokens']>;
275
+ semanticTokens: {
276
+ colors: Record<string, string | Record<string, string>>;
277
+ text: NonNullable<NonNullable<ThemeConfig['semanticTokens']>['text']>;
278
+ };
279
+ };
280
+ //#endregion
@@ -0,0 +1,280 @@
1
+ import { ColorKeys, FontSizeKeys, FontWeightKeys, LetterSpacingKeys, LineHeightKeys, RadiusKeys, SpacingKeys, ZIndexKeys } from "./style-props.mjs";
2
+ import { ImageStyle, TextStyle, ViewStyle } from "react-native";
3
+ //#region src/types.d.ts
4
+ /** One shadow preset — expands into these real RN style props. */
5
+ export type ShadowToken = Pick<ViewStyle, 'shadowColor' | 'shadowOffset' | 'shadowOpacity' | 'shadowRadius' | 'elevation'>;
6
+ /**
7
+ * One `role`/`size` entry in `semanticTokens.text` (Material Design 3
8
+ * type-scale shape). Each field is either a key of the matching primitive
9
+ * scale in `tokens` (e.g. `fontSize: 'lg'`) or a raw value. References are
10
+ * validated by `@react-native-rethemed/cli codegen` against the final config,
11
+ * since a partial theme may reference keys supplied by another theme it is
12
+ * later merged with.
13
+ *
14
+ * `color` is optional: type-scale packages (Material Design) leave it out,
15
+ * while an app's own presets (`caption`, `link`, …) may set it.
16
+ */
17
+ export type TextToken = {
18
+ /** A `tokens.fontSizes` key (`'lg'`) or a raw size (`18`). */
19
+ fontSize?: string | number;
20
+ /**
21
+ * A `tokens.lineHeights` key (`'short'` — a ratio of `fontSize`) or a raw,
22
+ * absolute line height (`24`).
23
+ */
24
+ lineHeight?: string | number;
25
+ /** A `tokens.letterSpacings` key (`'wide'`) or a raw value (`0.15`). */
26
+ letterSpacing?: string | number;
27
+ /** A `tokens.fontWeights` key (`'semibold'`) or a raw RN weight (`'600'`). */
28
+ fontWeight?: string | TextStyle['fontWeight'];
29
+ /**
30
+ * Optional case transform, passed through as-is (no token scale), e.g.
31
+ * Material UI's `button` and `overline` styles.
32
+ *
33
+ * @example
34
+ * button: { fontSize: 14, fontWeight: '500', textTransform: 'uppercase' }
35
+ */
36
+ textTransform?: TextStyle['textTransform'];
37
+ /**
38
+ * A raw color, the same in light and dark (`colors['gray.500']`), or
39
+ * one per scheme. A scheme left out gets no color from the preset, so the
40
+ * surrounding style (or React Native's default) applies there.
41
+ *
42
+ * @example
43
+ * color: { light: colors['gray.950'], dark: colors.white }
44
+ * color: { dark: colors.white } // light: no color from the preset
45
+ */
46
+ color?: TextColor;
47
+ };
48
+ /** One semantic color: its value in each scheme. */
49
+ export type SchemeColor = {
50
+ light: string;
51
+ dark: string;
52
+ };
53
+ /**
54
+ * `semanticTokens.colors`: groups of colors (`bg.default`) and top-level
55
+ * colors (`primary`), in any mix. See `isSchemeColor` for how they are told
56
+ * apart.
57
+ */
58
+ export type SemanticColors = Record<string, SchemeColor | Record<string, SchemeColor>>;
59
+ /** `TextToken['color']`: one raw color, or one per scheme. */
60
+ export type TextColor = string | {
61
+ light?: string;
62
+ dark?: string;
63
+ };
64
+ /**
65
+ * `semanticTokens.text`: groups of any depth whose leaves are presets.
66
+ * See `isTextPreset` for how leaves and groups are told apart.
67
+ */
68
+ export type TextTokenTree = {
69
+ [key: string]: TextToken | TextTokenTree;
70
+ };
71
+ /**
72
+ * The shape a theme package (or an app's local override) must satisfy.
73
+ * `tokens` holds primitive values (the smallest design-system units);
74
+ * `semanticTokens` holds role-named values built on top of them — the
75
+ * scheme-dependent `colors` and the `text` type scale — mirroring Chakra
76
+ * UI's `tokens` vs `semanticTokens` distinction.
77
+ */
78
+ export type ThemeConfig = {
79
+ /**
80
+ * Primitive, scheme-independent values. Keys are the token names used in
81
+ * styles (`themed.view({ padding: 4 })`).
82
+ */
83
+ tokens?: {
84
+ /**
85
+ * Fixed colors, the same in light and dark. Color style props accept
86
+ * them by name (`themed.view({ backgroundColor: 'red.500' })`) alongside
87
+ * `semanticTokens.colors`; when both define a name, the semantic color
88
+ * wins. Prefer semantic colors for anything that should follow the color
89
+ * scheme. Also readable via `useThemed().tokens.colors`.
90
+ *
91
+ * @example
92
+ * colors: { white: '#ffffff', 'gray.50': '#fafafa', 'gray.950': '#111111' }
93
+ */
94
+ colors?: Record<string, string>;
95
+ /**
96
+ * For `borderRadius` and every corner-radius variant.
97
+ *
98
+ * @example
99
+ * radii: { none: 0, sm: 4, md: 6, lg: 8, full: 9999 }
100
+ */
101
+ radii?: Record<string, number>;
102
+ /**
103
+ * For `padding*`, `margin*`, `gap`, `rowGap` and `columnGap`. Numeric
104
+ * keys are used as numbers: `themed.view({ padding: 4 })`.
105
+ *
106
+ * @example
107
+ * spacing: { px: 1, 0: 0, 0.5: 2, 1: 4, 2: 8, 4: 16 }
108
+ */
109
+ spacing?: Record<string, number>;
110
+ /**
111
+ * For `fontSize`.
112
+ *
113
+ * @example
114
+ * fontSizes: { sm: 14, md: 16, lg: 18, xl: 20 }
115
+ */
116
+ fontSizes?: Record<string, number>;
117
+ /**
118
+ * For `fontWeight`.
119
+ *
120
+ * @example
121
+ * fontWeights: { normal: '400', medium: '500', semibold: '600', bold: '700' }
122
+ */
123
+ fontWeights?: Record<string, TextStyle['fontWeight']>;
124
+ /**
125
+ * For `lineHeight`, as **ratios of `fontSize`** (like CSS unitless
126
+ * line-height). A token resolves to `fontSize × ratio`, using the
127
+ * `fontSize` in the same style or else `defaults.fontSize`. A raw number
128
+ * passed to `lineHeight` stays an absolute value, as in React Native.
129
+ *
130
+ * @example
131
+ * lineHeights: { shorter: 1.25, short: 1.375, moderate: 1.5, tall: 1.625 }
132
+ * // themed.text({ fontSize: 'lg', lineHeight: 'short' })
133
+ * // → { fontSize: 18, lineHeight: 24.75 }
134
+ */
135
+ lineHeights?: Record<string, number>;
136
+ /**
137
+ * For `letterSpacing`, in points.
138
+ *
139
+ * @example
140
+ * letterSpacings: { tight: -0.4, normal: 0, wide: 0.4 }
141
+ */
142
+ letterSpacings?: Record<string, number>;
143
+ /**
144
+ * For `zIndex`, which also accepts a raw number.
145
+ *
146
+ * @example
147
+ * zIndices: { base: 0, dropdown: 1000, modal: 1400, toast: 1700 }
148
+ * // themed.view({ zIndex: 'modal' }) → { zIndex: 1400 }
149
+ */
150
+ zIndices?: Record<string, number>;
151
+ /**
152
+ * Presets for the virtual `shadow` prop, which expands to these RN props.
153
+ *
154
+ * @example
155
+ * shadows: {
156
+ * sm: {
157
+ * shadowColor: '#000000',
158
+ * shadowOffset: { width: 0, height: 1 },
159
+ * shadowOpacity: 0.1,
160
+ * shadowRadius: 2,
161
+ * elevation: 2,
162
+ * },
163
+ * }
164
+ */
165
+ shadows?: Record<string, ShadowToken>;
166
+ };
167
+ /** Role-named values built on top of `tokens`. */
168
+ semanticTokens?: {
169
+ /**
170
+ * group -> token -> scheme, used in styles as `'group.token'`
171
+ * (`themed.view({ backgroundColor: 'bg.subtle' })`), or token -> scheme
172
+ * at the top level, used by its bare name (`'primary-foreground'`, as in
173
+ * shadcn/ui). Switched with the current light/dark scheme. Takes
174
+ * precedence over a `tokens.colors` entry with the same name.
175
+ *
176
+ * @example
177
+ * colors: {
178
+ * bg: {
179
+ * default: { light: '#ffffff', dark: '#111111' },
180
+ * subtle: { light: '#fafafa', dark: '#18181b' },
181
+ * },
182
+ * fg: {
183
+ * default: { light: '#111111', dark: '#fafafa' },
184
+ * },
185
+ * // top level: themed.view({ backgroundColor: 'primary' })
186
+ * primary: { light: '#171717', dark: '#e5e5e5' },
187
+ * }
188
+ */
189
+ colors?: SemanticColors;
190
+ /**
191
+ * Typography presets, as a tree of any depth. A node whose values are
192
+ * all primitives is a **preset** (`TextToken`); a node of objects is a
193
+ * **group**. Each preset is called by its path:
194
+ * `themed.text.<path>(override?)`. Fields reference `tokens` keys or
195
+ * take raw values (see `TextToken`).
196
+ *
197
+ * @example
198
+ * text: {
199
+ * // role -> size (Material Design 3 style)
200
+ * title: {
201
+ * md: { fontSize: 'md', lineHeight: 'moderate', fontWeight: 'semibold' },
202
+ * sm: { fontSize: 14, lineHeight: 20, fontWeight: '500' },
203
+ * },
204
+ * // flat: themed.text.caption()
205
+ * caption: { fontSize: 'xs', lineHeight: 'short' },
206
+ * // deeper: themed.text.heading.display.lg()
207
+ * heading: { display: { lg: { fontSize: '4xl', fontWeight: 'bold' } } },
208
+ * }
209
+ */
210
+ text?: TextTokenTree;
211
+ };
212
+ /** Theme-wide defaults. */
213
+ defaults?: {
214
+ /**
215
+ * The base font size: a `tokens.fontSizes` key or a raw size. Used to
216
+ * resolve a `lineHeight` token when the style has no `fontSize` of its
217
+ * own. Falls back to 14 (React Native's default) when unset.
218
+ *
219
+ * @example
220
+ * defaults: { fontSize: 'md' }
221
+ * // with fontSizes.md = 16:
222
+ * // themed.text({ lineHeight: 'short' }) → { lineHeight: 22 } (16 × 1.375)
223
+ */
224
+ fontSize?: string | number;
225
+ };
226
+ };
227
+ /**
228
+ * Exact token types for one theme. Written by
229
+ * `@react-native-rethemed/cli codegen` from the evaluated config, never by
230
+ * hand, so core never has to infer token names from literal types.
231
+ */
232
+ export type ThemedSchema = {
233
+ /**
234
+ * Every token-aware style prop (plus the virtual `shadow`). Each primitive
235
+ * only picks the props its RN style type actually has — see `TokenizeStyle`.
236
+ */
237
+ style: object;
238
+ /** `themed.text.<role>.<size>(override?)` */
239
+ textVariants: object;
240
+ /** `useThemed().tokens` */
241
+ tokens: object;
242
+ /** `useThemed().semanticTokens`, colors resolved for the current scheme. */
243
+ semanticTokens: object;
244
+ };
245
+ /**
246
+ * Swaps `Base`'s token-bearing props for the token-aware ones in `P`.
247
+ * `Omit` + `Pick` only — one level, no recursion. `Pick` is homomorphic, so
248
+ * the generated JSDoc (token tables) survives into editor hovers.
249
+ *
250
+ * Only props `Base` actually has are picked (e.g. no `color` on `View`); the
251
+ * virtual `shadow` prop is kept when `Base` has real shadow props.
252
+ */
253
+ export type TokenizeStyle<Base, P> = Omit<Base, keyof P> & Pick<P, Extract<keyof P, keyof Base | ('shadowColor' extends keyof Base ? 'shadow' : never)>>;
254
+ export type ThemedStyles<S extends ThemedSchema> = {
255
+ /** Resolves token values in a `View` style. */
256
+ view: (style: TokenizeStyle<ViewStyle, S['style']>) => ViewStyle;
257
+ /** Resolves token values in an `Image` style. */
258
+ image: (style: TokenizeStyle<ImageStyle, S['style']>) => ImageStyle;
259
+ /**
260
+ * Resolves token values in a `Text` style. Typography presets are
261
+ * available as `themed.text.<role>.<size>(override?)`.
262
+ */
263
+ text: ((style?: TokenizeStyle<TextStyle, S['style']>) => TextStyle) & S['textVariants'];
264
+ };
265
+ type LooseTokenKeys = ColorKeys | RadiusKeys | SpacingKeys | FontSizeKeys | FontWeightKeys | LineHeightKeys | LetterSpacingKeys | ZIndexKeys | 'shadow';
266
+ type LooseStyle = { [K in LooseTokenKeys]?: string | number; };
267
+ /** Any path under `themed.text`, callable at the leaves. */
268
+ type LooseTextVariants = {
269
+ [key: string]: ((override?: TokenizeStyle<TextStyle, LooseStyle>) => TextStyle) & LooseTextVariants;
270
+ };
271
+ export type LooseSchema = {
272
+ style: LooseStyle;
273
+ textVariants: LooseTextVariants;
274
+ tokens: NonNullable<ThemeConfig['tokens']>;
275
+ semanticTokens: {
276
+ colors: Record<string, string | Record<string, string>>;
277
+ text: NonNullable<NonNullable<ThemeConfig['semanticTokens']>['text']>;
278
+ };
279
+ };
280
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-rethemed/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Type-safe design tokens for React Native style props — light/dark themes, text presets, and a themed style API.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -22,28 +22,15 @@
22
22
  "directory": "packages/react-native-rethemed/core"
23
23
  },
24
24
  "sideEffects": false,
25
- "main": "./dist/cjs/index.js",
26
- "types": "./dist/cjs/index.d.ts",
25
+ "types": "./dist/index.d.cts",
27
26
  "exports": {
28
27
  ".": {
29
- "import": {
30
- "types": "./dist/esm/index.d.ts",
31
- "default": "./dist/esm/index.js"
32
- },
33
- "require": {
34
- "types": "./dist/cjs/index.d.ts",
35
- "default": "./dist/cjs/index.js"
36
- }
28
+ "import": "./dist/index.mjs",
29
+ "require": "./dist/index.cjs"
37
30
  },
38
31
  "./config": {
39
- "import": {
40
- "types": "./dist/esm/config.d.ts",
41
- "default": "./dist/esm/config.js"
42
- },
43
- "require": {
44
- "types": "./dist/cjs/config.d.ts",
45
- "default": "./dist/cjs/config.js"
46
- }
32
+ "import": "./dist/config.mjs",
33
+ "require": "./dist/config.cjs"
47
34
  },
48
35
  "./package.json": "./package.json"
49
36
  },
@@ -58,20 +45,25 @@
58
45
  "react-native": "*"
59
46
  },
60
47
  "devDependencies": {
61
- "react": "19.2.3",
62
- "react-native": "0.86.2",
63
- "@types/react": "19.2.17",
48
+ "@arethetypeswrong/core": "0.18.5",
64
49
  "@biomejs/biome": "2.5.4",
50
+ "@types/react": "19.2.17",
65
51
  "biome-config": "0.0.0",
52
+ "publint": "0.3.25",
53
+ "react": "19.2.3",
54
+ "react-native": "0.86.2",
55
+ "tsdown": "0.23.0",
56
+ "tsdown-config": "0.0.0",
66
57
  "typescript": "6.0.3",
67
58
  "vitest": "4.1.10"
68
59
  },
69
60
  "scripts": {
70
- "build": "node ../../../scripts/build-package.mjs",
61
+ "build": "tsdown",
71
62
  "lint": "biome check src",
72
63
  "lint:fix": "biome check --write --unsafe src",
73
64
  "tsc": "tsc --noEmit",
74
65
  "test": "vitest",
75
66
  "test:ci": "vitest run"
76
- }
67
+ },
68
+ "main": "./dist/index.cjs"
77
69
  }
@@ -1,32 +0,0 @@
1
- export type ColorMode = 'light' | 'dark' | 'system';
2
- /**
3
- * Structural subset of `expo-secure-store` / `AsyncStorage`, so either can be
4
- * passed as-is without an adapter.
5
- */
6
- export type ColorModeStorage = {
7
- getItem: (key: string) => string | null | Promise<string | null>;
8
- setItem: (key: string, value: string) => void | Promise<void>;
9
- };
10
- export declare const isColorMode: (value: unknown) => value is ColorMode;
11
- /**
12
- * External store holding the uncontrolled color mode. One is created per
13
- * `createThemed()` call (never at module level) so independent instances
14
- * don't share state.
15
- */
16
- export declare function createColorModeStore(): {
17
- /**
18
- * Seeds the mode on first use only. Called during render so the very
19
- * first render already sees `defaultMode`; later calls (e.g. a changed
20
- * `defaultColorMode` prop) are ignored rather than overwriting the
21
- * current mode.
22
- */
23
- init(defaultMode: ColorMode): void;
24
- subscribe(listener: () => void): () => void;
25
- getSnapshot(): ColorMode;
26
- setMode(next: ColorMode): void;
27
- /** Returns `false` if rehydration already ran for this store. */
28
- beginHydration(): boolean;
29
- /** Applies a stored value unless it's malformed or the user already chose. */
30
- hydrate(saved: unknown): void;
31
- };
32
- export type ColorModeStore = ReturnType<typeof createColorModeStore>;