@campxdev/react-native-blueprint 0.1.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.
- package/LICENSE +20 -0
- package/README.md +358 -0
- package/lib/module/app/_layout.js +23 -0
- package/lib/module/app/_layout.js.map +1 -0
- package/lib/module/assets/icons/weather_icons/drizzle.png +0 -0
- package/lib/module/assets/icons/weather_icons/foggy.png +0 -0
- package/lib/module/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/lib/module/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/lib/module/assets/icons/weather_icons/rainy.png +0 -0
- package/lib/module/assets/icons/weather_icons/showers.png +0 -0
- package/lib/module/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/lib/module/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/lib/module/components/theme-config.js +265 -0
- package/lib/module/components/theme-config.js.map +1 -0
- package/lib/module/components/ui/Accordion.js +228 -0
- package/lib/module/components/ui/Accordion.js.map +1 -0
- package/lib/module/components/ui/Alert-Dialog.js +266 -0
- package/lib/module/components/ui/Alert-Dialog.js.map +1 -0
- package/lib/module/components/ui/Alert.js +107 -0
- package/lib/module/components/ui/Alert.js.map +1 -0
- package/lib/module/components/ui/AppBar.js +403 -0
- package/lib/module/components/ui/AppBar.js.map +1 -0
- package/lib/module/components/ui/Aspect-Ratio.js +27 -0
- package/lib/module/components/ui/Aspect-Ratio.js.map +1 -0
- package/lib/module/components/ui/Avatar.js +97 -0
- package/lib/module/components/ui/Avatar.js.map +1 -0
- package/lib/module/components/ui/Badge.js +127 -0
- package/lib/module/components/ui/Badge.js.map +1 -0
- package/lib/module/components/ui/Bottom-Sheet.js +144 -0
- package/lib/module/components/ui/Bottom-Sheet.js.map +1 -0
- package/lib/module/components/ui/Button.js +88 -0
- package/lib/module/components/ui/Button.js.map +1 -0
- package/lib/module/components/ui/Card.js +176 -0
- package/lib/module/components/ui/Card.js.map +1 -0
- package/lib/module/components/ui/Checkbox.js +65 -0
- package/lib/module/components/ui/Checkbox.js.map +1 -0
- package/lib/module/components/ui/Collapsible.js +42 -0
- package/lib/module/components/ui/Collapsible.js.map +1 -0
- package/lib/module/components/ui/Context-Menu.js +287 -0
- package/lib/module/components/ui/Context-Menu.js.map +1 -0
- package/lib/module/components/ui/Custom-Card.js +202 -0
- package/lib/module/components/ui/Custom-Card.js.map +1 -0
- package/lib/module/components/ui/Dialog.js +202 -0
- package/lib/module/components/ui/Dialog.js.map +1 -0
- package/lib/module/components/ui/Dropdown-Menu.js +421 -0
- package/lib/module/components/ui/Dropdown-Menu.js.map +1 -0
- package/lib/module/components/ui/Floating-Action.js +50 -0
- package/lib/module/components/ui/Floating-Action.js.map +1 -0
- package/lib/module/components/ui/Greeting-Card.js +392 -0
- package/lib/module/components/ui/Greeting-Card.js.map +1 -0
- package/lib/module/components/ui/Hover-Card.js +96 -0
- package/lib/module/components/ui/Hover-Card.js.map +1 -0
- package/lib/module/components/ui/Icon.js +73 -0
- package/lib/module/components/ui/Icon.js.map +1 -0
- package/lib/module/components/ui/Input.js +74 -0
- package/lib/module/components/ui/Input.js.map +1 -0
- package/lib/module/components/ui/Label.js +44 -0
- package/lib/module/components/ui/Label.js.map +1 -0
- package/lib/module/components/ui/Menubar.js +375 -0
- package/lib/module/components/ui/Menubar.js.map +1 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js +41 -0
- package/lib/module/components/ui/Native-Only-Animated-View.js.map +1 -0
- package/lib/module/components/ui/NavBar.js +352 -0
- package/lib/module/components/ui/NavBar.js.map +1 -0
- package/lib/module/components/ui/Popover.js +101 -0
- package/lib/module/components/ui/Popover.js.map +1 -0
- package/lib/module/components/ui/Progress.js +124 -0
- package/lib/module/components/ui/Progress.js.map +1 -0
- package/lib/module/components/ui/Radio-Group.js +75 -0
- package/lib/module/components/ui/Radio-Group.js.map +1 -0
- package/lib/module/components/ui/Select.js +269 -0
- package/lib/module/components/ui/Select.js.map +1 -0
- package/lib/module/components/ui/Separator.js +58 -0
- package/lib/module/components/ui/Separator.js.map +1 -0
- package/lib/module/components/ui/SizedBox.js +101 -0
- package/lib/module/components/ui/SizedBox.js.map +1 -0
- package/lib/module/components/ui/Skeleton.js +57 -0
- package/lib/module/components/ui/Skeleton.js.map +1 -0
- package/lib/module/components/ui/Slider.js +169 -0
- package/lib/module/components/ui/Slider.js.map +1 -0
- package/lib/module/components/ui/Switch.js +55 -0
- package/lib/module/components/ui/Switch.js.map +1 -0
- package/lib/module/components/ui/Table.js +150 -0
- package/lib/module/components/ui/Table.js.map +1 -0
- package/lib/module/components/ui/Tabs.js +106 -0
- package/lib/module/components/ui/Tabs.js.map +1 -0
- package/lib/module/components/ui/Text.js +69 -0
- package/lib/module/components/ui/Text.js.map +1 -0
- package/lib/module/components/ui/Textarea.js +88 -0
- package/lib/module/components/ui/Textarea.js.map +1 -0
- package/lib/module/components/ui/Theme-Toggle.js +156 -0
- package/lib/module/components/ui/Theme-Toggle.js.map +1 -0
- package/lib/module/components/ui/Toast.js +101 -0
- package/lib/module/components/ui/Toast.js.map +1 -0
- package/lib/module/components/ui/Toggle-Group.js +129 -0
- package/lib/module/components/ui/Toggle-Group.js.map +1 -0
- package/lib/module/components/ui/Toggle.js +106 -0
- package/lib/module/components/ui/Toggle.js.map +1 -0
- package/lib/module/components/ui/Tooltip.js +106 -0
- package/lib/module/components/ui/Tooltip.js.map +1 -0
- package/lib/module/components/ui/index.js +45 -0
- package/lib/module/components/ui/index.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/ThemeProvider.js +173 -0
- package/lib/module/lib/ThemeProvider.js.map +1 -0
- package/lib/module/lib/cornerRadius.js +164 -0
- package/lib/module/lib/cornerRadius.js.map +1 -0
- package/lib/module/lib/fonts.js +25 -0
- package/lib/module/lib/fonts.js.map +1 -0
- package/lib/module/lib/theme.js +212 -0
- package/lib/module/lib/theme.js.map +1 -0
- package/lib/module/lib/utils.js +137 -0
- package/lib/module/lib/utils.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/package.json +208 -0
- package/src/app/_layout.tsx +25 -0
- package/src/assets/icons/weather_icons/drizzle.png +0 -0
- package/src/assets/icons/weather_icons/foggy.png +0 -0
- package/src/assets/icons/weather_icons/freezing_rain.png +0 -0
- package/src/assets/icons/weather_icons/partly_cloudy.png +0 -0
- package/src/assets/icons/weather_icons/rainy.png +0 -0
- package/src/assets/icons/weather_icons/showers.png +0 -0
- package/src/assets/icons/weather_icons/sunny_weather.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm.png +0 -0
- package/src/assets/icons/weather_icons/thunderstorm_hail.png +0 -0
- package/src/components/theme-config.ts +331 -0
- package/src/components/ui/Accordion.tsx +253 -0
- package/src/components/ui/Alert-Dialog.tsx +295 -0
- package/src/components/ui/Alert.tsx +137 -0
- package/src/components/ui/AppBar.tsx +551 -0
- package/src/components/ui/Aspect-Ratio.tsx +25 -0
- package/src/components/ui/Avatar.tsx +103 -0
- package/src/components/ui/Badge.tsx +121 -0
- package/src/components/ui/Bottom-Sheet.tsx +224 -0
- package/src/components/ui/Button.tsx +100 -0
- package/src/components/ui/Card.tsx +185 -0
- package/src/components/ui/Checkbox.tsx +81 -0
- package/src/components/ui/Collapsible.tsx +40 -0
- package/src/components/ui/Context-Menu.tsx +407 -0
- package/src/components/ui/Custom-Card.tsx +226 -0
- package/src/components/ui/Dialog.tsx +240 -0
- package/src/components/ui/Dropdown-Menu.tsx +544 -0
- package/src/components/ui/Floating-Action.tsx +54 -0
- package/src/components/ui/Greeting-Card.tsx +471 -0
- package/src/components/ui/Hover-Card.tsx +101 -0
- package/src/components/ui/Icon.tsx +75 -0
- package/src/components/ui/Input.tsx +90 -0
- package/src/components/ui/Label.tsx +48 -0
- package/src/components/ui/Menubar.tsx +509 -0
- package/src/components/ui/Native-Only-Animated-View.tsx +37 -0
- package/src/components/ui/NavBar.tsx +397 -0
- package/src/components/ui/Popover.tsx +110 -0
- package/src/components/ui/Progress.tsx +138 -0
- package/src/components/ui/Radio-Group.tsx +79 -0
- package/src/components/ui/Select.tsx +344 -0
- package/src/components/ui/Separator.tsx +68 -0
- package/src/components/ui/SizedBox.tsx +116 -0
- package/src/components/ui/Skeleton.tsx +55 -0
- package/src/components/ui/Slider.tsx +222 -0
- package/src/components/ui/Switch.tsx +67 -0
- package/src/components/ui/Table.tsx +170 -0
- package/src/components/ui/Tabs.tsx +119 -0
- package/src/components/ui/Text.tsx +73 -0
- package/src/components/ui/Textarea.tsx +93 -0
- package/src/components/ui/Theme-Toggle.tsx +204 -0
- package/src/components/ui/Toast.tsx +127 -0
- package/src/components/ui/Toggle-Group.tsx +160 -0
- package/src/components/ui/Toggle.tsx +122 -0
- package/src/components/ui/Tooltip.tsx +117 -0
- package/src/components/ui/index.ts +42 -0
- package/src/index.tsx +24 -0
- package/src/lib/ThemeProvider.tsx +204 -0
- package/src/lib/cornerRadius.ts +160 -0
- package/src/lib/fonts.ts +28 -0
- package/src/lib/theme.ts +151 -0
- package/src/lib/utils.ts +146 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Platform, View, ViewProps } from 'react-native';
|
|
3
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
4
|
+
import Animated, {
|
|
5
|
+
runOnJS,
|
|
6
|
+
useAnimatedStyle,
|
|
7
|
+
useSharedValue,
|
|
8
|
+
withSpring,
|
|
9
|
+
} from 'react-native-reanimated';
|
|
10
|
+
import { cn } from '../../lib/utils';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props for the Slider component
|
|
14
|
+
*
|
|
15
|
+
* @property {number} value - Current slider value
|
|
16
|
+
* @property {function} onValueChange - Callback when value changes
|
|
17
|
+
* @property {number} min - Minimum value (default: 0)
|
|
18
|
+
* @property {number} max - Maximum value (default: 100)
|
|
19
|
+
* @property {number} step - Step increment (default: 1)
|
|
20
|
+
* @property {boolean} disabled - Whether the slider is disabled
|
|
21
|
+
*/
|
|
22
|
+
export interface SliderProps extends Omit<ViewProps, 'children'> {
|
|
23
|
+
value: number;
|
|
24
|
+
onValueChange?: (value: number[]) => void;
|
|
25
|
+
min?: number;
|
|
26
|
+
max?: number;
|
|
27
|
+
step?: number;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ref interface for Slider component
|
|
34
|
+
*
|
|
35
|
+
* @property {function} reset - Resets slider to minimum value
|
|
36
|
+
*/
|
|
37
|
+
export interface SliderRef {
|
|
38
|
+
reset: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Interactive slider component for selecting numeric values
|
|
43
|
+
*
|
|
44
|
+
* Provides smooth gesture-based interaction with spring animations.
|
|
45
|
+
* Supports min/max ranges, step increments, and tap-to-select.
|
|
46
|
+
*
|
|
47
|
+
* @component
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <Slider
|
|
51
|
+
* value={volume}
|
|
52
|
+
* onValueChange={(values) => setVolume(values[0])}
|
|
53
|
+
* min={0}
|
|
54
|
+
* max={100}
|
|
55
|
+
* step={5}
|
|
56
|
+
* />
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @accessibility
|
|
60
|
+
* - Disabled state prevents interaction
|
|
61
|
+
* - Focus visible states on web
|
|
62
|
+
* - Value changes are announced by screen readers
|
|
63
|
+
*/
|
|
64
|
+
const Slider = React.forwardRef<SliderRef, SliderProps>(
|
|
65
|
+
(
|
|
66
|
+
{
|
|
67
|
+
value,
|
|
68
|
+
onValueChange,
|
|
69
|
+
min = 0,
|
|
70
|
+
max = 100,
|
|
71
|
+
step = 1,
|
|
72
|
+
disabled = false,
|
|
73
|
+
className,
|
|
74
|
+
style,
|
|
75
|
+
...props
|
|
76
|
+
},
|
|
77
|
+
ref
|
|
78
|
+
) => {
|
|
79
|
+
const sliderWidth = useSharedValue(0);
|
|
80
|
+
const translationX = useSharedValue(0);
|
|
81
|
+
const startPosition = useSharedValue(0);
|
|
82
|
+
const [sliderDimensions, setSliderDimensions] = React.useState({
|
|
83
|
+
width: 0,
|
|
84
|
+
x: 0,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Calculate thumb position from value
|
|
88
|
+
const getThumbPosition = React.useCallback(
|
|
89
|
+
(val: number) => {
|
|
90
|
+
const range = max - min;
|
|
91
|
+
const percentage = (val - min) / range;
|
|
92
|
+
return percentage * sliderDimensions.width;
|
|
93
|
+
},
|
|
94
|
+
[max, min, sliderDimensions.width]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Calculate value from position
|
|
98
|
+
const getValueFromPosition = React.useCallback(
|
|
99
|
+
(position: number) => {
|
|
100
|
+
if (sliderWidth.value === 0) return min;
|
|
101
|
+
const percentage = Math.max(
|
|
102
|
+
0,
|
|
103
|
+
Math.min(1, position / sliderWidth.value)
|
|
104
|
+
);
|
|
105
|
+
const range = max - min;
|
|
106
|
+
let newValue = min + percentage * range;
|
|
107
|
+
|
|
108
|
+
// Apply step
|
|
109
|
+
if (step > 0) {
|
|
110
|
+
newValue = Math.round(newValue / step) * step;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Math.max(min, Math.min(max, newValue));
|
|
114
|
+
},
|
|
115
|
+
[max, min, step, sliderWidth]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Initialize thumb position based on value
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (sliderDimensions.width > 0) {
|
|
121
|
+
const newPosition = getThumbPosition(value);
|
|
122
|
+
translationX.value = newPosition;
|
|
123
|
+
}
|
|
124
|
+
}, [value, sliderDimensions.width, getThumbPosition, translationX]);
|
|
125
|
+
|
|
126
|
+
// Expose reset method via ref
|
|
127
|
+
React.useImperativeHandle(ref, () => ({
|
|
128
|
+
reset: () => {
|
|
129
|
+
translationX.value = withSpring(getThumbPosition(min));
|
|
130
|
+
onValueChange?.([min]);
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
const handleValueChange = React.useCallback(
|
|
135
|
+
(newValue: number) => {
|
|
136
|
+
onValueChange?.([newValue]);
|
|
137
|
+
},
|
|
138
|
+
[onValueChange]
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const pan = Gesture.Pan()
|
|
142
|
+
.enabled(!disabled)
|
|
143
|
+
.onBegin(() => {
|
|
144
|
+
startPosition.value = translationX.value;
|
|
145
|
+
})
|
|
146
|
+
.onUpdate((event: { translationX: number }) => {
|
|
147
|
+
if (sliderWidth.value === 0) return;
|
|
148
|
+
const newPosition = Math.max(
|
|
149
|
+
0,
|
|
150
|
+
Math.min(sliderWidth.value, event.translationX + startPosition.value)
|
|
151
|
+
);
|
|
152
|
+
translationX.value = newPosition;
|
|
153
|
+
})
|
|
154
|
+
.onEnd(() => {
|
|
155
|
+
if (sliderWidth.value === 0) return;
|
|
156
|
+
const newValue = getValueFromPosition(translationX.value);
|
|
157
|
+
translationX.value = withSpring(getThumbPosition(newValue));
|
|
158
|
+
runOnJS(handleValueChange)(newValue);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const tap = Gesture.Tap()
|
|
162
|
+
.enabled(!disabled)
|
|
163
|
+
.onEnd((event: { x: number }) => {
|
|
164
|
+
if (sliderWidth.value === 0) return;
|
|
165
|
+
const newPosition = Math.max(0, Math.min(sliderWidth.value, event.x));
|
|
166
|
+
const newValue = getValueFromPosition(newPosition);
|
|
167
|
+
translationX.value = withSpring(getThumbPosition(newValue));
|
|
168
|
+
runOnJS(handleValueChange)(newValue);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const composed = Gesture.Race(tap, pan);
|
|
172
|
+
|
|
173
|
+
const thumbAnimatedStyle = useAnimatedStyle(() => ({
|
|
174
|
+
transform: [{ translateX: translationX.value }],
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
const rangeAnimatedStyle = useAnimatedStyle(() => ({
|
|
178
|
+
width: translationX.value,
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<View
|
|
183
|
+
className={cn(
|
|
184
|
+
'relative flex h-10 w-full flex-row items-center justify-center',
|
|
185
|
+
disabled && 'opacity-50',
|
|
186
|
+
className
|
|
187
|
+
)}
|
|
188
|
+
style={style}
|
|
189
|
+
{...props}
|
|
190
|
+
>
|
|
191
|
+
<GestureDetector gesture={composed}>
|
|
192
|
+
<View
|
|
193
|
+
className="relative h-2 w-full overflow-hidden rounded-full bg-primary/20"
|
|
194
|
+
onLayout={(event) => {
|
|
195
|
+
const { width, x } = event.nativeEvent.layout;
|
|
196
|
+
sliderWidth.value = width;
|
|
197
|
+
setSliderDimensions({ width, x });
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<Animated.View
|
|
201
|
+
className="absolute left-0 h-full rounded-full bg-primary"
|
|
202
|
+
style={rangeAnimatedStyle}
|
|
203
|
+
/>
|
|
204
|
+
<Animated.View
|
|
205
|
+
className={cn(
|
|
206
|
+
'absolute -top-1.5 size-5 -ml-2.5 rounded-full border-2 border-primary bg-background shadow-sm shadow-black/5',
|
|
207
|
+
Platform.select({
|
|
208
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-none focus-visible:ring-[3px] disabled:pointer-events-none',
|
|
209
|
+
})
|
|
210
|
+
)}
|
|
211
|
+
style={thumbAnimatedStyle}
|
|
212
|
+
/>
|
|
213
|
+
</View>
|
|
214
|
+
</GestureDetector>
|
|
215
|
+
</View>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
Slider.displayName = 'Slider';
|
|
221
|
+
|
|
222
|
+
export { Slider };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils';
|
|
2
|
+
import * as SwitchPrimitives from '@rn-primitives/switch';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A toggle switch component for binary on/off states
|
|
7
|
+
*
|
|
8
|
+
* Provides a visual toggle control with smooth animations and platform-specific styling.
|
|
9
|
+
* Automatically handles checked/unchecked states and disabled interactions.
|
|
10
|
+
*
|
|
11
|
+
* @component
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const [enabled, setEnabled] = useState(false);
|
|
15
|
+
*
|
|
16
|
+
* <Switch
|
|
17
|
+
* checked={enabled}
|
|
18
|
+
* onCheckedChange={setEnabled}
|
|
19
|
+
* />
|
|
20
|
+
*
|
|
21
|
+
* // Disabled switch
|
|
22
|
+
* <Switch checked={true} disabled />
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @param {boolean} checked - Current state of the switch
|
|
26
|
+
* @param {(checked: boolean) => void} onCheckedChange - Callback when switch is toggled
|
|
27
|
+
* @param {boolean} [disabled] - Whether the switch is disabled
|
|
28
|
+
* @param {string} [className] - Additional Tailwind classes
|
|
29
|
+
*
|
|
30
|
+
* @accessibility
|
|
31
|
+
* - Proper focus states on web for keyboard navigation
|
|
32
|
+
* - Disabled state prevents interaction
|
|
33
|
+
* - Visual feedback for checked/unchecked states
|
|
34
|
+
*/
|
|
35
|
+
function Switch({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: SwitchPrimitives.RootProps & React.RefAttributes<SwitchPrimitives.RootRef>) {
|
|
39
|
+
return (
|
|
40
|
+
<SwitchPrimitives.Root
|
|
41
|
+
className={cn(
|
|
42
|
+
'flex h-[1.15rem] w-8 shrink-0 flex-row items-center rounded-full border border-transparent shadow-sm shadow-black/5',
|
|
43
|
+
Platform.select({
|
|
44
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 peer inline-flex outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed',
|
|
45
|
+
}),
|
|
46
|
+
props.checked ? 'bg-primary' : 'bg-input dark:bg-input/80',
|
|
47
|
+
props.disabled && 'opacity-50',
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
<SwitchPrimitives.Thumb
|
|
53
|
+
className={cn(
|
|
54
|
+
'bg-background size-4 rounded-full transition-transform',
|
|
55
|
+
Platform.select({
|
|
56
|
+
web: 'pointer-events-none block ring-0',
|
|
57
|
+
}),
|
|
58
|
+
props.checked
|
|
59
|
+
? 'dark:bg-primary-foreground translate-x-3.5'
|
|
60
|
+
: 'dark:bg-foreground translate-x-0'
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
</SwitchPrimitives.Root>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { Switch };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils';
|
|
2
|
+
import { Text } from './Text';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { cssInterop } from 'nativewind';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
cssInterop(View, { className: 'style' });
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Root table container component
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Table>
|
|
16
|
+
* <TableHeader>
|
|
17
|
+
* <TableRow>
|
|
18
|
+
* <TableHead><Text>Name</Text></TableHead>
|
|
19
|
+
* <TableHead><Text>Email</Text></TableHead>
|
|
20
|
+
* </TableRow>
|
|
21
|
+
* </TableHeader>
|
|
22
|
+
* <TableBody>
|
|
23
|
+
* <TableRow>
|
|
24
|
+
* <TableCell><Text>John Doe</Text></TableCell>
|
|
25
|
+
* <TableCell><Text>john@example.com</Text></TableCell>
|
|
26
|
+
* </TableRow>
|
|
27
|
+
* </TableBody>
|
|
28
|
+
* </Table>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function Table({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
35
|
+
return <View className={cn('w-full', className)} {...props} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Table header container
|
|
40
|
+
*
|
|
41
|
+
* @component
|
|
42
|
+
*/
|
|
43
|
+
function TableHeader({
|
|
44
|
+
className,
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
47
|
+
return (
|
|
48
|
+
<View
|
|
49
|
+
className={cn('border-border flex-row border-b', className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Table body container for data rows
|
|
57
|
+
*
|
|
58
|
+
* @component
|
|
59
|
+
*/
|
|
60
|
+
function TableBody({
|
|
61
|
+
className,
|
|
62
|
+
...props
|
|
63
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
64
|
+
return <View className={cn('flex-col', className)} {...props} />;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Table footer container
|
|
69
|
+
*
|
|
70
|
+
* @component
|
|
71
|
+
*/
|
|
72
|
+
function TableFooter({
|
|
73
|
+
className,
|
|
74
|
+
...props
|
|
75
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
76
|
+
return (
|
|
77
|
+
<View
|
|
78
|
+
className={cn(
|
|
79
|
+
'border-border bg-muted/50 flex-row border-t font-medium',
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Table row component
|
|
89
|
+
*
|
|
90
|
+
* @component
|
|
91
|
+
*/
|
|
92
|
+
function TableRow({
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
96
|
+
return (
|
|
97
|
+
<View
|
|
98
|
+
className={cn('border-border flex-row border-b', className)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Table header cell component
|
|
106
|
+
*
|
|
107
|
+
* @component
|
|
108
|
+
*/
|
|
109
|
+
function TableHead({
|
|
110
|
+
className,
|
|
111
|
+
...props
|
|
112
|
+
}: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
|
|
113
|
+
return (
|
|
114
|
+
<Text
|
|
115
|
+
className={cn(
|
|
116
|
+
'text-muted-foreground flex-1 p-2 text-left align-middle text-xs font-medium',
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Table data cell component
|
|
126
|
+
*
|
|
127
|
+
* @component
|
|
128
|
+
*/
|
|
129
|
+
function TableCell({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
|
|
133
|
+
return (
|
|
134
|
+
<Text
|
|
135
|
+
className={cn('flex-1 p-2 align-middle text-sm', className)}
|
|
136
|
+
{...props}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Table caption for describing the table
|
|
143
|
+
*
|
|
144
|
+
* @component
|
|
145
|
+
*/
|
|
146
|
+
function TableCaption({
|
|
147
|
+
className,
|
|
148
|
+
...props
|
|
149
|
+
}: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
|
|
150
|
+
return (
|
|
151
|
+
<Text
|
|
152
|
+
className={cn(
|
|
153
|
+
'text-muted-foreground mt-2 text-center text-sm',
|
|
154
|
+
className
|
|
155
|
+
)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
Table,
|
|
163
|
+
TableBody,
|
|
164
|
+
TableCaption,
|
|
165
|
+
TableCell,
|
|
166
|
+
TableFooter,
|
|
167
|
+
TableHead,
|
|
168
|
+
TableHeader,
|
|
169
|
+
TableRow,
|
|
170
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { TextClassContext } from './Text';
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
import * as TabsPrimitive from '@rn-primitives/tabs';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Root tabs component for organizing content into panels
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
13
|
+
* <TabsList>
|
|
14
|
+
* <TabsTrigger value="account">
|
|
15
|
+
* <Text>Account</Text>
|
|
16
|
+
* </TabsTrigger>
|
|
17
|
+
* <TabsTrigger value="password">
|
|
18
|
+
* <Text>Password</Text>
|
|
19
|
+
* </TabsTrigger>
|
|
20
|
+
* </TabsList>
|
|
21
|
+
* <TabsContent value="account">
|
|
22
|
+
* <Text>Account settings content</Text>
|
|
23
|
+
* </TabsContent>
|
|
24
|
+
* <TabsContent value="password">
|
|
25
|
+
* <Text>Password settings content</Text>
|
|
26
|
+
* </TabsContent>
|
|
27
|
+
* </Tabs>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @accessibility
|
|
31
|
+
* - Keyboard navigation support (arrow keys)
|
|
32
|
+
* - Proper ARIA attributes for tab structure
|
|
33
|
+
* - Screen reader friendly
|
|
34
|
+
*/
|
|
35
|
+
function Tabs({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: TabsPrimitive.RootProps & React.RefAttributes<TabsPrimitive.RootRef>) {
|
|
39
|
+
return (
|
|
40
|
+
<TabsPrimitive.Root
|
|
41
|
+
className={cn('flex flex-col gap-2', className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Container for tab triggers
|
|
49
|
+
*
|
|
50
|
+
* @component
|
|
51
|
+
*/
|
|
52
|
+
function TabsList({
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
|
+
}: TabsPrimitive.ListProps & React.RefAttributes<TabsPrimitive.ListRef>) {
|
|
56
|
+
return (
|
|
57
|
+
<TabsPrimitive.List
|
|
58
|
+
className={cn(
|
|
59
|
+
'bg-muted flex h-9 flex-row items-center justify-center rounded-lg p-[3px]',
|
|
60
|
+
Platform.select({ web: 'inline-flex w-fit', native: 'mr-auto' }),
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Individual tab trigger button
|
|
70
|
+
*
|
|
71
|
+
* @component
|
|
72
|
+
*/
|
|
73
|
+
function TabsTrigger({
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: TabsPrimitive.TriggerProps & React.RefAttributes<TabsPrimitive.TriggerRef>) {
|
|
77
|
+
const { value } = TabsPrimitive.useRootContext();
|
|
78
|
+
return (
|
|
79
|
+
<TextClassContext.Provider
|
|
80
|
+
value={cn(
|
|
81
|
+
'text-foreground dark:text-muted-foreground text-sm font-medium',
|
|
82
|
+
value === props.value && 'dark:text-foreground'
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
<TabsPrimitive.Trigger
|
|
86
|
+
className={cn(
|
|
87
|
+
'flex h-[calc(100%-1px)] flex-row items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 shadow-none shadow-black/5',
|
|
88
|
+
Platform.select({
|
|
89
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring inline-flex cursor-default whitespace-nowrap transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
90
|
+
}),
|
|
91
|
+
props.disabled && 'opacity-50',
|
|
92
|
+
props.value === value &&
|
|
93
|
+
'bg-background dark:border-foreground/10 dark:bg-input/30',
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
</TextClassContext.Provider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Tab content panel
|
|
104
|
+
*
|
|
105
|
+
* @component
|
|
106
|
+
*/
|
|
107
|
+
function TabsContent({
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}: TabsPrimitive.ContentProps & React.RefAttributes<TabsPrimitive.ContentRef>) {
|
|
111
|
+
return (
|
|
112
|
+
<TabsPrimitive.Content
|
|
113
|
+
className={cn(Platform.select({ web: 'flex-1 outline-none' }), className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as Slot from '@rn-primitives/slot';
|
|
2
|
+
import type { SlottableTextProps, TextRef } from '@rn-primitives/types';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Text as RNText } from 'react-native';
|
|
6
|
+
import { cn } from '../../lib/utils';
|
|
7
|
+
|
|
8
|
+
cssInterop(RNText, { className: 'style' });
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Context for sharing text class styles with child Text components
|
|
12
|
+
* Used by components like Button, Card, Badge to automatically style nested Text
|
|
13
|
+
*/
|
|
14
|
+
const TextClassContext = React.createContext<string | undefined>(undefined);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Enhanced Text component with NativeWind styling support
|
|
18
|
+
*
|
|
19
|
+
* Extends React Native's Text with:
|
|
20
|
+
* - Automatic style inheritance from TextClassContext
|
|
21
|
+
* - NativeWind className support
|
|
22
|
+
* - Slot pattern support for composition
|
|
23
|
+
* - Web-specific selection styling
|
|
24
|
+
*
|
|
25
|
+
* @component
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* // Basic text
|
|
29
|
+
* <Text>Hello World</Text>
|
|
30
|
+
*
|
|
31
|
+
* // With custom styling
|
|
32
|
+
* <Text className="text-lg font-bold">Title</Text>
|
|
33
|
+
*
|
|
34
|
+
* // Inside a styled context (e.g., Button)
|
|
35
|
+
* <Button variant="default">
|
|
36
|
+
* <Text>Button Text</Text> // Automatically inherits button text styling
|
|
37
|
+
* </Button>
|
|
38
|
+
*
|
|
39
|
+
* // Using asChild for composition
|
|
40
|
+
* <Text asChild>
|
|
41
|
+
* <Link href="/home">Home</Link>
|
|
42
|
+
* </Text>
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
46
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
47
|
+
*/
|
|
48
|
+
const Text = React.forwardRef<
|
|
49
|
+
TextRef,
|
|
50
|
+
SlottableTextProps & {
|
|
51
|
+
className?: string;
|
|
52
|
+
children?: React.ReactNode;
|
|
53
|
+
}
|
|
54
|
+
>(({ className, asChild = false, style, ...props }, ref) => {
|
|
55
|
+
const textClass = React.useContext(TextClassContext);
|
|
56
|
+
const Component = asChild ? Slot.Text : RNText;
|
|
57
|
+
|
|
58
|
+
const finalClassName = textClass
|
|
59
|
+
? cn('text-base web:select-text', textClass, className)
|
|
60
|
+
: cn('text-base text-foreground web:select-text', className);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Component
|
|
64
|
+
className={finalClassName}
|
|
65
|
+
style={style}
|
|
66
|
+
ref={ref as any}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
Text.displayName = 'Text';
|
|
72
|
+
|
|
73
|
+
export { Text, TextClassContext };
|