@idealyst/components 1.0.53 → 1.0.56

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.
@@ -0,0 +1,209 @@
1
+ # SVGImage Component
2
+
3
+ A cross-platform React/React Native component for rendering SVG images with theme support and consistent styling.
4
+
5
+ ## Overview
6
+
7
+ The SVGImage component provides a unified way to display SVG images across web and React Native platforms. It supports both imported SVG files (as React components) and remote SVG URLs, with built-in theming and styling capabilities.
8
+
9
+ ## Installation
10
+
11
+ The SVGImage component is part of the `@idealyst/components` package:
12
+
13
+ ```bash
14
+ yarn add @idealyst/components
15
+ ```
16
+
17
+ For React Native projects, you also need:
18
+ ```bash
19
+ yarn add react-native-svg react-native-svg-transformer
20
+ ```
21
+
22
+ ## Basic Usage
23
+
24
+ ### Imported SVG Files (Recommended)
25
+
26
+ ```tsx
27
+ import { SVGImage } from '@idealyst/components';
28
+ import MyLogo from './assets/logo.svg';
29
+
30
+ function MyComponent() {
31
+ return (
32
+ <SVGImage
33
+ source={MyLogo}
34
+ width={100}
35
+ height={50}
36
+ />
37
+ );
38
+ }
39
+ ```
40
+
41
+ ### Remote SVG URLs
42
+
43
+ ```tsx
44
+ import { SVGImage } from '@idealyst/components';
45
+
46
+ function MyComponent() {
47
+ return (
48
+ <SVGImage
49
+ source={{ uri: "https://cdn.jsdelivr.net/npm/simple-icons@v9/icons/react.svg" }}
50
+ width={60}
51
+ height={60}
52
+ color="#61dafb"
53
+ />
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Props
59
+
60
+ | Prop | Type | Default | Description |
61
+ |------|------|---------|-------------|
62
+ | `source` | `string \| { uri: string } \| React.FC<SvgProps>` | **Required** | SVG source - can be imported component, URL string, or URI object |
63
+ | `width` | `number \| string` | `24` | Width of the SVG |
64
+ | `height` | `number \| string` | `24` | Height of the SVG |
65
+ | `size` | `number \| string` | - | Sets both width and height (overrides individual width/height) |
66
+ | `color` | `string` | - | Fill color for the SVG |
67
+ | `intent` | `'primary' \| 'success' \| 'error' \| 'warning' \| 'neutral'` | - | Theme-based color intent |
68
+ | `style` | `ViewStyle` | - | Additional container styles |
69
+ | `testID` | `string` | - | Test identifier |
70
+
71
+ ## Theme Integration
72
+
73
+ The SVGImage component integrates with the Idealyst theme system:
74
+
75
+ ```tsx
76
+ <SVGImage
77
+ source={MyIcon}
78
+ size={24}
79
+ intent="primary" // Uses theme's primary color
80
+ />
81
+
82
+ <SVGImage
83
+ source={MyIcon}
84
+ size={24}
85
+ intent="success" // Uses theme's success color
86
+ />
87
+ ```
88
+
89
+ ## Platform Setup
90
+
91
+ ### React Native Setup
92
+
93
+ 1. **Install dependencies:**
94
+ ```bash
95
+ yarn add react-native-svg react-native-svg-transformer
96
+ ```
97
+
98
+ 2. **Configure Metro bundler** (`metro.config.js`):
99
+ ```javascript
100
+ const config = {
101
+ transformer: {
102
+ babelTransformerPath: require.resolve('react-native-svg-transformer'),
103
+ },
104
+ resolver: {
105
+ sourceExts: ['js', 'jsx', 'ts', 'tsx', 'svg'],
106
+ assetExts: ['png', 'jpg', 'jpeg', 'gif', 'webp'],
107
+ },
108
+ };
109
+ ```
110
+
111
+ 3. **Add TypeScript declarations** (`types/svg.d.ts`):
112
+ ```typescript
113
+ declare module '*.svg' {
114
+ import React from 'react';
115
+ import { SvgProps } from 'react-native-svg';
116
+ const content: React.FC<SvgProps>;
117
+ export default content;
118
+ }
119
+ ```
120
+
121
+ 4. **iOS: Install pods:**
122
+ ```bash
123
+ cd ios && pod install
124
+ ```
125
+
126
+ ### Web Setup
127
+
128
+ For web projects using Vite, SVG imports work out of the box. Add TypeScript declarations if needed:
129
+
130
+ ```typescript
131
+ // types/svg.d.ts
132
+ declare module '*.svg' {
133
+ import React from 'react';
134
+ const content: React.FC<React.SVGProps<SVGSVGElement>>;
135
+ export default content;
136
+ }
137
+ ```
138
+
139
+ ## Examples
140
+
141
+ ### Basic Usage
142
+ ```tsx
143
+ import { SVGImage } from '@idealyst/components';
144
+ import LogoIcon from './logo.svg';
145
+
146
+ <SVGImage source={LogoIcon} size={40} />
147
+ ```
148
+
149
+ ### With Theme Colors
150
+ ```tsx
151
+ <SVGImage
152
+ source={LogoIcon}
153
+ size={32}
154
+ intent="primary"
155
+ />
156
+ ```
157
+
158
+ ### Remote SVG
159
+ ```tsx
160
+ <SVGImage
161
+ source={{ uri: "https://cdn.jsdelivr.net/npm/simple-icons@v9/icons/typescript.svg" }}
162
+ width={48}
163
+ height={48}
164
+ color="#3178c6"
165
+ />
166
+ ```
167
+
168
+ ### Custom Styling
169
+ ```tsx
170
+ <SVGImage
171
+ source={LogoIcon}
172
+ size={60}
173
+ style={{
174
+ backgroundColor: '#f0f0f0',
175
+ borderRadius: 8,
176
+ padding: 8
177
+ }}
178
+ />
179
+ ```
180
+
181
+ ## Best Practices
182
+
183
+ 1. **Prefer imported SVGs over URLs** - Better performance and reliability
184
+ 2. **Use theme intents** - Ensures consistency with your design system
185
+ 3. **Provide dimensions** - Always specify width/height or size
186
+ 4. **Test on both platforms** - Verify SVGs render correctly on web and mobile
187
+ 5. **Use CDN URLs** - For remote SVGs, use reliable CDNs like jsDelivr
188
+
189
+ ## Limitations
190
+
191
+ - **React Native**: Local SVG files must be imported as components (not file paths)
192
+ - **Remote URLs**: Some servers may block requests from mobile apps (403 errors)
193
+ - **Coloring**: Works best with single-color SVG icons
194
+
195
+ ## Troubleshooting
196
+
197
+ ### SVGs not showing on React Native
198
+ - Ensure `react-native-svg-transformer` is configured in Metro
199
+ - Check that SVGs are imported as components, not file paths
200
+ - Verify pods are installed on iOS
201
+
202
+ ### Remote SVGs failing
203
+ - Use CDN URLs (e.g., jsDelivr, unpkg) instead of direct server URLs
204
+ - Check browser console for CORS or 403 errors
205
+ - Consider hosting SVGs on your own CDN
206
+
207
+ ### TypeScript errors
208
+ - Add SVG type declarations to your project
209
+ - Ensure `react-native-svg` types are installed
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+ import { SvgUri } from 'react-native-svg';
4
+ import { SVGImageProps } from './types';
5
+ import { svgImageStyles } from './SVGImage.styles';
6
+
7
+ const SVGImage: React.FC<SVGImageProps> = ({
8
+ source,
9
+ width,
10
+ height,
11
+ size,
12
+ color,
13
+ intent,
14
+ style,
15
+ testID,
16
+ ...props
17
+ }) => {
18
+ // Apply variants using Unistyles 3.0 pattern
19
+ if (intent) {
20
+ svgImageStyles.useVariants({
21
+ intent: intent as 'primary' | 'success' | 'error' | 'warning' | 'neutral',
22
+ });
23
+ }
24
+
25
+ // Determine dimensions - size takes precedence over individual width/height
26
+ const finalWidth = size || width || 24;
27
+ const finalHeight = size || height || 24;
28
+
29
+ // Mode 1: Handle React components (imported SVG components)
30
+ if (typeof source === 'function') {
31
+ const SvgComponent = source;
32
+ return (
33
+ <View style={[svgImageStyles.container, style]} testID={testID} {...props}>
34
+ <SvgComponent
35
+ width={finalWidth}
36
+ height={finalHeight}
37
+ fill={color}
38
+ color={color}
39
+ />
40
+ </View>
41
+ );
42
+ }
43
+
44
+ // Mode 2: Handle URI-based SVG loading
45
+ const sourceUri = typeof source === 'string' ? source : source.uri;
46
+
47
+ return (
48
+ <View style={[svgImageStyles.container, style]} testID={testID} {...props}>
49
+ <SvgUri
50
+ uri={sourceUri}
51
+ width={finalWidth}
52
+ height={finalHeight}
53
+ fill={color}
54
+ color={color}
55
+ />
56
+ </View>
57
+ );
58
+ };
59
+
60
+ export default SVGImage;
@@ -0,0 +1,70 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const svgImageStyles = StyleSheet.create((theme) => ({
4
+ container: {
5
+ alignItems: 'center',
6
+ justifyContent: 'center',
7
+
8
+ variants: {
9
+ intent: {
10
+ primary: {
11
+ // Use CSS filter for web, tintColor for React Native
12
+ filter: `brightness(0) saturate(100%) invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg) brightness(104%) contrast(97%)`,
13
+ },
14
+ success: {
15
+ filter: `brightness(0) saturate(100%) invert(64%) sepia(88%) saturate(3323%) hue-rotate(84deg) brightness(119%) contrast(119%)`,
16
+ },
17
+ error: {
18
+ filter: `brightness(0) saturate(100%) invert(23%) sepia(89%) saturate(7395%) hue-rotate(4deg) brightness(102%) contrast(118%)`,
19
+ },
20
+ warning: {
21
+ filter: `brightness(0) saturate(100%) invert(54%) sepia(98%) saturate(4341%) hue-rotate(21deg) brightness(101%) contrast(101%)`,
22
+ },
23
+ neutral: {
24
+ filter: `brightness(0) saturate(100%) invert(52%) sepia(23%) saturate(3207%) hue-rotate(314deg) brightness(99%) contrast(96%)`,
25
+ },
26
+ },
27
+ },
28
+
29
+ // Web-specific styles
30
+ _web: {
31
+ userSelect: 'none',
32
+ },
33
+
34
+ // Native-specific styles
35
+ _native: {
36
+ variants: {
37
+ intent: {
38
+ primary: {
39
+ tintColor: theme.intents?.primary?.main || '#3b82f6',
40
+ },
41
+ success: {
42
+ tintColor: theme.intents?.success?.main || '#22c55e',
43
+ },
44
+ error: {
45
+ tintColor: theme.intents?.error?.main || '#ef4444',
46
+ },
47
+ warning: {
48
+ tintColor: theme.intents?.warning?.main || '#f59e0b',
49
+ },
50
+ neutral: {
51
+ tintColor: theme.intents?.neutral?.main || '#6b7280',
52
+ },
53
+ },
54
+ },
55
+ },
56
+ },
57
+
58
+ image: {
59
+ // Base image styles
60
+ _web: {
61
+ display: 'block',
62
+ maxWidth: '100%',
63
+ height: 'auto',
64
+ },
65
+
66
+ _native: {
67
+ // Native image styles will be applied via Image component
68
+ },
69
+ },
70
+ }));
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { SVGImageProps } from './types';
4
+ import { svgImageStyles } from './SVGImage.styles';
5
+
6
+ const SVGImage: React.FC<SVGImageProps> = ({
7
+ source,
8
+ width,
9
+ height,
10
+ size,
11
+ color,
12
+ intent,
13
+ resizeMode = 'contain',
14
+ style,
15
+ testID,
16
+ ...props
17
+ }) => {
18
+ // Apply variants using Unistyles 3.0 pattern
19
+ if (intent) {
20
+ svgImageStyles.useVariants({
21
+ intent: intent as 'primary' | 'success' | 'error' | 'warning' | 'neutral',
22
+ });
23
+ }
24
+
25
+ // Determine dimensions - size takes precedence over individual width/height
26
+ const finalWidth = size || width;
27
+ const finalHeight = size || height;
28
+
29
+ // Handle React components (imported SVG components)
30
+ if (typeof source === 'function') {
31
+ const SvgComponent = source;
32
+ return (
33
+ <div {...getWebProps([svgImageStyles.container, style])} {...props} data-testid={testID}>
34
+ <SvgComponent
35
+ width={finalWidth || 24}
36
+ height={finalHeight || 24}
37
+ fill={color || 'currentColor'}
38
+ color={color || 'currentColor'}
39
+ />
40
+ </div>
41
+ );
42
+ }
43
+
44
+ // Determine source URL
45
+ const sourceUrl = typeof source === 'string' ? source : source.uri;
46
+
47
+ // Create the style array
48
+ const containerStyleArray = [
49
+ svgImageStyles.container,
50
+ style,
51
+ ];
52
+
53
+ const imageStyleArray = [
54
+ svgImageStyles.image,
55
+ ];
56
+
57
+ // Use getWebProps to generate className and ref for web
58
+ const containerWebProps = getWebProps(containerStyleArray);
59
+ const imageWebProps = getWebProps(imageStyleArray);
60
+
61
+ // Apply custom color if provided
62
+ // Convert React Native resize modes to CSS object-fit values
63
+ const getObjectFit = (mode: 'contain' | 'cover' | 'stretch'): React.CSSProperties['objectFit'] => {
64
+ switch (mode) {
65
+ case 'contain': return 'contain';
66
+ case 'cover': return 'cover';
67
+ case 'stretch': return 'fill';
68
+ default: return 'contain';
69
+ }
70
+ };
71
+
72
+ const imageStyle: React.CSSProperties = {
73
+ width: finalWidth,
74
+ height: finalHeight,
75
+ objectFit: getObjectFit(resizeMode),
76
+ ...(color && {
77
+ filter: `brightness(0) saturate(100%) ${color}`,
78
+ }),
79
+ };
80
+
81
+ return (
82
+ <div {...containerWebProps} {...props} data-testid={testID}>
83
+ <img
84
+ {...imageWebProps}
85
+ src={sourceUrl}
86
+ alt="SVG Image"
87
+ style={imageStyle}
88
+ />
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export default SVGImage;
@@ -0,0 +1,2 @@
1
+ export { default } from './SVGImage.native';
2
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ // Platform-agnostic SVGImage export
2
+ // Metro will resolve to index.native.ts for React Native
3
+ // This file serves as fallback for web environments
4
+ export { default } from './SVGImage.web';
5
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './SVGImage.web';
2
+ export * from './types';
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { ViewProps } from 'react-native';
3
+ import { SvgProps } from 'react-native-svg';
4
+ import { IntentNames } from '../theme';
5
+
6
+ export interface SVGImageProps extends Omit<ViewProps, 'children'> {
7
+ source: string | { uri: string } | React.FC<SvgProps>;
8
+ width?: number | string;
9
+ height?: number | string;
10
+ size?: number | string;
11
+ color?: string;
12
+ intent?: IntentNames;
13
+ resizeMode?: 'contain' | 'cover' | 'stretch';
14
+ }
@@ -11,6 +11,7 @@ import { DividerExamples } from './DividerExamples';
11
11
  import { BadgeExamples } from './BadgeExamples';
12
12
  import { AvatarExamples } from './AvatarExamples';
13
13
  import { ScreenExamples } from './ScreenExamples';
14
+ import { SVGImageExamples } from './SVGImageExamples';
14
15
  import { DialogExamples } from './DialogExamples';
15
16
  import { PopoverExamples } from './PopoverExamples';
16
17
  import { ThemeExtensionExamples } from './ThemeExtensionExamples';
@@ -63,6 +64,9 @@ export const AllExamples = () => {
63
64
  <ScreenExamples />
64
65
  <Divider spacing="medium" />
65
66
 
67
+ <SVGImageExamples />
68
+ <Divider spacing="medium" />
69
+
66
70
  <DialogExamples />
67
71
  <Divider spacing="medium" />
68
72
 
@@ -0,0 +1,188 @@
1
+ import { Screen, View, SVGImage, Text } from '../index';
2
+ import testLogo from './test-logo.svg';
3
+
4
+ export const SVGImageExamples = () => {
5
+ return (
6
+ <Screen background="primary" padding="lg">
7
+ <View spacing="none">
8
+ <Text size="large" weight="bold" align="center">
9
+ SVG Image Examples
10
+ </Text>
11
+
12
+ {/* Local SVG File Example */}
13
+ <View spacing="md">
14
+ <Text size="medium" weight="semibold">Loading Local SVG File</Text>
15
+ <Text size="small">
16
+ Using the test-logo.svg file - works on web, limited support on React Native
17
+ </Text>
18
+ <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
19
+ {/* Original size */}
20
+ <SVGImage
21
+ source={testLogo}
22
+ width={102}
23
+ height={30}
24
+ />
25
+
26
+ {/* Smaller version */}
27
+ <SVGImage
28
+ source={testLogo}
29
+ width={68}
30
+ height={20}
31
+ />
32
+
33
+ {/* Using size prop */}
34
+ <SVGImage
35
+ source={testLogo}
36
+ size={40}
37
+ />
38
+ </View>
39
+ </View>
40
+
41
+ {/* Intent Colors */}
42
+ <View spacing="md">
43
+ <Text size="medium" weight="semibold">Intent Colors</Text>
44
+ <Text size="small">
45
+ SVG images with theme-based coloring
46
+ </Text>
47
+ <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
48
+ <SVGImage
49
+ source={testLogo}
50
+ width={102}
51
+ height={30}
52
+ intent="primary"
53
+ />
54
+ <SVGImage
55
+ source={testLogo}
56
+ width={102}
57
+ height={30}
58
+ intent="success"
59
+ />
60
+ <SVGImage
61
+ source={testLogo}
62
+ width={102}
63
+ height={30}
64
+ intent="error"
65
+ />
66
+ <SVGImage
67
+ source={testLogo}
68
+ width={102}
69
+ height={30}
70
+ intent="warning"
71
+ />
72
+ </View>
73
+ </View>
74
+
75
+ {/* Custom Colors */}
76
+ <View spacing="md">
77
+ <Text size="medium" weight="semibold">Custom Colors</Text>
78
+ <Text size="small">
79
+ Direct color specification
80
+ </Text>
81
+ <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
82
+ <SVGImage
83
+ source={testLogo}
84
+ width={102}
85
+ height={30}
86
+ color="#ff6b6b"
87
+ />
88
+ <SVGImage
89
+ source={testLogo}
90
+ width={102}
91
+ height={30}
92
+ color="#4ecdc4"
93
+ />
94
+ <SVGImage
95
+ source={testLogo}
96
+ width={102}
97
+ height={30}
98
+ color="#45b7d1"
99
+ />
100
+ </View>
101
+ </View>
102
+
103
+ {/* Remote URL Example */}
104
+ <View spacing="md">
105
+ <Text size="medium" weight="semibold">Loading from URL</Text>
106
+ <Text size="small">
107
+ SVG images loaded from remote URLs (web only for security)
108
+ </Text>
109
+ <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
110
+ <SVGImage
111
+ source={{ uri: "https://cdn.jsdelivr.net/npm/simple-icons@v9/icons/react.svg" }}
112
+ width={60}
113
+ height={60}
114
+ />
115
+ <SVGImage
116
+ source={{ uri: "https://cdn.jsdelivr.net/npm/simple-icons@v9/icons/javascript.svg" }}
117
+ width={60}
118
+ height={60}
119
+ intent="primary"
120
+ />
121
+ <SVGImage
122
+ source={{ uri: "https://cdn.jsdelivr.net/npm/simple-icons@v9/icons/typescript.svg" }}
123
+ width={60}
124
+ height={60}
125
+ intent="success"
126
+ />
127
+ </View>
128
+ </View>
129
+
130
+ {/* Resize Modes */}
131
+ <View spacing="md">
132
+ <Text size="medium" weight="semibold">Resize Modes</Text>
133
+ <Text size="small">
134
+ Different ways to fit SVG images in containers
135
+ </Text>
136
+ <View style={{ flexDirection: 'row', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
137
+ <View style={{ width: 80, height: 40, backgroundColor: 'rgba(0,0,0,0.1)' }}>
138
+ <SVGImage
139
+ source={testLogo}
140
+ width="100%"
141
+ height="100%"
142
+ resizeMode="contain"
143
+ />
144
+ </View>
145
+ <View style={{ width: 80, height: 40, backgroundColor: 'rgba(0,0,0,0.1)' }}>
146
+ <SVGImage
147
+ source={testLogo}
148
+ width="100%"
149
+ height="100%"
150
+ resizeMode="cover"
151
+ />
152
+ </View>
153
+ <View style={{ width: 80, height: 40, backgroundColor: 'rgba(0,0,0,0.1)' }}>
154
+ <SVGImage
155
+ source={testLogo}
156
+ width="100%"
157
+ height="100%"
158
+ resizeMode="stretch"
159
+ />
160
+ </View>
161
+ </View>
162
+ </View>
163
+
164
+ {/* Usage Tips */}
165
+ <View spacing="md">
166
+ <Text size="medium" weight="semibold">Usage Tips</Text>
167
+ <View spacing="sm">
168
+ <Text size="small">
169
+ • <Text weight="semibold">Local files:</Text> Use relative paths for bundled SVG files
170
+ </Text>
171
+ <Text size="small">
172
+ • <Text weight="semibold">Remote URLs:</Text> Use {`{ uri: "https://..." }`} format
173
+ </Text>
174
+ <Text size="small">
175
+ • <Text weight="semibold">React Native:</Text> Local SVGs have limited support - use remote URLs or convert to PNG
176
+ </Text>
177
+ <Text size="small">
178
+ • <Text weight="semibold">Coloring:</Text> Works best with single-color SVG icons
179
+ </Text>
180
+ <Text size="small">
181
+ • <Text weight="semibold">Performance:</Text> Cache remote SVGs for better performance
182
+ </Text>
183
+ </View>
184
+ </View>
185
+ </View>
186
+ </Screen>
187
+ );
188
+ };
@@ -9,6 +9,7 @@ export { DividerExamples } from './DividerExamples';
9
9
  export { BadgeExamples } from './BadgeExamples';
10
10
  export { AvatarExamples } from './AvatarExamples';
11
11
  export { ScreenExamples } from './ScreenExamples';
12
+ export { SVGImageExamples } from './SVGImageExamples';
12
13
  export { DialogExamples } from './DialogExamples';
13
14
  export { PopoverExamples } from './PopoverExamples';
14
15
  export { ThemeExtensionExamples } from './ThemeExtensionExamples';