@mrmeg/expo-ui 0.28.0 → 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.
@@ -1,7 +1,27 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Platform } from "react-native";
3
3
  import { useTheme } from "../hooks/useTheme.js";
4
- import { ICONS } from "./iconRegistry.generated.js";
4
+ import { BUILTIN_ICONS } from "./iconBuiltins.generated.js";
5
+ import { IconGlyph as GlyphRenderer } from "./IconGlyph";
6
+ /** Glyphs by name: the builtins, then whatever `registerIcons` adds. */
7
+ const registry = new Map(Object.entries(BUILTIN_ICONS));
8
+ /**
9
+ * Make glyphs drawable by name. Call it once at module scope in the root
10
+ * layout with the module `expo-ui-icons` generates
11
+ * (`registerIcons(appIcons)`), so it has run before any route renders, on the
12
+ * server too. Idempotent; a later call wins for a name it repeats.
13
+ */
14
+ export function registerIcons(icons) {
15
+ for (const [name, glyph] of Object.entries(icons))
16
+ registry.set(name, glyph);
17
+ }
18
+ /**
19
+ * Whether a runtime value (a database field, a server payload) names a
20
+ * registered icon. Narrows it to `IconName` for `<Icon name>`.
21
+ */
22
+ export function isIconName(value) {
23
+ return typeof value === "string" && registry.has(value);
24
+ }
5
25
  /**
6
26
  * Resolve an icon color against the active theme.
7
27
  *
@@ -46,28 +66,29 @@ function iconAccessibilityProps(decorative, label) {
46
66
  }
47
67
  return { accessible: true, accessibilityLabel: label };
48
68
  }
69
+ const warnedUnknownIconNames = new Set();
70
+ function warnUnknownIconName(name) {
71
+ if (!__DEV__ || warnedUnknownIconNames.has(name))
72
+ return;
73
+ warnedUnknownIconNames.add(name);
74
+ console.warn(`Icon: "${name}" is not a registered icon, so nothing was rendered. ` +
75
+ "Add it to client/icons.json, run `bun run icons`, and make sure " +
76
+ "`registerIcons(appIcons)` runs in the root layout.");
77
+ }
49
78
  /**
50
79
  * Universal Icon Component
51
- * Renders `lucide-react-native` SVG icons with theme integration and style
52
- * support. Icons are SVG on every platform: no font to load, and server markup
53
- * carries the glyph.
80
+ * Draws a registered glyph as SVG with theme integration and style support:
81
+ * an inline `<svg>` on web and the server, `react-native-svg` on iOS and
82
+ * Android. No font to load, and server markup carries the glyph.
54
83
  *
55
84
  * Usage:
56
85
  * ```tsx
57
86
  * <Icon name="check" color="primary" size={16} />
58
87
  * <Icon name="calendar" color="#FF0000" size={24} />
59
88
  * <Icon name="terminal" style={{ marginRight: 8 }} />
60
- * <Icon component={Rocket} color="accent" /> // any Lucide import
89
+ * <Icon component={BrandMark} color="accent" /> // non-Lucide art
61
90
  * ```
62
91
  */
63
- const warnedUnknownIconNames = new Set();
64
- function warnUnknownIconName(name) {
65
- if (!__DEV__ || warnedUnknownIconNames.has(name))
66
- return;
67
- warnedUnknownIconNames.add(name);
68
- console.warn(`Icon: "${name}" is not in the package icon registry, so nothing was rendered. ` +
69
- "Add it to icon-names.json and run `bun run ui:icons`, or pass a Lucide component through `component`.");
70
- }
71
92
  export function Icon(props) {
72
93
  const { size = 24, color, style, decorative = false, accessibilityLabel } = props;
73
94
  const { theme } = useTheme();
@@ -81,14 +102,14 @@ export function Icon(props) {
81
102
  if (CustomComponent) {
82
103
  return (_jsx(CustomComponent, { size: size, color: iconColor, style: iconStyle, ...accessibilityProps }));
83
104
  }
84
- const name = props.name;
85
- const Lucide = ICONS[name];
105
+ const { name, testID } = props;
106
+ const glyph = registry.get(name);
86
107
  // `IconName` makes an unknown name a type error, but names also arrive at
87
- // runtime (a database field, a config file). Rendering `undefined` would
88
- // throw; draw nothing and say so once per name in development instead.
89
- if (!Lucide) {
108
+ // runtime (a database field, a config file) and an app can forget to
109
+ // register. Draw nothing and say so once per name in development.
110
+ if (!glyph) {
90
111
  warnUnknownIconName(name);
91
112
  return null;
92
113
  }
93
- return (_jsx(Lucide, { size: size, color: iconColor, style: iconStyle, ...accessibilityProps }));
114
+ return (_jsx(GlyphRenderer, { glyph: glyph, size: size, color: iconColor, style: iconStyle, testID: testID ?? (__DEV__ ? `icon-${name}` : undefined), ...accessibilityProps }));
94
115
  }
@@ -0,0 +1,15 @@
1
+ import { type ReactElement } from "react";
2
+ import type { IconGlyphProps } from "./iconTypes.js";
3
+ /**
4
+ * Web and server renderer for a glyph: one inline `<svg>` built with
5
+ * react-native-web's `unstable_createElement`, so the style array, `testID`
6
+ * (`data-testid`), and ARIA props convert exactly as they do on a `View`, and
7
+ * `react-dom/server` emits the full markup with no DOM.
8
+ *
9
+ * The SVG strokes `currentColor` and carries the color as a `color` style. On
10
+ * web every theme color is a `var(--c-*)` string that is identical in both
11
+ * schemes, so server and client markup match and a theme change recolors the
12
+ * icon through CSS alone. No hooks and no ids: nothing here can differ
13
+ * between the server render and hydration.
14
+ */
15
+ export declare function IconGlyph({ glyph, size, color, strokeWidth, style, testID, ...accessibility }: IconGlyphProps): ReactElement;
@@ -0,0 +1,32 @@
1
+ import { createElement } from "react";
2
+ import { unstable_createElement } from "react-native-web";
3
+ /**
4
+ * Web and server renderer for a glyph: one inline `<svg>` built with
5
+ * react-native-web's `unstable_createElement`, so the style array, `testID`
6
+ * (`data-testid`), and ARIA props convert exactly as they do on a `View`, and
7
+ * `react-dom/server` emits the full markup with no DOM.
8
+ *
9
+ * The SVG strokes `currentColor` and carries the color as a `color` style. On
10
+ * web every theme color is a `var(--c-*)` string that is identical in both
11
+ * schemes, so server and client markup match and a theme change recolors the
12
+ * icon through CSS alone. No hooks and no ids: nothing here can differ
13
+ * between the server render and hydration.
14
+ */
15
+ export function IconGlyph({ glyph, size, color, strokeWidth = 2, style, testID, ...accessibility }) {
16
+ return unstable_createElement("svg", {
17
+ xmlns: "http://www.w3.org/2000/svg",
18
+ width: size,
19
+ height: size,
20
+ viewBox: "0 0 24 24",
21
+ fill: "none",
22
+ stroke: "currentColor",
23
+ strokeWidth,
24
+ strokeLinecap: "round",
25
+ strokeLinejoin: "round",
26
+ style: [{ color }, style, { pointerEvents: "none" }],
27
+ ...accessibility,
28
+ testID,
29
+ // react-native-web reads children from props; a third argument is options.
30
+ children: glyph.map(([tag, attrs], index) => createElement(tag, { key: index, ...attrs })),
31
+ });
32
+ }
@@ -0,0 +1,10 @@
1
+ import { type ReactElement } from "react";
2
+ import type { IconGlyphProps } from "./iconTypes.js";
3
+ /**
4
+ * iOS and Android renderer for a glyph, drawn with `react-native-svg` the way
5
+ * Lucide's own native component draws it, so the pixels match: the root sets
6
+ * the 24×24 view box, no fill, a round 2 px stroke in `color`, and `color` itself
7
+ * (so a `fill="currentColor"` node resolves), and every child repeats the fill,
8
+ * stroke, width, cap, and join before its own attributes.
9
+ */
10
+ export declare function IconGlyph({ glyph, size, color, strokeWidth, style, testID, ...accessibility }: IconGlyphProps): ReactElement;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createElement } from "react";
3
+ import { Circle, Ellipse, Line, Path, Polygon, Polyline, Rect, Svg } from "react-native-svg";
4
+ const ELEMENTS = {
5
+ path: Path,
6
+ circle: Circle,
7
+ rect: Rect,
8
+ line: Line,
9
+ polyline: Polyline,
10
+ polygon: Polygon,
11
+ ellipse: Ellipse,
12
+ };
13
+ /**
14
+ * iOS and Android renderer for a glyph, drawn with `react-native-svg` the way
15
+ * Lucide's own native component draws it, so the pixels match: the root sets
16
+ * the 24×24 view box, no fill, a round 2 px stroke in `color`, and `color` itself
17
+ * (so a `fill="currentColor"` node resolves), and every child repeats the fill,
18
+ * stroke, width, cap, and join before its own attributes.
19
+ */
20
+ export function IconGlyph({ glyph, size, color, strokeWidth = 2, style, testID, ...accessibility }) {
21
+ return (_jsx(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, color: color, strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round", style: style, testID: testID, ...accessibility, children: glyph.map(([tag, attrs], index) => createElement(ELEMENTS[tag], {
22
+ key: index,
23
+ fill: "none",
24
+ stroke: color,
25
+ strokeWidth,
26
+ strokeLinecap: "round",
27
+ strokeLinejoin: "round",
28
+ ...attrs,
29
+ })) }));
30
+ }
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { type StyleProp, type ViewStyle } from "react-native";
3
+ import { type ThemePreference } from "../state/themeStore.js";
4
+ /**
5
+ * ThemeSelector — a compact System / Light / Dark switch wired to the kit's
6
+ * theme store.
7
+ *
8
+ * Icon-only by default (about 116pt wide), sized to sit in an `Item` row's
9
+ * `ItemActions` so the whole appearance setting is one row instead of three.
10
+ * Pass `showLabels` for a full-width control with text under a group title.
11
+ *
12
+ * Reads and writes `useThemeStore` unless `value` / `onValueChange` are
13
+ * passed. The package has no i18n: pass translated `labels`, which become the
14
+ * visible text (with `showLabels`) and each segment's accessibility label.
15
+ *
16
+ * Drawn in RN on every platform (no native segmented control), so the icons
17
+ * render on iOS and Android too and the server render matches the client: the
18
+ * store boots `system` on both sides until the persisted preference loads.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <Item>
23
+ * <ItemContent>
24
+ * <ItemTitle>Theme</ItemTitle>
25
+ * </ItemContent>
26
+ * <ItemActions>
27
+ * <ThemeSelector />
28
+ * </ItemActions>
29
+ * </Item>
30
+ * ```
31
+ */
32
+ export type ThemeSelectorLabels = Record<ThemePreference, string>;
33
+ export interface ThemeSelectorProps {
34
+ /** Controlled preference. Defaults to the theme store's `userTheme`. */
35
+ value?: ThemePreference;
36
+ /** Called with the chosen preference. Defaults to the theme store's `setTheme`. */
37
+ onValueChange?: (value: ThemePreference) => void;
38
+ /** Segment labels, for screen readers and `showLabels`. @default English */
39
+ labels?: Partial<ThemeSelectorLabels>;
40
+ /** Show the label beside each icon and stretch to the parent's width. @default false */
41
+ showLabels?: boolean;
42
+ /** Disable interaction. @default false */
43
+ disabled?: boolean;
44
+ /** Names the group for screen readers. Inside an `Item`, defaults to the row title. */
45
+ accessibilityLabel?: string;
46
+ /** Style override for the track. */
47
+ style?: StyleProp<ViewStyle>;
48
+ /** Test id for the track; segments get `${testID}-${value}`. @default "theme-selector" */
49
+ testID?: string;
50
+ }
51
+ declare function ThemeSelector({ value, onValueChange, labels, showLabels, disabled, accessibilityLabel, style, testID, }: ThemeSelectorProps): React.JSX.Element;
52
+ export { ThemeSelector };
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Platform, Pressable, StyleSheet, View } from "react-native";
3
+ import { useTheme } from "../hooks/useTheme.js";
4
+ import { useFocusVisible } from "../hooks/useFocusVisible.js";
5
+ import { useThemeStore } from "../state/themeStore.js";
6
+ import { hapticSelection } from "../lib/haptics.js";
7
+ import { spacing } from "../constants/spacing.js";
8
+ import { interaction } from "../constants/interaction.js";
9
+ import { Icon } from "./Icon.js";
10
+ import { StyledText } from "./StyledText.js";
11
+ import { useItemControlLabel } from "./Item.js";
12
+ const DEFAULT_LABELS = { system: "System", light: "Light", dark: "Dark" };
13
+ const OPTIONS = [
14
+ // A phone reads as "device" on native; web visitors are mostly on a desktop.
15
+ { value: "system", icon: Platform.OS === "web" ? "monitor" : "smartphone" },
16
+ { value: "light", icon: "sun" },
17
+ { value: "dark", icon: "moon" },
18
+ ];
19
+ const TRACK_HEIGHT = 32;
20
+ const TRACK_INSET = 2;
21
+ const ICON_SEGMENT_WIDTH = 36;
22
+ function ThemeSelector({ value, onValueChange, labels, showLabels = false, disabled = false, accessibilityLabel, style, testID = "theme-selector", }) {
23
+ const rowLabel = useItemControlLabel({ accessibilityLabel });
24
+ const { theme } = useTheme();
25
+ const userTheme = useThemeStore((state) => state.userTheme);
26
+ const setTheme = useThemeStore((state) => state.setTheme);
27
+ const selected = value ?? userTheme;
28
+ const text = { ...DEFAULT_LABELS, ...labels };
29
+ const select = (next) => {
30
+ if (next === selected)
31
+ return;
32
+ hapticSelection();
33
+ (onValueChange ?? setTheme)(next);
34
+ };
35
+ return (_jsx(View, { testID: testID, accessibilityRole: "radiogroup", accessibilityLabel: accessibilityLabel, ...rowLabel, accessibilityState: { disabled }, style: [
36
+ styles.track,
37
+ { backgroundColor: theme.colors.muted, opacity: disabled ? interaction.disabledOpacity : 1 },
38
+ showLabels ? styles.trackStretch : styles.trackCompact,
39
+ style,
40
+ ], children: OPTIONS.map((option) => (_jsx(Segment, { testID: `${testID}-${option.value}`, icon: option.icon, label: text[option.value], showLabel: showLabels, selected: option.value === selected, disabled: disabled, onPress: () => select(option.value) }, option.value))) }));
41
+ }
42
+ function Segment({ testID, icon, label, showLabel, selected, disabled, onPress }) {
43
+ const { theme, getFocusRingStyle } = useTheme();
44
+ const focus = useFocusVisible();
45
+ const color = selected ? theme.colors.foreground : theme.colors.mutedForeground;
46
+ return (_jsxs(Pressable, { testID: testID, accessibilityRole: "radio", accessibilityLabel: label, accessibilityState: { checked: selected, disabled }, disabled: disabled, onPress: onPress, onFocus: focus.onFocus, onBlur: focus.onBlur,
47
+ // The track is 32pt; reach the 44pt touch target vertically.
48
+ hitSlop: { top: 6, bottom: 6 }, style: ({ pressed }) => [
49
+ styles.segment,
50
+ showLabel ? styles.segmentStretch : styles.segmentCompact,
51
+ selected && { backgroundColor: theme.colors.background, borderColor: theme.colors.border },
52
+ pressed && !selected && { opacity: interaction.pressedOpacity },
53
+ Platform.OS === "web" && {
54
+ cursor: (disabled ? "not-allowed" : "pointer"),
55
+ outlineStyle: "none",
56
+ },
57
+ focus.focused && !disabled && getFocusRingStyle(),
58
+ ], children: [_jsx(Icon, { name: icon, size: 16, color: color, decorative: true }), showLabel && (_jsx(StyledText, { selectable: false, numberOfLines: 1, fontWeight: "medium", style: { fontSize: 13, lineHeight: 18, color }, children: label }))] }));
59
+ }
60
+ const styles = /*#__PURE__*/ StyleSheet.create({
61
+ track: {
62
+ flexDirection: "row",
63
+ height: TRACK_HEIGHT,
64
+ padding: TRACK_INSET,
65
+ borderRadius: spacing.radiusMd,
66
+ },
67
+ trackCompact: {
68
+ alignSelf: "flex-start",
69
+ },
70
+ trackStretch: {
71
+ alignSelf: "stretch",
72
+ },
73
+ segment: {
74
+ flexDirection: "row",
75
+ alignItems: "center",
76
+ justifyContent: "center",
77
+ gap: spacing.xs,
78
+ borderRadius: spacing.radiusSm,
79
+ borderWidth: StyleSheet.hairlineWidth,
80
+ borderColor: "transparent",
81
+ },
82
+ segmentCompact: {
83
+ width: ICON_SEGMENT_WIDTH,
84
+ },
85
+ segmentStretch: {
86
+ flex: 1,
87
+ paddingHorizontal: spacing.sm,
88
+ },
89
+ });
90
+ export { ThemeSelector };
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import * as TogglePrimitive from "@rn-primitives/toggle";
3
3
  import { ViewStyle, StyleProp } from "react-native";
4
- import type { IconName } from "./Icon.js";
4
+ import type { IconProps } from "./Icon.js";
5
5
  export type ToggleVariant = "default" | "outline";
6
6
  export type ToggleSize = "sm" | "default" | "lg";
7
7
  export type ToggleShape = "default" | "square" | "circle";
@@ -48,7 +48,8 @@ declare function Toggle({ variant, size, shape, loading, iconOnly, style: styleO
48
48
  /**
49
49
  * ToggleIcon Component
50
50
  * Icon wrapper for use inside Toggle buttons
51
- * Automatically inherits sizing from parent Toggle
51
+ * Takes `Icon`'s props; the color defaults to the Toggle's text color, and the
52
+ * icon is decorative (the control's label names it).
52
53
  *
53
54
  * Usage:
54
55
  * ```tsx
@@ -57,11 +58,6 @@ declare function Toggle({ variant, size, shape, loading, iconOnly, style: styleO
57
58
  * </Toggle>
58
59
  * ```
59
60
  */
60
- interface ToggleIconProps {
61
- name: IconName;
62
- size?: number;
63
- color?: string;
64
- }
65
- declare function ToggleIcon({ name, size, color }: ToggleIconProps): React.JSX.Element;
61
+ declare function ToggleIcon(props: IconProps): React.JSX.Element;
66
62
  export { Toggle, ToggleIcon };
67
63
  export type { ToggleProps };
@@ -179,9 +179,22 @@ function Toggle({ variant = "default", size = "default", shape = "default", load
179
179
  busy: loading,
180
180
  }, children: loading ? (_jsxs(_Fragment, { children: [_jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(ActivityIndicator, { size: "small", color: textColor }) }), hitTarget] })) : typeof children === "function" ? ((state) => (_jsxs(_Fragment, { children: [_jsx(TextSelectabilityContext.Provider, { value: false, children: children(state) }), hitTarget] }))) : (_jsxs(_Fragment, { children: [_jsx(TextSelectabilityContext.Provider, { value: false, children: children }), hitTarget] })) }) }) }) }));
181
181
  }
182
- function ToggleIcon({ name, size, color }) {
182
+ /**
183
+ * ToggleIcon Component
184
+ * Icon wrapper for use inside Toggle buttons
185
+ * Takes `Icon`'s props; the color defaults to the Toggle's text color, and the
186
+ * icon is decorative (the control's label names it).
187
+ *
188
+ * Usage:
189
+ * ```tsx
190
+ * <Toggle pressed={pressed} onPressedChange={setPressed}>
191
+ * <ToggleIcon name="bold" />
192
+ * </Toggle>
193
+ * ```
194
+ */
195
+ function ToggleIcon(props) {
183
196
  const contextColor = React.use(TextColorContext);
184
- return _jsx(Icon, { name: name, size: size || spacing.iconMd, color: color || contextColor });
197
+ return _jsx(Icon, { decorative: true, color: contextColor, ...props });
185
198
  }
186
199
  const styles = /*#__PURE__*/ StyleSheet.create({
187
200
  root: {
@@ -1,4 +1,4 @@
1
- import type { IconName } from "./Icon.js";
1
+ import type { IconProps } from "./Icon.js";
2
2
  import * as ToggleGroupPrimitive from "@rn-primitives/toggle-group";
3
3
  import * as React from "react";
4
4
  type ToggleGroupVariant = "default" | "outline";
@@ -62,7 +62,8 @@ declare function ToggleGroupItem({ isFirst, isLast, children, ...props }: Toggle
62
62
  /**
63
63
  * ToggleGroupIcon Component
64
64
  * Icon wrapper for use inside ToggleGroup items
65
- * Automatically inherits sizing from parent ToggleGroup
65
+ * Takes `Icon`'s props; the color defaults to the ToggleGroup's text color, and the
66
+ * icon is decorative (the control's label names it).
66
67
  *
67
68
  * Usage:
68
69
  * ```tsx
@@ -71,11 +72,6 @@ declare function ToggleGroupItem({ isFirst, isLast, children, ...props }: Toggle
71
72
  * </ToggleGroupItem>
72
73
  * ```
73
74
  */
74
- interface ToggleGroupIconProps {
75
- name: IconName;
76
- size?: number;
77
- color?: string;
78
- }
79
- declare function ToggleGroupIcon({ name, size, color }: ToggleGroupIconProps): React.JSX.Element;
75
+ declare function ToggleGroupIcon(props: IconProps): React.JSX.Element;
80
76
  export { ToggleGroup, ToggleGroupIcon, ToggleGroupItem };
81
77
  export type { ToggleGroupProps, ToggleGroupSize, ToggleGroupVariant };
@@ -206,9 +206,22 @@ function ToggleGroupItem({ isFirst = false, isLast = false, children, ...props }
206
206
  ...(focused && !props.disabled ? focusRingStyle : null),
207
207
  }, hitSlop: DEFAULT_HIT_SLOP, children: typeof children === "function" ? ((state) => (_jsxs(_Fragment, { children: [_jsx(TextSelectabilityContext.Provider, { value: false, children: children(state) }), hitTarget] }))) : (_jsxs(_Fragment, { children: [_jsx(TextSelectabilityContext.Provider, { value: false, children: children }), hitTarget] })) }) }) }) }));
208
208
  }
209
- function ToggleGroupIcon({ name, size, color }) {
209
+ /**
210
+ * ToggleGroupIcon Component
211
+ * Icon wrapper for use inside ToggleGroup items
212
+ * Takes `Icon`'s props; the color defaults to the ToggleGroup's text color, and the
213
+ * icon is decorative (the control's label names it).
214
+ *
215
+ * Usage:
216
+ * ```tsx
217
+ * <ToggleGroupItem value="bold">
218
+ * <ToggleGroupIcon name="bold" />
219
+ * </ToggleGroupItem>
220
+ * ```
221
+ */
222
+ function ToggleGroupIcon(props) {
210
223
  const contextColor = React.use(TextColorContext);
211
- return _jsx(Icon, { name: name, size: size || spacing.iconMd, color: color || contextColor });
224
+ return _jsx(Icon, { decorative: true, color: contextColor, ...props });
212
225
  }
213
226
  const styles = /*#__PURE__*/ StyleSheet.create({
214
227
  item: {
@@ -0,0 +1,143 @@
1
+ export declare const BUILTIN_ICONS: {
2
+ readonly check: readonly [readonly ["path", {
3
+ readonly d: "M20 6 9 17l-5-5";
4
+ }]];
5
+ readonly "chevron-down": readonly [readonly ["path", {
6
+ readonly d: "m6 9 6 6 6-6";
7
+ }]];
8
+ readonly "chevron-right": readonly [readonly ["path", {
9
+ readonly d: "m9 18 6-6-6-6";
10
+ }]];
11
+ readonly "chevron-up": readonly [readonly ["path", {
12
+ readonly d: "m18 15-6-6-6 6";
13
+ }]];
14
+ readonly "circle-alert": readonly [readonly ["circle", {
15
+ readonly cx: "12";
16
+ readonly cy: "12";
17
+ readonly r: "10";
18
+ }], readonly ["line", {
19
+ readonly x1: "12";
20
+ readonly x2: "12";
21
+ readonly y1: "8";
22
+ readonly y2: "12";
23
+ }], readonly ["line", {
24
+ readonly x1: "12";
25
+ readonly x2: "12.01";
26
+ readonly y1: "16";
27
+ readonly y2: "16";
28
+ }]];
29
+ readonly "circle-check-big": readonly [readonly ["path", {
30
+ readonly d: "M21.801 10A10 10 0 1 1 17 3.335";
31
+ }], readonly ["path", {
32
+ readonly d: "m9 11 3 3L22 4";
33
+ }]];
34
+ readonly eye: readonly [readonly ["path", {
35
+ readonly d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0";
36
+ }], readonly ["circle", {
37
+ readonly cx: "12";
38
+ readonly cy: "12";
39
+ readonly r: "3";
40
+ }]];
41
+ readonly "eye-off": readonly [readonly ["path", {
42
+ readonly d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49";
43
+ }], readonly ["path", {
44
+ readonly d: "M14.084 14.158a3 3 0 0 1-4.242-4.242";
45
+ }], readonly ["path", {
46
+ readonly d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143";
47
+ }], readonly ["path", {
48
+ readonly d: "m2 2 20 20";
49
+ }]];
50
+ readonly info: readonly [readonly ["circle", {
51
+ readonly cx: "12";
52
+ readonly cy: "12";
53
+ readonly r: "10";
54
+ }], readonly ["path", {
55
+ readonly d: "M12 16v-4";
56
+ }], readonly ["path", {
57
+ readonly d: "M12 8h.01";
58
+ }]];
59
+ readonly minus: readonly [readonly ["path", {
60
+ readonly d: "M5 12h14";
61
+ }]];
62
+ readonly monitor: readonly [readonly ["rect", {
63
+ readonly width: "20";
64
+ readonly height: "14";
65
+ readonly x: "2";
66
+ readonly y: "3";
67
+ readonly rx: "2";
68
+ }], readonly ["line", {
69
+ readonly x1: "8";
70
+ readonly x2: "16";
71
+ readonly y1: "21";
72
+ readonly y2: "21";
73
+ }], readonly ["line", {
74
+ readonly x1: "12";
75
+ readonly x2: "12";
76
+ readonly y1: "17";
77
+ readonly y2: "21";
78
+ }]];
79
+ readonly moon: readonly [readonly ["path", {
80
+ readonly d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401";
81
+ }]];
82
+ readonly smartphone: readonly [readonly ["rect", {
83
+ readonly width: "14";
84
+ readonly height: "20";
85
+ readonly x: "5";
86
+ readonly y: "2";
87
+ readonly rx: "2";
88
+ readonly ry: "2";
89
+ }], readonly ["path", {
90
+ readonly d: "M12 18h.01";
91
+ }]];
92
+ readonly sun: readonly [readonly ["circle", {
93
+ readonly cx: "12";
94
+ readonly cy: "12";
95
+ readonly r: "4";
96
+ }], readonly ["path", {
97
+ readonly d: "M12 2v2";
98
+ }], readonly ["path", {
99
+ readonly d: "M12 20v2";
100
+ }], readonly ["path", {
101
+ readonly d: "m4.93 4.93 1.41 1.41";
102
+ }], readonly ["path", {
103
+ readonly d: "m17.66 17.66 1.41 1.41";
104
+ }], readonly ["path", {
105
+ readonly d: "M2 12h2";
106
+ }], readonly ["path", {
107
+ readonly d: "M20 12h2";
108
+ }], readonly ["path", {
109
+ readonly d: "m6.34 17.66-1.41 1.41";
110
+ }], readonly ["path", {
111
+ readonly d: "m19.07 4.93-1.41 1.41";
112
+ }]];
113
+ readonly "trending-down": readonly [readonly ["path", {
114
+ readonly d: "M16 17h6v-6";
115
+ }], readonly ["path", {
116
+ readonly d: "m22 17-8.5-8.5-5 5L2 7";
117
+ }]];
118
+ readonly "trending-up": readonly [readonly ["path", {
119
+ readonly d: "M16 7h6v6";
120
+ }], readonly ["path", {
121
+ readonly d: "m22 7-8.5 8.5-5-5L2 17";
122
+ }]];
123
+ readonly "triangle-alert": readonly [readonly ["path", {
124
+ readonly d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3";
125
+ }], readonly ["path", {
126
+ readonly d: "M12 9v4";
127
+ }], readonly ["path", {
128
+ readonly d: "M12 17h.01";
129
+ }]];
130
+ readonly user: readonly [readonly ["path", {
131
+ readonly d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2";
132
+ }], readonly ["circle", {
133
+ readonly cx: "12";
134
+ readonly cy: "7";
135
+ readonly r: "4";
136
+ }]];
137
+ readonly x: readonly [readonly ["path", {
138
+ readonly d: "M18 6 6 18";
139
+ }], readonly ["path", {
140
+ readonly d: "m6 6 12 12";
141
+ }]];
142
+ };
143
+ export type BuiltinIconName = keyof typeof BUILTIN_ICONS;
@@ -0,0 +1,25 @@
1
+ /* eslint-disable */
2
+ // GENERATED by `bun run ui:icons` from icon-names.json (lucide 1.55.0). Do not edit.
3
+ // The glyphs the package's own components draw. Apps register every other
4
+ // name through `client/icons.json`, `expo-ui-icons`, and `registerIcons`.
5
+ export const BUILTIN_ICONS = {
6
+ "check": [["path", { d: "M20 6 9 17l-5-5" }]],
7
+ "chevron-down": [["path", { d: "m6 9 6 6 6-6" }]],
8
+ "chevron-right": [["path", { d: "m9 18 6-6-6-6" }]],
9
+ "chevron-up": [["path", { d: "m18 15-6-6-6 6" }]],
10
+ "circle-alert": [["circle", { cx: "12", cy: "12", r: "10" }], ["line", { x1: "12", x2: "12", y1: "8", y2: "12" }], ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16" }]],
11
+ "circle-check-big": [["path", { d: "M21.801 10A10 10 0 1 1 17 3.335" }], ["path", { d: "m9 11 3 3L22 4" }]],
12
+ "eye": [["path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }], ["circle", { cx: "12", cy: "12", r: "3" }]],
13
+ "eye-off": [["path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }], ["path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }], ["path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }], ["path", { d: "m2 2 20 20" }]],
14
+ "info": [["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "M12 16v-4" }], ["path", { d: "M12 8h.01" }]],
15
+ "minus": [["path", { d: "M5 12h14" }]],
16
+ "monitor": [["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }], ["line", { x1: "8", x2: "16", y1: "21", y2: "21" }], ["line", { x1: "12", x2: "12", y1: "17", y2: "21" }]],
17
+ "moon": [["path", { d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" }]],
18
+ "smartphone": [["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2" }], ["path", { d: "M12 18h.01" }]],
19
+ "sun": [["circle", { cx: "12", cy: "12", r: "4" }], ["path", { d: "M12 2v2" }], ["path", { d: "M12 20v2" }], ["path", { d: "m4.93 4.93 1.41 1.41" }], ["path", { d: "m17.66 17.66 1.41 1.41" }], ["path", { d: "M2 12h2" }], ["path", { d: "M20 12h2" }], ["path", { d: "m6.34 17.66-1.41 1.41" }], ["path", { d: "m19.07 4.93-1.41 1.41" }]],
20
+ "trending-down": [["path", { d: "M16 17h6v-6" }], ["path", { d: "m22 17-8.5-8.5-5 5L2 7" }]],
21
+ "trending-up": [["path", { d: "M16 7h6v6" }], ["path", { d: "m22 7-8.5 8.5-5-5L2 17" }]],
22
+ "triangle-alert": [["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }], ["path", { d: "M12 9v4" }], ["path", { d: "M12 17h.01" }]],
23
+ "user": [["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }], ["circle", { cx: "12", cy: "7", r: "4" }]],
24
+ "x": [["path", { d: "M18 6 6 18" }], ["path", { d: "m6 6 12 12" }]],
25
+ };
@@ -0,0 +1,37 @@
1
+ import type { ComponentType } from "react";
2
+ import type { StyleProp, ViewProps, ViewStyle } from "react-native";
3
+ /** The SVG elements a glyph is drawn with: every element Lucide uses. */
4
+ export type IconTag = "path" | "circle" | "rect" | "line" | "polyline" | "polygon" | "ellipse";
5
+ /**
6
+ * One icon as plain data: `[tag, attrs]` nodes on a 24×24 grid, stroked 2 px
7
+ * with round caps and joins in the icon's color. `attrs` are the element's own
8
+ * SVG attributes (`d`, `cx`, `points`, …) as strings; a `fill` value is
9
+ * `currentColor`, which resolves to the icon's color.
10
+ */
11
+ export type IconGlyph = ReadonlyArray<readonly [tag: IconTag, attrs: Readonly<Record<string, string>>]>;
12
+ export type IconAccessibilityProps = Pick<ViewProps, "accessible" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityLabel" | "aria-hidden" | "aria-label" | "role">;
13
+ /**
14
+ * Escape hatch for non-Lucide art (a brand mark, an illustration): any
15
+ * component that takes `size` and `color`. Lucide icons go through `name`; the
16
+ * design-system lint rule (a follow-up) rejects Lucide imports here.
17
+ */
18
+ export type IconComponent = ComponentType<{
19
+ size: number;
20
+ color: string;
21
+ style?: StyleProp<ViewStyle>;
22
+ } & Partial<IconAccessibilityProps>>;
23
+ /**
24
+ * Props of the platform renderers (`IconGlyph.tsx` on web and the server,
25
+ * `IconGlyph.native.tsx` on iOS and Android). `Icon` resolves the color and
26
+ * the accessibility props before it hands a glyph over.
27
+ */
28
+ export type IconGlyphProps = {
29
+ glyph: IconGlyph;
30
+ size: number;
31
+ /** A resolved color: a literal, or a `var(--c-*)` theme variable on web. */
32
+ color: string;
33
+ /** @default 2 */
34
+ strokeWidth?: number;
35
+ style?: StyleProp<ViewStyle>;
36
+ testID?: string;
37
+ } & IconAccessibilityProps;
@@ -0,0 +1 @@
1
+ export {};