@hzblj/kara-expo-plugin 0.12.0 → 0.13.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @hzblj/kara-expo-plugin
2
2
 
3
+ ## 0.13.1
4
+
5
+ ## 0.13.0
6
+
7
+ ### Minor Changes
8
+
9
+ - [#31](https://github.com/hzblj/kara-ui/pull/31) [`4b20462`](https://github.com/hzblj/kara-ui/commit/4b204621670b6f640fc04bffa527007dda9e06a8) Thanks [@hzblj](https://github.com/hzblj)! - Icons and the zoom transition now come from two standalone packages, which work without kara too.
10
+
11
+ - **Icons: [@hzblj/expo-native-icons](https://www.npmjs.com/package/@hzblj/expo-native-icons).** The config plugin is the same (`plugins: [['@hzblj/kara-ui', {icons: {source: './assets/icons'}}]]`, the same options, `npx kara-icons` still works). The names change: the declaration is now `native-icons.d.ts` with the global `NativeIcons` (it was `kara-icons.d.ts` and `KaraIcons`), and each icon is `native_icon_<name>` natively (it was `kara_icon_<name>`). Delete the old `kara-icons.d.ts`, and run `expo prebuild --clean` once so the old assets go. `RnIcon` is now the package's `NativeIcon`: on Android it draws the drawable itself instead of hosting an `ImageView`, and remembers each icon's resource lookup, so a list of rows does less work. A glyph with a non-square viewBox no longer stretches on Android.
12
+ - **Zoom transition: [@hzblj/expo-zoom-transition](https://www.npmjs.com/package/@hzblj/expo-zoom-transition).** `ZoomTransition` and its helpers from `@hzblj/kara-ui/router` are unchanged: kara's tokens over the package. The native code moved out of `@hzblj/kara-native` into the package's own module, which `@hzblj/kara-native` depends on, so autolinking picks it up. A tapped context menu preview still zooms. Rebuild the native app.
13
+ - `iconResourceName` moved from `@hzblj/kara-native` to `@hzblj/expo-native-icons`, and `@hzblj/kara-expo-plugin` no longer exports the icon generator or `./cli`: import them from `@hzblj/expo-native-icons/plugin` and `@hzblj/expo-native-icons/cli`.
14
+
3
15
  ## 0.12.0
4
16
 
5
17
  ## 0.11.0
package/README.md CHANGED
@@ -23,9 +23,12 @@ plugins: [['@hzblj/kara-ui', {icons: {source: './assets/icons'}}]]
23
23
  ## What's inside
24
24
 
25
25
  - **Native icons from your SVGs**: at prebuild, every SVG in the folder becomes an iOS asset-catalog image and an
26
- Android vector drawable, and `kara-icons.d.ts` types their names (`bolt.svg` → `<Icon name="bolt" />`).
26
+ Android vector drawable, and `native-icons.d.ts` types their names (`bolt.svg` → `<Icon name="bolt" />`). The
27
+ work is [`@hzblj/expo-native-icons`](https://www.npmjs.com/package/@hzblj/expo-native-icons)'s; `icons` takes its
28
+ options.
27
29
  - **iOS semantic colours**: `iosSemanticColors` redefines a UIKit colour such as `labelColor` app-wide.
28
- - **`kara-icons`**: the same icon check without Expo, for CI; it ships as `@hzblj/kara-ui`'s bin.
30
+ - **`kara-icons`**: the same icon check without Expo, for CI; it ships as `@hzblj/kara-ui`'s bin
31
+ (`native-icons` in `@hzblj/expo-native-icons`).
29
32
 
30
33
  ## Documentation
31
34
 
package/build/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
+ import { type NativeIconsPluginOptions } from '@hzblj/expo-native-icons/plugin';
1
2
  import { type ConfigPlugin } from 'expo/config-plugins';
2
3
  import { type IosSemanticColorsProps } from './ios-semantic-colors';
3
- import { type IconsProps } from './with-icons';
4
- export * from './icons';
5
4
  export type { IosColorAppearances, IosSemanticColorsProps } from './ios-semantic-colors';
6
- export type { IconsProps } from './with-icons';
7
5
  export type KaraUIPluginProps = {
8
- /** The app's icons: a folder of SVGs turned into native assets on both platforms, and their names into types. */
9
- icons?: IconsProps;
6
+ /**
7
+ * The app's icons: a folder of SVGs turned into native assets on both platforms, and their names into types.
8
+ * `@hzblj/expo-native-icons` does the work; see its options.
9
+ */
10
+ icons?: NativeIconsPluginOptions;
10
11
  /** Redefines UIKit semantic colours app-wide, natively. See `withIosSemanticColors`. */
11
12
  iosSemanticColors?: IosSemanticColorsProps;
12
13
  };
package/build/index.js CHANGED
@@ -1,32 +1,17 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const plugin_1 = require("@hzblj/expo-native-icons/plugin");
17
4
  const config_plugins_1 = require("expo/config-plugins");
18
5
  const ios_semantic_colors_1 = require("./ios-semantic-colors");
19
- const with_icons_1 = require("./with-icons");
20
- __exportStar(require("./icons"), exports);
21
6
  /**
22
7
  * `plugins: [['@hzblj/kara-ui', {icons: {source: './assets/icons'}}]]` in the app config. Everything native the
23
- * library cannot ship itself: the app's own icons as native assets, and — optionally — what a UIKit semantic colour
24
- * itself resolves to, for every token (and every piece of UIKit) that names it.
8
+ * library cannot ship itself: the app's own icons as native assets (`@hzblj/expo-native-icons`), and optionally what a
9
+ * UIKit semantic colour itself resolves to, for every token (and every piece of UIKit) that names it.
25
10
  */
26
11
  const withKaraUI = (config, { icons, iosSemanticColors } = {}) => {
27
12
  const withColors = iosSemanticColors ? (0, ios_semantic_colors_1.withIosSemanticColors)(config, iosSemanticColors) : config;
28
- return icons ? (0, with_icons_1.withIcons)(withColors, icons) : withColors;
13
+ return icons ? (0, plugin_1.withNativeIcons)(withColors, icons) : withColors;
29
14
  };
30
- // Read at prebuild from the shipped package.json, beside `build/` — outside `rootDir`, so not an import.
15
+ // Read at prebuild from the shipped package.json, beside `build/`: outside `rootDir`, so not an import.
31
16
  const pkg = require('../package.json');
32
17
  exports.default = (0, config_plugins_1.createRunOncePlugin)(withKaraUI, pkg.name, pkg.version);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hzblj/kara-expo-plugin",
3
- "version": "0.12.0",
4
- "description": "The Expo config plugin and tooling of @hzblj/kara-ui: the app's SVG icons as native assets on iOS and Android, and UIKit semantic colour overrides.",
3
+ "version": "0.13.1",
4
+ "description": "The Expo config plugin and tooling of @hzblj/kara-ui: the app's SVG icons as native assets on iOS and Android (through @hzblj/expo-native-icons), and UIKit semantic colour overrides.",
5
5
  "author": "Jan Blazej",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -25,10 +25,6 @@
25
25
  "types": "./build/index.d.ts",
26
26
  "default": "./build/index.js"
27
27
  },
28
- "./cli": {
29
- "types": "./build/cli.d.ts",
30
- "default": "./build/cli.js"
31
- },
32
28
  "./app.plugin.js": "./app.plugin.js",
33
29
  "./package.json": "./package.json"
34
30
  },
@@ -45,6 +41,9 @@
45
41
  "biome:check": "yarn run -T biome check .",
46
42
  "tsc:check": "tsc -p ./tsconfig.json --noEmit"
47
43
  },
44
+ "dependencies": {
45
+ "@hzblj/expo-native-icons": "^0.1.0"
46
+ },
48
47
  "peerDependencies": {
49
48
  "expo": "*"
50
49
  },
package/build/cli.d.ts DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * `npx kara-icons ./assets/icons` — the half of custom icons that runs before prebuild: validation, so a bad SVG fails
3
- * here rather than mid-prebuild, and the names, so the app type-checks against the icons it actually ships.
4
- */
5
- export declare const main: (args: string[]) => number;
package/build/cli.js DELETED
@@ -1,41 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.main = void 0;
7
- const node_fs_1 = require("node:fs");
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const icons_1 = require("./icons");
10
- const USAGE = `Usage: kara-icons <source> [--out <file>]
11
-
12
- Checks every SVG in <source> the way prebuild will, and writes the TypeScript declaration that makes
13
- their names valid for <Icon name="…" />. The config plugin does the same whenever the app config is
14
- read; this is for CI and for projects that type-check without starting Expo.
15
-
16
- --out where to write the declaration (default: kara-icons.d.ts in the current directory)`;
17
- /**
18
- * `npx kara-icons ./assets/icons` — the half of custom icons that runs before prebuild: validation, so a bad SVG fails
19
- * here rather than mid-prebuild, and the names, so the app type-checks against the icons it actually ships.
20
- */
21
- const main = (args) => {
22
- const outIndex = args.indexOf('--out');
23
- const outValueIndex = outIndex === -1 ? -1 : outIndex + 1;
24
- const out = outIndex === -1 ? 'kara-icons.d.ts' : args[outValueIndex];
25
- const source = args.find((arg, index) => !arg.startsWith('--') && index !== outValueIndex);
26
- if (!source || !out || args.includes('--help')) {
27
- console.log(USAGE);
28
- return source ? 0 : 1;
29
- }
30
- try {
31
- const { android, ios, names } = (0, icons_1.readIconFolder)(node_path_1.default.resolve(source));
32
- (0, node_fs_1.writeFileSync)(node_path_1.default.resolve(out), (0, icons_1.iconTypes)(names, source));
33
- console.log(`kara-icons: ${names.length} icons (${ios.length} iOS, ${android.length} Android) → ${out}`);
34
- return 0;
35
- }
36
- catch (error) {
37
- console.error(error instanceof Error ? error.message : error);
38
- return 1;
39
- }
40
- };
41
- exports.main = main;
package/build/icons.d.ts DELETED
@@ -1,58 +0,0 @@
1
- /** One `<path>` of a glyph. `evenOdd` keeps holes punched by an inner contour, the way Figma exports them. */
2
- export type IconPath = {
3
- d: string;
4
- evenOdd: boolean;
5
- };
6
- /** A single-colour glyph, read from an SVG: its viewBox and the paths drawn in it. */
7
- export type SvgIcon = {
8
- height: number;
9
- minX: number;
10
- minY: number;
11
- name: string;
12
- paths: IconPath[];
13
- width: number;
14
- };
15
- export type IconPlatform = 'android' | 'ios';
16
- /** `arrow-left` → `arrow_left`: Android resource names allow neither `-` nor capitals. */
17
- export declare const snakeCase: (name: string) => string;
18
- /** An app icon's native name, the same on both platforms: `kara_icon_<name>` — the asset, and the drawable. */
19
- export declare const iconResourceName: (name: string) => string;
20
- /**
21
- * Reads one glyph. Only `<path>` elements (optionally `fill-rule="evenodd"`) inside one `<svg>` with a numeric
22
- * `viewBox` are accepted, and anything else fails loudly — a glyph that silently lost a clip or a mask is worse than
23
- * none (a mask leftover flattens into a solid square). The colour in the file is ignored: every icon is a template the
24
- * platform tints.
25
- */
26
- export declare const parseSvgIcon: (file: string) => SvgIcon;
27
- /** What one folder holds: each platform's glyphs, and every name that exists on at least one of them. */
28
- export type IconFolder = {
29
- android: SvgIcon[];
30
- ios: SvgIcon[];
31
- names: string[];
32
- };
33
- /**
34
- * Every icon in a folder, per platform: `<name>.svg` for both, `<name>.ios.svg` / `<name>.android.svg` for one — a
35
- * variant wins over the shared file on its platform, and a name with only a variant exists on that platform alone.
36
- * Refused when two names would land on one native resource name (`arrow-left` and `arrow_left`).
37
- */
38
- export declare const readIconFolder: (directory: string) => IconFolder;
39
- /**
40
- * An asset-catalog image set: the glyph as an SVG template that keeps its vector data, so SwiftUI's `resizable()` and
41
- * `UIImageView` scale it crisply to any size, and UIKit tints it with the view's tint colour. `size` is the point size
42
- * a system control draws it at when nothing resizes it.
43
- */
44
- export declare const iosImageset: (icon: SvgIcon, assetName: string, size: number, author: string) => {
45
- contents: string;
46
- svg: string;
47
- };
48
- /**
49
- * The glyph as an Android vector drawable — compiled by aapt like any resource, drawn by `ImageView` and Compose's
50
- * `painterResource` alike. White fill, because every consumer tints it. A viewBox that does not start at the origin
51
- * (Material Symbols use `0 -960 960 960`) is moved there by a group, since a vector's viewport always starts at 0,0.
52
- */
53
- export declare const androidVectorDrawable: (icon: SvgIcon, size?: number) => string;
54
- /**
55
- * The declaration that teaches TypeScript the app's icon names: a global `KaraIcons` the components read, so
56
- * `<Icon name="bolt" />` type-checks without any import — and an icon deleted from the folder fails at its call sites.
57
- */
58
- export declare const iconTypes: (names: string[], source: string) => string;
package/build/icons.js DELETED
@@ -1,152 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.iconTypes = exports.androidVectorDrawable = exports.iosImageset = exports.readIconFolder = exports.parseSvgIcon = exports.iconResourceName = exports.snakeCase = void 0;
7
- const node_fs_1 = require("node:fs");
8
- const node_path_1 = __importDefault(require("node:path"));
9
- /** Kebab or snake case, lower case, starting with a letter: the file name is the icon name. */
10
- const ICON_NAME = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
11
- /** `share.android.svg` replaces `share.svg` on Android only — the platform's own glyph where the two differ. */
12
- const PLATFORM_SUFFIX = /\.(android|ios)$/;
13
- /** Every element the generator understands. Anything else — groups, clips, masks, strokes — must be flattened first. */
14
- const ALLOWED_TAGS = new Set(['path', 'svg', 'title']);
15
- const fail = (file, message) => {
16
- throw new Error(`[kara-icons] ${node_path_1.default.basename(file)}: ${message}`);
17
- };
18
- /** `arrow-left` → `arrow_left`: Android resource names allow neither `-` nor capitals. */
19
- const snakeCase = (name) => name.replace(/-/g, '_').toLowerCase();
20
- exports.snakeCase = snakeCase;
21
- /** An app icon's native name, the same on both platforms: `kara_icon_<name>` — the asset, and the drawable. */
22
- const iconResourceName = (name) => `kara_icon_${(0, exports.snakeCase)(name)}`;
23
- exports.iconResourceName = iconResourceName;
24
- /**
25
- * Reads one glyph. Only `<path>` elements (optionally `fill-rule="evenodd"`) inside one `<svg>` with a numeric
26
- * `viewBox` are accepted, and anything else fails loudly — a glyph that silently lost a clip or a mask is worse than
27
- * none (a mask leftover flattens into a solid square). The colour in the file is ignored: every icon is a template the
28
- * platform tints.
29
- */
30
- const parseSvgIcon = (file) => {
31
- const name = node_path_1.default.basename(file, '.svg').replace(PLATFORM_SUFFIX, '');
32
- const svg = (0, node_fs_1.readFileSync)(file, 'utf8');
33
- if (!ICON_NAME.test(name)) {
34
- fail(file, 'name it in lower kebab or snake case, starting with a letter — the file name is the icon name');
35
- }
36
- const viewBox = svg.match(/viewBox="\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)\s*"/);
37
- if (!viewBox) {
38
- fail(file, 'expected a numeric viewBox="<minX> <minY> <width> <height>"');
39
- }
40
- for (const [, tag] of svg.matchAll(/<([a-zA-Z][\w-]*)/g)) {
41
- if (!ALLOWED_TAGS.has(tag)) {
42
- fail(file, `only <path> elements are supported, found <${tag}> — flatten groups, clips, masks and strokes first`);
43
- }
44
- }
45
- const paths = [];
46
- for (const [, attributes] of svg.matchAll(/<path\b([^>]*)>/g)) {
47
- const d = attributes.match(/\sd="([^"]+)"/)?.[1];
48
- if (!d) {
49
- fail(file, '<path> without d');
50
- }
51
- paths.push({ d: d, evenOdd: /fill-rule="evenodd"/.test(attributes) });
52
- }
53
- if (paths.length === 0) {
54
- fail(file, 'no <path> elements');
55
- }
56
- const [, minX, minY, width, height] = viewBox;
57
- return { height: Number(height), minX: Number(minX), minY: Number(minY), name, paths, width: Number(width) };
58
- };
59
- exports.parseSvgIcon = parseSvgIcon;
60
- const glyphsFor = (directory, names, platform) => names.flatMap(name => {
61
- const variant = node_path_1.default.join(directory, `${name}.${platform}.svg`);
62
- const shared = node_path_1.default.join(directory, `${name}.svg`);
63
- if ((0, node_fs_1.existsSync)(variant)) {
64
- return [(0, exports.parseSvgIcon)(variant)];
65
- }
66
- return (0, node_fs_1.existsSync)(shared) ? [(0, exports.parseSvgIcon)(shared)] : [];
67
- });
68
- /**
69
- * Every icon in a folder, per platform: `<name>.svg` for both, `<name>.ios.svg` / `<name>.android.svg` for one — a
70
- * variant wins over the shared file on its platform, and a name with only a variant exists on that platform alone.
71
- * Refused when two names would land on one native resource name (`arrow-left` and `arrow_left`).
72
- */
73
- const readIconFolder = (directory) => {
74
- if (!(0, node_fs_1.existsSync)(directory)) {
75
- throw new Error(`[kara-icons] No icon folder at ${directory}.`);
76
- }
77
- const names = [
78
- ...new Set((0, node_fs_1.readdirSync)(directory)
79
- .filter(file => file.endsWith('.svg'))
80
- .map(file => node_path_1.default.basename(file, '.svg').replace(PLATFORM_SUFFIX, ''))),
81
- ].sort();
82
- const byResource = new Map();
83
- for (const name of names) {
84
- const resource = (0, exports.snakeCase)(name);
85
- const existing = byResource.get(resource);
86
- if (existing) {
87
- throw new Error(`[kara-icons] "${existing}" and "${name}" would both become the resource "${resource}".`);
88
- }
89
- byResource.set(resource, name);
90
- }
91
- return { android: glyphsFor(directory, names, 'android'), ios: glyphsFor(directory, names, 'ios'), names };
92
- };
93
- exports.readIconFolder = readIconFolder;
94
- const fillRule = (iconPath) => (iconPath.evenOdd ? ' fill-rule="evenodd"' : '');
95
- /**
96
- * An asset-catalog image set: the glyph as an SVG template that keeps its vector data, so SwiftUI's `resizable()` and
97
- * `UIImageView` scale it crisply to any size, and UIKit tints it with the view's tint colour. `size` is the point size
98
- * a system control draws it at when nothing resizes it.
99
- */
100
- const iosImageset = (icon, assetName, size, author) => {
101
- const paths = icon.paths.map(iconPath => ` <path d="${iconPath.d}"${fillRule(iconPath)} fill="black"/>`);
102
- const viewBox = `${icon.minX} ${icon.minY} ${icon.width} ${icon.height}`;
103
- return {
104
- contents: `${JSON.stringify({
105
- images: [{ filename: `${assetName}.svg`, idiom: 'universal' }],
106
- info: { author, version: 1 },
107
- properties: { 'preserves-vector-representation': true, 'template-rendering-intent': 'template' },
108
- }, null, 2)}\n`,
109
- svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="${viewBox}">\n${paths.join('\n')}\n</svg>\n`,
110
- };
111
- };
112
- exports.iosImageset = iosImageset;
113
- /**
114
- * The glyph as an Android vector drawable — compiled by aapt like any resource, drawn by `ImageView` and Compose's
115
- * `painterResource` alike. White fill, because every consumer tints it. A viewBox that does not start at the origin
116
- * (Material Symbols use `0 -960 960 960`) is moved there by a group, since a vector's viewport always starts at 0,0.
117
- */
118
- const androidVectorDrawable = (icon, size = 24) => {
119
- const paths = icon.paths.map(iconPath => ` <path\n android:fillColor="@android:color/white"${iconPath.evenOdd ? '\n android:fillType="evenOdd"' : ''}\n android:pathData="${iconPath.d}"/>`);
120
- const offset = icon.minX !== 0 || icon.minY !== 0;
121
- const body = offset
122
- ? ` <group android:translateX="${-icon.minX}" android:translateY="${-icon.minY}">\n${paths.join('\n')}\n </group>`
123
- : paths.join('\n').replace(/^ {2}/gm, '');
124
- return `<vector xmlns:android="http://schemas.android.com/apk/res/android"
125
- android:width="${size}dp"
126
- android:height="${size}dp"
127
- android:viewportWidth="${icon.width}"
128
- android:viewportHeight="${icon.height}">
129
- ${body}
130
- </vector>
131
- `;
132
- };
133
- exports.androidVectorDrawable = androidVectorDrawable;
134
- /**
135
- * The declaration that teaches TypeScript the app's icon names: a global `KaraIcons` the components read, so
136
- * `<Icon name="bolt" />` type-checks without any import — and an icon deleted from the folder fails at its call sites.
137
- */
138
- const iconTypes = (names, source) => {
139
- const key = (name) => (/^[A-Za-z_$][\w$]*$/.test(name) ? name : `'${name}'`);
140
- const entries = names.map(name => ` ${key(name)}: true`);
141
- return `// Generated by @hzblj/kara-ui from ${source} — do not edit. It is rewritten whenever the app config is read
142
- // (\`expo start\`, \`expo prebuild\`), or by \`npx kara-icons ${source}\`.
143
- declare global {
144
- interface KaraIcons {
145
- ${entries.join('\n')}
146
- }
147
- }
148
-
149
- export {}
150
- `;
151
- };
152
- exports.iconTypes = iconTypes;
@@ -1,26 +0,0 @@
1
- import { type ConfigPlugin } from 'expo/config-plugins';
2
- export type IconsProps = {
3
- /**
4
- * A folder of SVGs, one glyph per file, relative to the project root. The file name is the icon name
5
- * (`bolt.svg` → `<Icon name="bolt" />`); `share.android.svg` replaces `share.svg` on Android. Only single-colour
6
- * `<path>`s are accepted — the platform tints every icon.
7
- */
8
- source: string;
9
- /**
10
- * Where the declaration of the icon names goes, relative to the project root — `false` to skip it. Rewritten
11
- * whenever the app config is read, so `expo start` alone keeps it current. `kara-icons.d.ts` by default.
12
- */
13
- types?: string | false;
14
- /**
15
- * The point size iOS system controls (bar items, menus, swipe actions) draw an icon at when nothing resizes it.
16
- * Everything kara-ui renders sizes the icon itself. 24 by default.
17
- */
18
- iosSize?: number;
19
- };
20
- /**
21
- * The app's icons as native assets on both platforms, generated from SVGs at prebuild: a template image set per icon in
22
- * the iOS asset catalog (vector data kept), a vector drawable per icon in Android's `res/drawable`. They have to live
23
- * in the app's own catalog and resources — `Image("…")` and `UIImage(named:)` look in the main bundle, and an asset
24
- * catalog shipped by a pod collides with the app's `Assets.car`. A new or changed SVG needs a prebuild.
25
- */
26
- export declare const withIcons: ConfigPlugin<IconsProps>;
@@ -1,99 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.withIcons = void 0;
7
- const node_fs_1 = require("node:fs");
8
- const promises_1 = require("node:fs/promises");
9
- const node_path_1 = __importDefault(require("node:path"));
10
- const config_plugins_1 = require("expo/config-plugins");
11
- const icons_1 = require("./icons");
12
- /** Stamped into every image set this writes, so a later run can tell ours from one added by hand. */
13
- const AUTHOR = '@hzblj/kara-ui';
14
- const IMAGESET_SUFFIX = '.imageset';
15
- const RESOURCE_PREFIX = (0, icons_1.iconResourceName)('');
16
- const DEFAULT_IOS_SIZE = 24;
17
- const DEFAULT_TYPES = 'kara-icons.d.ts';
18
- /** Removes what an earlier run wrote for icons no longer in the folder — a deleted SVG must stop existing natively. */
19
- const removeStaleImagesets = async (catalog, keep) => {
20
- const dirents = await (0, promises_1.readdir)(catalog, { withFileTypes: true }).catch(() => []);
21
- for (const dirent of dirents) {
22
- const name = dirent.name.slice(0, -IMAGESET_SUFFIX.length);
23
- if (!dirent.isDirectory() || !dirent.name.endsWith(IMAGESET_SUFFIX) || keep.has(name)) {
24
- continue;
25
- }
26
- const setPath = node_path_1.default.join(catalog, dirent.name);
27
- const contents = await (0, promises_1.readFile)(node_path_1.default.join(setPath, 'Contents.json'), 'utf8').catch(() => '');
28
- if (name.startsWith(RESOURCE_PREFIX) && contents.includes(`"author": "${AUTHOR}"`)) {
29
- await (0, promises_1.rm)(setPath, { force: true, recursive: true });
30
- }
31
- }
32
- };
33
- const withIosIcons = (config, { iosSize = DEFAULT_IOS_SIZE, source }) => (0, config_plugins_1.withDangerousMod)(config, [
34
- 'ios',
35
- async (config) => {
36
- const { projectRoot } = config.modRequest;
37
- const { ios } = (0, icons_1.readIconFolder)(node_path_1.default.resolve(projectRoot, source));
38
- const catalog = node_path_1.default.join(config_plugins_1.IOSConfig.Paths.getSourceRoot(projectRoot), 'Images.xcassets');
39
- const written = new Set();
40
- for (const icon of ios) {
41
- const assetName = (0, icons_1.iconResourceName)(icon.name);
42
- const setPath = node_path_1.default.join(catalog, `${assetName}${IMAGESET_SUFFIX}`);
43
- const { contents, svg } = (0, icons_1.iosImageset)(icon, assetName, iosSize, AUTHOR);
44
- written.add(assetName);
45
- await (0, promises_1.mkdir)(setPath, { recursive: true });
46
- await (0, promises_1.writeFile)(node_path_1.default.join(setPath, 'Contents.json'), contents, 'utf8');
47
- await (0, promises_1.writeFile)(node_path_1.default.join(setPath, `${assetName}.svg`), svg, 'utf8');
48
- }
49
- await removeStaleImagesets(catalog, written);
50
- return config;
51
- },
52
- ]);
53
- const withAndroidIcons = (config, { source }) => (0, config_plugins_1.withDangerousMod)(config, [
54
- 'android',
55
- async (config) => {
56
- const { platformProjectRoot, projectRoot } = config.modRequest;
57
- const { android } = (0, icons_1.readIconFolder)(node_path_1.default.resolve(projectRoot, source));
58
- const drawables = node_path_1.default.join(platformProjectRoot, 'app', 'src', 'main', 'res', 'drawable');
59
- const written = new Set(android.map(icon => `${(0, icons_1.iconResourceName)(icon.name)}.xml`));
60
- await (0, promises_1.mkdir)(drawables, { recursive: true });
61
- for (const icon of android) {
62
- await (0, promises_1.writeFile)(node_path_1.default.join(drawables, `${(0, icons_1.iconResourceName)(icon.name)}.xml`), (0, icons_1.androidVectorDrawable)(icon), 'utf8');
63
- }
64
- // The `kara_icon_` prefix is this plugin's alone, so any such drawable not written now was written before.
65
- for (const file of await (0, promises_1.readdir)(drawables)) {
66
- if (file.startsWith(RESOURCE_PREFIX) && file.endsWith('.xml') && !written.has(file)) {
67
- await (0, promises_1.rm)(node_path_1.default.join(drawables, file), { force: true });
68
- }
69
- }
70
- return config;
71
- },
72
- ]);
73
- /**
74
- * Keeps the declaration of icon names in step with the folder. Done while the config is evaluated rather than in a mod
75
- * on purpose: mods run only at prebuild, while `expo start` reads the config too — so adding an SVG and starting Metro
76
- * is all it takes for its name to type-check. Written only when the content changed.
77
- */
78
- const writeTypes = (projectRoot, { source, types = DEFAULT_TYPES }) => {
79
- if (types === false) {
80
- return;
81
- }
82
- const { names } = (0, icons_1.readIconFolder)(node_path_1.default.resolve(projectRoot, source));
83
- const file = node_path_1.default.resolve(projectRoot, types);
84
- const contents = (0, icons_1.iconTypes)(names, source);
85
- if (!(0, node_fs_1.existsSync)(file) || (0, node_fs_1.readFileSync)(file, 'utf8') !== contents) {
86
- (0, node_fs_1.writeFileSync)(file, contents);
87
- }
88
- };
89
- /**
90
- * The app's icons as native assets on both platforms, generated from SVGs at prebuild: a template image set per icon in
91
- * the iOS asset catalog (vector data kept), a vector drawable per icon in Android's `res/drawable`. They have to live
92
- * in the app's own catalog and resources — `Image("…")` and `UIImage(named:)` look in the main bundle, and an asset
93
- * catalog shipped by a pod collides with the app's `Assets.car`. A new or changed SVG needs a prebuild.
94
- */
95
- const withIcons = (config, props) => {
96
- writeTypes(config._internal?.projectRoot ?? process.cwd(), props);
97
- return withAndroidIcons(withIosIcons(config, props), props);
98
- };
99
- exports.withIcons = withIcons;