cpk-ui 0.5.4 → 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 (136) 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 +2 -1
  9. package/components/uis/Button/Button.d.ts.map +1 -1
  10. package/components/uis/Button/Button.js +11 -11
  11. package/components/uis/Card/Card.d.ts +2 -1
  12. package/components/uis/Card/Card.d.ts.map +1 -1
  13. package/components/uis/Card/Card.js +12 -3
  14. package/components/uis/Checkbox/Checkbox.d.ts +2 -1
  15. package/components/uis/Checkbox/Checkbox.d.ts.map +1 -1
  16. package/components/uis/Checkbox/Checkbox.js +2 -2
  17. package/components/uis/CustomPressable/CustomPressable.d.ts.map +1 -1
  18. package/components/uis/CustomPressable/CustomPressable.js +6 -6
  19. package/components/uis/EditText/EditText.d.ts +1 -0
  20. package/components/uis/EditText/EditText.d.ts.map +1 -1
  21. package/components/uis/EditText/EditText.js +18 -13
  22. package/components/uis/Fab/Fab.d.ts +2 -2
  23. package/components/uis/Fab/Fab.d.ts.map +1 -1
  24. package/components/uis/Fab/Fab.js +22 -4
  25. package/components/uis/Icon/Icon.d.ts.map +1 -1
  26. package/components/uis/Icon/Icon.js +6 -3
  27. package/components/uis/IconButton/IconButton.d.ts +2 -1
  28. package/components/uis/IconButton/IconButton.d.ts.map +1 -1
  29. package/components/uis/IconButton/IconButton.js +44 -42
  30. package/components/uis/LoadingIndicator/LoadingIndicator.d.ts +2 -1
  31. package/components/uis/LoadingIndicator/LoadingIndicator.d.ts.map +1 -1
  32. package/components/uis/LoadingIndicator/LoadingIndicator.js +4 -4
  33. package/components/uis/RadioGroup/RadioButton.d.ts +2 -1
  34. package/components/uis/RadioGroup/RadioButton.d.ts.map +1 -1
  35. package/components/uis/RadioGroup/RadioButton.js +2 -2
  36. package/components/uis/RadioGroup/RadioGroup.d.ts.map +1 -1
  37. package/components/uis/RadioGroup/RadioGroup.js +16 -3
  38. package/components/uis/Rating/Rating.d.ts.map +1 -1
  39. package/components/uis/Rating/Rating.js +23 -19
  40. package/components/uis/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  41. package/components/uis/SegmentedControl/SegmentedControl.js +1 -1
  42. package/components/uis/StatusbarBrightness/StatusBarBrightness.d.ts +2 -2
  43. package/components/uis/StatusbarBrightness/StatusBarBrightness.d.ts.map +1 -1
  44. package/components/uis/StatusbarBrightness/StatusBarBrightness.js +1 -1
  45. package/components/uis/SwitchToggle/SwitchToggle.d.ts +2 -1
  46. package/components/uis/SwitchToggle/SwitchToggle.d.ts.map +1 -1
  47. package/components/uis/SwitchToggle/SwitchToggle.js +2 -2
  48. package/hooks/useHoverState.d.ts +8 -0
  49. package/hooks/useHoverState.d.ts.map +1 -0
  50. package/hooks/useHoverState.js +16 -0
  51. package/package.json +18 -6
  52. package/providers/ThemeProvider.d.ts.map +1 -1
  53. package/providers/ThemeProvider.js +0 -1
  54. package/providers/index.d.ts.map +1 -1
  55. package/providers/index.js +3 -2
  56. package/components/uis/Icon/selection.json +0 -1
  57. package/src/components/common/ErrorBoundary/ErrorBoundary.tsx +0 -75
  58. package/src/components/modals/AlertDialog/AlertDialog.stories.tsx +0 -216
  59. package/src/components/modals/AlertDialog/AlertDialog.test.tsx +0 -251
  60. package/src/components/modals/AlertDialog/AlertDialog.tsx +0 -356
  61. package/src/components/modals/Snackbar/Snackbar.stories.tsx +0 -182
  62. package/src/components/modals/Snackbar/Snackbar.test.tsx +0 -319
  63. package/src/components/modals/Snackbar/Snackbar.tsx +0 -260
  64. package/src/components/modals/Snackbar/const.ts +0 -4
  65. package/src/components/uis/Accordion/Accordion.stories.tsx +0 -167
  66. package/src/components/uis/Accordion/Accordion.test.tsx +0 -272
  67. package/src/components/uis/Accordion/Accordion.tsx +0 -109
  68. package/src/components/uis/Accordion/AccordionItem.tsx +0 -306
  69. package/src/components/uis/Button/Button.stories.tsx +0 -218
  70. package/src/components/uis/Button/Button.test.tsx +0 -620
  71. package/src/components/uis/Button/Button.tsx +0 -436
  72. package/src/components/uis/Card/Card.test.tsx +0 -146
  73. package/src/components/uis/Card/Card.tsx +0 -102
  74. package/src/components/uis/Checkbox/Checkbox.stories.tsx +0 -216
  75. package/src/components/uis/Checkbox/Checkbox.test.tsx +0 -173
  76. package/src/components/uis/Checkbox/Checkbox.tsx +0 -220
  77. package/src/components/uis/CustomPressable/CustomPressable.tsx +0 -48
  78. package/src/components/uis/EditText/EditText.stories.tsx +0 -124
  79. package/src/components/uis/EditText/EditText.test.tsx +0 -477
  80. package/src/components/uis/EditText/EditText.tsx +0 -590
  81. package/src/components/uis/Fab/Fab.stories.tsx +0 -133
  82. package/src/components/uis/Fab/Fab.test.tsx +0 -110
  83. package/src/components/uis/Fab/Fab.tsx +0 -168
  84. package/src/components/uis/Hr/Hr.test.tsx +0 -26
  85. package/src/components/uis/Hr/Hr.tsx +0 -19
  86. package/src/components/uis/Icon/Icon.stories.tsx +0 -98
  87. package/src/components/uis/Icon/Icon.test.tsx +0 -49
  88. package/src/components/uis/Icon/Icon.tsx +0 -3819
  89. package/src/components/uis/IconButton/IconButton.test.tsx +0 -165
  90. package/src/components/uis/IconButton/IconButton.tsx +0 -275
  91. package/src/components/uis/LoadingIndicator/LoadingIndicator.tsx +0 -126
  92. package/src/components/uis/LoadingIndicator/LodingIndicator.stories.tsx +0 -81
  93. package/src/components/uis/PinchZoom/PinchZoom.stories.tsx +0 -266
  94. package/src/components/uis/PinchZoom/PinchZoom.test.tsx +0 -113
  95. package/src/components/uis/PinchZoom/PinchZoom.tsx +0 -424
  96. package/src/components/uis/RadioGroup/RadioButton.tsx +0 -188
  97. package/src/components/uis/RadioGroup/RadioGroup.stories.tsx +0 -188
  98. package/src/components/uis/RadioGroup/RadioGroup.test.tsx +0 -287
  99. package/src/components/uis/RadioGroup/RadioGroup.tsx +0 -109
  100. package/src/components/uis/Rating/Rating.test.tsx +0 -98
  101. package/src/components/uis/Rating/Rating.tsx +0 -200
  102. package/src/components/uis/SegmentedControl/SegmentedControl.stories.tsx +0 -546
  103. package/src/components/uis/SegmentedControl/SegmentedControl.test.tsx +0 -274
  104. package/src/components/uis/SegmentedControl/SegmentedControl.tsx +0 -229
  105. package/src/components/uis/StatusbarBrightness/StatusBarBrightness.test.tsx +0 -41
  106. package/src/components/uis/StatusbarBrightness/StatusBarBrightness.tsx +0 -26
  107. package/src/components/uis/Styled/StyledComponents.tsx +0 -217
  108. package/src/components/uis/SwitchToggle/SwitchToggle.stories.tsx +0 -75
  109. package/src/components/uis/SwitchToggle/SwitchToggle.test.tsx +0 -105
  110. package/src/components/uis/SwitchToggle/SwitchToggle.tsx +0 -305
  111. package/src/components/uis/Typography/Typography.stories.tsx +0 -149
  112. package/src/components/uis/Typography/Typography.test.tsx +0 -58
  113. package/src/components/uis/Typography/Typography.tsx +0 -386
  114. package/src/hooks/index.ts +0 -2
  115. package/src/hooks/useAnimations.ts +0 -71
  116. package/src/hooks/useDebouncedColorScheme.tsx +0 -30
  117. package/src/hooks/useOptimizedStyles.ts +0 -22
  118. package/src/hooks/usePerformance.ts +0 -93
  119. package/src/index.tsx +0 -38
  120. package/src/providers/ThemeProvider.tsx +0 -177
  121. package/src/providers/index.tsx +0 -130
  122. package/src/test/testUtils.tsx +0 -101
  123. package/src/types/common.ts +0 -77
  124. package/src/types/index.ts +0 -1
  125. package/src/types/kstyled.d.ts +0 -6
  126. package/src/utils/accessibility.ts +0 -110
  127. package/src/utils/colors.ts +0 -151
  128. package/src/utils/createCtx.tsx +0 -26
  129. package/src/utils/guards.ts +0 -93
  130. package/src/utils/index.ts +0 -2
  131. package/src/utils/performanceTest.ts +0 -174
  132. package/src/utils/platform.ts +0 -69
  133. package/src/utils/theme.ts +0 -38
  134. package/test/testUtils.d.ts +0 -113
  135. package/test/testUtils.d.ts.map +0 -1
  136. package/test/testUtils.js +0 -65
@@ -1,217 +0,0 @@
1
- import {Animated} from 'react-native';
2
- import {styled, css} from 'kstyled';
3
- import {getTheme} from '../../../utils/theme';
4
- import {Typography} from '../Typography/Typography';
5
-
6
- export type ButtonType =
7
- | 'primary'
8
- | 'secondary'
9
- | 'success'
10
- | 'danger'
11
- | 'warning'
12
- | 'info'
13
- | 'light';
14
-
15
- export type SnackbarType =
16
- | 'primary'
17
- | 'secondary'
18
- | 'success'
19
- | 'warning'
20
- | 'info'
21
- | 'danger';
22
-
23
- // ButtonWrapper Component
24
- export const ButtonWrapper = styled.View<{
25
- $type?: ButtonType;
26
- $outlined?: boolean;
27
- $disabled?: boolean;
28
- $loading?: boolean;
29
- }>`
30
- border-width: ${({$outlined}) => ($outlined ? '1px' : undefined)};
31
- background-color: ${({theme, $type, $outlined, $loading, $disabled}) => {
32
- const resolvedTheme = getTheme(theme);
33
-
34
- if ($loading) {
35
- return $outlined ? undefined : resolvedTheme.button[$type ?? 'primary'].bg;
36
- }
37
- if ($disabled) {
38
- return undefined;
39
- }
40
- return $outlined ? resolvedTheme.bg.basic : resolvedTheme.button[$type ?? 'primary'].bg;
41
- }};
42
- border-color: ${({theme, $type, $disabled}) => {
43
- const resolvedTheme = getTheme(theme);
44
-
45
- if ($disabled) {
46
- return resolvedTheme.bg.basic;
47
- }
48
- return resolvedTheme.button[$type ?? 'primary'].bg;
49
- }};
50
- `;
51
-
52
- // ButtonText Component
53
- export const ButtonText = styled(Typography.Body2)<{
54
- $outlined?: boolean;
55
- $type?: ButtonType | SnackbarType;
56
- $disabled?: boolean;
57
- $loading?: boolean;
58
- }>`
59
- color: ${({theme, $outlined, $type, $disabled}) => {
60
- const resolvedTheme = getTheme(theme);
61
-
62
- if ($outlined) {
63
- return resolvedTheme.text.basic;
64
- }
65
- if ($disabled) {
66
- return resolvedTheme.text.disabled;
67
- }
68
- return resolvedTheme.button[$type ?? 'primary'].text;
69
- }};
70
- `;
71
-
72
- // CheckboxWrapperOutlined Component
73
- export const CheckboxWrapperOutlined = styled.View<{
74
- $type: ButtonType;
75
- $disabled?: boolean;
76
- $checked?: boolean;
77
- }>`
78
- border-width: 1px;
79
- border-color: ${({theme, $type, $disabled}) => {
80
- const resolvedTheme = getTheme(theme);
81
-
82
- if ($disabled) {
83
- return resolvedTheme.bg.disabled;
84
- }
85
- return $type === 'light'
86
- ? resolvedTheme.role.primary
87
- : resolvedTheme.button[$type ?? 'primary'].bg;
88
- }};
89
- `;
90
-
91
- // CheckboxWrapper Component
92
- export const CheckboxWrapper = styled.View<{
93
- $type: ButtonType;
94
- $disabled?: boolean;
95
- $checked?: boolean;
96
- }>`
97
- background-color: ${({theme, $checked, $type, $disabled}) => {
98
- const resolvedTheme = getTheme(theme);
99
-
100
- if ($disabled) {
101
- return undefined;
102
- }
103
- if (!$checked) {
104
- return resolvedTheme.bg.disabled;
105
- }
106
- return $type === 'light'
107
- ? resolvedTheme.role.primary
108
- : resolvedTheme.button[$type ?? 'primary'].bg;
109
- }};
110
- `;
111
-
112
- // RadioButtonWrapper Component
113
- export const RadioButtonWrapper = styled.View<{
114
- $type: ButtonType;
115
- $selected?: boolean;
116
- $disabled?: boolean;
117
- $size?: 'small' | 'medium' | 'large' | number;
118
- }>`
119
- width: ${({$size = 'medium'}) => {
120
- if (typeof $size === 'number') return $size;
121
- return $size === 'small' ? 16 : $size === 'large' ? 24 : 20;
122
- }};
123
- height: ${({$size = 'medium'}) => {
124
- if (typeof $size === 'number') return $size;
125
- return $size === 'small' ? 16 : $size === 'large' ? 24 : 20;
126
- }};
127
- border-radius: ${({$size = 'medium'}) => {
128
- if (typeof $size === 'number') return $size / 2;
129
- return $size === 'small' ? 8 : $size === 'large' ? 12 : 10;
130
- }};
131
- margin: 0px 6px;
132
- justify-content: center;
133
- align-items: center;
134
- border-width: 1px;
135
- border-color: ${({theme, $type, $selected, $disabled}) => {
136
- const resolvedTheme = getTheme(theme);
137
-
138
- if ($disabled) {
139
- return resolvedTheme.bg.disabled;
140
- }
141
- const buttonColor = $type === 'light'
142
- ? resolvedTheme.role.primary
143
- : resolvedTheme.button[$type ?? 'primary'].bg;
144
- return $selected ? buttonColor : resolvedTheme.text.basic;
145
- }};
146
- background-color: transparent;
147
- `;
148
-
149
- // RadioWrapper Component
150
- export const RadioWrapper = styled(Animated.View)<{
151
- $type: ButtonType;
152
- $disabled?: boolean;
153
- $selected?: boolean;
154
- }>`
155
- flex: 1;
156
- align-self: stretch;
157
- background-color: ${({theme, $type, $disabled, $selected}) => {
158
- const resolvedTheme = getTheme(theme);
159
-
160
- if ($disabled) {
161
- return resolvedTheme.bg.disabled;
162
- }
163
- if (!$selected) {
164
- return 'transparent';
165
- }
166
- return $type === 'light'
167
- ? resolvedTheme.role.primary
168
- : resolvedTheme.button[$type ?? 'primary'].bg;
169
- }};
170
- `;
171
-
172
- // ColoredText Component
173
- export const ColoredText = styled(Typography.Body2)<{
174
- $type: ButtonType;
175
- $disabled?: boolean;
176
- $selected?: boolean;
177
- $size?: 'small' | 'medium' | 'large' | number;
178
- }>`
179
- color: ${({theme, $selected, $type, $disabled}) => {
180
- const resolvedTheme = getTheme(theme);
181
-
182
- if ($disabled) {
183
- return undefined;
184
- }
185
- if (!$selected) {
186
- return resolvedTheme.text.basic;
187
- }
188
- return $type === 'light'
189
- ? resolvedTheme.role.primary
190
- : resolvedTheme.button[$type ?? 'primary'].bg;
191
- }};
192
- font-size: ${({$size = 'medium'}) => {
193
- if (typeof $size === 'number') return $size * 0.7;
194
- return $size === 'small' ? 12 : $size === 'large' ? 16 : 14;
195
- }}px;
196
- `;
197
-
198
- // SnackbarWrapper Component
199
- export const SnackbarWrapper = styled.View<{
200
- $type?: SnackbarType;
201
- $checked?: boolean;
202
- }>`
203
- background-color: ${({theme, $type}) => {
204
- const resolvedTheme = getTheme(theme);
205
-
206
- return !$type ? resolvedTheme.bg.disabled : resolvedTheme.button[$type ?? 'light'].bg;
207
- }};
208
- flex-direction: row;
209
- text-align: left;
210
- align-items: center;
211
- align-self: center;
212
- position: absolute;
213
- font-size: 16px;
214
- padding: 10px 20px;
215
- bottom: 20px;
216
- border-radius: 20px;
217
- `;
@@ -1,75 +0,0 @@
1
- import type {ComponentProps} from 'react';
2
- import type {Meta, StoryObj} from '@storybook/react';
3
- import {SwitchToggle} from './SwitchToggle';
4
- import {withThemeProvider} from '../../../../.storybook/decorators';
5
-
6
- const meta = {
7
- title: 'SwitchToggle',
8
- component: (props) => <SwitchToggle {...props} />,
9
- parameters: {
10
- docs: {
11
- description: {
12
- component: `
13
- An animated SwitchToggle component with smooth transitions and proportional sizing.
14
-
15
- ## Features
16
- - **Smooth Animations**: Slide and color transitions
17
- - **Proportional Sizing**: Automatically calculates container and circle dimensions
18
- - **Flexible Sizing**: Preset sizes and custom numeric values
19
- - **Icon Elements**: Add elements before or after the switch
20
- - **Custom Colors**: Support for custom active and inactive colors
21
-
22
- ## Size Options
23
- - \`small\`: 24px height, 40px width
24
- - \`medium\`: 30px height, 56px width (default)
25
- - \`large\`: 40px height, 80px width
26
- - Custom number: Height = size, Width = size × 2, automatically calculated
27
-
28
- ## Size Calculation
29
- When using a custom number, dimensions are calculated as:
30
- - **Container width**: \`size × 2\`
31
- - **Container height**: \`size\`
32
- - **Circle size**: \`size × 0.8\`
33
- - **Padding**: \`size × 0.2\`
34
-
35
- ## Usage
36
- \`\`\`tsx
37
- <SwitchToggle
38
- isOn={isToggled}
39
- onPress={() => setIsToggled(!isToggled)}
40
- size="medium"
41
- />
42
- \`\`\`
43
- `,
44
- },
45
- },
46
- },
47
- argTypes: {
48
- size: {
49
- control: 'radio',
50
- options: ['small', 'medium', 'large', 24, 30, 36, 42],
51
- description: 'Switch size: "small" (24px), "medium" (30px), "large" (36px), or custom number with proportional scaling',
52
- },
53
- isOn: {
54
- description: 'Whether the switch is in the on position',
55
- },
56
- },
57
- decorators: [withThemeProvider],
58
- } satisfies Meta<typeof SwitchToggle>;
59
-
60
- export default meta;
61
-
62
- type Story = StoryObj<typeof meta>;
63
-
64
- export const Basic: Story = {
65
- args: {
66
- isOn: false,
67
- size: 'medium',
68
- },
69
- argTypes: {
70
- theme: {
71
- control: 'select',
72
- options: ['light', 'dark'],
73
- },
74
- },
75
- };
@@ -1,105 +0,0 @@
1
- import React, {type ReactElement} from 'react';
2
- import {Text} from 'react-native';
3
- import type {RenderAPI} from '@testing-library/react-native';
4
- import {fireEvent, render, waitFor} from '@testing-library/react-native';
5
- import {createComponent} from '../../../../test/testUtils';
6
- import {SwitchToggle, type SwitchToggleSizeType} from './SwitchToggle';
7
-
8
- let testingLib: RenderAPI;
9
-
10
- describe('[SwitchToggle]', () => {
11
- const handlePress = jest.fn();
12
-
13
- beforeEach(() => {
14
- handlePress.mockClear();
15
- });
16
-
17
- it('handles press event', async () => {
18
- testingLib = render(
19
- createComponent(<SwitchToggle isOn={false} onPress={handlePress} />),
20
- );
21
-
22
- const baseElement = await waitFor(() => testingLib.toJSON());
23
- expect(baseElement).toBeTruthy();
24
-
25
- const {getByRole} = testingLib;
26
- fireEvent.press(getByRole('switch'));
27
- expect(handlePress).toBeCalled();
28
- });
29
-
30
- const getSwitchToggle = ({isOn, size}: {isOn: boolean; size?: SwitchToggleSizeType}): ReactElement =>
31
- createComponent(
32
- <SwitchToggle
33
- isOn={isOn}
34
- size={size}
35
- offElement={<Text>off</Text>}
36
- onElement={<Text>on</Text>}
37
- onPress={handlePress}
38
- style={{padding: 6}}
39
- styles={{
40
- container: {paddingLeft: 6, paddingRight: 5},
41
- onElementContainer: {padding: 4},
42
- offElementContainer: {padding: 3},
43
- circle: {padding: 2},
44
- button: {padding: 1},
45
- circleColorOff: 'red',
46
- circleColorOn: 'blue',
47
- backgroundColorOn: 'green',
48
- backgroundColorOff: 'grey',
49
- }}
50
- />,
51
- );
52
-
53
- context('when switch toggle is on', () => {
54
- it('renders as on state', async () => {
55
- testingLib = render(createComponent(getSwitchToggle({isOn: true})));
56
-
57
- const baseElement = await waitFor(() => testingLib.toJSON());
58
- expect(baseElement).toBeTruthy();
59
- });
60
- });
61
-
62
- context('when switch toggle is off', () => {
63
- it('renders as off state', () => {
64
- const component = createComponent(getSwitchToggle({isOn: false}));
65
- testingLib = render(component);
66
-
67
- const baseElement = testingLib.toJSON();
68
- expect(baseElement).toBeTruthy();
69
- });
70
- });
71
-
72
- describe('sizes', () => {
73
- it('renders with small size', () => {
74
- const component = createComponent(getSwitchToggle({size: 'small'}));
75
- testingLib = render(component);
76
-
77
- const baseElement = testingLib.toJSON();
78
- expect(baseElement).toBeTruthy();
79
- });
80
-
81
- it('renders with medium size', () => {
82
- const component = createComponent(getSwitchToggle({size: 'medium'}));
83
- testingLib = render(component);
84
-
85
- const baseElement = testingLib.toJSON();
86
- expect(baseElement).toBeTruthy();
87
- });
88
-
89
- it('renders with large size', () => {
90
- const component = createComponent(getSwitchToggle({size: 'large'}));
91
- testingLib = render(component);
92
-
93
- const baseElement = testingLib.toJSON();
94
- expect(baseElement).toBeTruthy();
95
- });
96
-
97
- it('renders with custom numeric size', () => {
98
- const component = createComponent(getSwitchToggle({size: 28}));
99
- testingLib = render(component);
100
-
101
- const baseElement = testingLib.toJSON();
102
- expect(baseElement).toBeTruthy();
103
- });
104
- });
105
- });
@@ -1,305 +0,0 @@
1
- import React, {useCallback, useEffect, useMemo, useState, type ReactElement} from 'react';
2
- import type {StyleProp, ViewStyle} from 'react-native';
3
- import {Animated, TouchableOpacity, View} from 'react-native';
4
- import {styled} from 'kstyled';
5
- import {useTheme} from '../../../providers/ThemeProvider';
6
-
7
- // Metro cache workaround: Using explicit pattern until cache is cleared
8
- const AnimatedView = Animated.createAnimatedComponent(View);
9
-
10
- type Styles = {
11
- container?: ViewStyle;
12
- onElementContainer?: StyleProp<ViewStyle>;
13
- offElementContainer?: StyleProp<ViewStyle>;
14
- circle?: ViewStyle;
15
- button?: StyleProp<ViewStyle>;
16
- circleColorOff?: string;
17
- circleColorOn?: string;
18
- backgroundColorOn?: string;
19
- backgroundColorOff?: string;
20
- };
21
-
22
- export type SwitchToggleSizeType = 'small' | 'medium' | 'large' | number;
23
-
24
- type Props = {
25
- testID?: string;
26
- size?: SwitchToggleSizeType;
27
- isOn: boolean;
28
- style?: StyleProp<ViewStyle>;
29
- styles?: Styles;
30
- duration?: number;
31
- onElement?: any;
32
- offElement?: any;
33
- onPress?: () => void;
34
- };
35
-
36
- // Using AnimatedView created above - functionally identical to styled(Animated.View)
37
- const AnimatedContainer = styled(AnimatedView)`
38
- flex-direction: row;
39
- align-items: center;
40
- `;
41
-
42
- const smallContainer: ViewStyle = {
43
- width: 40,
44
- height: 24,
45
- borderRadius: 25,
46
- padding: 5,
47
- };
48
-
49
- const smallCircle: ViewStyle = {
50
- width: 16,
51
- height: 16,
52
- borderRadius: 8,
53
- };
54
-
55
- const mediumContainer: ViewStyle = {
56
- width: 56,
57
- height: 30,
58
- borderRadius: 25,
59
- padding: 5,
60
- };
61
-
62
- const mediumCircle: ViewStyle = {
63
- width: 20,
64
- height: 20,
65
- borderRadius: 10,
66
- };
67
-
68
- const largeContainer: ViewStyle = {
69
- width: 80,
70
- height: 40,
71
- borderRadius: 25,
72
- padding: 5,
73
- };
74
-
75
- const largeCircle: ViewStyle = {
76
- width: 32,
77
- height: 32,
78
- borderRadius: 16,
79
- };
80
-
81
- export function SwitchToggle({
82
- testID,
83
- isOn,
84
- style,
85
- styles,
86
- duration = 300,
87
- onElement,
88
- size = 'medium',
89
- offElement,
90
- onPress,
91
- }: Props): ReactElement {
92
- const {theme} = useTheme();
93
-
94
- // Memoize size-based configurations
95
- const sizeConfig = useMemo(() => {
96
- if (typeof size === 'number') {
97
- const containerConfig: ViewStyle = {
98
- width: size * 1.87,
99
- height: size,
100
- borderRadius: size / 2,
101
- padding: size * 0.17,
102
- };
103
- const circleConfig: ViewStyle = {
104
- width: size * 0.67,
105
- height: size * 0.67,
106
- borderRadius: size / 3,
107
- };
108
- return {containerConfig, circleConfig};
109
- }
110
-
111
- const containerConfig =
112
- size === 'large'
113
- ? largeContainer
114
- : size === 'small'
115
- ? smallContainer
116
- : mediumContainer;
117
-
118
- const circleConfig =
119
- size === 'large'
120
- ? largeCircle
121
- : size === 'small'
122
- ? smallCircle
123
- : mediumCircle;
124
-
125
- return {containerConfig, circleConfig};
126
- }, [size]);
127
-
128
- // Memoize styles with defaults
129
- const switchStyles = useMemo(() => {
130
- const {
131
- backgroundColorOn = theme.role.primary,
132
- backgroundColorOff = theme.bg.disabled,
133
- circleColorOn = theme.text.contrast,
134
- circleColorOff = theme.text.basic,
135
- container = sizeConfig.containerConfig,
136
- circle = sizeConfig.circleConfig,
137
- button,
138
- onElementContainer,
139
- offElementContainer,
140
- } = styles ?? {};
141
-
142
- return {
143
- backgroundColorOn,
144
- backgroundColorOff,
145
- circleColorOn,
146
- circleColorOff,
147
- container,
148
- circle,
149
- button,
150
- onElementContainer,
151
- offElementContainer,
152
- };
153
- }, [styles, theme, sizeConfig]);
154
-
155
- // Memoize padding calculations
156
- const padding = useMemo(() => {
157
- const paddingLeft =
158
- (switchStyles.container.padding as number) ||
159
- (switchStyles.container.paddingLeft as number) ||
160
- 0;
161
- const paddingRight =
162
- (switchStyles.container.padding as number) ||
163
- (switchStyles.container.paddingRight as number) ||
164
- 0;
165
- return {paddingLeft, paddingRight};
166
- }, [switchStyles.container]);
167
-
168
- // Memoize position calculations
169
- const positions = useMemo(() => {
170
- const circlePosXStart = 0;
171
- const circlePosXEnd =
172
- ((switchStyles.container.width ?? mediumContainer.width) as number) -
173
- ((switchStyles.circle.width ?? mediumCircle.width) as number) -
174
- (padding.paddingRight + padding.paddingLeft);
175
-
176
- return {circlePosXStart, circlePosXEnd};
177
- }, [
178
- switchStyles.container.width,
179
- switchStyles.circle.width,
180
- padding,
181
- mediumContainer,
182
- smallContainer,
183
- largeContainer,
184
- ]);
185
-
186
- const [animXValue] = useState(new Animated.Value(isOn ? 1 : 0));
187
-
188
- // Memoize animation function
189
- const runAnimation = useCallback(() => {
190
- const option = {
191
- fromValue: isOn ? 0 : 1,
192
- toValue: isOn ? 1 : 0,
193
- duration,
194
- useNativeDriver: false,
195
- };
196
-
197
- Animated.timing(animXValue, option).start();
198
- }, [animXValue, isOn, duration]);
199
-
200
- useEffect(() => {
201
- runAnimation();
202
- }, [runAnimation]);
203
-
204
- // Memoize animated components
205
- const CircleButton = useMemo(
206
- () => (
207
- <Animated.View
208
- style={[
209
- switchStyles.circle,
210
- {
211
- backgroundColor: animXValue.interpolate({
212
- inputRange: [0.5, 1],
213
- outputRange: [
214
- switchStyles.circleColorOff as string | number,
215
- switchStyles.circleColorOn as string | number,
216
- ] as string[] | number[],
217
- }),
218
- },
219
- {
220
- transform: [
221
- {
222
- translateX: animXValue.interpolate({
223
- inputRange: [0, 1],
224
- outputRange: [
225
- positions.circlePosXStart as string | number,
226
- positions.circlePosXEnd as string | number,
227
- ] as string[] | number[],
228
- }),
229
- },
230
- ],
231
- },
232
- switchStyles.button,
233
- ]}
234
- />
235
- ),
236
- [switchStyles, animXValue, positions]
237
- );
238
-
239
- const OnElement = useMemo(
240
- () => (
241
- <Animated.View style={[{opacity: animXValue}, switchStyles.onElementContainer]}>
242
- {onElement}
243
- </Animated.View>
244
- ),
245
- [animXValue, switchStyles.onElementContainer, onElement]
246
- );
247
-
248
- const OffElement = useMemo(
249
- () => (
250
- <Animated.View
251
- style={[
252
- {
253
- opacity: animXValue.interpolate({
254
- inputRange: [0, 1],
255
- outputRange: [1, 0],
256
- }),
257
- },
258
- switchStyles.offElementContainer,
259
- ]}
260
- >
261
- {offElement}
262
- </Animated.View>
263
- ),
264
- [animXValue, switchStyles.offElementContainer, offElement]
265
- );
266
-
267
- // Memoize container styles
268
- const containerStyles = useMemo(
269
- () => [
270
- switchStyles.container,
271
- {
272
- paddingLeft: padding.paddingLeft,
273
- paddingRight: padding.paddingRight,
274
- },
275
- {
276
- backgroundColor: animXValue.interpolate({
277
- inputRange: [0, 1],
278
- outputRange: [
279
- switchStyles.backgroundColorOff as string | number,
280
- switchStyles.backgroundColorOn as string | number,
281
- ] as string[] | number[],
282
- }),
283
- },
284
- ],
285
- [switchStyles, padding, animXValue]
286
- );
287
-
288
- return (
289
- <TouchableOpacity
290
- accessibilityRole="switch"
291
- activeOpacity={0.8}
292
- onPress={onPress}
293
- style={style}
294
- testID={testID}
295
- >
296
- <AnimatedContainer style={containerStyles}>
297
- {isOn ? OnElement : OffElement}
298
- {CircleButton}
299
- </AnimatedContainer>
300
- </TouchableOpacity>
301
- );
302
- }
303
-
304
- // Export memoized component for better performance
305
- export default React.memo(SwitchToggle) as typeof SwitchToggle;