@mrmeg/expo-ui 0.27.1 → 0.29.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.
- package/CHANGELOG.md +666 -0
- package/LLM_USAGE.md +241 -37
- package/README.md +591 -136
- package/bin/expo-ui-icons.mjs +509 -0
- package/dist/components/Accordion.js +12 -6
- package/dist/components/AnimatedView.d.ts +1 -1
- package/dist/components/AnimatedView.js +5 -2
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +2 -2
- package/dist/components/Badge.js +5 -1
- package/dist/components/BottomSheet.d.ts +29 -1
- package/dist/components/BottomSheet.js +107 -30
- package/dist/components/Button.d.ts +20 -4
- package/dist/components/Button.js +60 -43
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +24 -4
- package/dist/components/Carousel.js +2 -2
- package/dist/components/Checkbox.js +51 -44
- package/dist/components/Collapsible.js +7 -3
- package/dist/components/Dialog.js +87 -16
- package/dist/components/Drawer.d.ts +1 -1
- package/dist/components/Drawer.js +65 -55
- package/dist/components/DropdownMenu.js +11 -7
- package/dist/components/EmptyState.d.ts +2 -2
- package/dist/components/EmptyState.js +5 -2
- package/dist/components/Hydrated.d.ts +31 -0
- package/dist/components/Hydrated.js +30 -0
- package/dist/components/Icon.d.ts +71 -28
- package/dist/components/Icon.js +70 -28
- package/dist/components/IconGlyph.d.ts +15 -0
- package/dist/components/IconGlyph.js +32 -0
- package/dist/components/IconGlyph.native.d.ts +10 -0
- package/dist/components/IconGlyph.native.js +30 -0
- package/dist/components/InputOTP.js +5 -2
- package/dist/components/Item.d.ts +103 -4
- package/dist/components/Item.js +168 -15
- package/dist/components/KeyboardAvoidingView.d.ts +1 -1
- package/dist/components/KeyboardAvoidingView.js +4 -1
- package/dist/components/Label.js +3 -2
- package/dist/components/MaxWidthContainer.js +1 -1
- package/dist/components/Notification.js +45 -27
- package/dist/components/Popover.d.ts +18 -5
- package/dist/components/Popover.js +72 -8
- package/dist/components/Progress.js +8 -6
- package/dist/components/RadioGroup.js +52 -49
- package/dist/components/Screen.d.ts +61 -0
- package/dist/components/Screen.js +62 -0
- package/dist/components/SectionHeader.d.ts +1 -1
- package/dist/components/SegmentedControl.d.ts +12 -4
- package/dist/components/SegmentedControl.js +94 -4
- package/dist/components/Select.js +21 -26
- package/dist/components/Skeleton.js +24 -10
- package/dist/components/Slider.d.ts +11 -3
- package/dist/components/Slider.js +73 -6
- package/dist/components/StatCard.d.ts +4 -2
- package/dist/components/StatCard.js +3 -1
- package/dist/components/StyledText.d.ts +7 -1
- package/dist/components/StyledText.js +5 -3
- package/dist/components/Switch.d.ts +1 -1
- package/dist/components/Switch.js +20 -30
- package/dist/components/Tabs.d.ts +2 -2
- package/dist/components/Tabs.js +13 -24
- package/dist/components/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +71 -26
- package/dist/components/ThemeSelector.d.ts +52 -0
- package/dist/components/ThemeSelector.js +90 -0
- package/dist/components/Toggle.d.ts +5 -49
- package/dist/components/Toggle.js +53 -29
- package/dist/components/ToggleGroup.d.ts +4 -8
- package/dist/components/ToggleGroup.js +52 -34
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +9 -3
- package/dist/components/UIProvider.d.ts +22 -2
- package/dist/components/UIProvider.js +15 -3
- package/dist/components/bottomSheetDismiss.d.ts +19 -0
- package/dist/components/bottomSheetDismiss.js +44 -0
- package/dist/components/iconBuiltins.generated.d.ts +143 -0
- package/dist/components/iconBuiltins.generated.js +25 -0
- package/dist/components/iconTypes.d.ts +37 -0
- package/dist/components/iconTypes.js +1 -0
- package/dist/components/index.d.ts +47 -44
- package/dist/components/index.js +3 -0
- package/dist/components/keyboardDismiss.js +12 -4
- package/dist/components/nativeTextField.android.js +13 -9
- package/dist/constants/colors.d.ts +40 -5
- package/dist/constants/colors.js +26 -8
- package/dist/constants/fonts.d.ts +41 -2
- package/dist/constants/fonts.js +46 -7
- package/dist/constants/index.d.ts +5 -4
- package/dist/constants/index.js +1 -0
- package/dist/constants/interaction.d.ts +18 -0
- package/dist/constants/interaction.js +18 -0
- package/dist/constants/spacing.d.ts +2 -1
- package/dist/constants/spacing.js +2 -1
- package/dist/design-system.json +429 -132
- package/dist/hooks/index.d.ts +12 -7
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/useDimensions.d.ts +13 -8
- package/dist/hooks/useDimensions.js +123 -48
- package/dist/hooks/useFocusVisible.d.ts +30 -0
- package/dist/hooks/useFocusVisible.js +45 -0
- package/dist/hooks/useFontStyle.d.ts +2 -2
- package/dist/hooks/useFontStyle.js +3 -2
- package/dist/hooks/useHydrated.d.ts +13 -0
- package/dist/hooks/useHydrated.js +19 -0
- package/dist/hooks/useNotificationOffset.d.ts +20 -0
- package/dist/hooks/useNotificationOffset.js +34 -0
- package/dist/hooks/useResources.d.ts +21 -20
- package/dist/hooks/useResources.js +72 -68
- package/dist/hooks/useScalePress.d.ts +8 -5
- package/dist/hooks/useScalePress.js +16 -9
- package/dist/hooks/useShape.d.ts +24 -0
- package/dist/hooks/useShape.js +24 -0
- package/dist/hooks/useStaggeredEntrance.js +9 -7
- package/dist/hooks/useTheme.d.ts +3 -3
- package/dist/hooks/useTheme.js +177 -66
- package/dist/hooks/useWindowInsets.d.ts +13 -0
- package/dist/hooks/useWindowInsets.js +23 -0
- package/dist/icons/LICENSE.lucide +43 -0
- package/dist/icons/lucide.json +2144 -0
- package/dist/index.d.ts +5 -5
- package/dist/lib/composeRefs.d.ts +8 -0
- package/dist/lib/composeRefs.js +20 -0
- package/dist/lib/haptics.d.ts +12 -0
- package/dist/lib/haptics.js +21 -0
- package/dist/lib/index.d.ts +6 -5
- package/dist/lib/index.js +1 -0
- package/dist/lib/interFonts.d.ts +15 -0
- package/dist/lib/interFonts.js +1 -0
- package/dist/lib/interFonts.native.d.ts +3 -0
- package/dist/lib/interFonts.native.js +20 -0
- package/dist/lib/popoverPlacement.d.ts +36 -0
- package/dist/lib/popoverPlacement.js +29 -0
- package/dist/lib/themedStyles.d.ts +1 -1
- package/dist/lib/useAnimatedValue.d.ts +15 -0
- package/dist/lib/useAnimatedValue.js +19 -0
- package/dist/lib/webHitTarget.d.ts +40 -0
- package/dist/lib/webHitTarget.js +37 -0
- package/dist/state/feedbackStore.d.ts +24 -0
- package/dist/state/feedbackStore.js +16 -0
- package/dist/state/globalUIStore.d.ts +27 -0
- package/dist/state/globalUIStore.js +23 -0
- package/dist/state/index.d.ts +7 -5
- package/dist/state/index.js +2 -0
- package/dist/state/initialScheme.d.ts +27 -0
- package/dist/state/initialScheme.js +29 -0
- package/dist/state/notify.d.ts +1 -1
- package/dist/state/themeColorScope.d.ts +1 -1
- package/dist/state/themeStore.d.ts +69 -16
- package/dist/state/themeStore.js +65 -38
- package/llms-full.md +59 -10
- package/llms.txt +10 -1
- package/package.json +79 -4
- package/dist/components/iconRegistry.generated.d.ts +0 -148
- package/dist/components/iconRegistry.generated.js +0 -296
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* expo-ui-icons — compile an app's icon list into glyph data for
|
|
4
|
+
* `@mrmeg/expo-ui`'s `Icon`.
|
|
5
|
+
*
|
|
6
|
+
* `Icon` draws plain glyph data with renderers the package owns. The package
|
|
7
|
+
* registers the names its own components use; an app lists every other Lucide
|
|
8
|
+
* name it draws in `client/icons.json` and this CLI writes one module with
|
|
9
|
+
* those glyphs (`appIcons`) and the `IconRegistry` augmentation that types
|
|
10
|
+
* them. The root layout calls `registerIcons(appIcons)` once at module scope.
|
|
11
|
+
* `tabs` rasterizes the `tabIcons` subset to template PNGs for a native tab
|
|
12
|
+
* bar.
|
|
13
|
+
*
|
|
14
|
+
* The glyphs come from the Lucide snapshot the package ships
|
|
15
|
+
* (`dist/icons/lucide.json`, ISC, see `dist/icons/LICENSE.lucide`), so adding
|
|
16
|
+
* a name needs no package release. Node >= 20, no dependencies.
|
|
17
|
+
*
|
|
18
|
+
* Usage: expo-ui-icons [generate|add|init|tabs] [options]; see --help.
|
|
19
|
+
*/
|
|
20
|
+
import { execFileSync } from "node:child_process";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
const USAGE = `
|
|
28
|
+
Usage:
|
|
29
|
+
expo-ui-icons [generate] [--check] Write the glyph module from the manifest
|
|
30
|
+
expo-ui-icons add <name...> Add Lucide names to the manifest and regenerate
|
|
31
|
+
expo-ui-icons init [--force] Seed the manifest from names used in app/, client/, shared/
|
|
32
|
+
expo-ui-icons tabs [--check] Rasterize tabIcons to template PNGs (needs rsvg-convert)
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--manifest <path> Icon list (default client/icons.json): { "icons": [...], "tabIcons": [...] }
|
|
36
|
+
--out <path> Generated glyph module (default client/icons.generated.ts)
|
|
37
|
+
--module <spec> Module to import types from and augment (default @mrmeg/expo-ui/components/Icon)
|
|
38
|
+
--assets <dir> Tab icon PNGs and manifest.json (default assets/icons/tabs)
|
|
39
|
+
--tabs-out <path> Generated tab icon module (default client/tabIcons.generated.ts)
|
|
40
|
+
--check Exit 1 if the output is missing or stale; write nothing
|
|
41
|
+
--force Let init overwrite an existing manifest
|
|
42
|
+
-h, --help Usage info
|
|
43
|
+
|
|
44
|
+
Environment:
|
|
45
|
+
RSVG_CONVERT Path to rsvg-convert; when set, the only rasterizer tried
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const COMMANDS = new Set(["generate", "add", "init", "tabs"]);
|
|
49
|
+
const VALUE_OPTIONS = new Set(["--manifest", "--out", "--module", "--assets", "--tabs-out"]);
|
|
50
|
+
const FLAG_OPTIONS = new Set(["--check", "--force", "-h", "--help"]);
|
|
51
|
+
|
|
52
|
+
class CliError extends Error {}
|
|
53
|
+
|
|
54
|
+
const count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
55
|
+
|
|
56
|
+
function fail(message) {
|
|
57
|
+
throw new CliError(message);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Paths in messages and headers: relative to the working directory, `/`-separated. */
|
|
61
|
+
function label(path) {
|
|
62
|
+
return relative(process.cwd(), path).split(sep).join("/") || ".";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseArgs(argv) {
|
|
66
|
+
const options = {
|
|
67
|
+
manifest: "client/icons.json",
|
|
68
|
+
out: "client/icons.generated.ts",
|
|
69
|
+
module: "@mrmeg/expo-ui/components/Icon",
|
|
70
|
+
assets: "assets/icons/tabs",
|
|
71
|
+
"tabs-out": "client/tabIcons.generated.ts",
|
|
72
|
+
};
|
|
73
|
+
const flags = new Set();
|
|
74
|
+
const positional = [];
|
|
75
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
76
|
+
const arg = argv[index];
|
|
77
|
+
const [key, inlineValue] = arg.startsWith("--") && arg.includes("=") ? [arg.slice(0, arg.indexOf("=")), arg.slice(arg.indexOf("=") + 1)] : [arg, undefined];
|
|
78
|
+
if (VALUE_OPTIONS.has(key)) {
|
|
79
|
+
const value = inlineValue ?? argv[(index += 1)];
|
|
80
|
+
if (!value || value.startsWith("--")) fail(`${key} needs a value`);
|
|
81
|
+
options[key.slice(2)] = value;
|
|
82
|
+
} else if (FLAG_OPTIONS.has(key) && inlineValue === undefined) {
|
|
83
|
+
flags.add(key);
|
|
84
|
+
} else if (arg.startsWith("-")) {
|
|
85
|
+
fail(`unknown option ${arg}`);
|
|
86
|
+
} else {
|
|
87
|
+
positional.push(arg);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const command = COMMANDS.has(positional[0]) ? positional.shift() : "generate";
|
|
91
|
+
return { command, names: positional, options, flags };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- Snapshot ------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The published package ships `dist/icons/lucide.json`. In the package's own
|
|
100
|
+
* repository `src/icons/lucide.json` is the source of truth (a `dist` there may
|
|
101
|
+
* predate a snapshot bump), so it wins when present.
|
|
102
|
+
*/
|
|
103
|
+
function loadSnapshot() {
|
|
104
|
+
const candidates = [join(binDir, "..", "src", "icons", "lucide.json"), join(binDir, "..", "dist", "icons", "lucide.json")];
|
|
105
|
+
const path = candidates.find((candidate) => existsSync(candidate));
|
|
106
|
+
if (!path) fail(`no Lucide snapshot next to this CLI (looked for ${candidates.join(", ")}); reinstall @mrmeg/expo-ui`);
|
|
107
|
+
const snapshot = JSON.parse(readFileSync(path, "utf8"));
|
|
108
|
+
if (typeof snapshot.lucide !== "string" || !snapshot.icons || !snapshot.aliases) fail(`${path} is not a Lucide snapshot`);
|
|
109
|
+
return snapshot;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Manifest ------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
const KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
115
|
+
|
|
116
|
+
function readManifest(path, { allowMissing = false } = {}) {
|
|
117
|
+
if (!existsSync(path)) {
|
|
118
|
+
if (allowMissing) return { icons: [], tabIcons: [] };
|
|
119
|
+
fail(`${label(path)} not found. Run \`expo-ui-icons init\` to seed it, or \`expo-ui-icons add <name>\`.`);
|
|
120
|
+
}
|
|
121
|
+
let manifest;
|
|
122
|
+
try {
|
|
123
|
+
manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
fail(`${label(path)} is not valid JSON: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) fail(`${label(path)} must be an object: { "icons": [...], "tabIcons": [...] }`);
|
|
128
|
+
const extra = Object.keys(manifest).filter((key) => key !== "icons" && key !== "tabIcons");
|
|
129
|
+
if (extra.length > 0) fail(`${label(path)} has unknown key(s) ${extra.join(", ")}; expected "icons" and "tabIcons"`);
|
|
130
|
+
const icons = manifest.icons ?? [];
|
|
131
|
+
const tabIcons = manifest.tabIcons ?? [];
|
|
132
|
+
for (const [key, list] of [["icons", icons], ["tabIcons", tabIcons]]) {
|
|
133
|
+
if (!Array.isArray(list) || list.some((name) => typeof name !== "string")) fail(`${label(path)}: "${key}" must be an array of strings`);
|
|
134
|
+
}
|
|
135
|
+
return { icons, tabIcons };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Every problem with one name list: shape, order, duplicates, and names outside the snapshot. */
|
|
139
|
+
function listProblems(list, key, snapshot) {
|
|
140
|
+
const problems = [];
|
|
141
|
+
const duplicates = [...new Set(list.filter((name, index) => list.indexOf(name) !== index))];
|
|
142
|
+
if (duplicates.length > 0) problems.push(`"${key}" lists the same name twice: ${duplicates.join(", ")}`);
|
|
143
|
+
const sorted = [...list].sort();
|
|
144
|
+
const firstOutOfOrder = list.find((name, index) => name !== sorted[index]);
|
|
145
|
+
if (firstOutOfOrder !== undefined) problems.push(`"${key}" must be sorted (first out-of-order entry: "${firstOutOfOrder}")`);
|
|
146
|
+
for (const name of list) {
|
|
147
|
+
if (name in snapshot.icons) continue;
|
|
148
|
+
if (snapshot.aliases[name]) {
|
|
149
|
+
problems.push(`"${name}" is an alias; use the canonical name: ${name} → ${snapshot.aliases[name]}`);
|
|
150
|
+
} else if (!KEBAB.test(name)) {
|
|
151
|
+
problems.push(`"${name}" is not a kebab-case icon name`);
|
|
152
|
+
} else {
|
|
153
|
+
problems.push(`"${name}" is not a Lucide ${snapshot.lucide} icon (search https://lucide.dev/icons)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return problems;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validateManifest(manifest, path, snapshot) {
|
|
160
|
+
const problems = [...listProblems(manifest.icons, "icons", snapshot), ...listProblems(manifest.tabIcons, "tabIcons", snapshot)];
|
|
161
|
+
if (problems.length > 0) fail(`${label(path)}:\n ${problems.join("\n ")}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function writeManifest(path, manifest) {
|
|
165
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
166
|
+
writeFileSync(path, `${JSON.stringify({ icons: manifest.icons, tabIcons: manifest.tabIcons }, null, 2)}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// --- Glyph module ----------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
172
|
+
const propertyKey = (key) => (IDENTIFIER.test(key) ? key : JSON.stringify(key));
|
|
173
|
+
|
|
174
|
+
/** One glyph as a TypeScript literal: `[["path", { d: "…" }], …]`. */
|
|
175
|
+
function glyphLiteral(glyph) {
|
|
176
|
+
const nodes = glyph.map(([tag, attrs]) => {
|
|
177
|
+
const fields = Object.entries(attrs).map(([key, value]) => `${propertyKey(key)}: ${JSON.stringify(value)}`);
|
|
178
|
+
return `[${JSON.stringify(tag)}, { ${fields.join(", ")} }]`;
|
|
179
|
+
});
|
|
180
|
+
return `[${nodes.join(", ")}]`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderGlyphModule(manifest, manifestPath, moduleName, snapshot) {
|
|
184
|
+
return [
|
|
185
|
+
"/* eslint-disable */",
|
|
186
|
+
`// GENERATED by \`bun run icons\` from ${label(manifestPath)} (lucide ${snapshot.lucide}). Do not edit.`,
|
|
187
|
+
`import type { IconGlyph } from ${JSON.stringify(moduleName)};`,
|
|
188
|
+
"",
|
|
189
|
+
"export const appIcons = {",
|
|
190
|
+
...manifest.icons.map((name) => ` ${JSON.stringify(name)}: ${glyphLiteral(snapshot.icons[name])},`),
|
|
191
|
+
"} as const satisfies Record<string, IconGlyph>;",
|
|
192
|
+
"",
|
|
193
|
+
`declare module ${JSON.stringify(moduleName)} {`,
|
|
194
|
+
" interface IconRegistry {",
|
|
195
|
+
...manifest.icons.map((name) => ` ${JSON.stringify(name)}: true;`),
|
|
196
|
+
" }",
|
|
197
|
+
"}",
|
|
198
|
+
"",
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function generate({ options, flags }, snapshot) {
|
|
203
|
+
const manifestPath = resolve(options.manifest);
|
|
204
|
+
const outPath = resolve(options.out);
|
|
205
|
+
const manifest = readManifest(manifestPath);
|
|
206
|
+
validateManifest(manifest, manifestPath, snapshot);
|
|
207
|
+
const content = renderGlyphModule(manifest, manifestPath, options.module, snapshot);
|
|
208
|
+
const current = existsSync(outPath) ? readFileSync(outPath, "utf8") : null;
|
|
209
|
+
const summary = `${count(manifest.icons.length, "icon")}, lucide ${snapshot.lucide}`;
|
|
210
|
+
|
|
211
|
+
if (flags.has("--check")) {
|
|
212
|
+
if (current === content) {
|
|
213
|
+
console.log(`${label(outPath)} is up to date (${summary}).`);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
console.error(
|
|
217
|
+
`expo-ui-icons: ${label(outPath)} is ${current === null ? "missing" : "stale"}. Run \`bun run icons\` and commit the result.`
|
|
218
|
+
);
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
if (current === content) {
|
|
222
|
+
console.log(`${label(outPath)} already up to date (${summary}).`);
|
|
223
|
+
} else {
|
|
224
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
225
|
+
writeFileSync(outPath, content);
|
|
226
|
+
console.log(`Wrote ${label(outPath)} (${summary}).`);
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// --- add -------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
function add(args, snapshot) {
|
|
234
|
+
if (args.names.length === 0) fail("add needs at least one icon name: expo-ui-icons add archive bell");
|
|
235
|
+
const manifestPath = resolve(args.options.manifest);
|
|
236
|
+
const manifest = readManifest(manifestPath, { allowMissing: true });
|
|
237
|
+
validateManifest(manifest, manifestPath, snapshot);
|
|
238
|
+
|
|
239
|
+
const problems = [];
|
|
240
|
+
const added = [];
|
|
241
|
+
for (const requested of args.names) {
|
|
242
|
+
let name = requested;
|
|
243
|
+
if (!(name in snapshot.icons)) {
|
|
244
|
+
if (snapshot.aliases[name]) {
|
|
245
|
+
name = snapshot.aliases[name];
|
|
246
|
+
console.log(`"${requested}" is an alias: adding the canonical name (${requested} → ${name}).`);
|
|
247
|
+
} else {
|
|
248
|
+
problems.push(`"${requested}" is not a Lucide ${snapshot.lucide} icon (search https://lucide.dev/icons)`);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!manifest.icons.includes(name) && !added.includes(name)) added.push(name);
|
|
253
|
+
}
|
|
254
|
+
if (problems.length > 0) fail(problems.join("\n "));
|
|
255
|
+
|
|
256
|
+
manifest.icons = [...manifest.icons, ...added].sort();
|
|
257
|
+
writeManifest(manifestPath, manifest);
|
|
258
|
+
console.log(added.length > 0 ? `Added ${added.join(", ")} to ${label(manifestPath)}.` : `${label(manifestPath)} already lists them.`);
|
|
259
|
+
return generate({ ...args, flags: new Set() }, snapshot);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// --- init ------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
const SCAN_DIRS = ["app", "client", "shared"];
|
|
265
|
+
const SCAN_EXTENSIONS = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
266
|
+
const SKIP_FILES = /(\.test\.|\.spec\.|\.generated\.ts$)/;
|
|
267
|
+
const SKIP_DIRS = new Set(["node_modules", "__tests__", "__mocks__"]);
|
|
268
|
+
const ICON_LITERAL = /\b(name|icon)\s*[=:]\s*\{?\s*"([a-z0-9-]+)"/g;
|
|
269
|
+
|
|
270
|
+
function sourceFiles(directory) {
|
|
271
|
+
if (!existsSync(directory)) return [];
|
|
272
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
273
|
+
const path = join(directory, entry.name);
|
|
274
|
+
if (entry.isDirectory()) return SKIP_DIRS.has(entry.name) || entry.name.startsWith(".") ? [] : sourceFiles(path);
|
|
275
|
+
return SCAN_EXTENSIONS.test(entry.name) && !SKIP_FILES.test(entry.name) ? [path] : [];
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function init(args, snapshot) {
|
|
280
|
+
const manifestPath = resolve(args.options.manifest);
|
|
281
|
+
let tabIcons = [];
|
|
282
|
+
if (existsSync(manifestPath)) {
|
|
283
|
+
if (!args.flags.has("--force")) fail(`${label(manifestPath)} already exists; pass --force to overwrite it.`);
|
|
284
|
+
try {
|
|
285
|
+
tabIcons = readManifest(manifestPath).tabIcons;
|
|
286
|
+
} catch {
|
|
287
|
+
tabIcons = [];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const files = SCAN_DIRS.flatMap((dir) => sourceFiles(resolve(dir)));
|
|
292
|
+
const found = new Set();
|
|
293
|
+
const aliasHits = new Map();
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
for (const match of readFileSync(file, "utf8").matchAll(ICON_LITERAL)) {
|
|
296
|
+
const name = match[2];
|
|
297
|
+
if (name in snapshot.icons) found.add(name);
|
|
298
|
+
else if (snapshot.aliases[name]) aliasHits.set(name, snapshot.aliases[name]);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// An overwritten manifest keeps its tab icons, which must also be in "icons".
|
|
303
|
+
const manifest = { icons: [...new Set([...found, ...tabIcons])].sort(), tabIcons };
|
|
304
|
+
writeManifest(manifestPath, manifest);
|
|
305
|
+
console.log(`Seeded ${label(manifestPath)} with ${count(manifest.icons.length, "name")} found in ${count(files.length, "file")} under ${SCAN_DIRS.join("/, ")}/.`);
|
|
306
|
+
if (aliasHits.size > 0) {
|
|
307
|
+
console.log("Skipped alias names (if they are icons, use the canonical name and `expo-ui-icons add` it):");
|
|
308
|
+
for (const [alias, canonical] of aliasHits) console.log(` ${alias} → ${canonical}`);
|
|
309
|
+
}
|
|
310
|
+
const status = generate({ ...args, flags: new Set() }, snapshot);
|
|
311
|
+
console.log(
|
|
312
|
+
"Next: typecheck and `expo-ui-icons add` any name it reports (names built at runtime are not found by the scan), " +
|
|
313
|
+
"and call `registerIcons(appIcons)` at module scope in the root layout."
|
|
314
|
+
);
|
|
315
|
+
return status;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// --- tabs ------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
const TAB_SCALES = [
|
|
321
|
+
["", 24],
|
|
322
|
+
["@2x", 48],
|
|
323
|
+
["@3x", 72],
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
327
|
+
|
|
328
|
+
/** A glyph as a standalone SVG document, black on transparent (tab bars tint it). */
|
|
329
|
+
function glyphSvg(glyph) {
|
|
330
|
+
const children = glyph
|
|
331
|
+
.map(([tag, attrs]) => `<${tag}${Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeXml(value)}"`).join("")}/>`)
|
|
332
|
+
.join("");
|
|
333
|
+
return (
|
|
334
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" ' +
|
|
335
|
+
`stroke-linecap="round" stroke-linejoin="round">${children}</svg>`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
340
|
+
|
|
341
|
+
function renderTabModule(names, tabsOutPath, assetsDir, manifestPath, snapshot) {
|
|
342
|
+
let assetsSpecifier = relative(dirname(tabsOutPath), assetsDir).split(sep).join("/");
|
|
343
|
+
if (!assetsSpecifier.startsWith(".")) assetsSpecifier = `./${assetsSpecifier}`;
|
|
344
|
+
return [
|
|
345
|
+
"/* eslint-disable */",
|
|
346
|
+
`// GENERATED by \`bun run icons:tabs\` from ${label(manifestPath)} (lucide ${snapshot.lucide}). Do not edit.`,
|
|
347
|
+
"// Template PNGs of the tabIcons glyphs for a native tab bar, which tints them.",
|
|
348
|
+
"",
|
|
349
|
+
"export const TAB_ICONS = {",
|
|
350
|
+
...names.map((name) => ` ${propertyKey(name)}: require(${JSON.stringify(`${assetsSpecifier}/${name}.png`)}),`),
|
|
351
|
+
"} as const;",
|
|
352
|
+
"",
|
|
353
|
+
"export type TabIconName = keyof typeof TAB_ICONS;",
|
|
354
|
+
"",
|
|
355
|
+
].join("\n");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Explicit `RSVG_CONVERT` is authoritative; otherwise PATH and the Homebrew
|
|
360
|
+
* prefixes, like the template's brand-asset script.
|
|
361
|
+
*/
|
|
362
|
+
function findRasterizer() {
|
|
363
|
+
const candidates = process.env.RSVG_CONVERT
|
|
364
|
+
? [process.env.RSVG_CONVERT]
|
|
365
|
+
: ["rsvg-convert", "/opt/homebrew/bin/rsvg-convert", "/usr/local/bin/rsvg-convert"];
|
|
366
|
+
for (const candidate of candidates) {
|
|
367
|
+
try {
|
|
368
|
+
execFileSync(candidate, ["--version"], { stdio: "ignore" });
|
|
369
|
+
return candidate;
|
|
370
|
+
} catch {
|
|
371
|
+
// try the next one
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function tabs({ options, flags }, snapshot) {
|
|
378
|
+
const manifestPath = resolve(options.manifest);
|
|
379
|
+
const assetsDir = resolve(options.assets);
|
|
380
|
+
const tabsOutPath = resolve(options["tabs-out"]);
|
|
381
|
+
const assetManifestPath = join(assetsDir, "manifest.json");
|
|
382
|
+
const manifest = readManifest(manifestPath);
|
|
383
|
+
validateManifest(manifest, manifestPath, snapshot);
|
|
384
|
+
|
|
385
|
+
const notListed = manifest.tabIcons.filter((name) => !manifest.icons.includes(name));
|
|
386
|
+
if (notListed.length > 0) {
|
|
387
|
+
fail(`${label(manifestPath)}: every "tabIcons" name must also be in "icons" (missing: ${notListed.join(", ")})`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const svgs = Object.fromEntries(manifest.tabIcons.map((name) => [name, glyphSvg(snapshot.icons[name])]));
|
|
391
|
+
const expectedAssetManifest = `${JSON.stringify(
|
|
392
|
+
{ lucide: snapshot.lucide, icons: Object.fromEntries(manifest.tabIcons.map((name) => [name, sha256(svgs[name])])) },
|
|
393
|
+
null,
|
|
394
|
+
2
|
|
395
|
+
)}\n`;
|
|
396
|
+
const expectedModule = renderTabModule(manifest.tabIcons, tabsOutPath, assetsDir, manifestPath, snapshot);
|
|
397
|
+
|
|
398
|
+
// Freshness from hashes of the SVG source, never PNG bytes: rasterizers
|
|
399
|
+
// differ by version and platform, and CI has none.
|
|
400
|
+
const pngProblems = [];
|
|
401
|
+
const currentAssetManifest = existsSync(assetManifestPath) ? readFileSync(assetManifestPath, "utf8") : null;
|
|
402
|
+
let recorded = null;
|
|
403
|
+
try {
|
|
404
|
+
recorded = currentAssetManifest === null ? null : JSON.parse(currentAssetManifest);
|
|
405
|
+
} catch {
|
|
406
|
+
pngProblems.push(`${label(assetManifestPath)} is not valid JSON`);
|
|
407
|
+
}
|
|
408
|
+
if (currentAssetManifest === null) {
|
|
409
|
+
pngProblems.push(`${label(assetManifestPath)} is missing`);
|
|
410
|
+
} else if (recorded) {
|
|
411
|
+
if (recorded.lucide !== snapshot.lucide) {
|
|
412
|
+
pngProblems.push(`${label(assetManifestPath)} was rendered from lucide ${recorded.lucide}, the snapshot is ${snapshot.lucide}`);
|
|
413
|
+
}
|
|
414
|
+
const recordedIcons = recorded.icons ?? {};
|
|
415
|
+
for (const name of manifest.tabIcons) {
|
|
416
|
+
if (recordedIcons[name] !== sha256(svgs[name])) pngProblems.push(`${name}: hash mismatch (glyph changed or manifest edited)`);
|
|
417
|
+
}
|
|
418
|
+
for (const name of Object.keys(recordedIcons)) {
|
|
419
|
+
if (!manifest.tabIcons.includes(name)) pngProblems.push(`${name}: rendered but no longer in "tabIcons"`);
|
|
420
|
+
}
|
|
421
|
+
if (pngProblems.length === 0 && currentAssetManifest !== expectedAssetManifest) {
|
|
422
|
+
pngProblems.push(`${label(assetManifestPath)} is not in its generated form`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (const name of manifest.tabIcons) {
|
|
426
|
+
for (const [suffix] of TAB_SCALES) {
|
|
427
|
+
const png = join(assetsDir, `${name}${suffix}.png`);
|
|
428
|
+
if (!existsSync(png)) pngProblems.push(`${label(png)} is missing`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const currentModule = existsSync(tabsOutPath) ? readFileSync(tabsOutPath, "utf8") : null;
|
|
432
|
+
const moduleStale = currentModule !== expectedModule;
|
|
433
|
+
const summary = `${count(manifest.tabIcons.length, "tab icon")}, lucide ${snapshot.lucide}`;
|
|
434
|
+
|
|
435
|
+
if (flags.has("--check")) {
|
|
436
|
+
if (pngProblems.length === 0 && !moduleStale) {
|
|
437
|
+
console.log(`Tab icons are up to date (${summary}).`);
|
|
438
|
+
return 0;
|
|
439
|
+
}
|
|
440
|
+
const problems = [...pngProblems];
|
|
441
|
+
if (moduleStale) problems.push(`${label(tabsOutPath)} is ${currentModule === null ? "missing" : "stale"}`);
|
|
442
|
+
console.error(`expo-ui-icons: tab icons are stale:\n ${problems.join("\n ")}\nRun \`bun run icons:tabs\` and commit the result.`);
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (pngProblems.length === 0 && !moduleStale) {
|
|
447
|
+
console.log(`Tab icons are up to date (${summary}); nothing to rasterize.`);
|
|
448
|
+
return 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (pngProblems.length > 0) {
|
|
452
|
+
const rasterizer = findRasterizer();
|
|
453
|
+
if (!rasterizer) {
|
|
454
|
+
fail(
|
|
455
|
+
`tab icons need rasterizing (${pngProblems[0]}${pngProblems.length > 1 ? `, and ${pngProblems.length - 1} more` : ""}), ` +
|
|
456
|
+
"but rsvg-convert was not found. Install librsvg (brew install librsvg) or set RSVG_CONVERT."
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
mkdirSync(assetsDir, { recursive: true });
|
|
460
|
+
const scratch = mkdtempSync(join(tmpdir(), "expo-ui-icons-"));
|
|
461
|
+
try {
|
|
462
|
+
for (const name of manifest.tabIcons) {
|
|
463
|
+
const svgPath = join(scratch, `${name}.svg`);
|
|
464
|
+
writeFileSync(svgPath, svgs[name]);
|
|
465
|
+
for (const [suffix, px] of TAB_SCALES) {
|
|
466
|
+
execFileSync(rasterizer, ["-w", String(px), "-h", String(px), "-o", join(assetsDir, `${name}${suffix}.png`), svgPath], {
|
|
467
|
+
stdio: "inherit",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
console.log(`Rendered ${name} at ${TAB_SCALES.map(([, px]) => `${px}px`).join(", ")}`);
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
474
|
+
}
|
|
475
|
+
for (const name of Object.keys(recorded?.icons ?? {})) {
|
|
476
|
+
if (manifest.tabIcons.includes(name)) continue;
|
|
477
|
+
for (const [suffix] of TAB_SCALES) rmSync(join(assetsDir, `${name}${suffix}.png`), { force: true });
|
|
478
|
+
console.log(`Removed ${name} PNGs (no longer in "tabIcons")`);
|
|
479
|
+
}
|
|
480
|
+
writeFileSync(assetManifestPath, expectedAssetManifest);
|
|
481
|
+
console.log(`Wrote ${label(assetManifestPath)}`);
|
|
482
|
+
}
|
|
483
|
+
if (moduleStale) {
|
|
484
|
+
mkdirSync(dirname(tabsOutPath), { recursive: true });
|
|
485
|
+
writeFileSync(tabsOutPath, expectedModule);
|
|
486
|
+
console.log(`Wrote ${label(tabsOutPath)} (${summary}).`);
|
|
487
|
+
}
|
|
488
|
+
return 0;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// --- main ------------------------------------------------------------------------
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const args = parseArgs(process.argv.slice(2));
|
|
495
|
+
if (args.flags.has("-h") || args.flags.has("--help")) {
|
|
496
|
+
console.log(USAGE);
|
|
497
|
+
process.exit(0);
|
|
498
|
+
}
|
|
499
|
+
if (args.command !== "add" && args.names.length > 0) fail(`unexpected argument(s) ${args.names.join(" ")}`);
|
|
500
|
+
if (args.flags.has("--check") && (args.command === "add" || args.command === "init")) fail(`--check does not apply to ${args.command}`);
|
|
501
|
+
const snapshot = loadSnapshot();
|
|
502
|
+
const handlers = { generate, add, init, tabs };
|
|
503
|
+
process.exit(handlers[args.command](args, snapshot));
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!(error instanceof CliError)) throw error;
|
|
506
|
+
console.error(`expo-ui-icons: ${error.message}`);
|
|
507
|
+
console.error("Run expo-ui-icons --help for usage.");
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
3
|
import { Animated, Platform, Pressable, View } from "react-native";
|
|
4
4
|
import { Icon } from "./Icon.js";
|
|
5
|
-
import { TextClassContext, TextSelectabilityContext } from "./StyledText.context";
|
|
5
|
+
import { TextClassContext, TextSelectabilityContext } from "./StyledText.context.js";
|
|
6
6
|
import { useTheme } from "../hooks/useTheme.js";
|
|
7
7
|
import { useReducedMotion } from "../hooks/useReduceMotion.js";
|
|
8
8
|
import { useScalePress } from "../hooks/useScalePress.js";
|
|
9
|
+
import { useFocusVisible } from "../hooks/useFocusVisible.js";
|
|
10
|
+
import { useAnimatedValue } from "../lib/useAnimatedValue.js";
|
|
9
11
|
import { spacing } from "../constants/spacing.js";
|
|
12
|
+
import { shouldUseNativeDriver } from "../lib/animations.js";
|
|
10
13
|
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
11
14
|
function normalizeSingleValue(value) {
|
|
12
15
|
return value ?? "";
|
|
@@ -98,21 +101,22 @@ const Trigger = Platform.OS === "web" ? View : Pressable;
|
|
|
98
101
|
* Includes animated chevron icon
|
|
99
102
|
*/
|
|
100
103
|
function AccordionTrigger({ children, style: styleOverride, disabled, ...props }) {
|
|
101
|
-
const { theme } = useTheme();
|
|
104
|
+
const { theme, getFocusRingStyle } = useTheme();
|
|
102
105
|
const reduceMotion = useReducedMotion();
|
|
103
106
|
const { isExpanded } = AccordionPrimitive.useItemContext();
|
|
104
|
-
const rotation =
|
|
107
|
+
const rotation = useAnimatedValue(isExpanded ? 1 : 0);
|
|
105
108
|
const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
|
|
106
109
|
disabled: !!disabled,
|
|
107
110
|
scaleTo: 0.97,
|
|
108
111
|
haptic: false,
|
|
109
112
|
});
|
|
113
|
+
const focus = useFocusVisible();
|
|
110
114
|
useEffect(() => {
|
|
111
115
|
const target = isExpanded ? 1 : 0;
|
|
112
116
|
Animated.timing(rotation, {
|
|
113
117
|
toValue: target,
|
|
114
118
|
duration: reduceMotion ? 0 : isExpanded ? 200 : 150,
|
|
115
|
-
useNativeDriver:
|
|
119
|
+
useNativeDriver: shouldUseNativeDriver,
|
|
116
120
|
}).start();
|
|
117
121
|
}, [isExpanded, reduceMotion, rotation]);
|
|
118
122
|
const chevronStyle = {
|
|
@@ -125,7 +129,7 @@ function AccordionTrigger({ children, style: styleOverride, disabled, ...props }
|
|
|
125
129
|
},
|
|
126
130
|
],
|
|
127
131
|
};
|
|
128
|
-
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(AccordionPrimitive.Header, { children: _jsx(Animated.View, { style: scaleStyle, children: _jsx(AccordionPrimitive.Trigger, { ...props, disabled: disabled, asChild: true, children: _jsxs(Trigger, { onPressIn: pressHandlers.onPressIn, onPressOut: pressHandlers.onPressOut, style: [
|
|
132
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(AccordionPrimitive.Header, { children: _jsx(Animated.View, { style: scaleStyle, children: _jsx(AccordionPrimitive.Trigger, { ...props, disabled: disabled, asChild: true, children: _jsxs(Trigger, { onPressIn: pressHandlers.onPressIn, onPressOut: pressHandlers.onPressOut, onFocus: focus.onFocus, onBlur: focus.onBlur, style: [
|
|
129
133
|
{
|
|
130
134
|
flexDirection: "row",
|
|
131
135
|
alignItems: "center",
|
|
@@ -136,8 +140,10 @@ function AccordionTrigger({ children, style: styleOverride, disabled, ...props }
|
|
|
136
140
|
...(Platform.OS === "web" && {
|
|
137
141
|
cursor: "pointer",
|
|
138
142
|
userSelect: "none",
|
|
143
|
+
outlineStyle: "none",
|
|
139
144
|
}),
|
|
140
145
|
},
|
|
146
|
+
focus.focused && !disabled && getFocusRingStyle(),
|
|
141
147
|
// Spread array styles from primitives to prevent nested arrays on web
|
|
142
148
|
...(styleOverride && typeof styleOverride !== "function"
|
|
143
149
|
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
@@ -51,5 +51,5 @@ interface AnimatedViewProps extends ViewProps {
|
|
|
51
51
|
* </AnimatedView>
|
|
52
52
|
* ```
|
|
53
53
|
*/
|
|
54
|
-
export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): React.JSX.Element;
|
|
54
|
+
export declare function AnimatedView({ children, type, enterDuration, delay, style, pointerEvents, ...props }: AnimatedViewProps): React.JSX.Element;
|
|
55
55
|
export {};
|
|
@@ -29,11 +29,14 @@ import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance.js";
|
|
|
29
29
|
* </AnimatedView>
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style, ...props }) {
|
|
32
|
+
export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style, pointerEvents, ...props }) {
|
|
33
33
|
const entranceStyle = useStaggeredEntrance({
|
|
34
34
|
type,
|
|
35
35
|
delay,
|
|
36
36
|
duration: enterDuration,
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
// The `pointerEvents` prop is deprecated on web (react-native-web warns and
|
|
39
|
+
// lazily loads its warning module mid-render, which overflowed the first SSR
|
|
40
|
+
// render of deep routes); keep accepting it for consumers, but as a style.
|
|
41
|
+
return (_jsx(Animated.View, { style: [style, pointerEvents ? { pointerEvents } : null, entranceStyle], ...props, children: children }));
|
|
39
42
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { type ImageSourcePropType, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
-
import { type IconName } from "./Icon";
|
|
3
|
+
import { type IconName } from "./Icon.js";
|
|
4
4
|
/** Named size tokens, or an explicit pixel diameter. */
|
|
5
5
|
export type AvatarSize = "sm" | "md" | "lg" | number;
|
|
6
6
|
export type AvatarShape = "circle" | "square";
|
|
@@ -223,7 +223,7 @@ const SIZE_TOKEN_FOR_PX = {
|
|
|
223
223
|
// Geometry carries no theme values, so a plain module-scope sheet is enough to
|
|
224
224
|
// get these rules into the prerendered <head>. Only colors go through
|
|
225
225
|
// createThemedStyles below. See "Enable Server Output" in docs/server-guide.md.
|
|
226
|
-
const geometry = StyleSheet.create({
|
|
226
|
+
const geometry = /*#__PURE__*/ StyleSheet.create({
|
|
227
227
|
base: {
|
|
228
228
|
alignItems: "center",
|
|
229
229
|
justifyContent: "center",
|
|
@@ -297,5 +297,5 @@ const createStyles = (theme) => StyleSheet.create({
|
|
|
297
297
|
color: theme.colors.accentForeground,
|
|
298
298
|
},
|
|
299
299
|
});
|
|
300
|
-
const themedStyles = createThemedStyles(createStyles);
|
|
300
|
+
const themedStyles = /*#__PURE__*/ createThemedStyles(createStyles);
|
|
301
301
|
export { Avatar, AvatarGroup };
|
package/dist/components/Badge.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { View, StyleSheet } from "react-native";
|
|
4
4
|
import { useTheme } from "../hooks/useTheme.js";
|
|
5
|
+
import { shapeRadius, useShape } from "../hooks/useShape.js";
|
|
5
6
|
import { spacing } from "../constants/spacing.js";
|
|
6
7
|
import { StyledText } from "./StyledText.js";
|
|
7
8
|
import { createThemedStyles } from "../lib/themedStyles.js";
|
|
@@ -21,6 +22,8 @@ import { createThemedStyles } from "../lib/themedStyles.js";
|
|
|
21
22
|
function Badge({ children, text, variant = "default", style: styleOverride }) {
|
|
22
23
|
const { theme } = useTheme();
|
|
23
24
|
const styles = themedStyles(theme);
|
|
25
|
+
// Host-app `setShape({ badge })`; the default is a pill.
|
|
26
|
+
const badgeRadius = shapeRadius(useShape("badge"));
|
|
24
27
|
const badgeContent = text ?? children;
|
|
25
28
|
const textStyle = [
|
|
26
29
|
styles.text,
|
|
@@ -43,6 +46,7 @@ function Badge({ children, text, variant = "default", style: styleOverride }) {
|
|
|
43
46
|
}));
|
|
44
47
|
return (_jsx(View, { accessibilityRole: "text", style: [
|
|
45
48
|
styles.badge,
|
|
49
|
+
badgeRadius,
|
|
46
50
|
variant === "default" && styles.default,
|
|
47
51
|
variant === "secondary" && styles.secondary,
|
|
48
52
|
variant === "outline" && styles.outline,
|
|
@@ -75,5 +79,5 @@ const createStyles = (theme) => StyleSheet.create({
|
|
|
75
79
|
userSelect: "none",
|
|
76
80
|
},
|
|
77
81
|
});
|
|
78
|
-
const themedStyles = createThemedStyles(createStyles);
|
|
82
|
+
const themedStyles = /*#__PURE__*/ createThemedStyles(createStyles);
|
|
79
83
|
export { Badge };
|