@idealyst/components 1.0.68 → 1.0.69

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.0.68",
3
+ "version": "1.0.69",
4
4
  "description": "Shared component library for React and React Native",
5
5
  "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
6
6
  "readme": "README.md",
@@ -41,7 +41,7 @@
41
41
  "publish:npm": "npm publish"
42
42
  },
43
43
  "peerDependencies": {
44
- "@idealyst/theme": "^1.0.68",
44
+ "@idealyst/theme": "^1.0.69",
45
45
  "@mdi/js": "^7.4.47",
46
46
  "@mdi/react": "^1.6.1",
47
47
  "@react-native-vector-icons/common": "^12.0.1",
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { ActivityIndicator as RNActivityIndicator, View } from 'react-native';
3
+ import { ActivityIndicatorProps } from './types';
4
+ import { activityIndicatorStyles } from './ActivityIndicator.styles';
5
+
6
+ const ActivityIndicator: React.FC<ActivityIndicatorProps> = ({
7
+ animating = true,
8
+ size = 'medium',
9
+ intent = 'primary',
10
+ color,
11
+ style,
12
+ testID,
13
+ hidesWhenStopped = true,
14
+ }) => {
15
+ // Handle numeric size
16
+ const sizeVariant = typeof size === 'number' ? 'medium' : size;
17
+ const customSize = typeof size === 'number' ? size : undefined;
18
+
19
+ // Map our size variants to React Native's size prop
20
+ const rnSize = sizeVariant === 'small' ? 'small' : 'large';
21
+
22
+ activityIndicatorStyles.useVariants({
23
+ size: sizeVariant,
24
+ intent,
25
+ animating,
26
+ });
27
+
28
+ // Get the color from styles or use custom color
29
+ const indicatorColor = color || activityIndicatorStyles.spinner.color;
30
+
31
+ return (
32
+ <View
33
+ style={[
34
+ activityIndicatorStyles.container,
35
+ customSize && {
36
+ width: customSize,
37
+ height: customSize,
38
+ },
39
+ style
40
+ ]}
41
+ testID={testID}
42
+ >
43
+ <RNActivityIndicator
44
+ animating={animating}
45
+ size={customSize || rnSize}
46
+ color={indicatorColor}
47
+ hidesWhenStopped={hidesWhenStopped}
48
+ />
49
+ </View>
50
+ );
51
+ };
52
+
53
+ export default ActivityIndicator;
@@ -0,0 +1,113 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const activityIndicatorStyles = StyleSheet.create((theme) => ({
4
+ container: {
5
+ alignItems: 'center',
6
+ justifyContent: 'center',
7
+
8
+ variants: {
9
+ size: {
10
+ small: {
11
+ width: 20,
12
+ height: 20,
13
+ },
14
+ medium: {
15
+ width: 36,
16
+ height: 36,
17
+ },
18
+ large: {
19
+ width: 48,
20
+ height: 48,
21
+ },
22
+ },
23
+ intent: {
24
+ primary: {},
25
+ success: {},
26
+ error: {},
27
+ warning: {},
28
+ neutral: {},
29
+ },
30
+ animating: {
31
+ true: {
32
+ opacity: 1,
33
+ },
34
+ false: {
35
+ opacity: 0,
36
+ },
37
+ },
38
+ },
39
+ },
40
+
41
+ spinner: {
42
+ borderRadius: 9999,
43
+ borderStyle: 'solid',
44
+
45
+ // Web-specific animation styles
46
+ _web: {
47
+ borderColor: 'transparent',
48
+ animation: 'spin 1s linear infinite',
49
+ boxSizing: 'border-box',
50
+ },
51
+
52
+ variants: {
53
+ size: {
54
+ small: {
55
+ width: 20,
56
+ height: 20,
57
+ borderWidth: 2,
58
+ },
59
+ medium: {
60
+ width: 36,
61
+ height: 36,
62
+ borderWidth: 3,
63
+ },
64
+ large: {
65
+ width: 48,
66
+ height: 48,
67
+ borderWidth: 4,
68
+ },
69
+ },
70
+ intent: {
71
+ primary: {
72
+ color: theme.intents?.primary?.main || '#3b82f6',
73
+ _web: {
74
+ borderTopColor: theme.intents?.primary?.main || '#3b82f6',
75
+ borderRightColor: theme.intents?.primary?.main || '#3b82f6',
76
+ },
77
+ },
78
+ success: {
79
+ color: theme.intents?.success?.main || '#22c55e',
80
+ _web: {
81
+ borderTopColor: theme.intents?.success?.main || '#22c55e',
82
+ borderRightColor: theme.intents?.success?.main || '#22c55e',
83
+ },
84
+ },
85
+ error: {
86
+ color: theme.intents?.error?.main || '#ef4444',
87
+ _web: {
88
+ borderTopColor: theme.intents?.error?.main || '#ef4444',
89
+ borderRightColor: theme.intents?.error?.main || '#ef4444',
90
+ },
91
+ },
92
+ warning: {
93
+ color: theme.intents?.warning?.main || '#f59e0b',
94
+ _web: {
95
+ borderTopColor: theme.intents?.warning?.main || '#f59e0b',
96
+ borderRightColor: theme.intents?.warning?.main || '#f59e0b',
97
+ },
98
+ },
99
+ neutral: {
100
+ color: theme.intents?.neutral?.main || '#6b7280',
101
+ _web: {
102
+ borderTopColor: theme.intents?.neutral?.main || '#6b7280',
103
+ borderRightColor: theme.intents?.neutral?.main || '#6b7280',
104
+ },
105
+ },
106
+ },
107
+ animating: {
108
+ true: {},
109
+ false: {},
110
+ },
111
+ },
112
+ },
113
+ }));
@@ -0,0 +1,8 @@
1
+ @keyframes spin {
2
+ from {
3
+ transform: rotate(0deg);
4
+ }
5
+ to {
6
+ transform: rotate(360deg);
7
+ }
8
+ }
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { ActivityIndicatorProps } from './types';
4
+ import { activityIndicatorStyles } from './ActivityIndicator.styles';
5
+ import './ActivityIndicator.web.css';
6
+
7
+ const ActivityIndicator: React.FC<ActivityIndicatorProps> = ({
8
+ animating = true,
9
+ size = 'medium',
10
+ intent = 'primary',
11
+ color,
12
+ style,
13
+ testID,
14
+ hidesWhenStopped = true,
15
+ }) => {
16
+ // Handle numeric size
17
+ const sizeVariant = typeof size === 'number' ? 'medium' : size;
18
+ const customSize = typeof size === 'number' ? size : undefined;
19
+
20
+ // Apply variants using the correct Unistyles 3.0 pattern
21
+ activityIndicatorStyles.useVariants({
22
+ size: sizeVariant as 'small' | 'medium' | 'large',
23
+ intent: intent as 'primary' | 'success' | 'error' | 'warning' | 'neutral',
24
+ animating: animating as boolean,
25
+ });
26
+
27
+ // Don't render if not animating and hidesWhenStopped is true
28
+ if (!animating && hidesWhenStopped) {
29
+ return null;
30
+ }
31
+
32
+ // Create the style array following the official documentation pattern
33
+ const containerStyleArray = [
34
+ activityIndicatorStyles.container,
35
+ customSize && {
36
+ width: customSize,
37
+ height: customSize,
38
+ },
39
+ style,
40
+ ];
41
+
42
+ const spinnerStyleArray = [
43
+ activityIndicatorStyles.spinner,
44
+ customSize && {
45
+ width: customSize,
46
+ height: customSize,
47
+ borderWidth: Math.max(2, customSize / 10),
48
+ },
49
+ color && { borderTopColor: color, borderRightColor: color },
50
+ ];
51
+
52
+ // Use getWebProps to generate className and ref for web
53
+ const containerProps = getWebProps(containerStyleArray);
54
+ const spinnerProps = getWebProps(spinnerStyleArray);
55
+
56
+ return (
57
+ <div {...containerProps} data-testid={testID}>
58
+ <div {...spinnerProps} />
59
+ </div>
60
+ );
61
+ };
62
+
63
+ export default ActivityIndicator;
@@ -0,0 +1,132 @@
1
+ # ActivityIndicator
2
+
3
+ A cross-platform loading indicator component that displays a spinning animation to indicate loading or processing state.
4
+
5
+ ## Features
6
+
7
+ - **Cross-platform**: Works seamlessly on both React and React Native
8
+ - **Intent-based colors**: Uses semantic color system (primary, neutral, success, error, warning)
9
+ - **Multiple sizes**: Supports small, medium, large, or custom numeric sizes
10
+ - **Customizable**: Override colors and styles as needed
11
+ - **Animation control**: Start/stop animation with `animating` prop
12
+ - **Auto-hide**: Optionally hide when not animating
13
+
14
+ ## Usage
15
+
16
+ ```tsx
17
+ import { ActivityIndicator } from '@idealyst/components';
18
+
19
+ // Basic usage
20
+ <ActivityIndicator />
21
+
22
+ // With different sizes
23
+ <ActivityIndicator size="small" />
24
+ <ActivityIndicator size="medium" />
25
+ <ActivityIndicator size="large" />
26
+ <ActivityIndicator size={64} /> // Custom size in pixels
27
+
28
+ // With different intents
29
+ <ActivityIndicator intent="primary" />
30
+ <ActivityIndicator intent="success" />
31
+ <ActivityIndicator intent="error" />
32
+ <ActivityIndicator intent="warning" />
33
+ <ActivityIndicator intent="neutral" />
34
+
35
+ // Custom color
36
+ <ActivityIndicator color="#FF5733" />
37
+
38
+ // Control animation
39
+ <ActivityIndicator animating={isLoading} />
40
+
41
+ // Don't hide when stopped
42
+ <ActivityIndicator animating={false} hidesWhenStopped={false} />
43
+ ```
44
+
45
+ ## Props
46
+
47
+ | Prop | Type | Default | Description |
48
+ |------|------|---------|-------------|
49
+ | `animating` | `boolean` | `true` | Whether the indicator is animating (visible) |
50
+ | `size` | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | The size of the indicator |
51
+ | `intent` | `'primary' \| 'neutral' \| 'success' \| 'error' \| 'warning'` | `'primary'` | The color intent of the indicator |
52
+ | `color` | `string` | - | Custom color to override intent |
53
+ | `style` | `ViewStyle` | - | Additional styles to apply to the container |
54
+ | `testID` | `string` | - | Test identifier for testing |
55
+ | `hidesWhenStopped` | `boolean` | `true` | Whether to hide the indicator when not animating |
56
+
57
+ ## Examples
58
+
59
+ ### Loading State
60
+ ```tsx
61
+ const LoadingScreen = () => {
62
+ const [isLoading, setIsLoading] = useState(true);
63
+
64
+ return (
65
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
66
+ <ActivityIndicator
67
+ animating={isLoading}
68
+ size="large"
69
+ intent="primary"
70
+ />
71
+ {isLoading && <Text>Loading data...</Text>}
72
+ </View>
73
+ );
74
+ };
75
+ ```
76
+
77
+ ### Button with Loading
78
+ ```tsx
79
+ const SubmitButton = ({ onSubmit }) => {
80
+ const [isSubmitting, setIsSubmitting] = useState(false);
81
+
82
+ const handleSubmit = async () => {
83
+ setIsSubmitting(true);
84
+ await onSubmit();
85
+ setIsSubmitting(false);
86
+ };
87
+
88
+ return (
89
+ <Button onPress={handleSubmit} disabled={isSubmitting}>
90
+ {isSubmitting ? (
91
+ <ActivityIndicator size="small" color="white" />
92
+ ) : (
93
+ 'Submit'
94
+ )}
95
+ </Button>
96
+ );
97
+ };
98
+ ```
99
+
100
+ ### Custom Styled Indicator
101
+ ```tsx
102
+ <ActivityIndicator
103
+ size={50}
104
+ color="#8B5CF6"
105
+ style={{
106
+ backgroundColor: 'rgba(139, 92, 246, 0.1)',
107
+ padding: 20,
108
+ borderRadius: 10,
109
+ }}
110
+ />
111
+ ```
112
+
113
+ ## Platform Differences
114
+
115
+ - **Web**: Uses CSS animations with a custom spinner implementation
116
+ - **Native**: Uses React Native's built-in `ActivityIndicator` component
117
+ - Both platforms support all the same props for consistency
118
+
119
+ ## Accessibility
120
+
121
+ The ActivityIndicator component includes:
122
+ - Proper ARIA roles for screen readers on web
123
+ - Visual indication of loading state
124
+ - Support for test IDs for testing
125
+
126
+ ## Best Practices
127
+
128
+ 1. **Always provide context**: Pair with text to explain what's loading
129
+ 2. **Use appropriate sizes**: Small for inline, large for full-screen loading
130
+ 3. **Match intent to context**: Use error intent for retry states, success for completion
131
+ 4. **Consider animation performance**: Avoid too many simultaneous indicators
132
+ 5. **Provide alternative content**: Show skeleton screens or placeholders when appropriate
@@ -0,0 +1,2 @@
1
+ export { default as ActivityIndicator } from './ActivityIndicator.native';
2
+ export type { ActivityIndicatorProps } from './types';
@@ -0,0 +1,2 @@
1
+ export { default as ActivityIndicator } from './ActivityIndicator.web';
2
+ export type { ActivityIndicatorProps } from './types';
@@ -0,0 +1,42 @@
1
+ import { ViewStyle } from 'react-native';
2
+
3
+ export interface ActivityIndicatorProps {
4
+ /**
5
+ * Whether the indicator is animating (visible)
6
+ * @default true
7
+ */
8
+ animating?: boolean;
9
+
10
+ /**
11
+ * The size of the indicator
12
+ * @default "medium"
13
+ */
14
+ size?: 'small' | 'medium' | 'large' | number;
15
+
16
+ /**
17
+ * The color/intent of the indicator
18
+ * @default "primary"
19
+ */
20
+ intent?: 'primary' | 'neutral' | 'success' | 'error' | 'warning';
21
+
22
+ /**
23
+ * Custom color to override intent
24
+ */
25
+ color?: string;
26
+
27
+ /**
28
+ * Additional styles to apply to the container
29
+ */
30
+ style?: ViewStyle;
31
+
32
+ /**
33
+ * Test identifier for testing
34
+ */
35
+ testID?: string;
36
+
37
+ /**
38
+ * Whether to hide the indicator when not animating
39
+ * @default true
40
+ */
41
+ hidesWhenStopped?: boolean;
42
+ }
package/src/index.ts CHANGED
@@ -44,6 +44,9 @@ export * from './Dialog/types';
44
44
  export { default as Popover } from './Popover';
45
45
  export * from './Popover/types';
46
46
 
47
+ export { default as ActivityIndicator } from './ActivityIndicator';
48
+ export * from './ActivityIndicator/types';
49
+
47
50
  export type { ButtonProps } from './Button/types';
48
51
  export type { TextProps } from './Text/types';
49
52
  export type { ViewProps } from './View/types';
@@ -58,6 +61,7 @@ export type { IconProps } from './Icon/types';
58
61
  export type { SVGImageProps } from './SVGImage/types';
59
62
  export type { DialogProps } from './Dialog/types';
60
63
  export type { PopoverProps } from './Popover/types';
64
+ export type { ActivityIndicatorProps } from './ActivityIndicator/types';
61
65
 
62
66
  export { breakpoints } from '@idealyst/theme';
63
67
  export type { AppTheme } from '@idealyst/theme';