@urbint/cl 1.0.1
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/.cursor/rules +313 -0
- package/.rnstorybook/index.ts +11 -0
- package/.rnstorybook/main.ts +8 -0
- package/.rnstorybook/preview.tsx +14 -0
- package/.rnstorybook/storybook.requires.ts +49 -0
- package/.storybook/main.ts +16 -0
- package/.storybook/preview.ts +32 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/App.tsx +422 -0
- package/README.md +229 -0
- package/app.json +33 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash-icon.png +0 -0
- package/babel.config.js +16 -0
- package/docs/components/CodeBlock.tsx +80 -0
- package/docs/components/PropTable.tsx +93 -0
- package/docs/components/Sidebar.tsx +199 -0
- package/docs/components/index.ts +8 -0
- package/docs/data/colorTokens.ts +70 -0
- package/docs/data/componentData.tsx +1685 -0
- package/docs/data/index.ts +7 -0
- package/docs/index.ts +19 -0
- package/docs/navigation.ts +94 -0
- package/docs/pages/ColorsPage.tsx +226 -0
- package/docs/pages/ComponentPage.tsx +235 -0
- package/docs/pages/InstallationPage.tsx +232 -0
- package/docs/pages/IntroductionPage.tsx +163 -0
- package/docs/pages/ThemingPage.tsx +251 -0
- package/docs/pages/index.ts +10 -0
- package/docs/theme.ts +64 -0
- package/docs/types.ts +54 -0
- package/index.ts +8 -0
- package/llms.txt +1893 -0
- package/mcp-config.example.json +10 -0
- package/mcp-server/README.md +192 -0
- package/mcp-server/package-lock.json +1707 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/src/index.ts +1136 -0
- package/mcp-server/src/registry/components.ts +1446 -0
- package/mcp-server/src/registry/index.ts +3 -0
- package/mcp-server/src/registry/tokens.ts +256 -0
- package/mcp-server/tsconfig.json +19 -0
- package/package.json +92 -0
- package/src/components/Accordion/Accordion.stories.tsx +226 -0
- package/src/components/Accordion/Accordion.tsx +255 -0
- package/src/components/Accordion/index.ts +12 -0
- package/src/components/ActionSheet/ActionSheet.stories.tsx +393 -0
- package/src/components/ActionSheet/ActionSheet.tsx +258 -0
- package/src/components/ActionSheet/index.ts +2 -0
- package/src/components/Alert/Alert.stories.tsx +165 -0
- package/src/components/Alert/Alert.tsx +164 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AlertDialog/AlertDialog.stories.tsx +330 -0
- package/src/components/AlertDialog/AlertDialog.tsx +234 -0
- package/src/components/AlertDialog/index.ts +2 -0
- package/src/components/Avatar/Avatar.stories.tsx +154 -0
- package/src/components/Avatar/Avatar.tsx +219 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.stories.tsx +146 -0
- package/src/components/Badge/Badge.tsx +125 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Box/Box.stories.tsx +192 -0
- package/src/components/Box/Box.tsx +184 -0
- package/src/components/Box/index.ts +2 -0
- package/src/components/Button/Button.stories.tsx +157 -0
- package/src/components/Button/Button.tsx +180 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +145 -0
- package/src/components/Card/Card.tsx +169 -0
- package/src/components/Card/index.ts +11 -0
- package/src/components/Center/Center.stories.tsx +215 -0
- package/src/components/Center/Center.tsx +29 -0
- package/src/components/Center/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +94 -0
- package/src/components/Checkbox/Checkbox.tsx +242 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +623 -0
- package/src/components/DatePicker/DatePicker.tsx +1228 -0
- package/src/components/DatePicker/index.ts +8 -0
- package/src/components/Divider/Divider.stories.tsx +224 -0
- package/src/components/Divider/Divider.tsx +73 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Drawer/Drawer.stories.tsx +414 -0
- package/src/components/Drawer/Drawer.tsx +342 -0
- package/src/components/Drawer/index.ts +11 -0
- package/src/components/Fab/Fab.stories.tsx +360 -0
- package/src/components/Fab/Fab.tsx +185 -0
- package/src/components/Fab/index.ts +2 -0
- package/src/components/FormControl/FormControl.stories.tsx +276 -0
- package/src/components/FormControl/FormControl.tsx +185 -0
- package/src/components/FormControl/index.ts +12 -0
- package/src/components/Grid/Grid.stories.tsx +244 -0
- package/src/components/Grid/Grid.tsx +93 -0
- package/src/components/Grid/index.ts +2 -0
- package/src/components/HStack/HStack.stories.tsx +230 -0
- package/src/components/HStack/HStack.tsx +80 -0
- package/src/components/HStack/index.ts +2 -0
- package/src/components/Heading/Heading.stories.tsx +111 -0
- package/src/components/Heading/Heading.tsx +85 -0
- package/src/components/Heading/index.ts +2 -0
- package/src/components/Icon/Icon.stories.tsx +320 -0
- package/src/components/Icon/Icon.tsx +117 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Image/Image.stories.tsx +357 -0
- package/src/components/Image/Image.tsx +168 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/Input/Input.stories.tsx +164 -0
- package/src/components/Input/Input.tsx +274 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Link/Link.stories.tsx +187 -0
- package/src/components/Link/Link.tsx +104 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/Menu/Menu.stories.tsx +363 -0
- package/src/components/Menu/Menu.tsx +238 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/Modal/Modal.stories.tsx +156 -0
- package/src/components/Modal/Modal.tsx +280 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/Popover/Popover.stories.tsx +330 -0
- package/src/components/Popover/Popover.tsx +315 -0
- package/src/components/Popover/index.ts +11 -0
- package/src/components/Portal/Portal.stories.tsx +376 -0
- package/src/components/Portal/Portal.tsx +100 -0
- package/src/components/Portal/index.ts +2 -0
- package/src/components/Pressable/Pressable.stories.tsx +338 -0
- package/src/components/Pressable/Pressable.tsx +71 -0
- package/src/components/Pressable/index.ts +2 -0
- package/src/components/Progress/Progress.stories.tsx +131 -0
- package/src/components/Progress/Progress.tsx +219 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/Radio/Radio.stories.tsx +101 -0
- package/src/components/Radio/Radio.tsx +234 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Select/Select.stories.tsx +908 -0
- package/src/components/Select/Select.tsx +659 -0
- package/src/components/Select/index.ts +8 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +154 -0
- package/src/components/Skeleton/Skeleton.tsx +192 -0
- package/src/components/Skeleton/index.ts +8 -0
- package/src/components/Slider/Slider.stories.tsx +363 -0
- package/src/components/Slider/Slider.tsx +209 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Spinner/Spinner.stories.tsx +108 -0
- package/src/components/Spinner/Spinner.tsx +121 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Switch/Switch.stories.tsx +116 -0
- package/src/components/Switch/Switch.tsx +172 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.stories.tsx +417 -0
- package/src/components/Table/Table.tsx +233 -0
- package/src/components/Table/index.ts +2 -0
- package/src/components/Text/Text.stories.tsx +93 -0
- package/src/components/Text/Text.tsx +119 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.stories.tsx +280 -0
- package/src/components/Textarea/Textarea.tsx +212 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Toast/Toast.stories.tsx +446 -0
- package/src/components/Toast/Toast.tsx +221 -0
- package/src/components/Toast/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +354 -0
- package/src/components/Tooltip/Tooltip.tsx +261 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/VStack/VStack.stories.tsx +183 -0
- package/src/components/VStack/VStack.tsx +76 -0
- package/src/components/VStack/index.ts +2 -0
- package/src/components/index.ts +62 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useControllableState.ts +41 -0
- package/src/hooks/useDisclosure.ts +51 -0
- package/src/index.ts +22 -0
- package/src/stories/Button.stories.tsx +53 -0
- package/src/stories/Button.tsx +101 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.tsx +33 -0
- package/src/stories/Header.tsx +75 -0
- package/src/stories/Page.stories.tsx +25 -0
- package/src/stories/Page.tsx +154 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/styles/index.ts +7 -0
- package/src/styles/tokens.ts +318 -0
- package/src/styles/unistyles.ts +254 -0
- package/src/utils/createContext.tsx +25 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/mergeRefs.ts +21 -0
- package/tsconfig.json +26 -0
- package/urbint-cl-1.0.0.tgz +0 -0
- package/vitest.config.ts +37 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slider Component
|
|
3
|
+
* Range slider for numeric value selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { forwardRef, useRef, useState } from 'react';
|
|
7
|
+
import { View, ViewProps, Text, PanResponder, LayoutChangeEvent, StyleSheet } from 'react-native';
|
|
8
|
+
import { colors, spacing, borderRadius, typography, elevation } from '../../styles/tokens';
|
|
9
|
+
|
|
10
|
+
export interface SliderProps extends Omit<ViewProps, 'children'> {
|
|
11
|
+
/** Current value */
|
|
12
|
+
value?: number;
|
|
13
|
+
/** Default value */
|
|
14
|
+
defaultValue?: number;
|
|
15
|
+
/** Minimum value */
|
|
16
|
+
min?: number;
|
|
17
|
+
/** Maximum value */
|
|
18
|
+
max?: number;
|
|
19
|
+
/** Step increment */
|
|
20
|
+
step?: number;
|
|
21
|
+
/** On change handler */
|
|
22
|
+
onChange?: (value: number) => void;
|
|
23
|
+
/** On change end handler */
|
|
24
|
+
onChangeEnd?: (value: number) => void;
|
|
25
|
+
/** Is disabled */
|
|
26
|
+
isDisabled?: boolean;
|
|
27
|
+
/** Show value label */
|
|
28
|
+
showValue?: boolean;
|
|
29
|
+
/** Slider size */
|
|
30
|
+
size?: 'sm' | 'md' | 'lg';
|
|
31
|
+
/** Color scheme */
|
|
32
|
+
colorScheme?: 'primary' | 'success' | 'danger';
|
|
33
|
+
/** Label */
|
|
34
|
+
label?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sizeConfig = {
|
|
38
|
+
sm: { track: 4, thumb: 16 },
|
|
39
|
+
md: { track: 6, thumb: 20 },
|
|
40
|
+
lg: { track: 8, thumb: 24 },
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
const colorMap = {
|
|
44
|
+
primary: colors.brand.blue,
|
|
45
|
+
success: colors.feedback.success.content,
|
|
46
|
+
danger: colors.feedback.error.content,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Slider = forwardRef<View, SliderProps>(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
style,
|
|
53
|
+
value: controlledValue,
|
|
54
|
+
defaultValue = 0,
|
|
55
|
+
min = 0,
|
|
56
|
+
max = 100,
|
|
57
|
+
step = 1,
|
|
58
|
+
onChange,
|
|
59
|
+
onChangeEnd,
|
|
60
|
+
isDisabled = false,
|
|
61
|
+
showValue = false,
|
|
62
|
+
size = 'md',
|
|
63
|
+
colorScheme = 'primary',
|
|
64
|
+
label,
|
|
65
|
+
...props
|
|
66
|
+
},
|
|
67
|
+
ref
|
|
68
|
+
) => {
|
|
69
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
70
|
+
const [sliderWidth, setSliderWidth] = useState(0);
|
|
71
|
+
const isControlled = controlledValue !== undefined;
|
|
72
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
73
|
+
|
|
74
|
+
const config = sizeConfig[size];
|
|
75
|
+
const percentage = ((value - min) / (max - min)) * 100;
|
|
76
|
+
const activeColor = colorMap[colorScheme];
|
|
77
|
+
|
|
78
|
+
const updateValue = (locationX: number) => {
|
|
79
|
+
if (isDisabled) return;
|
|
80
|
+
|
|
81
|
+
const newPercentage = Math.max(0, Math.min(1, locationX / sliderWidth));
|
|
82
|
+
let newValue = min + newPercentage * (max - min);
|
|
83
|
+
|
|
84
|
+
newValue = Math.round(newValue / step) * step;
|
|
85
|
+
newValue = Math.max(min, Math.min(max, newValue));
|
|
86
|
+
|
|
87
|
+
if (!isControlled) {
|
|
88
|
+
setInternalValue(newValue);
|
|
89
|
+
}
|
|
90
|
+
onChange?.(newValue);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const panResponder = useRef(
|
|
94
|
+
PanResponder.create({
|
|
95
|
+
onStartShouldSetPanResponder: () => !isDisabled,
|
|
96
|
+
onMoveShouldSetPanResponder: () => !isDisabled,
|
|
97
|
+
onPanResponderGrant: (e) => {
|
|
98
|
+
updateValue(e.nativeEvent.locationX);
|
|
99
|
+
},
|
|
100
|
+
onPanResponderMove: (e) => {
|
|
101
|
+
updateValue(e.nativeEvent.locationX);
|
|
102
|
+
},
|
|
103
|
+
onPanResponderRelease: () => {
|
|
104
|
+
onChangeEnd?.(value);
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
).current;
|
|
108
|
+
|
|
109
|
+
const handleLayout = (e: LayoutChangeEvent) => {
|
|
110
|
+
setSliderWidth(e.nativeEvent.layout.width);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<View ref={ref} style={[styles.container, style]} {...props}>
|
|
115
|
+
{(label || showValue) && (
|
|
116
|
+
<View style={styles.header}>
|
|
117
|
+
{label && <Text style={styles.label}>{label}</Text>}
|
|
118
|
+
{showValue && <Text style={styles.valueLabel}>{value}</Text>}
|
|
119
|
+
</View>
|
|
120
|
+
)}
|
|
121
|
+
<View
|
|
122
|
+
style={[styles.track, { height: config.track }]}
|
|
123
|
+
onLayout={handleLayout}
|
|
124
|
+
{...panResponder.panHandlers}
|
|
125
|
+
>
|
|
126
|
+
<View
|
|
127
|
+
style={[
|
|
128
|
+
styles.filledTrack,
|
|
129
|
+
{
|
|
130
|
+
width: `${percentage}%`,
|
|
131
|
+
height: config.track,
|
|
132
|
+
backgroundColor: isDisabled ? colors.border.disabled : activeColor,
|
|
133
|
+
},
|
|
134
|
+
]}
|
|
135
|
+
/>
|
|
136
|
+
<View
|
|
137
|
+
style={[
|
|
138
|
+
styles.thumb,
|
|
139
|
+
{
|
|
140
|
+
width: config.thumb,
|
|
141
|
+
height: config.thumb,
|
|
142
|
+
left: `${percentage}%`,
|
|
143
|
+
marginLeft: -(config.thumb / 2),
|
|
144
|
+
backgroundColor: isDisabled ? colors.border.default : activeColor,
|
|
145
|
+
},
|
|
146
|
+
isDisabled && styles.thumbDisabled,
|
|
147
|
+
]}
|
|
148
|
+
/>
|
|
149
|
+
</View>
|
|
150
|
+
<View style={styles.footer}>
|
|
151
|
+
<Text style={styles.rangeLabel}>{min}</Text>
|
|
152
|
+
<Text style={styles.rangeLabel}>{max}</Text>
|
|
153
|
+
</View>
|
|
154
|
+
</View>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
Slider.displayName = 'Slider';
|
|
160
|
+
|
|
161
|
+
const styles = StyleSheet.create({
|
|
162
|
+
container: {
|
|
163
|
+
width: '100%',
|
|
164
|
+
},
|
|
165
|
+
header: {
|
|
166
|
+
flexDirection: 'row',
|
|
167
|
+
justifyContent: 'space-between',
|
|
168
|
+
alignItems: 'center',
|
|
169
|
+
marginBottom: spacing['2x'],
|
|
170
|
+
},
|
|
171
|
+
label: {
|
|
172
|
+
fontSize: typography.fontSize.componentLabel,
|
|
173
|
+
fontWeight: typography.fontWeight.semiBold,
|
|
174
|
+
color: colors.text.default,
|
|
175
|
+
},
|
|
176
|
+
valueLabel: {
|
|
177
|
+
fontSize: typography.fontSize.body,
|
|
178
|
+
fontWeight: typography.fontWeight.medium,
|
|
179
|
+
color: colors.brand.blue,
|
|
180
|
+
},
|
|
181
|
+
track: {
|
|
182
|
+
width: '100%',
|
|
183
|
+
backgroundColor: colors.border.disabled,
|
|
184
|
+
borderRadius: borderRadius.full,
|
|
185
|
+
justifyContent: 'center',
|
|
186
|
+
},
|
|
187
|
+
filledTrack: {
|
|
188
|
+
position: 'absolute',
|
|
189
|
+
left: 0,
|
|
190
|
+
borderRadius: borderRadius.full,
|
|
191
|
+
},
|
|
192
|
+
thumb: {
|
|
193
|
+
position: 'absolute',
|
|
194
|
+
borderRadius: borderRadius.full,
|
|
195
|
+
...elevation['10'],
|
|
196
|
+
},
|
|
197
|
+
thumbDisabled: {
|
|
198
|
+
shadowOpacity: 0,
|
|
199
|
+
},
|
|
200
|
+
footer: {
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
justifyContent: 'space-between',
|
|
203
|
+
marginTop: spacing.base,
|
|
204
|
+
},
|
|
205
|
+
rangeLabel: {
|
|
206
|
+
fontSize: typography.fontSize.caption,
|
|
207
|
+
color: colors.text.secondary,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { Spinner } from './Spinner';
|
|
4
|
+
import { VStack } from '../VStack';
|
|
5
|
+
import { HStack } from '../HStack';
|
|
6
|
+
import { Text } from '../Text';
|
|
7
|
+
import { colors, spacing, borderRadius } from '../../styles/tokens';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Story container with design system tokens
|
|
11
|
+
*/
|
|
12
|
+
const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
13
|
+
<View style={styles.container}>{children}</View>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Spinner> = {
|
|
17
|
+
title: 'Components/Spinner',
|
|
18
|
+
component: Spinner,
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<StoryContainer>
|
|
22
|
+
<Story />
|
|
23
|
+
</StoryContainer>
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
args: {
|
|
27
|
+
size: 'md',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof Spinner>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {};
|
|
36
|
+
|
|
37
|
+
export const Sizes: Story = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<VStack space={spacing.lg}>
|
|
40
|
+
<Text weight="semiBold">Spinner Sizes</Text>
|
|
41
|
+
<HStack space={spacing.lg} alignItems="center">
|
|
42
|
+
<Spinner size="xs" />
|
|
43
|
+
<Spinner size="sm" />
|
|
44
|
+
<Spinner size="md" />
|
|
45
|
+
<Spinner size="lg" />
|
|
46
|
+
<Spinner size="xl" />
|
|
47
|
+
</HStack>
|
|
48
|
+
</VStack>
|
|
49
|
+
),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Colors: Story = {
|
|
53
|
+
render: () => (
|
|
54
|
+
<VStack space={spacing.lg}>
|
|
55
|
+
<Text weight="semiBold">Custom Colors</Text>
|
|
56
|
+
<HStack space={spacing.lg}>
|
|
57
|
+
<Spinner color={colors.brand.blue} />
|
|
58
|
+
<Spinner color={colors.feedback.success.content} />
|
|
59
|
+
<Spinner color={colors.feedback.error.content} />
|
|
60
|
+
<Spinner color={colors.feedback.warning.content} />
|
|
61
|
+
<Spinner color={colors.badge.purple} />
|
|
62
|
+
</HStack>
|
|
63
|
+
</VStack>
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const WithLabel: Story = {
|
|
68
|
+
render: () => (
|
|
69
|
+
<VStack space={spacing.lg}>
|
|
70
|
+
<Text weight="semiBold">With Label</Text>
|
|
71
|
+
<Spinner size="md" label="Loading..." />
|
|
72
|
+
</VStack>
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const CustomTrack: Story = {
|
|
77
|
+
render: () => (
|
|
78
|
+
<VStack space={spacing.lg}>
|
|
79
|
+
<Text weight="semiBold">Custom Track Color</Text>
|
|
80
|
+
<HStack space={spacing.lg}>
|
|
81
|
+
<Spinner color={colors.brand.blue} trackColor={colors.border.disabled} />
|
|
82
|
+
<Spinner color={colors.feedback.success.content} trackColor={colors.background.tertiary} />
|
|
83
|
+
</HStack>
|
|
84
|
+
</VStack>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const LoadingState: Story = {
|
|
89
|
+
render: () => (
|
|
90
|
+
<VStack space={spacing.lg} alignItems="center" style={{ padding: spacing.xl }}>
|
|
91
|
+
<Spinner size="lg" color={colors.brand.blue} />
|
|
92
|
+
<Text>Loading your data...</Text>
|
|
93
|
+
<Text variant="caption" color={colors.text.secondary}>This may take a few moments</Text>
|
|
94
|
+
</VStack>
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Styles using design system tokens
|
|
100
|
+
*/
|
|
101
|
+
const styles = StyleSheet.create({
|
|
102
|
+
container: {
|
|
103
|
+
padding: spacing.lg,
|
|
104
|
+
backgroundColor: colors.background.default,
|
|
105
|
+
borderRadius: borderRadius.lg,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner Component
|
|
3
|
+
* Loading spinner indicator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { forwardRef, useEffect, useRef } from 'react';
|
|
7
|
+
import { View, ViewProps, Animated, Easing, StyleSheet } from 'react-native';
|
|
8
|
+
import Svg, { Circle } from 'react-native-svg';
|
|
9
|
+
import { colors, spacing, typography } from '../../styles/tokens';
|
|
10
|
+
|
|
11
|
+
export interface SpinnerProps extends ViewProps {
|
|
12
|
+
/** Spinner size */
|
|
13
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
14
|
+
/** Spinner color */
|
|
15
|
+
color?: string;
|
|
16
|
+
/** Track color */
|
|
17
|
+
trackColor?: string;
|
|
18
|
+
/** Stroke width */
|
|
19
|
+
strokeWidth?: number;
|
|
20
|
+
/** Label */
|
|
21
|
+
label?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sizeMap = {
|
|
25
|
+
xs: 16,
|
|
26
|
+
sm: 24,
|
|
27
|
+
md: 32,
|
|
28
|
+
lg: 48,
|
|
29
|
+
xl: 64,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
const AnimatedSvg = Animated.createAnimatedComponent(Svg);
|
|
33
|
+
|
|
34
|
+
export const Spinner = forwardRef<View, SpinnerProps>(
|
|
35
|
+
(
|
|
36
|
+
{
|
|
37
|
+
style,
|
|
38
|
+
size = 'md',
|
|
39
|
+
color,
|
|
40
|
+
trackColor,
|
|
41
|
+
strokeWidth,
|
|
42
|
+
label,
|
|
43
|
+
...props
|
|
44
|
+
},
|
|
45
|
+
ref
|
|
46
|
+
) => {
|
|
47
|
+
const spinnerSize = typeof size === 'string' ? sizeMap[size] : size;
|
|
48
|
+
const defaultStrokeWidth = strokeWidth ?? Math.max(2, spinnerSize / 10);
|
|
49
|
+
const spinnerColor = color || colors.brand.blue;
|
|
50
|
+
const spinnerTrackColor = trackColor || colors.border.disabled;
|
|
51
|
+
|
|
52
|
+
const rotateAnim = useRef(new Animated.Value(0)).current;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
Animated.loop(
|
|
56
|
+
Animated.timing(rotateAnim, {
|
|
57
|
+
toValue: 1,
|
|
58
|
+
duration: 1000,
|
|
59
|
+
easing: Easing.linear,
|
|
60
|
+
useNativeDriver: true,
|
|
61
|
+
})
|
|
62
|
+
).start();
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const rotation = rotateAnim.interpolate({
|
|
66
|
+
inputRange: [0, 1],
|
|
67
|
+
outputRange: ['0deg', '360deg'],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const circumference = 2 * Math.PI * ((spinnerSize - defaultStrokeWidth) / 2);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<View ref={ref} style={[styles.container, style]} {...props}>
|
|
74
|
+
<AnimatedSvg
|
|
75
|
+
width={spinnerSize}
|
|
76
|
+
height={spinnerSize}
|
|
77
|
+
style={{ transform: [{ rotate: rotation }] }}
|
|
78
|
+
>
|
|
79
|
+
{/* Track */}
|
|
80
|
+
<Circle
|
|
81
|
+
cx={spinnerSize / 2}
|
|
82
|
+
cy={spinnerSize / 2}
|
|
83
|
+
r={(spinnerSize - defaultStrokeWidth) / 2}
|
|
84
|
+
stroke={spinnerTrackColor}
|
|
85
|
+
strokeWidth={defaultStrokeWidth}
|
|
86
|
+
fill="none"
|
|
87
|
+
/>
|
|
88
|
+
{/* Spinner */}
|
|
89
|
+
<Circle
|
|
90
|
+
cx={spinnerSize / 2}
|
|
91
|
+
cy={spinnerSize / 2}
|
|
92
|
+
r={(spinnerSize - defaultStrokeWidth) / 2}
|
|
93
|
+
stroke={spinnerColor}
|
|
94
|
+
strokeWidth={defaultStrokeWidth}
|
|
95
|
+
fill="none"
|
|
96
|
+
strokeDasharray={`${circumference * 0.75} ${circumference * 0.25}`}
|
|
97
|
+
strokeLinecap="round"
|
|
98
|
+
/>
|
|
99
|
+
</AnimatedSvg>
|
|
100
|
+
{label && <View style={styles.labelContainer}><Animated.Text style={styles.label}>{label}</Animated.Text></View>}
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
Spinner.displayName = 'Spinner';
|
|
107
|
+
|
|
108
|
+
const styles = StyleSheet.create({
|
|
109
|
+
container: {
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
},
|
|
113
|
+
labelContainer: {
|
|
114
|
+
marginTop: spacing['2x'],
|
|
115
|
+
},
|
|
116
|
+
label: {
|
|
117
|
+
fontSize: typography.fontSize.caption,
|
|
118
|
+
color: colors.text.secondary,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { Switch } from './Switch';
|
|
4
|
+
import { VStack } from '../VStack';
|
|
5
|
+
import { HStack } from '../HStack';
|
|
6
|
+
import { Text } from '../Text';
|
|
7
|
+
import { colors, spacing, borderRadius } from '../../styles/tokens';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Story container with design system tokens
|
|
11
|
+
*/
|
|
12
|
+
const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
13
|
+
<View style={styles.container}>{children}</View>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Switch> = {
|
|
17
|
+
title: 'Components/Switch',
|
|
18
|
+
component: Switch,
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<StoryContainer>
|
|
22
|
+
<Story />
|
|
23
|
+
</StoryContainer>
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
args: {
|
|
27
|
+
label: 'Enable notifications',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof Switch>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {};
|
|
36
|
+
|
|
37
|
+
export const Sizes: Story = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<VStack space={spacing.lg}>
|
|
40
|
+
<Switch label="Small" size="sm" />
|
|
41
|
+
<Switch label="Medium" size="md" />
|
|
42
|
+
<Switch label="Large" size="lg" />
|
|
43
|
+
</VStack>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ColorSchemes: Story = {
|
|
48
|
+
render: () => (
|
|
49
|
+
<VStack space={spacing.lg}>
|
|
50
|
+
<Switch label="Primary" colorScheme="primary" defaultChecked />
|
|
51
|
+
<Switch label="Success" colorScheme="success" defaultChecked />
|
|
52
|
+
<Switch label="Danger" colorScheme="danger" defaultChecked />
|
|
53
|
+
</VStack>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const States: Story = {
|
|
58
|
+
render: () => (
|
|
59
|
+
<VStack space={spacing.lg}>
|
|
60
|
+
<Switch label="Unchecked" />
|
|
61
|
+
<Switch label="Checked" defaultChecked />
|
|
62
|
+
<Switch label="Disabled" isDisabled />
|
|
63
|
+
<Switch label="Disabled Checked" isDisabled defaultChecked />
|
|
64
|
+
</VStack>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const LabelPositions: Story = {
|
|
69
|
+
render: () => (
|
|
70
|
+
<VStack space={spacing.lg}>
|
|
71
|
+
<Switch label="Label on right" labelPosition="right" />
|
|
72
|
+
<Switch label="Label on left" labelPosition="left" />
|
|
73
|
+
</VStack>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const SettingsExample: Story = {
|
|
78
|
+
render: () => (
|
|
79
|
+
<VStack space={spacing.xs}>
|
|
80
|
+
<Text weight="semiBold" style={{ marginBottom: spacing.sm }}>Notification Settings</Text>
|
|
81
|
+
<HStack justifyContent="space-between" style={{ paddingVertical: spacing.md }}>
|
|
82
|
+
<VStack>
|
|
83
|
+
<Text>Email notifications</Text>
|
|
84
|
+
<Text variant="caption" color={colors.text.secondary}>Receive email updates</Text>
|
|
85
|
+
</VStack>
|
|
86
|
+
<Switch defaultChecked />
|
|
87
|
+
</HStack>
|
|
88
|
+
<HStack justifyContent="space-between" style={{ paddingVertical: spacing.md }}>
|
|
89
|
+
<VStack>
|
|
90
|
+
<Text>Push notifications</Text>
|
|
91
|
+
<Text variant="caption" color={colors.text.secondary}>Receive push alerts</Text>
|
|
92
|
+
</VStack>
|
|
93
|
+
<Switch defaultChecked />
|
|
94
|
+
</HStack>
|
|
95
|
+
<HStack justifyContent="space-between" style={{ paddingVertical: spacing.md }}>
|
|
96
|
+
<VStack>
|
|
97
|
+
<Text>SMS notifications</Text>
|
|
98
|
+
<Text variant="caption" color={colors.text.secondary}>Receive text messages</Text>
|
|
99
|
+
</VStack>
|
|
100
|
+
<Switch />
|
|
101
|
+
</HStack>
|
|
102
|
+
</VStack>
|
|
103
|
+
),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Styles using design system tokens
|
|
108
|
+
*/
|
|
109
|
+
const styles = StyleSheet.create({
|
|
110
|
+
container: {
|
|
111
|
+
padding: spacing.lg,
|
|
112
|
+
backgroundColor: colors.background.default,
|
|
113
|
+
borderRadius: borderRadius.lg,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|