@idealyst/components 1.1.7 → 1.1.9
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/package.json +3 -3
- package/plugin/web.js +280 -532
- package/src/Accordion/Accordion.native.tsx +8 -6
- package/src/Accordion/Accordion.styles.old.tsx +298 -0
- package/src/Accordion/Accordion.styles.tsx +102 -236
- package/src/Accordion/Accordion.web.tsx +1 -3
- package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +44 -74
- package/src/Alert/Alert.native.tsx +16 -6
- package/src/Alert/Alert.styles.old.tsx +209 -0
- package/src/Alert/Alert.styles.tsx +67 -149
- package/src/Alert/Alert.web.tsx +3 -4
- package/src/Avatar/Avatar.styles.old.tsx +99 -0
- package/src/Avatar/Avatar.styles.tsx +35 -80
- package/src/Badge/Badge.styles.old.tsx +157 -0
- package/src/Badge/Badge.styles.tsx +61 -121
- package/src/Badge/Badge.web.tsx +8 -15
- package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
- package/src/Breadcrumb/Breadcrumb.styles.tsx +83 -200
- package/src/Breadcrumb/Breadcrumb.web.tsx +31 -30
- package/src/Button/Button.native.tsx +14 -21
- package/src/Button/Button.styles.tsx +103 -140
- package/src/Button/Button.web.tsx +9 -19
- package/src/Card/Card.native.tsx +7 -11
- package/src/Card/Card.styles.old.tsx +160 -0
- package/src/Card/Card.styles.tsx +105 -142
- package/src/Card/Card.web.tsx +5 -4
- package/src/Checkbox/Checkbox.native.tsx +9 -5
- package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
- package/src/Checkbox/Checkbox.styles.tsx +104 -216
- package/src/Checkbox/Checkbox.web.tsx +7 -8
- package/src/Chip/Chip.styles.old.tsx +184 -0
- package/src/Chip/Chip.styles.tsx +34 -72
- package/src/Chip/Chip.web.tsx +3 -5
- package/src/Dialog/Dialog.native.tsx +7 -4
- package/src/Dialog/Dialog.styles.old.tsx +202 -0
- package/src/Dialog/Dialog.styles.tsx +69 -133
- package/src/Dialog/Dialog.web.tsx +3 -3
- package/src/Dialog/types.ts +1 -1
- package/src/Divider/Divider.styles.old.tsx +172 -0
- package/src/Divider/Divider.styles.tsx +62 -84
- package/src/Icon/Icon.native.tsx +8 -8
- package/src/Icon/Icon.styles.old.tsx +81 -0
- package/src/Icon/Icon.styles.tsx +52 -66
- package/src/Icon/Icon.web.tsx +62 -21
- package/src/Icon/IconRegistry.native.ts +41 -0
- package/src/Icon/IconRegistry.ts +107 -0
- package/src/Icon/IconSvg/IconSvg.web.tsx +28 -5
- package/src/Icon/icon-resolver.ts +12 -43
- package/src/Icon/index.native.ts +2 -1
- package/src/Icon/index.ts +1 -0
- package/src/Icon/index.web.ts +1 -0
- package/src/Image/Image.styles.old.tsx +69 -0
- package/src/Image/Image.styles.tsx +46 -60
- package/src/Input/Input.native.tsx +138 -53
- package/src/Input/Input.styles.old.tsx +289 -0
- package/src/Input/Input.styles.tsx +134 -232
- package/src/Input/Input.web.tsx +5 -8
- package/src/List/List.native.tsx +5 -2
- package/src/List/List.styles.old.tsx +242 -0
- package/src/List/List.styles.tsx +179 -215
- package/src/List/ListItem.native.tsx +16 -11
- package/src/List/ListItem.web.tsx +26 -16
- package/src/Menu/Menu.styles.old.tsx +197 -0
- package/src/Menu/Menu.styles.tsx +68 -150
- package/src/Menu/MenuItem.native.tsx +5 -3
- package/src/Menu/MenuItem.styles.old.tsx +114 -0
- package/src/Menu/MenuItem.styles.tsx +57 -89
- package/src/Menu/MenuItem.web.tsx +10 -7
- package/src/Popover/Popover.native.tsx +10 -4
- package/src/Popover/Popover.styles.old.tsx +135 -0
- package/src/Popover/Popover.styles.tsx +51 -112
- package/src/Pressable/Pressable.styles.old.tsx +27 -0
- package/src/Pressable/Pressable.styles.tsx +35 -27
- package/src/Progress/Progress.styles.old.tsx +200 -0
- package/src/Progress/Progress.styles.tsx +75 -164
- package/src/RadioButton/RadioButton.native.tsx +4 -3
- package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
- package/src/RadioButton/RadioButton.styles.tsx +83 -154
- package/src/RadioButton/RadioButton.web.tsx +2 -2
- package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
- package/src/SVGImage/SVGImage.styles.tsx +35 -78
- package/src/Screen/Screen.native.tsx +19 -26
- package/src/Screen/Screen.styles.old.tsx +87 -0
- package/src/Screen/Screen.styles.tsx +103 -68
- package/src/Screen/Screen.web.tsx +2 -2
- package/src/Select/Select.native.tsx +42 -33
- package/src/Select/Select.styles.old.tsx +353 -0
- package/src/Select/Select.styles.tsx +214 -300
- package/src/Select/Select.web.tsx +45 -33
- package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
- package/src/Skeleton/Skeleton.styles.tsx +29 -53
- package/src/Slider/Slider.styles.old.tsx +259 -0
- package/src/Slider/Slider.styles.tsx +153 -234
- package/src/Slider/Slider.web.tsx +2 -4
- package/src/Switch/Switch.native.tsx +9 -7
- package/src/Switch/Switch.styles.old.tsx +203 -0
- package/src/Switch/Switch.styles.tsx +101 -174
- package/src/Switch/Switch.web.tsx +7 -8
- package/src/TabBar/TabBar.native.tsx +3 -2
- package/src/TabBar/TabBar.styles.old.tsx +343 -0
- package/src/TabBar/TabBar.styles.tsx +145 -279
- package/src/Table/Table.native.tsx +180 -68
- package/src/Table/Table.styles.old.tsx +311 -0
- package/src/Table/Table.styles.tsx +140 -281
- package/src/Table/Table.web.tsx +169 -70
- package/src/Text/Text.native.tsx +1 -3
- package/src/Text/Text.style.demo.tsx +16 -0
- package/src/Text/Text.styles.old.tsx +219 -0
- package/src/Text/Text.styles.tsx +94 -84
- package/src/Text/Text.web.tsx +3 -2
- package/src/Text/index.ts +1 -0
- package/src/TextArea/TextArea.native.tsx +21 -8
- package/src/TextArea/TextArea.styles.old.tsx +213 -0
- package/src/TextArea/TextArea.styles.tsx +87 -187
- package/src/TextArea/TextArea.web.tsx +17 -6
- package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
- package/src/Tooltip/Tooltip.styles.tsx +32 -56
- package/src/Video/Video.styles.old.tsx +51 -0
- package/src/Video/Video.styles.tsx +32 -44
- package/src/View/View.native.tsx +41 -13
- package/src/View/View.styles.old.tsx +125 -0
- package/src/View/View.styles.tsx +76 -106
- package/src/View/View.web.tsx +5 -21
- package/src/View/types.ts +31 -3
- package/src/examples/ButtonExamples.tsx +20 -0
- package/src/examples/CardExamples.tsx +0 -6
- package/src/extensions/extendComponent.ts +61 -0
- package/src/index.ts +1 -1
package/src/Icon/Icon.native.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { forwardRef, useMemo } from 'react';
|
|
2
2
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
3
3
|
import { IconProps } from './types';
|
|
4
|
-
import { iconStyles
|
|
4
|
+
import { iconStyles } from './Icon.styles';
|
|
5
5
|
import { useUnistyles } from 'react-native-unistyles';
|
|
6
6
|
|
|
7
7
|
const Icon = forwardRef<any, IconProps>(({
|
|
@@ -14,18 +14,17 @@ const Icon = forwardRef<any, IconProps>(({
|
|
|
14
14
|
accessibilityLabel,
|
|
15
15
|
id,
|
|
16
16
|
}: IconProps, ref) => {
|
|
17
|
+
const { theme } = useUnistyles();
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
// Call dynamic style with variants
|
|
19
|
+
// Call dynamic style with variants - includes theme-reactive color
|
|
20
20
|
const iconStyle = (iconStyles.icon as any)({ color, intent, size });
|
|
21
21
|
|
|
22
|
-
const { theme } = useUnistyles();
|
|
23
|
-
|
|
24
22
|
const iconSize = useMemo(() => {
|
|
25
|
-
return
|
|
26
|
-
}, [
|
|
23
|
+
return iconStyle.width;
|
|
24
|
+
}, [iconStyle]);
|
|
27
25
|
|
|
28
|
-
//
|
|
26
|
+
// Extract color from iconStyle for explicit color prop (RN vector icons need this)
|
|
27
|
+
const iconColor = iconStyle.color;
|
|
29
28
|
|
|
30
29
|
return (
|
|
31
30
|
<MaterialCommunityIcons
|
|
@@ -33,6 +32,7 @@ const Icon = forwardRef<any, IconProps>(({
|
|
|
33
32
|
nativeID={id}
|
|
34
33
|
size={iconSize}
|
|
35
34
|
name={name}
|
|
35
|
+
color={iconColor}
|
|
36
36
|
style={[iconStyle, style]}
|
|
37
37
|
testID={testID}
|
|
38
38
|
accessibilityLabel={accessibilityLabel}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
+
import { Theme, StylesheetStyles, Intent, Color, getColorFromString } from '@idealyst/theme';
|
|
3
|
+
import { buildSizeVariants } from '../utils/buildSizeVariants';
|
|
4
|
+
import { IconSizeVariant } from './types';
|
|
5
|
+
import { applyExtensions } from '../extensions/applyExtension';
|
|
6
|
+
|
|
7
|
+
type IconVariants = {
|
|
8
|
+
size: IconSizeVariant;
|
|
9
|
+
intent?: Intent;
|
|
10
|
+
color?: Color;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ExpandedIconStyles = StylesheetStyles<keyof IconVariants>;
|
|
14
|
+
|
|
15
|
+
export type IconStylesheet = {
|
|
16
|
+
icon: ExpandedIconStyles;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create color variants for icon
|
|
21
|
+
*/
|
|
22
|
+
function getIconColor(theme: Theme, color?: Color, intent?: Intent): string {
|
|
23
|
+
if (intent) {
|
|
24
|
+
return theme.intents[intent]?.primary
|
|
25
|
+
} else if (color) {
|
|
26
|
+
return getColorFromString(theme, color);
|
|
27
|
+
}
|
|
28
|
+
return theme.colors.text.primary;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildIconSize(theme: Theme, size?: IconSizeVariant) {
|
|
32
|
+
// Handle direct numeric sizes
|
|
33
|
+
if (typeof size === 'number') {
|
|
34
|
+
return {
|
|
35
|
+
width: size,
|
|
36
|
+
height: size,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Default to 'md' if size is undefined
|
|
41
|
+
const sizeKey = size || 'md';
|
|
42
|
+
const iconSize = theme.sizes.icon[sizeKey];
|
|
43
|
+
|
|
44
|
+
if (typeof iconSize === 'number') {
|
|
45
|
+
return {
|
|
46
|
+
width: iconSize,
|
|
47
|
+
height: iconSize,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return buildSizeVariants(theme, 'icon', (size) => ({
|
|
52
|
+
width: size.width,
|
|
53
|
+
height: size.height,
|
|
54
|
+
}))[sizeKey];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createIconStyles(theme: Theme) {
|
|
58
|
+
return ({ color, intent, size }: Partial<IconVariants>) => {
|
|
59
|
+
const iconSize = buildIconSize(theme, size);
|
|
60
|
+
return {
|
|
61
|
+
width: iconSize.width,
|
|
62
|
+
height: iconSize.height,
|
|
63
|
+
color: getIconColor(theme, color, intent),
|
|
64
|
+
_web: {
|
|
65
|
+
display: 'inline-block',
|
|
66
|
+
verticalAlign: 'middle',
|
|
67
|
+
flexShrink: 0,
|
|
68
|
+
lineHeight: 0,
|
|
69
|
+
},
|
|
70
|
+
} as const;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
|
|
75
|
+
// transform on native cannot resolve function calls to extract variant structures.
|
|
76
|
+
export const iconStyles = StyleSheet.create((theme: Theme) => {
|
|
77
|
+
// Apply extensions to main visual elements
|
|
78
|
+
return applyExtensions('Icon', theme, {
|
|
79
|
+
icon: createIconStyles(theme),
|
|
80
|
+
});
|
|
81
|
+
});
|
package/src/Icon/Icon.styles.tsx
CHANGED
|
@@ -1,85 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon styles using defineStyle with dynamic functions.
|
|
3
|
+
*/
|
|
1
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
5
|
+
import { defineStyle, ThemeStyleWrapper, getColorFromString } from '@idealyst/theme';
|
|
6
|
+
import type { Theme as BaseTheme, Intent, Color } from '@idealyst/theme';
|
|
4
7
|
import { IconSizeVariant } from './types';
|
|
5
|
-
import { applyExtensions } from '../extensions/applyExtension';
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
// Required: Unistyles must see StyleSheet usage in original source to process this file
|
|
10
|
+
void StyleSheet;
|
|
11
|
+
|
|
12
|
+
// Wrap theme for $iterator support
|
|
13
|
+
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
14
|
+
|
|
15
|
+
export type IconVariants = {
|
|
8
16
|
size: IconSizeVariant;
|
|
9
17
|
intent?: Intent;
|
|
10
18
|
color?: Color;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type ExpandedIconStyles = StylesheetStyles<keyof IconVariants>;
|
|
19
|
+
};
|
|
14
20
|
|
|
15
|
-
export type
|
|
16
|
-
icon: ExpandedIconStyles;
|
|
17
|
-
}
|
|
21
|
+
export type IconDynamicProps = Partial<IconVariants>;
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
|
-
*
|
|
24
|
+
* Icon styles with dynamic color/size handling.
|
|
21
25
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
return theme.colors.text.primary;
|
|
29
|
-
}
|
|
26
|
+
export const iconStyles = defineStyle('Icon', (theme: Theme) => ({
|
|
27
|
+
icon: ({ color, intent, size = 'md' }: IconDynamicProps) => {
|
|
28
|
+
// Handle size - can be a named size or number
|
|
29
|
+
let iconWidth: number;
|
|
30
|
+
let iconHeight: number;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
if (typeof size === 'number') {
|
|
33
|
+
iconWidth = size;
|
|
34
|
+
iconHeight = size;
|
|
35
|
+
} else {
|
|
36
|
+
const sizeKey = size || 'md';
|
|
37
|
+
const iconSize = theme.sizes.icon[sizeKey];
|
|
38
|
+
if (typeof iconSize === 'number') {
|
|
39
|
+
iconWidth = iconSize;
|
|
40
|
+
iconHeight = iconSize;
|
|
41
|
+
} else {
|
|
42
|
+
iconWidth = (iconSize?.width as number) ?? 24;
|
|
43
|
+
iconHeight = (iconSize?.height as number) ?? 24;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
// Get color - intent takes priority, then color prop, then default
|
|
48
|
+
const iconColor = intent
|
|
49
|
+
? theme.intents[intent]?.primary
|
|
50
|
+
: color
|
|
51
|
+
? getColorFromString(theme as unknown as BaseTheme, color)
|
|
52
|
+
: theme.colors.text.primary;
|
|
43
53
|
|
|
44
|
-
if (typeof iconSize === 'number') {
|
|
45
54
|
return {
|
|
46
|
-
width:
|
|
47
|
-
height:
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return buildSizeVariants(theme, 'icon', (size) => ({
|
|
52
|
-
width: size.width,
|
|
53
|
-
height: size.height,
|
|
54
|
-
}))[sizeKey];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function createIconStyles(theme: Theme) {
|
|
58
|
-
return ({ color, intent, size }: Partial<IconVariants>) => {
|
|
59
|
-
const iconSize = buildIconSize(theme, size);
|
|
60
|
-
return {
|
|
61
|
-
width: iconSize.width,
|
|
62
|
-
height: iconSize.height,
|
|
63
|
-
color: getIconColor(theme, color, intent),
|
|
55
|
+
width: iconWidth,
|
|
56
|
+
height: iconHeight,
|
|
57
|
+
color: iconColor,
|
|
64
58
|
_web: {
|
|
65
|
-
|
|
59
|
+
fontSize: iconWidth,
|
|
60
|
+
width: '1em',
|
|
61
|
+
height: '1em',
|
|
62
|
+
display: 'inline-flex',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'center',
|
|
66
65
|
verticalAlign: 'middle',
|
|
67
66
|
flexShrink: 0,
|
|
68
|
-
lineHeight:
|
|
67
|
+
lineHeight: 1,
|
|
69
68
|
},
|
|
70
69
|
} as const;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
|
|
75
|
-
// transform on native cannot resolve function calls to extract variant structures.
|
|
76
|
-
export const iconStyles = StyleSheet.create((theme: Theme) => {
|
|
77
|
-
// Apply extensions to main visual elements
|
|
78
|
-
const extended = applyExtensions('Icon', theme, {
|
|
79
|
-
icon: createIconStyles(theme),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
...extended,
|
|
84
|
-
};
|
|
85
|
-
});
|
|
70
|
+
},
|
|
71
|
+
}));
|
package/src/Icon/Icon.web.tsx
CHANGED
|
@@ -3,14 +3,12 @@ import MdiIcon from '@mdi/react';
|
|
|
3
3
|
import { IconProps } from './types';
|
|
4
4
|
import { iconStyles } from './Icon.styles';
|
|
5
5
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
6
|
+
import { useUnistyles } from 'react-native-unistyles';
|
|
6
7
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
8
|
+
import { getColorFromString, Intent, Color } from '@idealyst/theme';
|
|
9
|
+
import { IconRegistry } from './IconRegistry';
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
interface InternalIconProps extends IconProps {
|
|
10
|
-
path?: string; // Added by Babel plugin transformation
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const Icon = forwardRef<HTMLDivElement, IconProps>((props: InternalIconProps, ref) => {
|
|
11
|
+
const Icon = forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
|
|
14
12
|
const {
|
|
15
13
|
name,
|
|
16
14
|
size = 'md',
|
|
@@ -23,29 +21,72 @@ const Icon = forwardRef<HTMLDivElement, IconProps>((props: InternalIconProps, re
|
|
|
23
21
|
...restProps
|
|
24
22
|
} = props;
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
const { theme } = useUnistyles();
|
|
25
|
+
|
|
26
|
+
// Look up the icon path from the registry
|
|
27
|
+
const path = IconRegistry.get(name);
|
|
28
|
+
|
|
29
|
+
// Warn in development if icon is not registered
|
|
30
|
+
if (!path && process.env.NODE_ENV !== 'production') {
|
|
31
|
+
console.warn(
|
|
32
|
+
`[Icon] Icon "${name}" is not registered. ` +
|
|
33
|
+
`Add it to the 'icons' array in your babel config, or ensure it's used in a way that static analysis can detect.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Compute size from theme
|
|
38
|
+
let iconSize: number;
|
|
39
|
+
if (typeof size === 'number') {
|
|
40
|
+
iconSize = size;
|
|
41
|
+
} else {
|
|
42
|
+
const themeSize = theme.sizes.icon[size as keyof typeof theme.sizes.icon];
|
|
43
|
+
iconSize = typeof themeSize === 'number' ? themeSize : (themeSize?.width ?? 24);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Compute color from intent or color prop or default
|
|
47
|
+
const iconColor = intent
|
|
48
|
+
? theme.intents[intent as Intent]?.primary
|
|
49
|
+
: color
|
|
50
|
+
? getColorFromString(theme, color as Color)
|
|
51
|
+
: theme.colors.text.primary;
|
|
52
|
+
|
|
53
|
+
// Use getWebProps for className generation but override with computed values
|
|
54
|
+
const iconStyle = (iconStyles.icon as any)({ intent, color, size });
|
|
55
|
+
const iconProps = getWebProps([iconStyle, style]);
|
|
29
56
|
|
|
30
57
|
const mergedRef = useMergeRefs(ref, iconProps.ref);
|
|
31
58
|
|
|
32
|
-
// Use MDI React icon when path is provided (transformed by Babel plugin)
|
|
33
59
|
return (
|
|
34
|
-
<
|
|
60
|
+
<span
|
|
35
61
|
{...iconProps}
|
|
36
62
|
ref={mergedRef}
|
|
37
|
-
id={id}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
id={id}
|
|
64
|
+
style={{
|
|
65
|
+
...iconProps.style,
|
|
66
|
+
fontSize: iconSize,
|
|
67
|
+
width: '1em',
|
|
68
|
+
height: '1em',
|
|
69
|
+
display: 'inline-flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'center',
|
|
72
|
+
flexShrink: 0,
|
|
73
|
+
lineHeight: 1,
|
|
74
|
+
color: iconColor,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{path && (
|
|
78
|
+
<MdiIcon
|
|
79
|
+
path={path}
|
|
80
|
+
size="1em"
|
|
81
|
+
color="currentColor"
|
|
82
|
+
data-testid={testID}
|
|
83
|
+
aria-label={accessibilityLabel || name}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</span>
|
|
46
87
|
);
|
|
47
88
|
});
|
|
48
89
|
|
|
49
90
|
Icon.displayName = 'Icon';
|
|
50
91
|
|
|
51
|
-
export default Icon;
|
|
92
|
+
export default Icon;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Registry stub for native platform
|
|
3
|
+
*
|
|
4
|
+
* On native, icons are handled by react-native-vector-icons which uses
|
|
5
|
+
* icon names directly. The registry is only used on web for SVG path lookup.
|
|
6
|
+
*
|
|
7
|
+
* This stub exists to prevent import errors when code is shared between platforms.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class IconRegistryStub {
|
|
11
|
+
register(_name: string, _path: string): void {
|
|
12
|
+
// No-op on native
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
registerMany(_icons: Record<string, string>): void {
|
|
16
|
+
// No-op on native
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(_name: string): string | undefined {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
has(_name: string): boolean {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getRegisteredNames(): string[] {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get size(): number {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get isInitialized(): boolean {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const IconRegistry = new IconRegistryStub();
|
|
41
|
+
export { IconRegistryStub as IconRegistryClass };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Registry for web platform
|
|
3
|
+
*
|
|
4
|
+
* This registry stores SVG paths for icons that are populated at build time
|
|
5
|
+
* by the Babel plugin. At runtime, components look up icon paths by their
|
|
6
|
+
* canonical name (e.g., "home", "account-circle").
|
|
7
|
+
*
|
|
8
|
+
* The registry is populated by:
|
|
9
|
+
* 1. Static analysis - Babel plugin scans for icon names in JSX
|
|
10
|
+
* 2. Config icons - User specifies additional icons in babel config
|
|
11
|
+
*
|
|
12
|
+
* This approach:
|
|
13
|
+
* - Enables dynamic/variable icon names (if registered)
|
|
14
|
+
* - Tree-shakes unused icons (only registered icons are bundled)
|
|
15
|
+
* - Provides a single source of truth for icon resolution
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
type IconPath = string;
|
|
19
|
+
|
|
20
|
+
class IconRegistryClass {
|
|
21
|
+
private icons = new Map<string, IconPath>();
|
|
22
|
+
private initialized = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register a single icon
|
|
26
|
+
* @internal Called by generated registration code
|
|
27
|
+
*/
|
|
28
|
+
register(name: string, path: IconPath): void {
|
|
29
|
+
// Normalize the name (strip mdi: prefix, lowercase)
|
|
30
|
+
const normalizedName = this.normalizeName(name);
|
|
31
|
+
this.icons.set(normalizedName, path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register multiple icons at once
|
|
36
|
+
* @internal Called by generated registration code
|
|
37
|
+
*/
|
|
38
|
+
registerMany(icons: Record<string, IconPath>): void {
|
|
39
|
+
Object.entries(icons).forEach(([name, path]) => {
|
|
40
|
+
this.register(name, path);
|
|
41
|
+
});
|
|
42
|
+
this.initialized = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get an icon path by name
|
|
47
|
+
* Returns undefined if the icon is not registered
|
|
48
|
+
*/
|
|
49
|
+
get(name: string): IconPath | undefined {
|
|
50
|
+
const normalizedName = this.normalizeName(name);
|
|
51
|
+
return this.icons.get(normalizedName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if an icon is registered
|
|
56
|
+
*/
|
|
57
|
+
has(name: string): boolean {
|
|
58
|
+
const normalizedName = this.normalizeName(name);
|
|
59
|
+
return this.icons.has(normalizedName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all registered icon names
|
|
64
|
+
*/
|
|
65
|
+
getRegisteredNames(): string[] {
|
|
66
|
+
return Array.from(this.icons.keys());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the count of registered icons
|
|
71
|
+
*/
|
|
72
|
+
get size(): number {
|
|
73
|
+
return this.icons.size;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if the registry has been initialized
|
|
78
|
+
*/
|
|
79
|
+
get isInitialized(): boolean {
|
|
80
|
+
return this.initialized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Normalize icon name for consistent lookup
|
|
85
|
+
* - Strips "mdi:" prefix
|
|
86
|
+
* - Converts to lowercase for case-insensitive matching
|
|
87
|
+
*/
|
|
88
|
+
private normalizeName(name: string): string {
|
|
89
|
+
if (!name || typeof name !== 'string') {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Strip mdi: prefix if present
|
|
94
|
+
let normalized = name.startsWith('mdi:') ? name.slice(4) : name;
|
|
95
|
+
|
|
96
|
+
// Lowercase for consistent lookup
|
|
97
|
+
normalized = normalized.toLowerCase();
|
|
98
|
+
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Singleton instance
|
|
104
|
+
export const IconRegistry = new IconRegistryClass();
|
|
105
|
+
|
|
106
|
+
// Also export the class for testing purposes
|
|
107
|
+
export { IconRegistryClass };
|
|
@@ -1,36 +1,59 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import MdiIcon from '@mdi/react';
|
|
3
|
+
import { IconRegistry } from '../IconRegistry';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Internal component for rendering SVG icons
|
|
6
|
+
* Internal component for rendering SVG icons from the icon registry.
|
|
6
7
|
* This is used internally by components like Button, Badge, etc. to render icons
|
|
7
8
|
* without going through the full Icon component.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Icons are looked up from the registry by name. The registry is populated
|
|
11
|
+
* at build time by the Babel plugin.
|
|
10
12
|
*/
|
|
11
13
|
interface IconSvgProps {
|
|
12
|
-
|
|
14
|
+
/** Icon name in canonical format (e.g., "home", "account-circle") */
|
|
15
|
+
name: string;
|
|
13
16
|
size?: string | number;
|
|
14
17
|
color?: string;
|
|
15
18
|
style?: React.CSSProperties;
|
|
19
|
+
className?: string;
|
|
16
20
|
'aria-label'?: string;
|
|
17
21
|
'data-testid'?: string;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export const IconSvg: React.FC<IconSvgProps> = ({
|
|
21
|
-
|
|
25
|
+
name,
|
|
26
|
+
size = '1em',
|
|
22
27
|
color = 'currentColor',
|
|
23
28
|
style,
|
|
29
|
+
className,
|
|
24
30
|
'aria-label': ariaLabel,
|
|
25
31
|
'data-testid': testID,
|
|
26
32
|
...rest
|
|
27
33
|
}) => {
|
|
34
|
+
// Look up path from registry
|
|
35
|
+
const path = IconRegistry.get(name);
|
|
36
|
+
|
|
37
|
+
// Warn in development if icon is not registered
|
|
38
|
+
if (!path && process.env.NODE_ENV !== 'production') {
|
|
39
|
+
console.warn(
|
|
40
|
+
`[IconSvg] Icon "${name}" is not registered. ` +
|
|
41
|
+
`Add it to the 'icons' array in your babel config.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!path) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
return (
|
|
29
50
|
<MdiIcon
|
|
30
51
|
style={style}
|
|
52
|
+
className={className}
|
|
31
53
|
path={path}
|
|
54
|
+
size={size}
|
|
32
55
|
color={color}
|
|
33
|
-
aria-label={ariaLabel}
|
|
56
|
+
aria-label={ariaLabel || name}
|
|
34
57
|
data-testid={testID}
|
|
35
58
|
{...rest}
|
|
36
59
|
/>
|
|
@@ -1,62 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime utility for resolving MDI icon names to their SVG paths.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import * as mdiIcons from '@mdi/js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Formats an icon name from kebab-case to the MDI export name format.
|
|
11
|
-
* Examples:
|
|
12
|
-
* "home" -> "mdiHome"
|
|
13
|
-
* "account-circle" -> "mdiAccountCircle"
|
|
14
|
-
* "star-outline" -> "mdiStarOutline"
|
|
3
|
+
*
|
|
4
|
+
* Icons are looked up from the IconRegistry, which is populated at build time
|
|
5
|
+
* by the Babel plugin. This replaces the previous approach of importing all
|
|
6
|
+
* 7,447 icons from @mdi/js.
|
|
15
7
|
*/
|
|
16
|
-
function formatIconName(name: string): string {
|
|
17
|
-
if (!name || typeof name !== 'string') {
|
|
18
|
-
return 'mdiHelpCircle';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Remove mdi: prefix if present
|
|
22
|
-
const cleanName = name.startsWith('mdi:') ? name.substring(4) : name;
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
if (!/^[a-zA-Z0-9-_]+$/.test(cleanName)) {
|
|
26
|
-
console.warn(
|
|
27
|
-
`[icon-resolver] Invalid icon name "${name}" (contains special characters), using "help-circle" as fallback`
|
|
28
|
-
);
|
|
29
|
-
return 'mdiHelpCircle';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Convert kebab-case to PascalCase
|
|
33
|
-
const pascalCase = cleanName
|
|
34
|
-
.split('-')
|
|
35
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
36
|
-
.join('');
|
|
37
|
-
|
|
38
|
-
return `mdi${pascalCase}`;
|
|
39
|
-
}
|
|
9
|
+
import { IconRegistry } from './IconRegistry';
|
|
40
10
|
|
|
41
11
|
/**
|
|
42
|
-
* Resolves an icon name to its SVG path data.
|
|
43
|
-
* Returns undefined if the icon is not
|
|
12
|
+
* Resolves an icon name to its SVG path data from the registry.
|
|
13
|
+
* Returns undefined if the icon is not registered.
|
|
44
14
|
*
|
|
45
15
|
* @param iconName - The icon name in kebab-case (e.g., "home", "account-circle")
|
|
46
16
|
* @returns The SVG path string or undefined if not found
|
|
47
17
|
*/
|
|
48
18
|
export function resolveIconPath(iconName: string): string | undefined {
|
|
49
|
-
const
|
|
50
|
-
const iconPath = (mdiIcons as any)[mdiIconName];
|
|
19
|
+
const path = IconRegistry.get(iconName);
|
|
51
20
|
|
|
52
|
-
if (!
|
|
21
|
+
if (!path && process.env.NODE_ENV !== 'production') {
|
|
53
22
|
console.warn(
|
|
54
|
-
`[icon-resolver] Icon "${iconName}"
|
|
23
|
+
`[icon-resolver] Icon "${iconName}" is not registered. ` +
|
|
24
|
+
`Add it to the 'icons' array in your babel config.`
|
|
55
25
|
);
|
|
56
|
-
return (mdiIcons as any).mdiHelpCircle;
|
|
57
26
|
}
|
|
58
27
|
|
|
59
|
-
return
|
|
28
|
+
return path;
|
|
60
29
|
}
|
|
61
30
|
|
|
62
31
|
/**
|
package/src/Icon/index.native.ts
CHANGED
package/src/Icon/index.ts
CHANGED
package/src/Icon/index.web.ts
CHANGED