@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.
- package/CHANGELOG.md +68 -0
- package/LLM_USAGE.md +10 -4
- package/README.md +104 -50
- package/bin/expo-ui-icons.mjs +509 -0
- package/dist/components/Icon.d.ts +57 -26
- package/dist/components/Icon.js +40 -19
- 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/ThemeSelector.d.ts +52 -0
- package/dist/components/ThemeSelector.js +90 -0
- package/dist/components/Toggle.d.ts +4 -8
- package/dist/components/Toggle.js +15 -2
- package/dist/components/ToggleGroup.d.ts +4 -8
- package/dist/components/ToggleGroup.js +15 -2
- 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 +1 -0
- package/dist/components/index.js +1 -0
- package/dist/design-system.json +276 -131
- package/dist/icons/LICENSE.lucide +43 -0
- package/dist/icons/lucide.json +2144 -0
- package/llms-full.md +3 -2
- package/package.json +15 -7
- package/dist/components/iconRegistry.generated.d.ts +0 -148
- package/dist/components/iconRegistry.generated.js +0 -296
package/dist/components/Icon.js
CHANGED
|
@@ -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 {
|
|
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
|
-
*
|
|
52
|
-
*
|
|
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={
|
|
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
|
|
85
|
-
const
|
|
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)
|
|
88
|
-
//
|
|
89
|
-
if (!
|
|
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(
|
|
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 {
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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 {
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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 {};
|