@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,79 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils';
|
|
2
|
+
import * as RadioGroupPrimitive from '@rn-primitives/radio-group';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Radio group component for single-choice selection
|
|
7
|
+
*
|
|
8
|
+
* Groups radio buttons together to ensure only one can be selected at a time.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <RadioGroup value={selectedValue} onValueChange={setSelectedValue}>
|
|
14
|
+
* <View className="flex-row items-center gap-2">
|
|
15
|
+
* <RadioGroupItem value="option1" />
|
|
16
|
+
* <Label>Option 1</Label>
|
|
17
|
+
* </View>
|
|
18
|
+
* <View className="flex-row items-center gap-2">
|
|
19
|
+
* <RadioGroupItem value="option2" />
|
|
20
|
+
* <Label>Option 2</Label>
|
|
21
|
+
* </View>
|
|
22
|
+
* </RadioGroup>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @accessibility
|
|
26
|
+
* - Uses proper ARIA role for radio group
|
|
27
|
+
* - Keyboard navigation support (arrow keys)
|
|
28
|
+
* - Screen reader friendly with proper labeling
|
|
29
|
+
*/
|
|
30
|
+
function RadioGroup({
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: RadioGroupPrimitive.RootProps &
|
|
34
|
+
React.RefAttributes<RadioGroupPrimitive.RootRef>) {
|
|
35
|
+
return (
|
|
36
|
+
<RadioGroupPrimitive.Root className={cn('gap-3', className)} {...props} />
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Individual radio button item
|
|
42
|
+
*
|
|
43
|
+
* Displays as a circular button with an inner indicator when selected.
|
|
44
|
+
*
|
|
45
|
+
* @component
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* <RadioGroupItem value="dark" />
|
|
49
|
+
* <RadioGroupItem value="light" disabled />
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @accessibility
|
|
53
|
+
* - Disabled state prevents interaction
|
|
54
|
+
* - Focus visible states on web
|
|
55
|
+
* - Invalid state styling for form validation
|
|
56
|
+
*/
|
|
57
|
+
function RadioGroupItem({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: RadioGroupPrimitive.ItemProps &
|
|
61
|
+
React.RefAttributes<RadioGroupPrimitive.ItemRef>) {
|
|
62
|
+
return (
|
|
63
|
+
<RadioGroupPrimitive.Item
|
|
64
|
+
className={cn(
|
|
65
|
+
'border-input dark:bg-input/30 aspect-square size-4 shrink-0 items-center justify-center rounded-full border shadow-sm shadow-black/5',
|
|
66
|
+
Platform.select({
|
|
67
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed',
|
|
68
|
+
}),
|
|
69
|
+
props.disabled && 'opacity-50',
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<RadioGroupPrimitive.Indicator className="bg-primary size-2 rounded-full" />
|
|
75
|
+
</RadioGroupPrimitive.Item>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { Icon } from './Icon';
|
|
2
|
+
import { NativeOnlyAnimatedView } from './Native-Only-Animated-View';
|
|
3
|
+
import { TextClassContext } from './Text';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import * as SelectPrimitive from '@rn-primitives/select';
|
|
6
|
+
import {
|
|
7
|
+
Check,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
ChevronDownIcon,
|
|
10
|
+
ChevronUpIcon,
|
|
11
|
+
} from 'lucide-react-native';
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import { Platform, ScrollView, StyleSheet, View } from 'react-native';
|
|
14
|
+
import { cssInterop } from 'nativewind';
|
|
15
|
+
import { FadeIn, FadeOut } from 'react-native-reanimated';
|
|
16
|
+
import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
|
|
17
|
+
|
|
18
|
+
cssInterop(View, { className: 'style' });
|
|
19
|
+
cssInterop(ScrollView, { className: 'style' });
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Option type for select items
|
|
23
|
+
*/
|
|
24
|
+
type Option = SelectPrimitive.Option;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Root select component for dropdown selection
|
|
28
|
+
*
|
|
29
|
+
* @component
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <Select value={value} onValueChange={setValue}>
|
|
33
|
+
* <SelectTrigger>
|
|
34
|
+
* <SelectValue placeholder="Select an option" />
|
|
35
|
+
* </SelectTrigger>
|
|
36
|
+
* <SelectContent>
|
|
37
|
+
* <SelectItem value="option1">
|
|
38
|
+
* <Text>Option 1</Text>
|
|
39
|
+
* </SelectItem>
|
|
40
|
+
* </SelectContent>
|
|
41
|
+
* </Select>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
const Select = SelectPrimitive.Root;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Groups related select items together
|
|
48
|
+
* @component
|
|
49
|
+
*/
|
|
50
|
+
const SelectGroup = SelectPrimitive.Group;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Displays the selected value or placeholder text
|
|
54
|
+
* @component
|
|
55
|
+
*/
|
|
56
|
+
function SelectValue({
|
|
57
|
+
ref,
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: SelectPrimitive.ValueProps &
|
|
61
|
+
React.RefAttributes<SelectPrimitive.ValueRef> & {
|
|
62
|
+
className?: string;
|
|
63
|
+
}) {
|
|
64
|
+
const { value } = SelectPrimitive.useRootContext();
|
|
65
|
+
return (
|
|
66
|
+
<SelectPrimitive.Value
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
'text-foreground line-clamp-1 flex flex-row items-center gap-2 text-sm',
|
|
70
|
+
!value && 'text-muted-foreground',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Trigger button that opens the select dropdown
|
|
80
|
+
* @component
|
|
81
|
+
*/
|
|
82
|
+
function SelectTrigger({
|
|
83
|
+
ref,
|
|
84
|
+
className,
|
|
85
|
+
children,
|
|
86
|
+
size = 'default',
|
|
87
|
+
...props
|
|
88
|
+
}: SelectPrimitive.TriggerProps &
|
|
89
|
+
React.RefAttributes<SelectPrimitive.TriggerRef> & {
|
|
90
|
+
children?: React.ReactNode;
|
|
91
|
+
size?: 'default' | 'sm';
|
|
92
|
+
}) {
|
|
93
|
+
return (
|
|
94
|
+
<SelectPrimitive.Trigger
|
|
95
|
+
ref={ref}
|
|
96
|
+
className={cn(
|
|
97
|
+
'border-input dark:bg-input/30 dark:active:bg-input/50 bg-background flex h-10 flex-row items-center justify-between gap-2 rounded-md border px-3 py-2 shadow-sm shadow-black/5 sm:h-9',
|
|
98
|
+
Platform.select({
|
|
99
|
+
web: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:hover:bg-input/50 w-fit whitespace-nowrap text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
100
|
+
}),
|
|
101
|
+
props.disabled && 'opacity-50',
|
|
102
|
+
size === 'sm' && 'h-8 py-2 sm:py-1.5',
|
|
103
|
+
className
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
<>{children}</>
|
|
108
|
+
<Icon
|
|
109
|
+
as={ChevronDown}
|
|
110
|
+
aria-hidden={true}
|
|
111
|
+
className="text-muted-foreground size-4"
|
|
112
|
+
/>
|
|
113
|
+
</SelectPrimitive.Trigger>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Full window overlay wrapper for iOS
|
|
119
|
+
*/
|
|
120
|
+
const FullWindowOverlay =
|
|
121
|
+
Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Content container for select items
|
|
125
|
+
* @component
|
|
126
|
+
*/
|
|
127
|
+
function SelectContent({
|
|
128
|
+
className,
|
|
129
|
+
children,
|
|
130
|
+
position = 'popper',
|
|
131
|
+
portalHost,
|
|
132
|
+
...props
|
|
133
|
+
}: SelectPrimitive.ContentProps &
|
|
134
|
+
React.RefAttributes<SelectPrimitive.ContentRef> & {
|
|
135
|
+
className?: string;
|
|
136
|
+
portalHost?: string;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<SelectPrimitive.Portal hostName={portalHost}>
|
|
140
|
+
<FullWindowOverlay>
|
|
141
|
+
<SelectPrimitive.Overlay
|
|
142
|
+
style={Platform.select({ native: StyleSheet.absoluteFill })}
|
|
143
|
+
>
|
|
144
|
+
<TextClassContext.Provider value="text-popover-foreground">
|
|
145
|
+
<NativeOnlyAnimatedView
|
|
146
|
+
className="z-50"
|
|
147
|
+
entering={FadeIn}
|
|
148
|
+
exiting={FadeOut}
|
|
149
|
+
>
|
|
150
|
+
<SelectPrimitive.Content
|
|
151
|
+
className={cn(
|
|
152
|
+
'bg-popover border-border relative z-50 min-w-[8rem] rounded-md border shadow-md shadow-black/5',
|
|
153
|
+
Platform.select({
|
|
154
|
+
web: cn(
|
|
155
|
+
'animate-in fade-in-0 zoom-in-95 origin-(--radix-select-content-transform-origin) max-h-52 overflow-y-auto overflow-x-hidden',
|
|
156
|
+
props.side === 'bottom' && 'slide-in-from-top-2',
|
|
157
|
+
props.side === 'top' && 'slide-in-from-bottom-2'
|
|
158
|
+
),
|
|
159
|
+
native: 'p-1',
|
|
160
|
+
}),
|
|
161
|
+
position === 'popper' &&
|
|
162
|
+
Platform.select({
|
|
163
|
+
web: cn(
|
|
164
|
+
props.side === 'bottom' && 'translate-y-1',
|
|
165
|
+
props.side === 'top' && '-translate-y-1'
|
|
166
|
+
),
|
|
167
|
+
}),
|
|
168
|
+
className
|
|
169
|
+
)}
|
|
170
|
+
position={position}
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
173
|
+
<SelectScrollUpButton />
|
|
174
|
+
<SelectPrimitive.Viewport
|
|
175
|
+
className={cn(
|
|
176
|
+
'p-1',
|
|
177
|
+
position === 'popper' &&
|
|
178
|
+
cn(
|
|
179
|
+
'w-full',
|
|
180
|
+
Platform.select({
|
|
181
|
+
web: 'h-[var(--radix-select-trigger-height)] min-w-[var(--radix-select-trigger-width)]',
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{children}
|
|
187
|
+
</SelectPrimitive.Viewport>
|
|
188
|
+
<SelectScrollDownButton />
|
|
189
|
+
</SelectPrimitive.Content>
|
|
190
|
+
</NativeOnlyAnimatedView>
|
|
191
|
+
</TextClassContext.Provider>
|
|
192
|
+
</SelectPrimitive.Overlay>
|
|
193
|
+
</FullWindowOverlay>
|
|
194
|
+
</SelectPrimitive.Portal>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Label for grouping select items
|
|
200
|
+
* @component
|
|
201
|
+
*/
|
|
202
|
+
function SelectLabel({
|
|
203
|
+
className,
|
|
204
|
+
...props
|
|
205
|
+
}: SelectPrimitive.LabelProps & React.RefAttributes<SelectPrimitive.LabelRef>) {
|
|
206
|
+
return (
|
|
207
|
+
<SelectPrimitive.Label
|
|
208
|
+
className={cn(
|
|
209
|
+
'text-muted-foreground px-2 py-2 text-xs sm:py-1.5',
|
|
210
|
+
className
|
|
211
|
+
)}
|
|
212
|
+
{...props}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Individual selectable item
|
|
219
|
+
* @component
|
|
220
|
+
*/
|
|
221
|
+
function SelectItem({
|
|
222
|
+
className,
|
|
223
|
+
children: _children,
|
|
224
|
+
...props
|
|
225
|
+
}: SelectPrimitive.ItemProps & React.RefAttributes<SelectPrimitive.ItemRef>) {
|
|
226
|
+
return (
|
|
227
|
+
<SelectPrimitive.Item
|
|
228
|
+
className={cn(
|
|
229
|
+
'active:bg-accent group relative flex w-full flex-row items-center gap-2 rounded-sm py-2 pl-2 pr-8 sm:py-1.5',
|
|
230
|
+
Platform.select({
|
|
231
|
+
web: 'focus:bg-accent focus:text-accent-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 cursor-default outline-none data-[disabled]:pointer-events-none [&_svg]:pointer-events-none',
|
|
232
|
+
}),
|
|
233
|
+
props.disabled && 'opacity-50',
|
|
234
|
+
className
|
|
235
|
+
)}
|
|
236
|
+
{...props}
|
|
237
|
+
>
|
|
238
|
+
<View className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
239
|
+
<SelectPrimitive.ItemIndicator>
|
|
240
|
+
<Icon as={Check} className="text-muted-foreground size-4 shrink-0" />
|
|
241
|
+
</SelectPrimitive.ItemIndicator>
|
|
242
|
+
</View>
|
|
243
|
+
<SelectPrimitive.ItemText className="text-foreground group-active:text-accent-foreground select-none text-sm" />
|
|
244
|
+
</SelectPrimitive.Item>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Visual separator between select items
|
|
250
|
+
* @component
|
|
251
|
+
*/
|
|
252
|
+
function SelectSeparator({
|
|
253
|
+
className,
|
|
254
|
+
...props
|
|
255
|
+
}: SelectPrimitive.SeparatorProps &
|
|
256
|
+
React.RefAttributes<SelectPrimitive.SeparatorRef>) {
|
|
257
|
+
return (
|
|
258
|
+
<SelectPrimitive.Separator
|
|
259
|
+
className={cn(
|
|
260
|
+
'bg-border -mx-1 my-1 h-px',
|
|
261
|
+
Platform.select({ web: 'pointer-events-none' }),
|
|
262
|
+
className
|
|
263
|
+
)}
|
|
264
|
+
{...props}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @platform Web only
|
|
271
|
+
* Returns null on native platforms
|
|
272
|
+
*/
|
|
273
|
+
function SelectScrollUpButton({
|
|
274
|
+
className,
|
|
275
|
+
...props
|
|
276
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
277
|
+
if (Platform.OS !== 'web') {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return (
|
|
281
|
+
<SelectPrimitive.ScrollUpButton
|
|
282
|
+
className={cn(
|
|
283
|
+
'flex cursor-default items-center justify-center py-1',
|
|
284
|
+
className
|
|
285
|
+
)}
|
|
286
|
+
{...props}
|
|
287
|
+
>
|
|
288
|
+
<Icon as={ChevronUpIcon} className="size-4" />
|
|
289
|
+
</SelectPrimitive.ScrollUpButton>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @platform Web only
|
|
295
|
+
* Returns null on native platforms
|
|
296
|
+
*/
|
|
297
|
+
function SelectScrollDownButton({
|
|
298
|
+
className,
|
|
299
|
+
...props
|
|
300
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
301
|
+
if (Platform.OS !== 'web') {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
return (
|
|
305
|
+
<SelectPrimitive.ScrollDownButton
|
|
306
|
+
className={cn(
|
|
307
|
+
'flex cursor-default items-center justify-center py-1',
|
|
308
|
+
className
|
|
309
|
+
)}
|
|
310
|
+
{...props}
|
|
311
|
+
>
|
|
312
|
+
<Icon as={ChevronDownIcon} className="size-4" />
|
|
313
|
+
</SelectPrimitive.ScrollDownButton>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* @platform Native only
|
|
319
|
+
* Returns the children on the web
|
|
320
|
+
*/
|
|
321
|
+
function NativeSelectScrollView({
|
|
322
|
+
className,
|
|
323
|
+
...props
|
|
324
|
+
}: React.ComponentProps<typeof ScrollView>) {
|
|
325
|
+
if (Platform.OS === 'web') {
|
|
326
|
+
return <>{props.children}</>;
|
|
327
|
+
}
|
|
328
|
+
return <ScrollView className={cn('max-h-52', className)} {...props} />;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export {
|
|
332
|
+
NativeSelectScrollView,
|
|
333
|
+
Select,
|
|
334
|
+
SelectContent,
|
|
335
|
+
SelectGroup,
|
|
336
|
+
SelectItem,
|
|
337
|
+
SelectLabel,
|
|
338
|
+
SelectScrollDownButton,
|
|
339
|
+
SelectScrollUpButton,
|
|
340
|
+
SelectSeparator,
|
|
341
|
+
SelectTrigger,
|
|
342
|
+
SelectValue,
|
|
343
|
+
type Option,
|
|
344
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
import type { ViewRef, SlottableViewProps } from '@rn-primitives/types';
|
|
6
|
+
|
|
7
|
+
cssInterop(View, { className: 'style' });
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Visual divider for separating content sections
|
|
11
|
+
*
|
|
12
|
+
* A thin line that creates visual separation between content areas. Can be oriented
|
|
13
|
+
* horizontally or vertically and supports decorative or semantic roles.
|
|
14
|
+
*
|
|
15
|
+
* @component
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Horizontal separator (default)
|
|
19
|
+
* <View>
|
|
20
|
+
* <Text>Section 1</Text>
|
|
21
|
+
* <Separator className="my-4" />
|
|
22
|
+
* <Text>Section 2</Text>
|
|
23
|
+
* </View>
|
|
24
|
+
*
|
|
25
|
+
* // Vertical separator
|
|
26
|
+
* <View className="flex-row items-center gap-4">
|
|
27
|
+
* <Text>Left</Text>
|
|
28
|
+
* <Separator orientation="vertical" className="h-4" />
|
|
29
|
+
* <Text>Right</Text>
|
|
30
|
+
* </View>
|
|
31
|
+
*
|
|
32
|
+
* // Semantic separator for screen readers
|
|
33
|
+
* <Separator decorative={false} />
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @accessibility
|
|
37
|
+
* - Decorative separators (default) have role="none" for screen readers
|
|
38
|
+
* - Non-decorative separators have role="separator" for semantic meaning
|
|
39
|
+
*/
|
|
40
|
+
const Separator = React.forwardRef<
|
|
41
|
+
ViewRef,
|
|
42
|
+
SlottableViewProps & {
|
|
43
|
+
className?: string;
|
|
44
|
+
orientation?: 'horizontal' | 'vertical';
|
|
45
|
+
decorative?: boolean;
|
|
46
|
+
}
|
|
47
|
+
>(
|
|
48
|
+
(
|
|
49
|
+
{ className, orientation = 'horizontal', decorative = true, ...props },
|
|
50
|
+
ref
|
|
51
|
+
) => {
|
|
52
|
+
return (
|
|
53
|
+
<View
|
|
54
|
+
role={decorative ? 'none' : 'separator'}
|
|
55
|
+
className={cn(
|
|
56
|
+
'shrink-0 bg-border dark:bg-border',
|
|
57
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
ref={ref}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
Separator.displayName = 'Separator';
|
|
67
|
+
|
|
68
|
+
export { Separator };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { View, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface SizedBoxProps {
|
|
5
|
+
/**
|
|
6
|
+
* The width of the box
|
|
7
|
+
*/
|
|
8
|
+
width?: number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The height of the box
|
|
12
|
+
*/
|
|
13
|
+
height?: number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Optional child element to render inside the box
|
|
17
|
+
*/
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Additional styles to apply
|
|
22
|
+
*/
|
|
23
|
+
style?: ViewStyle;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A box with a specified size.
|
|
28
|
+
*
|
|
29
|
+
* If a child is provided, this widget forces its child to have a specific width and/or height.
|
|
30
|
+
* If either the width or height is not specified, this widget will size itself to match the child's size in that dimension.
|
|
31
|
+
*
|
|
32
|
+
* If no child is provided, this widget will size itself to the specified width and height,
|
|
33
|
+
* treating null values as zero.
|
|
34
|
+
*
|
|
35
|
+
* This is similar to Flutter's SizedBox widget and is useful for:
|
|
36
|
+
* - Adding fixed spacing between widgets
|
|
37
|
+
* - Constraining child widget dimensions
|
|
38
|
+
* - Creating empty space
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Create spacing
|
|
42
|
+
* <SizedBox height={16} />
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Constrain child size
|
|
46
|
+
* <SizedBox width={100} height={100}>
|
|
47
|
+
* <Image source={...} />
|
|
48
|
+
* </SizedBox>
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Create horizontal spacing
|
|
52
|
+
* <View style={{ flexDirection: 'row' }}>
|
|
53
|
+
* <Button />
|
|
54
|
+
* <SizedBox width={12} />
|
|
55
|
+
* <Button />
|
|
56
|
+
* </View>
|
|
57
|
+
*/
|
|
58
|
+
export function SizedBox({ width, height, children, style }: SizedBoxProps) {
|
|
59
|
+
const boxStyle: ViewStyle = {
|
|
60
|
+
...(width !== undefined && { width }),
|
|
61
|
+
...(height !== undefined && { height }),
|
|
62
|
+
...style,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return <View style={boxStyle}>{children}</View>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Convenience components for common spacing values
|
|
69
|
+
export namespace SizedBox {
|
|
70
|
+
/**
|
|
71
|
+
* Creates a square SizedBox with equal width and height
|
|
72
|
+
*/
|
|
73
|
+
export function Square({
|
|
74
|
+
size,
|
|
75
|
+
children,
|
|
76
|
+
style,
|
|
77
|
+
}: {
|
|
78
|
+
size: number;
|
|
79
|
+
children?: React.ReactNode;
|
|
80
|
+
style?: ViewStyle;
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<SizedBox width={size} height={size} style={style}>
|
|
84
|
+
{children}
|
|
85
|
+
</SizedBox>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a SizedBox that expands to fill available space
|
|
91
|
+
*/
|
|
92
|
+
export function Expand({
|
|
93
|
+
children,
|
|
94
|
+
style,
|
|
95
|
+
}: {
|
|
96
|
+
children?: React.ReactNode;
|
|
97
|
+
style?: ViewStyle;
|
|
98
|
+
}) {
|
|
99
|
+
const expandStyle: ViewStyle = { flex: 1 };
|
|
100
|
+
return <View style={[expandStyle, style]}>{children}</View>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates a SizedBox that shrinks to fit its content
|
|
105
|
+
*/
|
|
106
|
+
export function Shrink({
|
|
107
|
+
children,
|
|
108
|
+
style,
|
|
109
|
+
}: {
|
|
110
|
+
children?: React.ReactNode;
|
|
111
|
+
style?: ViewStyle;
|
|
112
|
+
}) {
|
|
113
|
+
const shrinkStyle: ViewStyle = { flexShrink: 1 };
|
|
114
|
+
return <View style={[shrinkStyle, style]}>{children}</View>;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
|
|
5
|
+
cssInterop(View, { className: 'style' });
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Skeleton loading placeholder component
|
|
9
|
+
*
|
|
10
|
+
* Displays an animated placeholder during content loading states. Provides visual feedback
|
|
11
|
+
* to users while data is being fetched, improving perceived performance.
|
|
12
|
+
*
|
|
13
|
+
* @component
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // Text skeleton
|
|
17
|
+
* <Skeleton className="h-4 w-[250px]" />
|
|
18
|
+
*
|
|
19
|
+
* // Avatar skeleton
|
|
20
|
+
* <Skeleton className="h-12 w-12 rounded-full" />
|
|
21
|
+
*
|
|
22
|
+
* // Card skeleton
|
|
23
|
+
* <View className="flex gap-3">
|
|
24
|
+
* <Skeleton className="h-[125px] w-full rounded-xl" />
|
|
25
|
+
* <View className="gap-2">
|
|
26
|
+
* <Skeleton className="h-4 w-full" />
|
|
27
|
+
* <Skeleton className="h-4 w-[80%]" />
|
|
28
|
+
* </View>
|
|
29
|
+
* </View>
|
|
30
|
+
*
|
|
31
|
+
* // List skeleton
|
|
32
|
+
* {[1, 2, 3].map((i) => (
|
|
33
|
+
* <View key={i} className="flex-row items-center gap-4">
|
|
34
|
+
* <Skeleton className="h-12 w-12 rounded-full" />
|
|
35
|
+
* <View className="flex-1 gap-2">
|
|
36
|
+
* <Skeleton className="h-4 w-full" />
|
|
37
|
+
* <Skeleton className="h-4 w-3/4" />
|
|
38
|
+
* </View>
|
|
39
|
+
* </View>
|
|
40
|
+
* ))}
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function Skeleton({
|
|
44
|
+
className,
|
|
45
|
+
...props
|
|
46
|
+
}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {
|
|
47
|
+
return (
|
|
48
|
+
<View
|
|
49
|
+
className={cn('bg-accent animate-pulse rounded-md', className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { Skeleton };
|