@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.
- package/README.md +2 -0
- package/dist/color-mode-store.cjs +57 -0
- package/dist/color-mode-store.d.cts +11 -0
- package/dist/color-mode-store.d.mts +11 -0
- package/dist/color-mode-store.mjs +56 -0
- package/dist/config.cjs +26 -0
- package/dist/config.d.cts +8 -0
- package/dist/config.d.mts +8 -0
- package/dist/config.mjs +7 -0
- package/dist/create-themed-styles.cjs +31 -0
- package/dist/{esm/create-themed-styles.d.ts → create-themed-styles.d.cts} +4 -2
- package/dist/{cjs/create-themed-styles.d.ts → create-themed-styles.d.mts} +4 -2
- package/dist/create-themed-styles.mjs +31 -0
- package/dist/create-themed.cjs +93 -0
- package/dist/create-themed.d.cts +48 -0
- package/dist/create-themed.d.mts +48 -0
- package/dist/create-themed.mjs +93 -0
- package/dist/define-theme.cjs +15 -0
- package/dist/{esm/define-theme.d.ts → define-theme.d.cts} +3 -1
- package/dist/{cjs/define-theme.d.ts → define-theme.d.mts} +3 -1
- package/dist/define-theme.mjs +15 -0
- package/dist/extend-theme.cjs +109 -0
- package/dist/{esm/extend-theme.d.ts → extend-theme.d.cts} +3 -1
- package/dist/{cjs/extend-theme.d.ts → extend-theme.d.mts} +3 -1
- package/dist/extend-theme.mjs +109 -0
- package/dist/index.cjs +30 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.mjs +9 -0
- package/dist/resolvers/color-resolver.cjs +23 -0
- package/dist/resolvers/color-resolver.d.cts +4 -0
- package/dist/resolvers/color-resolver.d.mts +4 -0
- package/dist/resolvers/color-resolver.mjs +23 -0
- package/dist/resolvers/line-height-resolver.cjs +17 -0
- package/dist/{esm/resolvers/line-height-resolver.d.ts → resolvers/line-height-resolver.d.cts} +3 -1
- package/dist/{cjs/resolvers/line-height-resolver.d.ts → resolvers/line-height-resolver.d.mts} +3 -1
- package/dist/resolvers/line-height-resolver.mjs +16 -0
- package/dist/resolvers/style-resolver.cjs +86 -0
- package/dist/resolvers/style-resolver.mjs +86 -0
- package/dist/semantic-colors.cjs +29 -0
- package/dist/{esm/semantic-colors.d.ts → semantic-colors.d.cts} +3 -1
- package/dist/{cjs/semantic-colors.d.ts → semantic-colors.d.mts} +3 -1
- package/dist/semantic-colors.mjs +28 -0
- package/dist/style-props.cjs +103 -0
- package/dist/{cjs/style-props.d.ts → style-props.d.cts} +2 -1
- package/dist/{esm/style-props.d.ts → style-props.d.mts} +2 -1
- package/dist/style-props.mjs +95 -0
- package/dist/text-tree.cjs +70 -0
- package/dist/{cjs/text-tree.d.ts → text-tree.d.cts} +4 -8
- package/dist/{esm/text-tree.d.ts → text-tree.d.mts} +4 -8
- package/dist/text-tree.mjs +66 -0
- package/dist/text-variants.cjs +25 -0
- package/dist/text-variants.mjs +25 -0
- package/dist/types.d.cts +280 -0
- package/dist/types.d.mts +280 -0
- package/package.json +16 -24
- package/dist/cjs/color-mode-store.d.ts +0 -32
- package/dist/cjs/color-mode-store.js +0 -64
- package/dist/cjs/config.d.ts +0 -14
- package/dist/cjs/config.js +0 -35
- package/dist/cjs/create-themed-styles.js +0 -37
- package/dist/cjs/create-themed.d.ts +0 -46
- package/dist/cjs/create-themed.js +0 -118
- package/dist/cjs/define-theme.js +0 -15
- package/dist/cjs/extend-theme.js +0 -118
- package/dist/cjs/index.d.ts +0 -4
- package/dist/cjs/index.js +0 -22
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/resolvers/color-resolver.d.ts +0 -10
- package/dist/cjs/resolvers/color-resolver.js +0 -26
- package/dist/cjs/resolvers/line-height-resolver.js +0 -20
- package/dist/cjs/resolvers/style-resolver.d.ts +0 -21
- package/dist/cjs/resolvers/style-resolver.js +0 -104
- package/dist/cjs/semantic-colors.js +0 -32
- package/dist/cjs/style-props.js +0 -95
- package/dist/cjs/text-tree.js +0 -79
- package/dist/cjs/text-variants.d.ts +0 -22
- package/dist/cjs/text-variants.js +0 -29
- package/dist/cjs/types.d.ts +0 -281
- package/dist/cjs/types.js +0 -2
- package/dist/esm/color-mode-store.d.ts +0 -32
- package/dist/esm/color-mode-store.js +0 -59
- package/dist/esm/config.d.ts +0 -14
- package/dist/esm/config.js +0 -13
- package/dist/esm/create-themed-styles.js +0 -34
- package/dist/esm/create-themed.d.ts +0 -46
- package/dist/esm/create-themed.js +0 -115
- package/dist/esm/define-theme.js +0 -12
- package/dist/esm/extend-theme.js +0 -115
- package/dist/esm/index.d.ts +0 -4
- package/dist/esm/index.js +0 -3
- package/dist/esm/package.json +0 -1
- package/dist/esm/resolvers/color-resolver.d.ts +0 -10
- package/dist/esm/resolvers/color-resolver.js +0 -23
- package/dist/esm/resolvers/line-height-resolver.js +0 -16
- package/dist/esm/resolvers/style-resolver.d.ts +0 -21
- package/dist/esm/resolvers/style-resolver.js +0 -101
- package/dist/esm/semantic-colors.js +0 -28
- package/dist/esm/style-props.js +0 -92
- package/dist/esm/text-tree.js +0 -72
- package/dist/esm/text-variants.d.ts +0 -22
- package/dist/esm/text-variants.js +0 -26
- package/dist/esm/types.d.ts +0 -281
- package/dist/esm/types.js +0 -1
package/dist/types.d.cts
ADDED
|
@@ -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
|
package/dist/types.d.mts
ADDED
|
@@ -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.
|
|
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
|
-
"
|
|
26
|
-
"types": "./dist/cjs/index.d.ts",
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
27
26
|
"exports": {
|
|
28
27
|
".": {
|
|
29
|
-
"import":
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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": "
|
|
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>;
|