@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,40 @@
|
|
|
1
|
+
import * as CollapsiblePrimitive from '@rn-primitives/collapsible';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Root collapsible container
|
|
5
|
+
*
|
|
6
|
+
* An interactive component that can show and hide content. Unlike Accordion, it doesn't enforce
|
|
7
|
+
* any specific visual styling, making it more flexible for custom layouts.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
13
|
+
* <CollapsibleTrigger asChild>
|
|
14
|
+
* <Button variant="ghost"><Text>Toggle</Text></Button>
|
|
15
|
+
* </CollapsibleTrigger>
|
|
16
|
+
* <CollapsibleContent>
|
|
17
|
+
* <Text>This content can be shown or hidden</Text>
|
|
18
|
+
* </CollapsibleContent>
|
|
19
|
+
* </Collapsible>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
const Collapsible = CollapsiblePrimitive.Root;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Trigger button that toggles collapsible content visibility
|
|
26
|
+
*
|
|
27
|
+
* @component
|
|
28
|
+
*/
|
|
29
|
+
const CollapsibleTrigger = CollapsiblePrimitive.Trigger;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Collapsible content container
|
|
33
|
+
*
|
|
34
|
+
* Automatically animates height when expanding or collapsing.
|
|
35
|
+
*
|
|
36
|
+
* @component
|
|
37
|
+
*/
|
|
38
|
+
const CollapsibleContent = CollapsiblePrimitive.Content;
|
|
39
|
+
|
|
40
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
|
@@ -0,0 +1,407 @@
|
|
|
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 ContextMenuPrimitive from '@rn-primitives/context-menu';
|
|
6
|
+
import {
|
|
7
|
+
Check,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
ChevronUp,
|
|
11
|
+
} from 'lucide-react-native';
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import {
|
|
14
|
+
Platform,
|
|
15
|
+
type StyleProp,
|
|
16
|
+
StyleSheet,
|
|
17
|
+
Text,
|
|
18
|
+
type TextProps,
|
|
19
|
+
View,
|
|
20
|
+
type ViewStyle,
|
|
21
|
+
} from 'react-native';
|
|
22
|
+
import { cssInterop } from 'nativewind';
|
|
23
|
+
import { FadeIn } from 'react-native-reanimated';
|
|
24
|
+
import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
|
|
25
|
+
|
|
26
|
+
cssInterop(View, { className: 'style' });
|
|
27
|
+
cssInterop(Text, { className: 'style' });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Root context menu component
|
|
31
|
+
*
|
|
32
|
+
* @component
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* <ContextMenu>
|
|
36
|
+
* <ContextMenuTrigger>
|
|
37
|
+
* <View><Text>Right-click me</Text></View>
|
|
38
|
+
* </ContextMenuTrigger>
|
|
39
|
+
* <ContextMenuContent>
|
|
40
|
+
* <ContextMenuItem><Text>Edit</Text></ContextMenuItem>
|
|
41
|
+
* <ContextMenuItem><Text>Delete</Text></ContextMenuItem>
|
|
42
|
+
* </ContextMenuContent>
|
|
43
|
+
* </ContextMenu>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
const ContextMenu = ContextMenuPrimitive.Root;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Trigger component that opens context menu on right-click or long-press
|
|
50
|
+
* @component
|
|
51
|
+
*/
|
|
52
|
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Groups related context menu items
|
|
56
|
+
* @component
|
|
57
|
+
*/
|
|
58
|
+
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Root component for nested submenu
|
|
62
|
+
* @component
|
|
63
|
+
*/
|
|
64
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Groups radio items for single selection
|
|
68
|
+
* @component
|
|
69
|
+
*/
|
|
70
|
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Trigger for opening a nested submenu
|
|
74
|
+
* @component
|
|
75
|
+
*/
|
|
76
|
+
function ContextMenuSubTrigger({
|
|
77
|
+
className: _className,
|
|
78
|
+
inset,
|
|
79
|
+
children,
|
|
80
|
+
iconClassName,
|
|
81
|
+
...props
|
|
82
|
+
}: ContextMenuPrimitive.SubTriggerProps &
|
|
83
|
+
React.RefAttributes<ContextMenuPrimitive.SubTriggerRef> & {
|
|
84
|
+
children?: React.ReactNode;
|
|
85
|
+
iconClassName?: string;
|
|
86
|
+
inset?: boolean;
|
|
87
|
+
}) {
|
|
88
|
+
const { open } = ContextMenuPrimitive.useSubContext();
|
|
89
|
+
const icon =
|
|
90
|
+
Platform.OS === 'web' ? ChevronRight : open ? ChevronUp : ChevronDown;
|
|
91
|
+
return (
|
|
92
|
+
<TextClassContext.Provider
|
|
93
|
+
value={cn(
|
|
94
|
+
'text-sm select-none group-active:text-accent-foreground',
|
|
95
|
+
open && 'text-accent-foreground'
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
<ContextMenuPrimitive.SubTrigger
|
|
99
|
+
className={cn(
|
|
100
|
+
'active:bg-accent group flex flex-row items-center rounded-sm px-2 py-2 sm:py-1.5',
|
|
101
|
+
Platform.select({
|
|
102
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none [&_svg]:pointer-events-none',
|
|
103
|
+
}),
|
|
104
|
+
open && cn('bg-accent', Platform.select({ native: 'mb-1' })),
|
|
105
|
+
inset && 'pl-8'
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
<>{children}</>
|
|
110
|
+
<Icon
|
|
111
|
+
as={icon}
|
|
112
|
+
className={cn(
|
|
113
|
+
'text-foreground ml-auto size-4 shrink-0',
|
|
114
|
+
iconClassName
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
118
|
+
</TextClassContext.Provider>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Content container for nested submenu items
|
|
124
|
+
* @component
|
|
125
|
+
*/
|
|
126
|
+
function ContextMenuSubContent({
|
|
127
|
+
className,
|
|
128
|
+
...props
|
|
129
|
+
}: ContextMenuPrimitive.SubContentProps &
|
|
130
|
+
React.RefAttributes<ContextMenuPrimitive.SubContentRef>) {
|
|
131
|
+
return (
|
|
132
|
+
<NativeOnlyAnimatedView entering={FadeIn}>
|
|
133
|
+
<ContextMenuPrimitive.SubContent
|
|
134
|
+
className={cn(
|
|
135
|
+
'bg-popover border-border overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
|
|
136
|
+
Platform.select({
|
|
137
|
+
web: 'animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 fade-in-0 data-[state=closed]:zoom-out-95 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-context-menu-content-transform-origin) z-50 min-w-[8rem]',
|
|
138
|
+
}),
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
</NativeOnlyAnimatedView>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Full window overlay wrapper for iOS
|
|
149
|
+
*/
|
|
150
|
+
const FullWindowOverlay =
|
|
151
|
+
Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Main content container for context menu items
|
|
155
|
+
* @component
|
|
156
|
+
*/
|
|
157
|
+
function ContextMenuContent({
|
|
158
|
+
className,
|
|
159
|
+
overlayClassName,
|
|
160
|
+
overlayStyle,
|
|
161
|
+
portalHost,
|
|
162
|
+
...props
|
|
163
|
+
}: ContextMenuPrimitive.ContentProps &
|
|
164
|
+
React.RefAttributes<ContextMenuPrimitive.ContentRef> & {
|
|
165
|
+
overlayStyle?: StyleProp<ViewStyle>;
|
|
166
|
+
overlayClassName?: string;
|
|
167
|
+
portalHost?: string;
|
|
168
|
+
}) {
|
|
169
|
+
return (
|
|
170
|
+
<ContextMenuPrimitive.Portal hostName={portalHost}>
|
|
171
|
+
<FullWindowOverlay>
|
|
172
|
+
<ContextMenuPrimitive.Overlay
|
|
173
|
+
style={Platform.select({
|
|
174
|
+
web: overlayStyle ?? undefined,
|
|
175
|
+
native: overlayStyle
|
|
176
|
+
? StyleSheet.flatten([
|
|
177
|
+
StyleSheet.absoluteFill,
|
|
178
|
+
overlayStyle as typeof StyleSheet.absoluteFill,
|
|
179
|
+
])
|
|
180
|
+
: StyleSheet.absoluteFill,
|
|
181
|
+
})}
|
|
182
|
+
className={overlayClassName}
|
|
183
|
+
>
|
|
184
|
+
<NativeOnlyAnimatedView entering={FadeIn}>
|
|
185
|
+
<TextClassContext.Provider value="text-popover-foreground">
|
|
186
|
+
<ContextMenuPrimitive.Content
|
|
187
|
+
className={cn(
|
|
188
|
+
'bg-popover border-border min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
|
|
189
|
+
Platform.select({
|
|
190
|
+
web: cn(
|
|
191
|
+
'animate-in fade-in-0 zoom-in-95 max-h-(--radix-context-menu-content-available-height) origin-(--radix-context-menu-content-transform-origin) z-50 cursor-default',
|
|
192
|
+
props.side === 'bottom' && 'slide-in-from-top-2',
|
|
193
|
+
props.side === 'top' && 'slide-in-from-bottom-2'
|
|
194
|
+
),
|
|
195
|
+
}),
|
|
196
|
+
className
|
|
197
|
+
)}
|
|
198
|
+
{...props}
|
|
199
|
+
/>
|
|
200
|
+
</TextClassContext.Provider>
|
|
201
|
+
</NativeOnlyAnimatedView>
|
|
202
|
+
</ContextMenuPrimitive.Overlay>
|
|
203
|
+
</FullWindowOverlay>
|
|
204
|
+
</ContextMenuPrimitive.Portal>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Individual context menu item
|
|
210
|
+
* @component
|
|
211
|
+
*/
|
|
212
|
+
function ContextMenuItem({
|
|
213
|
+
className,
|
|
214
|
+
inset,
|
|
215
|
+
variant,
|
|
216
|
+
...props
|
|
217
|
+
}: ContextMenuPrimitive.ItemProps &
|
|
218
|
+
React.RefAttributes<ContextMenuPrimitive.ItemRef> & {
|
|
219
|
+
className?: string;
|
|
220
|
+
inset?: boolean;
|
|
221
|
+
variant?: 'default' | 'destructive';
|
|
222
|
+
}) {
|
|
223
|
+
return (
|
|
224
|
+
<TextClassContext.Provider
|
|
225
|
+
value={cn(
|
|
226
|
+
'select-none text-sm text-popover-foreground group-active:text-popover-foreground',
|
|
227
|
+
variant === 'destructive' &&
|
|
228
|
+
'text-destructive group-active:text-destructive'
|
|
229
|
+
)}
|
|
230
|
+
>
|
|
231
|
+
<ContextMenuPrimitive.Item
|
|
232
|
+
className={cn(
|
|
233
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm px-2 py-2 sm:py-1.5',
|
|
234
|
+
Platform.select({
|
|
235
|
+
web: cn(
|
|
236
|
+
'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
237
|
+
variant === 'destructive' &&
|
|
238
|
+
'focus:bg-destructive/10 dark:focus:bg-destructive/20'
|
|
239
|
+
),
|
|
240
|
+
}),
|
|
241
|
+
variant === 'destructive' &&
|
|
242
|
+
'active:bg-destructive/10 dark:active:bg-destructive/20',
|
|
243
|
+
props.disabled && 'opacity-50',
|
|
244
|
+
inset && 'pl-8',
|
|
245
|
+
className
|
|
246
|
+
)}
|
|
247
|
+
{...props}
|
|
248
|
+
/>
|
|
249
|
+
</TextClassContext.Provider>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Menu item with checkbox for multi-selection
|
|
255
|
+
* @component
|
|
256
|
+
*/
|
|
257
|
+
function ContextMenuCheckboxItem({
|
|
258
|
+
className,
|
|
259
|
+
children,
|
|
260
|
+
...props
|
|
261
|
+
}: ContextMenuPrimitive.CheckboxItemProps &
|
|
262
|
+
React.RefAttributes<ContextMenuPrimitive.CheckboxItemRef> & {
|
|
263
|
+
children?: React.ReactNode;
|
|
264
|
+
}) {
|
|
265
|
+
return (
|
|
266
|
+
<TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
|
|
267
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
268
|
+
className={cn(
|
|
269
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
|
|
270
|
+
Platform.select({
|
|
271
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
272
|
+
}),
|
|
273
|
+
props.disabled && 'opacity-50',
|
|
274
|
+
className
|
|
275
|
+
)}
|
|
276
|
+
{...props}
|
|
277
|
+
>
|
|
278
|
+
<View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
279
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
280
|
+
<Icon
|
|
281
|
+
as={Check}
|
|
282
|
+
className={cn(
|
|
283
|
+
'text-foreground size-4',
|
|
284
|
+
Platform.select({ web: 'pointer-events-none' })
|
|
285
|
+
)}
|
|
286
|
+
/>
|
|
287
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
288
|
+
</View>
|
|
289
|
+
<>{children}</>
|
|
290
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
291
|
+
</TextClassContext.Provider>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Menu item with radio button for single selection
|
|
297
|
+
* @component
|
|
298
|
+
*/
|
|
299
|
+
function ContextMenuRadioItem({
|
|
300
|
+
className,
|
|
301
|
+
children,
|
|
302
|
+
...props
|
|
303
|
+
}: ContextMenuPrimitive.RadioItemProps &
|
|
304
|
+
React.RefAttributes<ContextMenuPrimitive.RadioItemRef> & {
|
|
305
|
+
children?: React.ReactNode;
|
|
306
|
+
}) {
|
|
307
|
+
return (
|
|
308
|
+
<TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
|
|
309
|
+
<ContextMenuPrimitive.RadioItem
|
|
310
|
+
className={cn(
|
|
311
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
|
|
312
|
+
Platform.select({
|
|
313
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
314
|
+
}),
|
|
315
|
+
props.disabled && 'opacity-50',
|
|
316
|
+
className
|
|
317
|
+
)}
|
|
318
|
+
{...props}
|
|
319
|
+
>
|
|
320
|
+
<View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
321
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
322
|
+
<View className="bg-foreground h-2 w-2 rounded-full" />
|
|
323
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
324
|
+
</View>
|
|
325
|
+
<>{children}</>
|
|
326
|
+
</ContextMenuPrimitive.RadioItem>
|
|
327
|
+
</TextClassContext.Provider>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Label for grouping context menu sections
|
|
333
|
+
* @component
|
|
334
|
+
*/
|
|
335
|
+
function ContextMenuLabel({
|
|
336
|
+
className,
|
|
337
|
+
inset,
|
|
338
|
+
...props
|
|
339
|
+
}: ContextMenuPrimitive.LabelProps &
|
|
340
|
+
React.RefAttributes<ContextMenuPrimitive.LabelRef> & {
|
|
341
|
+
className?: string;
|
|
342
|
+
inset?: boolean;
|
|
343
|
+
}) {
|
|
344
|
+
return (
|
|
345
|
+
<ContextMenuPrimitive.Label
|
|
346
|
+
className={cn(
|
|
347
|
+
'text-foreground px-2 py-2 text-sm font-medium sm:py-1.5',
|
|
348
|
+
inset && 'pl-8',
|
|
349
|
+
className
|
|
350
|
+
)}
|
|
351
|
+
{...props}
|
|
352
|
+
/>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Visual separator between menu items
|
|
358
|
+
* @component
|
|
359
|
+
*/
|
|
360
|
+
function ContextMenuSeparator({
|
|
361
|
+
className,
|
|
362
|
+
...props
|
|
363
|
+
}: ContextMenuPrimitive.SeparatorProps &
|
|
364
|
+
React.RefAttributes<ContextMenuPrimitive.SeparatorRef>) {
|
|
365
|
+
return (
|
|
366
|
+
<ContextMenuPrimitive.Separator
|
|
367
|
+
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
|
368
|
+
{...props}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Displays keyboard shortcut hint for menu items
|
|
375
|
+
* @component
|
|
376
|
+
*/
|
|
377
|
+
function ContextMenuShortcut({
|
|
378
|
+
className,
|
|
379
|
+
...props
|
|
380
|
+
}: TextProps & React.RefAttributes<Text>) {
|
|
381
|
+
return (
|
|
382
|
+
<Text
|
|
383
|
+
className={cn(
|
|
384
|
+
'text-muted-foreground ml-auto text-xs tracking-widest',
|
|
385
|
+
className
|
|
386
|
+
)}
|
|
387
|
+
{...props}
|
|
388
|
+
/>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export {
|
|
393
|
+
ContextMenu,
|
|
394
|
+
ContextMenuCheckboxItem,
|
|
395
|
+
ContextMenuContent,
|
|
396
|
+
ContextMenuGroup,
|
|
397
|
+
ContextMenuItem,
|
|
398
|
+
ContextMenuLabel,
|
|
399
|
+
ContextMenuRadioGroup,
|
|
400
|
+
ContextMenuRadioItem,
|
|
401
|
+
ContextMenuSeparator,
|
|
402
|
+
ContextMenuShortcut,
|
|
403
|
+
ContextMenuSub,
|
|
404
|
+
ContextMenuSubContent,
|
|
405
|
+
ContextMenuSubTrigger,
|
|
406
|
+
ContextMenuTrigger,
|
|
407
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as Slot from '@rn-primitives/slot';
|
|
2
|
+
import type { SlottableViewProps, ViewRef } from '@rn-primitives/types';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { View, useColorScheme } from 'react-native';
|
|
5
|
+
import { SquircleView } from 'react-native-figma-squircle';
|
|
6
|
+
import { cn } from '../../lib/utils';
|
|
7
|
+
import { TextClassContext } from './Text';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* CustomCard container component with squircle shape
|
|
11
|
+
*
|
|
12
|
+
* Provides elevated container with smooth squircle corners (iOS-like rounded corners),
|
|
13
|
+
* border, and shadow. Uses Figma's squircle algorithm for premium look.
|
|
14
|
+
* Use with CardHeader, CardTitle, CardDescription, CardContent, and CardFooter
|
|
15
|
+
* for a complete card structure.
|
|
16
|
+
*
|
|
17
|
+
* @component
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <CustomCard>
|
|
21
|
+
* <CardHeader>
|
|
22
|
+
* <CardTitle><Text>Title</Text></CardTitle>
|
|
23
|
+
* <CardDescription><Text>Description</Text></CardDescription>
|
|
24
|
+
* </CardHeader>
|
|
25
|
+
* <CardContent>
|
|
26
|
+
* <Text>Card content goes here</Text>
|
|
27
|
+
* </CardContent>
|
|
28
|
+
* <CardFooter>
|
|
29
|
+
* <Button>Action</Button>
|
|
30
|
+
* </CardFooter>
|
|
31
|
+
* </CustomCard>
|
|
32
|
+
*
|
|
33
|
+
* // With custom corner smoothing
|
|
34
|
+
* <CustomCard cornerSmoothing={1}>
|
|
35
|
+
* <Text>Extra smooth corners</Text>
|
|
36
|
+
* </CustomCard>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
40
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
41
|
+
* @property {number} [cornerRadius=16] - Corner radius in pixels
|
|
42
|
+
* @property {number} [cornerSmoothing=0.8] - Smoothing factor (0-1), higher = more iOS-like
|
|
43
|
+
*/
|
|
44
|
+
const CustomCard = React.forwardRef<
|
|
45
|
+
ViewRef,
|
|
46
|
+
SlottableViewProps & {
|
|
47
|
+
className?: string;
|
|
48
|
+
cornerRadius?: number;
|
|
49
|
+
cornerSmoothing?: number;
|
|
50
|
+
}
|
|
51
|
+
>(
|
|
52
|
+
(
|
|
53
|
+
{
|
|
54
|
+
className: _className,
|
|
55
|
+
asChild = false,
|
|
56
|
+
cornerRadius = 16,
|
|
57
|
+
cornerSmoothing = 1.0,
|
|
58
|
+
children,
|
|
59
|
+
...props
|
|
60
|
+
},
|
|
61
|
+
ref
|
|
62
|
+
) => {
|
|
63
|
+
const colorScheme = useColorScheme();
|
|
64
|
+
const isDark = colorScheme === 'dark';
|
|
65
|
+
|
|
66
|
+
if (asChild) {
|
|
67
|
+
const Component = Slot.View;
|
|
68
|
+
return (
|
|
69
|
+
<Component ref={ref as any} {...props}>
|
|
70
|
+
{children}
|
|
71
|
+
</Component>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Use SquircleView directly as the container with inner View for content
|
|
76
|
+
return (
|
|
77
|
+
<SquircleView
|
|
78
|
+
squircleParams={{
|
|
79
|
+
cornerRadius,
|
|
80
|
+
cornerSmoothing,
|
|
81
|
+
fillColor: isDark ? 'hsl(224 71.4% 4.1%)' : 'hsl(0 0% 100%)',
|
|
82
|
+
strokeColor: isDark
|
|
83
|
+
? 'hsl(215 27.9% 16.9%)'
|
|
84
|
+
: 'hsl(214.3 31.8% 91.4%)',
|
|
85
|
+
strokeWidth: 1,
|
|
86
|
+
}}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
<View ref={ref as any} className="px-4">
|
|
90
|
+
{children}
|
|
91
|
+
</View>
|
|
92
|
+
</SquircleView>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
CustomCard.displayName = 'CustomCard';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* CustomCardHeader - Container for card title and description
|
|
100
|
+
*
|
|
101
|
+
* Provides proper spacing for title and description elements at the top of a card.
|
|
102
|
+
*
|
|
103
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
104
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
105
|
+
*/
|
|
106
|
+
const CustomCardHeader = React.forwardRef<
|
|
107
|
+
ViewRef,
|
|
108
|
+
SlottableViewProps & { className?: string }
|
|
109
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
110
|
+
const Component = asChild ? Slot.View : View;
|
|
111
|
+
return (
|
|
112
|
+
<Component
|
|
113
|
+
className={cn('flex flex-col gap-1.5 p-6', className)}
|
|
114
|
+
ref={ref as any}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
CustomCardHeader.displayName = 'CustomCardHeader';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* CustomCardTitle - Main title text for the card
|
|
123
|
+
*
|
|
124
|
+
* Automatically styles child Text components with large, semibold typography.
|
|
125
|
+
* Uses TextClassContext to apply consistent styling to nested Text elements.
|
|
126
|
+
*
|
|
127
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
128
|
+
* @property {string} [className] - Additional Tailwind classes for text styling
|
|
129
|
+
*/
|
|
130
|
+
const CustomCardTitle = React.forwardRef<
|
|
131
|
+
ViewRef,
|
|
132
|
+
SlottableViewProps & { className?: string }
|
|
133
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
134
|
+
const Component = asChild ? Slot.View : View;
|
|
135
|
+
return (
|
|
136
|
+
<TextClassContext.Provider
|
|
137
|
+
value={cn(
|
|
138
|
+
'text-2xl font-semibold leading-none tracking-tight text-card-foreground dark:text-card-foreground',
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
<Component ref={ref as any} {...props} />
|
|
143
|
+
</TextClassContext.Provider>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
CustomCardTitle.displayName = 'CustomCardTitle';
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* CustomCardDescription - Subtitle or description text for the card
|
|
150
|
+
*
|
|
151
|
+
* Automatically styles child Text components with muted, smaller typography.
|
|
152
|
+
* Uses TextClassContext to apply consistent styling to nested Text elements.
|
|
153
|
+
*
|
|
154
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
155
|
+
* @property {string} [className] - Additional Tailwind classes for text styling
|
|
156
|
+
*/
|
|
157
|
+
const CustomCardDescription = React.forwardRef<
|
|
158
|
+
ViewRef,
|
|
159
|
+
SlottableViewProps & { className?: string }
|
|
160
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
161
|
+
const Component = asChild ? Slot.View : View;
|
|
162
|
+
return (
|
|
163
|
+
<TextClassContext.Provider
|
|
164
|
+
value={cn(
|
|
165
|
+
'text-sm text-muted-foreground dark:text-muted-foreground',
|
|
166
|
+
className
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
<Component ref={ref as any} {...props} />
|
|
170
|
+
</TextClassContext.Provider>
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
CustomCardDescription.displayName = 'CustomCardDescription';
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* CustomCardContent - Main content area of the card
|
|
177
|
+
*
|
|
178
|
+
* Container for the primary card content with appropriate padding.
|
|
179
|
+
* Top padding is removed to flow naturally after CustomCardHeader.
|
|
180
|
+
*
|
|
181
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
182
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
183
|
+
*/
|
|
184
|
+
const CustomCardContent = React.forwardRef<
|
|
185
|
+
ViewRef,
|
|
186
|
+
SlottableViewProps & { className?: string }
|
|
187
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
188
|
+
const Component = asChild ? Slot.View : View;
|
|
189
|
+
return (
|
|
190
|
+
<Component className={cn('p-6 pt-0', className)} ref={ref} {...props} />
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
CustomCardContent.displayName = 'CustomCardContent';
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* CustomCardFooter - Footer area for actions or additional info
|
|
197
|
+
*
|
|
198
|
+
* Typically contains buttons or other interactive elements.
|
|
199
|
+
* Arranged as a row with centered items.
|
|
200
|
+
*
|
|
201
|
+
* @property {boolean} [asChild] - When true, merges props into immediate child
|
|
202
|
+
* @property {string} [className] - Additional Tailwind classes
|
|
203
|
+
*/
|
|
204
|
+
const CustomCardFooter = React.forwardRef<
|
|
205
|
+
ViewRef,
|
|
206
|
+
SlottableViewProps & { className?: string }
|
|
207
|
+
>(({ className, asChild = false, ...props }, ref) => {
|
|
208
|
+
const Component = asChild ? Slot.View : View;
|
|
209
|
+
return (
|
|
210
|
+
<Component
|
|
211
|
+
className={cn('flex flex-row items-center p-6 pt-0', className)}
|
|
212
|
+
ref={ref as any}
|
|
213
|
+
{...props}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
CustomCardFooter.displayName = 'CustomCardFooter';
|
|
218
|
+
|
|
219
|
+
export {
|
|
220
|
+
CustomCard,
|
|
221
|
+
CustomCardContent,
|
|
222
|
+
CustomCardDescription,
|
|
223
|
+
CustomCardFooter,
|
|
224
|
+
CustomCardHeader,
|
|
225
|
+
CustomCardTitle,
|
|
226
|
+
};
|