@vvedantb/native-ui 0.6.0 → 0.7.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.
@@ -0,0 +1,248 @@
1
+ /**
2
+ * `@vvedantb/native-ui/tailwind-preset`: the NativeWind / Tailwind preset the
3
+ * Expo apps share. CommonJS, because `tailwind.config.js` `require`s it and
4
+ * Tailwind loads it outside Metro.
5
+ *
6
+ * const { nativeUiPreset, nativeUiContent } = require("@vvedantb/native-ui/tailwind-preset");
7
+ * module.exports = {
8
+ * content: ["./app/**\/*.{js,ts,tsx}", nativeUiContent()],
9
+ * presets: [nativeUiPreset()],
10
+ * };
11
+ *
12
+ * It nests `nativewind/preset`, so callers pass only this one. It sets
13
+ * `darkMode`, the Instrument Sans font classes, the colour keys native-ui
14
+ * classes use, `borderWidth.hairline` and, when asked, `borderRadius` from
15
+ * `--radius`.
16
+ * App colours, display fonts, keyframes and font-weight plugins stay in the
17
+ * app config.
18
+ *
19
+ * `color` maps a CSS variable name (without `--`) to a Tailwind colour, as the
20
+ * web `tokensPreset({ color })` of `@vvedantb/ui`. With `injectCssVariables`,
21
+ * the preset also writes those variables (light and dark) from `tokens.ts` and
22
+ * `palette.ts`, as space-separated RGB channels, and the default `color` is
23
+ * `rgbAlpha`.
24
+ */
25
+ "use strict";
26
+
27
+ const fs = require("node:fs");
28
+ const path = require("node:path");
29
+
30
+ /** `var(--name)`. */
31
+ const cssVar = (name) => `var(--${name})`;
32
+ /** `hsl(var(--name))`, for variables that hold `h s% l%` channels. */
33
+ const hslVar = (name) => `hsl(var(--${name}))`;
34
+ /** `rgb(var(--name) / <alpha-value>)`, for variables that hold `r g b` channels. Keeps opacity modifiers. */
35
+ const rgbAlpha = (name) => `rgb(var(--${name}) / <alpha-value>)`;
36
+
37
+ const INSTRUMENT_SANS = {
38
+ regular: "InstrumentSans_400Regular",
39
+ medium: "InstrumentSans_500Medium",
40
+ semibold: "InstrumentSans_600SemiBold",
41
+ bold: "InstrumentSans_700Bold",
42
+ };
43
+
44
+ /** The glob Tailwind scans for native-ui class names: this package's `src`, at its real path (pnpm symlinks resolve). */
45
+ function nativeUiContent() {
46
+ return path.join(__dirname, "**/*.{js,ts,tsx}").split(path.sep).join("/");
47
+ }
48
+
49
+ /**
50
+ * Reads one `export const name = { … } as const;` object from a sibling
51
+ * `.ts` file. Plain object literals only, so no TypeScript loader is needed:
52
+ * `tokens.ts` and `palette.ts` stay the single source of the colours.
53
+ */
54
+ function readConst(file, name) {
55
+ const source = fs.readFileSync(path.join(__dirname, file), "utf8");
56
+ const match = source.match(new RegExp(`export const ${name} = (\\{[\\s\\S]*?\\n\\}) as const;`));
57
+ if (!match) throw new Error(`@vvedantb/native-ui/tailwind-preset: no \`${name}\` object in ${file}`);
58
+ return new Function(`return (${match[1]});`)();
59
+ }
60
+
61
+ let cachedSchemeColors = null;
62
+
63
+ /** CSS variable name → `{ light, dark }` hex, from `tokens.ts` and `palette.ts`. */
64
+ function schemeColors() {
65
+ if (cachedSchemeColors) return cachedSchemeColors;
66
+ const tokens = readConst("tokens.ts", "tokens");
67
+ const palette = readConst("palette.ts", "palette");
68
+ const c = tokens.color;
69
+ cachedSchemeColors = {
70
+ background: c.background,
71
+ foreground: c.foreground,
72
+ border: c.border,
73
+ input: c.border,
74
+ ring: palette.primary,
75
+ separator: c.separator,
76
+ surface: c.surface,
77
+ "surface-foreground": c.foreground,
78
+ "surface-secondary": c.surfaceSecondary,
79
+ "surface-tertiary": palette.surfaceTertiary,
80
+ "surface-card": c.surfaceCard,
81
+ muted: c.muted,
82
+ "muted-foreground": c.muted,
83
+ primary: palette.primary,
84
+ "primary-foreground": palette.primaryForeground,
85
+ secondary: palette.default,
86
+ "secondary-foreground": palette.defaultForeground,
87
+ destructive: palette.danger,
88
+ "destructive-foreground": palette.dangerForeground,
89
+ accent: palette.primary,
90
+ "accent-foreground": palette.primaryForeground,
91
+ popover: palette.overlay,
92
+ "popover-foreground": palette.overlayForeground,
93
+ card: c.surfaceCard,
94
+ "card-foreground": c.foreground,
95
+ danger: palette.danger,
96
+ "danger-foreground": palette.dangerForeground,
97
+ success: palette.success,
98
+ "success-foreground": palette.successForeground,
99
+ warning: palette.warning,
100
+ "warning-foreground": palette.warningForeground,
101
+ default: palette.default,
102
+ "default-foreground": palette.defaultForeground,
103
+ overlay: palette.overlay,
104
+ "overlay-foreground": palette.overlayForeground,
105
+ field: c.surface,
106
+ "field-placeholder": palette.fieldPlaceholder,
107
+ };
108
+ return cachedSchemeColors;
109
+ }
110
+
111
+ /** `#rrggbb` as `r g b`. */
112
+ function channels(hex) {
113
+ const n = Number.parseInt(hex.slice(1, 7), 16);
114
+ return `${(n >> 16) & 255} ${(n >> 8) & 255} ${n & 255}`;
115
+ }
116
+
117
+ /** `--name: r g b` for every colour in one scheme. */
118
+ function cssVariables(scheme) {
119
+ return Object.fromEntries(
120
+ Object.entries(schemeColors()).map(([name, value]) => [`--${name}`, channels(value[scheme])]),
121
+ );
122
+ }
123
+
124
+ /** `--radius` from `tokens.radius.lg` (16), so `rounded-lg`, `-md` and `-sm` are 16, 14 and 12 as in `tokens.radius`. */
125
+ function radiusVariable() {
126
+ return { "--radius": `${readConst("tokens.ts", "tokens").radius.lg}px` };
127
+ }
128
+
129
+ /** The Tailwind colour keys native-ui classes and the apps share. */
130
+ function themeColors(v) {
131
+ return {
132
+ background: v("background"),
133
+ foreground: v("foreground"),
134
+ border: v("border"),
135
+ input: v("input"),
136
+ ring: v("ring"),
137
+ separator: v("separator"),
138
+ surface: {
139
+ DEFAULT: v("surface"),
140
+ foreground: v("surface-foreground"),
141
+ secondary: v("surface-secondary"),
142
+ tertiary: v("surface-tertiary"),
143
+ card: v("surface-card"),
144
+ },
145
+ primary: { DEFAULT: v("primary"), foreground: v("primary-foreground") },
146
+ secondary: { DEFAULT: v("secondary"), foreground: v("secondary-foreground") },
147
+ destructive: { DEFAULT: v("destructive"), foreground: v("destructive-foreground") },
148
+ muted: { DEFAULT: v("muted"), foreground: v("muted-foreground") },
149
+ accent: { DEFAULT: v("accent"), foreground: v("accent-foreground") },
150
+ popover: { DEFAULT: v("popover"), foreground: v("popover-foreground") },
151
+ card: { DEFAULT: v("card"), foreground: v("card-foreground") },
152
+ danger: { DEFAULT: v("danger"), foreground: v("danger-foreground") },
153
+ success: { DEFAULT: v("success"), foreground: v("success-foreground") },
154
+ warning: { DEFAULT: v("warning"), foreground: v("warning-foreground") },
155
+ default: { DEFAULT: v("default"), foreground: v("default-foreground") },
156
+ overlay: { DEFAULT: v("overlay"), foreground: v("overlay-foreground") },
157
+ field: { DEFAULT: v("field"), placeholder: v("field-placeholder") },
158
+ };
159
+ }
160
+
161
+ /** `nativewind/preset`, or none where NativeWind is not installed (this repository's tests). */
162
+ function nestedPresets() {
163
+ try {
164
+ return [require("nativewind/preset")];
165
+ } catch {
166
+ return [];
167
+ }
168
+ }
169
+
170
+ /** `{ hairline: hairlineWidth() }` from `nativewind/theme`, or none without NativeWind. */
171
+ function hairlineBorder() {
172
+ try {
173
+ const { hairlineWidth } = require("nativewind/theme");
174
+ return typeof hairlineWidth === "function" ? { hairline: hairlineWidth() } : {};
175
+ } catch {
176
+ return {};
177
+ }
178
+ }
179
+
180
+ /** A Tailwind plugin that writes the colour variables: `:root` (light) and the dark set for `darkMode`. */
181
+ function cssVariablesPlugin(darkMode) {
182
+ return function nativeUiCssVariables({ addBase }) {
183
+ const dark = cssVariables("dark");
184
+ addBase(
185
+ darkMode === "media"
186
+ ? {
187
+ ":root": { ...cssVariables("light"), ...radiusVariable() },
188
+ "@media (prefers-color-scheme: dark)": { ":root": dark },
189
+ }
190
+ : {
191
+ ":root": { ...cssVariables("light"), ...radiusVariable() },
192
+ ".dark:root, .dark": dark,
193
+ },
194
+ );
195
+ };
196
+ }
197
+
198
+ /**
199
+ * The preset. Options:
200
+ * - `darkMode`: `"class"` (default; `ThemeToggle` and `setColorScheme` need it) or `"media"`.
201
+ * - `color`: CSS variable name → colour value. Default `cssVar`, or `rgbAlpha` with `injectCssVariables`.
202
+ * - `injectCssVariables`: write the colour variables and `--radius` from `tokens.ts` and `palette.ts`. Default `false`.
203
+ * - `radiusVariable`: `rounded-lg`, `-md` and `-sm` from `var(--radius)`, as vibot and Velth. Default: on with
204
+ * `injectCssVariables`, else off, so an app without `--radius` keeps the Tailwind radii.
205
+ */
206
+ function nativeUiPreset(options = {}) {
207
+ const darkMode = options.darkMode === "media" ? "media" : "class";
208
+ const inject = options.injectCssVariables === true;
209
+ const color = options.color || (inject ? rgbAlpha : cssVar);
210
+ const radius = options.radiusVariable ?? inject;
211
+ return {
212
+ darkMode,
213
+ presets: nestedPresets(),
214
+ theme: {
215
+ extend: {
216
+ colors: themeColors(color),
217
+ fontFamily: {
218
+ sans: [INSTRUMENT_SANS.regular],
219
+ "instrument-sans": [INSTRUMENT_SANS.regular],
220
+ "instrument-sans-medium": [INSTRUMENT_SANS.medium],
221
+ "instrument-sans-semibold": [INSTRUMENT_SANS.semibold],
222
+ "instrument-sans-bold": [INSTRUMENT_SANS.bold],
223
+ },
224
+ borderRadius: radius
225
+ ? {
226
+ lg: "var(--radius)",
227
+ md: "calc(var(--radius) - 2px)",
228
+ sm: "calc(var(--radius) - 4px)",
229
+ }
230
+ : {},
231
+ borderWidth: hairlineBorder(),
232
+ },
233
+ },
234
+ plugins: inject ? [cssVariablesPlugin(darkMode)] : [],
235
+ };
236
+ }
237
+
238
+ module.exports = {
239
+ nativeUiPreset,
240
+ nativeUiContent,
241
+ cssVar,
242
+ hslVar,
243
+ rgbAlpha,
244
+ cssVariables,
245
+ schemeColors,
246
+ INSTRUMENT_SANS,
247
+ };
248
+ module.exports.default = nativeUiPreset();
@@ -0,0 +1,50 @@
1
+ /** Maps a CSS variable name (without `--`) to a Tailwind colour value. */
2
+ export type ColorFn = (name: string) => string;
3
+
4
+ type Colors = { [key: string]: string | Colors };
5
+ type CssDeclarations = Record<string, string>;
6
+ type AddBase = (base: Record<string, CssDeclarations | Record<string, CssDeclarations>>) => void;
7
+
8
+ export interface NativeUiPresetOptions {
9
+ /** Default `"class"`: `ThemeToggle` and NativeWind `setColorScheme` need it. */
10
+ darkMode?: "class" | "media";
11
+ /** Default `cssVar`, or `rgbAlpha` with `injectCssVariables`. */
12
+ color?: ColorFn;
13
+ /** Write the colour variables (as `r g b` channels) and `--radius` from `tokens.ts` and `palette.ts`. Default `false`. */
14
+ injectCssVariables?: boolean;
15
+ /** `rounded-lg`, `-md` and `-sm` from `var(--radius)`. Default: the value of `injectCssVariables`. */
16
+ radiusVariable?: boolean;
17
+ }
18
+
19
+ export interface NativeUiPreset {
20
+ darkMode: "class" | "media";
21
+ presets: unknown[];
22
+ theme: {
23
+ extend: {
24
+ colors: Colors;
25
+ fontFamily: Record<string, string[]>;
26
+ borderRadius: Record<string, string>;
27
+ borderWidth: Record<string, unknown>;
28
+ };
29
+ };
30
+ plugins: Array<(api: { addBase: AddBase }) => void>;
31
+ }
32
+
33
+ export declare const cssVar: ColorFn;
34
+ export declare const hslVar: ColorFn;
35
+ export declare const rgbAlpha: ColorFn;
36
+ export declare const INSTRUMENT_SANS: {
37
+ regular: "InstrumentSans_400Regular";
38
+ medium: "InstrumentSans_500Medium";
39
+ semibold: "InstrumentSans_600SemiBold";
40
+ bold: "InstrumentSans_700Bold";
41
+ };
42
+ export declare function nativeUiPreset(options?: NativeUiPresetOptions): NativeUiPreset;
43
+ /** Absolute glob of this package's `src/**\/*.{js,ts,tsx}`, for Tailwind `content`. */
44
+ export declare function nativeUiContent(): string;
45
+ /** CSS variable name → `{ light, dark }` hex, from `tokens.ts` and `palette.ts`. */
46
+ export declare function schemeColors(): Record<string, { light: string; dark: string }>;
47
+ /** `{ "--name": "r g b" }` for one scheme. */
48
+ export declare function cssVariables(scheme: "light" | "dark"): CssDeclarations;
49
+ declare const preset: NativeUiPreset;
50
+ export default preset;