cpk-ui 0.5.5 → 0.7.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.
Files changed (130) hide show
  1. package/README.md +80 -147
  2. package/components/modals/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/components/modals/AlertDialog/AlertDialog.js +14 -7
  4. package/components/modals/Snackbar/Snackbar.d.ts.map +1 -1
  5. package/components/modals/Snackbar/Snackbar.js +20 -17
  6. package/components/uis/Accordion/AccordionItem.d.ts.map +1 -1
  7. package/components/uis/Accordion/AccordionItem.js +41 -17
  8. package/components/uis/Button/Button.d.ts.map +1 -1
  9. package/components/uis/Button/Button.js +10 -10
  10. package/components/uis/Card/Card.d.ts +2 -1
  11. package/components/uis/Card/Card.d.ts.map +1 -1
  12. package/components/uis/Card/Card.js +12 -3
  13. package/components/uis/Checkbox/Checkbox.d.ts.map +1 -1
  14. package/components/uis/Checkbox/Checkbox.js +1 -1
  15. package/components/uis/CustomPressable/CustomPressable.d.ts.map +1 -1
  16. package/components/uis/CustomPressable/CustomPressable.js +6 -6
  17. package/components/uis/EditText/EditText.d.ts.map +1 -1
  18. package/components/uis/EditText/EditText.js +17 -12
  19. package/components/uis/Fab/Fab.d.ts +2 -2
  20. package/components/uis/Fab/Fab.d.ts.map +1 -1
  21. package/components/uis/Fab/Fab.js +22 -4
  22. package/components/uis/Icon/Icon.d.ts.map +1 -1
  23. package/components/uis/Icon/Icon.js +6 -3
  24. package/components/uis/IconButton/IconButton.d.ts.map +1 -1
  25. package/components/uis/IconButton/IconButton.js +43 -41
  26. package/components/uis/LoadingIndicator/LoadingIndicator.d.ts +2 -1
  27. package/components/uis/LoadingIndicator/LoadingIndicator.d.ts.map +1 -1
  28. package/components/uis/LoadingIndicator/LoadingIndicator.js +4 -4
  29. package/components/uis/RadioGroup/RadioButton.d.ts.map +1 -1
  30. package/components/uis/RadioGroup/RadioButton.js +1 -1
  31. package/components/uis/RadioGroup/RadioGroup.d.ts.map +1 -1
  32. package/components/uis/RadioGroup/RadioGroup.js +16 -3
  33. package/components/uis/Rating/Rating.d.ts.map +1 -1
  34. package/components/uis/Rating/Rating.js +23 -19
  35. package/components/uis/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  36. package/components/uis/SegmentedControl/SegmentedControl.js +1 -1
  37. package/components/uis/StatusbarBrightness/StatusBarBrightness.d.ts +2 -2
  38. package/components/uis/StatusbarBrightness/StatusBarBrightness.d.ts.map +1 -1
  39. package/components/uis/StatusbarBrightness/StatusBarBrightness.js +1 -1
  40. package/components/uis/SwitchToggle/SwitchToggle.d.ts.map +1 -1
  41. package/components/uis/SwitchToggle/SwitchToggle.js +1 -1
  42. package/hooks/useHoverState.d.ts +8 -0
  43. package/hooks/useHoverState.d.ts.map +1 -0
  44. package/hooks/useHoverState.js +16 -0
  45. package/package.json +18 -6
  46. package/providers/ThemeProvider.d.ts.map +1 -1
  47. package/providers/ThemeProvider.js +0 -1
  48. package/providers/index.d.ts.map +1 -1
  49. package/providers/index.js +3 -2
  50. package/components/uis/Icon/selection.json +0 -1
  51. package/src/components/common/ErrorBoundary/ErrorBoundary.tsx +0 -75
  52. package/src/components/modals/AlertDialog/AlertDialog.stories.tsx +0 -216
  53. package/src/components/modals/AlertDialog/AlertDialog.test.tsx +0 -251
  54. package/src/components/modals/AlertDialog/AlertDialog.tsx +0 -356
  55. package/src/components/modals/Snackbar/Snackbar.stories.tsx +0 -182
  56. package/src/components/modals/Snackbar/Snackbar.test.tsx +0 -319
  57. package/src/components/modals/Snackbar/Snackbar.tsx +0 -260
  58. package/src/components/modals/Snackbar/const.ts +0 -4
  59. package/src/components/uis/Accordion/Accordion.stories.tsx +0 -167
  60. package/src/components/uis/Accordion/Accordion.test.tsx +0 -272
  61. package/src/components/uis/Accordion/Accordion.tsx +0 -109
  62. package/src/components/uis/Accordion/AccordionItem.tsx +0 -306
  63. package/src/components/uis/Button/Button.stories.tsx +0 -218
  64. package/src/components/uis/Button/Button.test.tsx +0 -694
  65. package/src/components/uis/Button/Button.tsx +0 -440
  66. package/src/components/uis/Card/Card.test.tsx +0 -146
  67. package/src/components/uis/Card/Card.tsx +0 -102
  68. package/src/components/uis/Checkbox/Checkbox.stories.tsx +0 -216
  69. package/src/components/uis/Checkbox/Checkbox.test.tsx +0 -267
  70. package/src/components/uis/Checkbox/Checkbox.tsx +0 -225
  71. package/src/components/uis/CustomPressable/CustomPressable.tsx +0 -48
  72. package/src/components/uis/EditText/EditText.stories.tsx +0 -124
  73. package/src/components/uis/EditText/EditText.test.tsx +0 -477
  74. package/src/components/uis/EditText/EditText.tsx +0 -593
  75. package/src/components/uis/Fab/Fab.stories.tsx +0 -133
  76. package/src/components/uis/Fab/Fab.test.tsx +0 -110
  77. package/src/components/uis/Fab/Fab.tsx +0 -168
  78. package/src/components/uis/Hr/Hr.test.tsx +0 -26
  79. package/src/components/uis/Hr/Hr.tsx +0 -19
  80. package/src/components/uis/Icon/Icon.stories.tsx +0 -98
  81. package/src/components/uis/Icon/Icon.test.tsx +0 -49
  82. package/src/components/uis/Icon/Icon.tsx +0 -3819
  83. package/src/components/uis/IconButton/IconButton.test.tsx +0 -204
  84. package/src/components/uis/IconButton/IconButton.tsx +0 -279
  85. package/src/components/uis/LoadingIndicator/LoadingIndicator.tsx +0 -126
  86. package/src/components/uis/LoadingIndicator/LodingIndicator.stories.tsx +0 -81
  87. package/src/components/uis/PinchZoom/PinchZoom.stories.tsx +0 -266
  88. package/src/components/uis/PinchZoom/PinchZoom.test.tsx +0 -113
  89. package/src/components/uis/PinchZoom/PinchZoom.tsx +0 -424
  90. package/src/components/uis/RadioGroup/RadioButton.tsx +0 -193
  91. package/src/components/uis/RadioGroup/RadioGroup.stories.tsx +0 -188
  92. package/src/components/uis/RadioGroup/RadioGroup.test.tsx +0 -354
  93. package/src/components/uis/RadioGroup/RadioGroup.tsx +0 -109
  94. package/src/components/uis/Rating/Rating.test.tsx +0 -98
  95. package/src/components/uis/Rating/Rating.tsx +0 -200
  96. package/src/components/uis/SegmentedControl/SegmentedControl.stories.tsx +0 -546
  97. package/src/components/uis/SegmentedControl/SegmentedControl.test.tsx +0 -274
  98. package/src/components/uis/SegmentedControl/SegmentedControl.tsx +0 -232
  99. package/src/components/uis/StatusbarBrightness/StatusBarBrightness.test.tsx +0 -41
  100. package/src/components/uis/StatusbarBrightness/StatusBarBrightness.tsx +0 -26
  101. package/src/components/uis/Styled/StyledComponents.tsx +0 -217
  102. package/src/components/uis/SwitchToggle/SwitchToggle.stories.tsx +0 -75
  103. package/src/components/uis/SwitchToggle/SwitchToggle.test.tsx +0 -149
  104. package/src/components/uis/SwitchToggle/SwitchToggle.tsx +0 -309
  105. package/src/components/uis/Typography/Typography.stories.tsx +0 -149
  106. package/src/components/uis/Typography/Typography.test.tsx +0 -58
  107. package/src/components/uis/Typography/Typography.tsx +0 -386
  108. package/src/hooks/index.ts +0 -2
  109. package/src/hooks/useAnimations.ts +0 -71
  110. package/src/hooks/useDebouncedColorScheme.tsx +0 -30
  111. package/src/hooks/useOptimizedStyles.ts +0 -22
  112. package/src/hooks/usePerformance.ts +0 -93
  113. package/src/index.tsx +0 -38
  114. package/src/providers/ThemeProvider.tsx +0 -177
  115. package/src/providers/index.tsx +0 -130
  116. package/src/test/testUtils.tsx +0 -101
  117. package/src/types/common.ts +0 -77
  118. package/src/types/index.ts +0 -1
  119. package/src/types/kstyled.d.ts +0 -6
  120. package/src/utils/accessibility.ts +0 -110
  121. package/src/utils/colors.ts +0 -151
  122. package/src/utils/createCtx.tsx +0 -26
  123. package/src/utils/guards.ts +0 -93
  124. package/src/utils/index.ts +0 -2
  125. package/src/utils/performanceTest.ts +0 -174
  126. package/src/utils/platform.ts +0 -69
  127. package/src/utils/theme.ts +0 -38
  128. package/test/testUtils.d.ts +0 -113
  129. package/test/testUtils.d.ts.map +0 -1
  130. package/test/testUtils.js +0 -65
@@ -1,204 +0,0 @@
1
- import React, {type ReactElement} from 'react';
2
- import type {RenderAPI} from '@testing-library/react-native';
3
- import {render} from '@testing-library/react-native';
4
-
5
- import {createComponent} from '../../../../test/testUtils';
6
- import type {IconButtonProps} from './IconButton';
7
- import {IconButton} from './IconButton';
8
-
9
- let testingLib: RenderAPI;
10
-
11
- jest.mock('react-native-web-hooks', () => ({
12
- useHover: () => true,
13
- }));
14
-
15
- const Component = (props?: IconButtonProps): ReactElement =>
16
- createComponent(<IconButton {...props} />);
17
-
18
- describe('[IconButton]', () => {
19
- it('should render without crashing', () => {
20
- testingLib = render(Component());
21
-
22
- const json = testingLib.toJSON();
23
-
24
- expect(json).toBeTruthy();
25
- });
26
-
27
- it('should render `outlined` button', () => {
28
- testingLib = render(
29
- Component({
30
- type: 'outlined',
31
- }),
32
- );
33
-
34
- const json = testingLib.toJSON();
35
-
36
- expect(json).toBeTruthy();
37
- });
38
-
39
- describe('loading', () => {
40
- it('should render `loading` button', () => {
41
- testingLib = render(
42
- Component({
43
- loading: true,
44
- }),
45
- );
46
-
47
- const json = testingLib.toJSON();
48
-
49
- expect(json).toBeTruthy();
50
- });
51
-
52
- it('should render `disabled` button', () => {
53
- testingLib = render(
54
- Component({
55
- loading: true,
56
- disabled: true,
57
- }),
58
- );
59
-
60
- const json = testingLib.toJSON();
61
-
62
- expect(json).toBeTruthy();
63
- });
64
- });
65
-
66
- describe('size', () => {
67
- it('should renders large size', () => {
68
- testingLib = render(
69
- Component({
70
- size: 'large',
71
- }),
72
- );
73
-
74
- const json = testingLib.toJSON();
75
-
76
- expect(json).toBeTruthy();
77
- });
78
-
79
- it('should renders small size', () => {
80
- testingLib = render(
81
- Component({
82
- size: 'small',
83
- }),
84
- );
85
-
86
- const json = testingLib.toJSON();
87
-
88
- expect(json).toBeTruthy();
89
- });
90
- });
91
-
92
- describe('type', () => {
93
- it('should render color==="info"', () => {
94
- testingLib = render(
95
- Component({
96
- color: 'info',
97
- }),
98
- );
99
-
100
- const json = testingLib.toJSON();
101
-
102
- expect(json).toBeTruthy();
103
- });
104
-
105
- it('should renders primary color', () => {
106
- testingLib = render(
107
- Component({
108
- color: 'primary',
109
- }),
110
- );
111
-
112
- const json = testingLib.toJSON();
113
-
114
- expect(json).toBeTruthy();
115
- });
116
-
117
- it('should renders secondary color', () => {
118
- testingLib = render(
119
- Component({
120
- color: 'secondary',
121
- }),
122
- );
123
-
124
- const json = testingLib.toJSON();
125
-
126
- expect(json).toBeTruthy();
127
- });
128
-
129
- it('should renders danger color', () => {
130
- testingLib = render(
131
- Component({
132
- color: 'danger',
133
- }),
134
- );
135
-
136
- const json = testingLib.toJSON();
137
-
138
- expect(json).toBeTruthy();
139
- });
140
-
141
- it('should renders warning color', () => {
142
- testingLib = render(
143
- Component({
144
- color: 'warning',
145
- }),
146
- );
147
-
148
- const json = testingLib.toJSON();
149
-
150
- expect(json).toBeTruthy();
151
- });
152
- });
153
-
154
- it('should render `disabled` button', () => {
155
- testingLib = render(
156
- Component({
157
- disabled: true,
158
- }),
159
- );
160
-
161
- const json = testingLib.toJSON();
162
-
163
- expect(json).toBeTruthy();
164
- });
165
-
166
- describe('accessibility', () => {
167
- it('should have accessibilityRole="button"', () => {
168
- testingLib = render(
169
- Component({
170
- testID: 'a11y-icon-button',
171
- icon: 'Plus',
172
- }),
173
- );
174
-
175
- const button = testingLib.getByTestId('a11y-icon-button');
176
- expect(button.props.accessibilityRole).toBe('button');
177
- });
178
-
179
- it('should use icon name as default accessibilityLabel', () => {
180
- testingLib = render(
181
- Component({
182
- testID: 'a11y-icon-button',
183
- icon: 'Plus',
184
- }),
185
- );
186
-
187
- const button = testingLib.getByTestId('a11y-icon-button');
188
- expect(button.props.accessibilityLabel).toBe('Plus');
189
- });
190
-
191
- it('should use custom accessibilityLabel when provided', () => {
192
- testingLib = render(
193
- Component({
194
- testID: 'a11y-icon-button',
195
- icon: 'Plus',
196
- accessibilityLabel: 'Add new item',
197
- }),
198
- );
199
-
200
- const button = testingLib.getByTestId('a11y-icon-button');
201
- expect(button.props.accessibilityLabel).toBe('Add new item');
202
- });
203
- });
204
- });
@@ -1,279 +0,0 @@
1
- import React, {useCallback, useMemo, useRef, type ReactElement} from 'react';
2
- import type {StyleProp, TouchableHighlightProps, ViewStyle} from 'react-native';
3
- import {Platform, TouchableHighlight, View} from 'react-native';
4
- import {useHover} from 'react-native-web-hooks';
5
- import {css} from 'kstyled';
6
-
7
- import type {IconName} from '../Icon/Icon';
8
- import {Icon} from '../Icon/Icon';
9
- import {LoadingIndicator} from '../LoadingIndicator/LoadingIndicator';
10
- import {CpkTheme, getTheme} from '../../../utils/theme';
11
- import {useTheme} from '../../../providers/ThemeProvider';
12
- import * as Haptics from 'expo-haptics';
13
-
14
- type Styles = {
15
- container?: StyleProp<ViewStyle>;
16
- icon?: StyleProp<ViewStyle>;
17
- disabled?: StyleProp<ViewStyle>;
18
- hovered?: StyleProp<ViewStyle>;
19
- };
20
-
21
- type ButtonType = 'text' | 'solid' | 'outlined';
22
-
23
- type ButtonColorType =
24
- | 'primary'
25
- | 'secondary'
26
- | 'success'
27
- | 'danger'
28
- | 'warning'
29
- | 'info'
30
- | 'light';
31
-
32
- type ButtonSizeType = 'small' | 'medium' | 'large' | number;
33
-
34
- // Memoized styles calculation function
35
- const ButtonStyles = ({
36
- theme,
37
- type,
38
- color,
39
- loading,
40
- disabled,
41
- }: {
42
- theme: CpkTheme;
43
- type: ButtonType;
44
- color: ButtonColorType;
45
- loading?: boolean;
46
- disabled?: boolean;
47
- }) => {
48
- const isDisabled = disabled || loading;
49
-
50
- let backgroundColor = theme.bg.basic;
51
- let borderColor = 'transparent';
52
- let iconColor = theme.text.basic;
53
-
54
- if (type === 'solid') {
55
- backgroundColor = isDisabled
56
- ? theme.button.disabled.bg
57
- : theme.button[color].bg;
58
- borderColor = backgroundColor;
59
- }
60
-
61
- if (type === 'outlined') {
62
- backgroundColor = isDisabled
63
- ? theme.bg.disabled
64
- : theme.bg.basic;
65
- borderColor = isDisabled
66
- ? theme.bg.disabled
67
- : theme.button[color].bg;
68
- }
69
-
70
- if (type === 'solid' || color === 'light') {
71
- iconColor = theme.button[color].text;
72
- }
73
-
74
- return {
75
- backgroundColor,
76
- borderColor,
77
- borderWidth: type === 'outlined' ? 1 : 0,
78
- iconColor,
79
- disabledBackgroundColor:
80
- type === 'solid' && !loading ? theme.button.disabled.bg : theme.bg.basic,
81
- disabledBorderColor: theme.bg.disabled,
82
- disabledTextColor: theme.button.disabled.text,
83
- };
84
- };
85
-
86
- export type IconButtonProps = {
87
- testID?: string;
88
- type?: ButtonType;
89
- color?: ButtonColorType;
90
- size?: ButtonSizeType | number;
91
- disabled?: boolean;
92
- loading?: boolean;
93
- loadingElement?: ReactElement;
94
- icon?: IconName;
95
- iconElement?: ReactElement;
96
- style?: StyleProp<Omit<ViewStyle, 'borderRadius' | 'padding'>>;
97
- styles?: Styles;
98
- onPress?: TouchableHighlightProps['onPress'];
99
- activeOpacity?: number;
100
- touchableHighlightProps?: Omit<TouchableHighlightProps, 'onPress' | 'style'>;
101
- hapticFeedback?: Haptics.ImpactFeedbackStyle;
102
- accessibilityLabel?: string;
103
- };
104
-
105
- export function IconButton({
106
- testID,
107
- type = 'solid',
108
- color = 'primary',
109
- size = 'medium',
110
- disabled,
111
- loading = false,
112
- loadingElement,
113
- icon,
114
- iconElement,
115
- style,
116
- styles,
117
- onPress,
118
- activeOpacity = 0.95,
119
- touchableHighlightProps,
120
- hapticFeedback,
121
- accessibilityLabel,
122
- }: IconButtonProps): ReactElement {
123
- const ref = useRef<React.ElementRef<typeof TouchableHighlight>>(null);
124
- const hovered = useHover(ref);
125
- const {theme} = useTheme();
126
-
127
- // Memoize button styles
128
- const buttonStylesConfig = useMemo(() => ButtonStyles({
129
- theme,
130
- type,
131
- color,
132
- loading,
133
- disabled,
134
- }), [theme, type, color, loading, disabled]);
135
-
136
- // Memoize icon size calculation
137
- const iconSize = useMemo(() => {
138
- return size === 'large'
139
- ? 32
140
- : size === 'medium'
141
- ? 24
142
- : size === 'small'
143
- ? 16
144
- : size;
145
- }, [size]);
146
-
147
- // Memoize style strings
148
- const borderRadiusStr = useMemo(() => '99px', []);
149
- const borderWidthStr = useMemo(() => `${buttonStylesConfig.borderWidth}px`, [buttonStylesConfig.borderWidth]);
150
-
151
- // Memoize composite styles
152
- const compositeStyles: Styles = useMemo(() => ({
153
- container: [
154
- css`
155
- background-color: ${buttonStylesConfig.backgroundColor};
156
- border-color: ${buttonStylesConfig.borderColor};
157
- border-width: ${borderWidthStr};
158
- padding: 12px;
159
- `,
160
- styles?.container,
161
- ],
162
- icon: [
163
- css`
164
- color: ${buttonStylesConfig.iconColor};
165
- `,
166
- styles?.icon,
167
- ],
168
- disabled: [
169
- css`
170
- background-color: ${buttonStylesConfig.disabledBackgroundColor};
171
- border-color: ${buttonStylesConfig.disabledBorderColor};
172
- `,
173
- styles?.disabled,
174
- ],
175
- hovered: [
176
- {
177
- shadowColor: theme.text.basic,
178
- shadowOpacity: 0.24,
179
- shadowRadius: 16,
180
- elevation: 10,
181
- },
182
- styles?.hovered,
183
- ],
184
- }), [buttonStylesConfig, borderWidthStr, theme.text.basic, styles]);
185
-
186
- // Memoize press handler
187
- const handlePress = useCallback((e: any) => {
188
- onPress?.(e);
189
- if (hapticFeedback) {
190
- Haptics.impactAsync(hapticFeedback);
191
- }
192
- }, [onPress, hapticFeedback]);
193
-
194
- // Memoize loading view
195
- const LoadingView = useMemo(() =>
196
- loadingElement || (
197
- <LoadingIndicator
198
- color={theme.text.disabled}
199
- size="small"
200
- style={css`
201
- justify-content: center;
202
- align-items: center;
203
- height: ${iconSize}px;
204
- width: ${iconSize}px;
205
- `}
206
- />
207
- ),
208
- [loadingElement, theme.text.disabled, iconSize]
209
- );
210
-
211
- // Memoize icon view
212
- const IconView = useMemo(() =>
213
- iconElement || (
214
- <Icon
215
- color={buttonStylesConfig.iconColor}
216
- name={icon || 'QuestBoxFill'}
217
- size={iconSize}
218
- style={compositeStyles?.icon}
219
- />
220
- ),
221
- [iconElement, buttonStylesConfig.iconColor, icon, iconSize, compositeStyles?.icon]
222
- );
223
-
224
- // Memoize container styles
225
- const containerStyles = useMemo(() => [
226
- css`
227
- flex-direction: row;
228
- border-radius: ${borderRadiusStr};
229
- `,
230
- style,
231
- ], [borderRadiusStr, style]);
232
-
233
- // Memoize inner container styles
234
- const innerContainerStyles = useMemo(() => [
235
- css`
236
- padding: 4px;
237
- border-radius: ${borderRadiusStr};
238
- flex-direction: row;
239
- justify-content: center;
240
- align-items: center;
241
- `,
242
- compositeStyles.container,
243
- hovered && !disabled && compositeStyles.hovered,
244
- disabled && compositeStyles.disabled,
245
- ], [borderRadiusStr, compositeStyles, hovered, disabled]);
246
-
247
- return (
248
- <View style={containerStyles}>
249
- <TouchableHighlight
250
- accessibilityLabel={accessibilityLabel ?? icon}
251
- accessibilityRole="button"
252
- activeOpacity={activeOpacity}
253
- delayPressIn={50}
254
- disabled={disabled || loading}
255
- onPress={handlePress}
256
- ref={Platform.select({
257
- web: ref,
258
- default: undefined,
259
- })}
260
- style={css`
261
- border-radius: ${borderRadiusStr};
262
- `}
263
- testID={testID}
264
- underlayColor={theme.role.underlay}
265
- {...touchableHighlightProps}
266
- >
267
- <View
268
- style={innerContainerStyles}
269
- testID={loading ? 'loading-view' : 'button-container'}
270
- >
271
- {loading ? LoadingView : IconView}
272
- </View>
273
- </TouchableHighlight>
274
- </View>
275
- );
276
- }
277
-
278
- // Export memoized component for better performance
279
- export default React.memo(IconButton) as typeof IconButton;
@@ -1,126 +0,0 @@
1
- import React, {useMemo, useCallback, type ReactElement} from "react";
2
- import type {
3
- ImageSourcePropType,
4
- ImageStyle,
5
- StyleProp,
6
- ViewStyle,
7
- } from "react-native";
8
- import { ActivityIndicator, Image, Platform, View } from "react-native";
9
- import {styled} from "kstyled";
10
- import { useTheme } from "../../../providers/ThemeProvider";
11
- import type {BaseComponentProps} from "../../../types/common";
12
-
13
- type Styles = {
14
- activityIndicator?: ViewStyle;
15
- image?: ImageStyle;
16
- };
17
-
18
- export type LoadingIndicatorSizeType = 'small' | 'medium' | 'large' | number;
19
-
20
- export interface LoadingIndicatorProps extends BaseComponentProps {
21
- style?: StyleProp<ViewStyle>;
22
- styles?: Styles;
23
- color?: string;
24
- size?: LoadingIndicatorSizeType;
25
- imgSource?: string | ImageSourcePropType;
26
- customElement?: ReactElement | (() => ReactElement);
27
- }
28
-
29
- const Container = styled(View)`
30
- justify-content: center;
31
- align-items: center;
32
- `;
33
-
34
- function LoadingIndicator({
35
- customElement,
36
- style,
37
- styles,
38
- size = "large",
39
- color,
40
- imgSource,
41
- testID,
42
- }: LoadingIndicatorProps): ReactElement {
43
- const { theme } = useTheme();
44
-
45
- // Convert size to ActivityIndicator compatible format
46
- // Note: iOS only supports 'small' | 'large', Android also supports numbers
47
- const activityIndicatorSize: 'small' | 'large' | number = useMemo(() => {
48
- if (typeof size === 'number') {
49
- // On iOS, convert numeric sizes to 'large' since numeric sizes aren't supported
50
- if (Platform.OS === 'ios') return 'large';
51
- return size;
52
- }
53
- if (size === 'medium') return 'large';
54
- return size;
55
- }, [size]);
56
-
57
- // Memoize image source processing
58
- const imageSource = useMemo((): ImageSourcePropType | undefined => {
59
- if (!imgSource) return undefined;
60
-
61
- if (typeof imgSource === "string") {
62
- return { uri: imgSource };
63
- }
64
- return imgSource;
65
- }, [imgSource]);
66
-
67
- // Memoize image style calculation
68
- const imageStyle = useMemo(() => {
69
- if (typeof size === 'number') {
70
- return [{ width: size, height: size }, styles?.image];
71
- }
72
- return [
73
- size === "large"
74
- ? { width: 50, height: 50 }
75
- : size === "medium"
76
- ? { width: 40, height: 40 }
77
- : size === "small"
78
- ? { width: 30, height: 30 }
79
- : undefined,
80
- styles?.image,
81
- ];
82
- }, [size, styles?.image]);
83
-
84
- // Memoize activity indicator color
85
- const activityIndicatorColor = useMemo(() => {
86
- return color || theme.role.secondary;
87
- }, [color, theme.role.secondary]);
88
-
89
- // Memoize content renderer
90
- const renderContent = useCallback(() => {
91
- if (customElement) {
92
- return typeof customElement === "function" ? customElement() : customElement;
93
- }
94
-
95
- if (imageSource) {
96
- return (
97
- <Image
98
- source={imageSource}
99
- style={imageStyle}
100
- testID={`${testID}-image`}
101
- />
102
- );
103
- }
104
-
105
- return (
106
- <ActivityIndicator
107
- color={activityIndicatorColor}
108
- size={activityIndicatorSize}
109
- style={styles?.activityIndicator}
110
- testID={`${testID}-activity-indicator`}
111
- />
112
- );
113
- }, [customElement, imageSource, imageStyle, testID, activityIndicatorColor, activityIndicatorSize, styles?.activityIndicator]);
114
-
115
- return (
116
- <Container style={style} testID={testID}>
117
- {renderContent()}
118
- </Container>
119
- );
120
- }
121
-
122
- // Export memoized component for better performance
123
- export default React.memo(LoadingIndicator) as typeof LoadingIndicator;
124
-
125
- // Also export the non-memoized version for cases where memoization is not needed
126
- export {LoadingIndicator};
@@ -1,81 +0,0 @@
1
- import type {ComponentProps} from 'react';
2
- import type {Meta, StoryObj} from '@storybook/react';
3
- import {withThemeProvider} from '../../../../.storybook/decorators';
4
- import {LoadingIndicator} from './LoadingIndicator';
5
-
6
- const meta = {
7
- title: 'LoadingIndicator',
8
- component: (props) => <LoadingIndicator {...props} />,
9
- parameters: {
10
- docs: {
11
- description: {
12
- component: `
13
- A loading indicator component that displays a spinner or custom loading animation.
14
-
15
- ## Features
16
- - **Flexible Sizing**: Preset sizes and custom numeric values
17
- - **Platform Adaptive**: Uses native ActivityIndicator on mobile platforms
18
- - **Custom Colors**: Support for custom loading indicator colors
19
- - **Custom Elements**: Can replace with custom loading animation
20
-
21
- ## Size Options
22
- - \`small\`: 30px indicator
23
- - \`medium\`: 40px indicator
24
- - \`large\`: 50px indicator (default on native)
25
- - Custom number: Any pixel value (e.g., \`size={45}\`)
26
-
27
- ## Platform Behavior
28
- - **iOS/Android**: Uses native ActivityIndicator with "small" or "large" sizes
29
- - **Web**: Custom implementation with flexible sizing
30
- - **Medium size**: Maps to "large" on native platforms
31
-
32
- ## Usage
33
- \`\`\`tsx
34
- <LoadingIndicator
35
- size="medium"
36
- color="#0000FF"
37
- />
38
- \`\`\`
39
-
40
- ### With Custom Element
41
- \`\`\`tsx
42
- <LoadingIndicator
43
- size={50}
44
- customElement={<CustomSpinner />}
45
- />
46
- \`\`\`
47
- `,
48
- },
49
- },
50
- },
51
- argTypes: {
52
- size: {
53
- control: 'radio',
54
- options: ['small', 'medium', 'large', 20, 30, 40, 50],
55
- description: 'Indicator size: "small" (30px), "medium" (40px), "large" (50px), or custom number in pixels',
56
- },
57
- color: {
58
- description: 'Custom color for the loading indicator',
59
- },
60
- customElement: {
61
- description: 'Custom loading element to replace the default spinner',
62
- },
63
- },
64
- decorators: [withThemeProvider],
65
- } satisfies Meta<typeof LoadingIndicator>;
66
-
67
- export default meta;
68
-
69
- type Story = StoryObj<typeof meta>;
70
-
71
- export const Basic: Story = {
72
- args: {
73
- size: 'medium',
74
- },
75
- argTypes: {
76
- theme: {
77
- control: 'select',
78
- options: ['light', 'dark'],
79
- },
80
- },
81
- };