@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,90 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils';
|
|
2
|
+
import { Platform, TextInput, type TextInputProps } from 'react-native';
|
|
3
|
+
import { cssInterop } from 'nativewind';
|
|
4
|
+
|
|
5
|
+
cssInterop(TextInput, { className: 'style' });
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props for Input component
|
|
9
|
+
*
|
|
10
|
+
* @extends TextInputProps - All standard React Native TextInput props
|
|
11
|
+
* @property {string} [className] - Additional Tailwind classes for custom styling
|
|
12
|
+
* @property {boolean} [editable=true] - Whether the input is editable (disabled when false)
|
|
13
|
+
*/
|
|
14
|
+
interface InputProps extends TextInputProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A styled text input component with consistent design across platforms
|
|
20
|
+
*
|
|
21
|
+
* Features platform-specific behaviors:
|
|
22
|
+
* - Web: Focus ring, selection styling, aria-invalid support
|
|
23
|
+
* - Native: Optimized placeholder opacity and input styling
|
|
24
|
+
*
|
|
25
|
+
* Automatically adapts styling when disabled/non-editable.
|
|
26
|
+
*
|
|
27
|
+
* @component
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // Basic input
|
|
31
|
+
* <Input
|
|
32
|
+
* placeholder="Enter your name"
|
|
33
|
+
* value={name}
|
|
34
|
+
* onChangeText={setName}
|
|
35
|
+
* />
|
|
36
|
+
*
|
|
37
|
+
* // Disabled input
|
|
38
|
+
* <Input
|
|
39
|
+
* value="Read only"
|
|
40
|
+
* editable={false}
|
|
41
|
+
* />
|
|
42
|
+
*
|
|
43
|
+
* // With custom styling
|
|
44
|
+
* <Input
|
|
45
|
+
* className="border-primary"
|
|
46
|
+
* placeholder="Search..."
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @accessibility
|
|
51
|
+
* - Supports aria-invalid for form validation states (web)
|
|
52
|
+
* - Placeholder text uses appropriate contrast colors
|
|
53
|
+
* - Disabled state is visually indicated with opacity
|
|
54
|
+
*/
|
|
55
|
+
function Input({
|
|
56
|
+
className,
|
|
57
|
+
editable = true,
|
|
58
|
+
...props
|
|
59
|
+
}: InputProps & React.RefAttributes<TextInput>) {
|
|
60
|
+
return (
|
|
61
|
+
<TextInput
|
|
62
|
+
className={cn(
|
|
63
|
+
'bg-background dark:bg-background border-input dark:border-input text-foreground dark:text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 dark:shadow-white/5 sm:h-9',
|
|
64
|
+
!editable &&
|
|
65
|
+
cn(
|
|
66
|
+
'opacity-50',
|
|
67
|
+
Platform.select({
|
|
68
|
+
web: 'disabled:pointer-events-none disabled:cursor-not-allowed',
|
|
69
|
+
})
|
|
70
|
+
),
|
|
71
|
+
Platform.select({
|
|
72
|
+
web: cn(
|
|
73
|
+
'placeholder:text-muted-foreground dark:placeholder:text-muted-foreground selection:bg-primary dark:selection:bg-primary selection:text-primary-foreground dark:selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm',
|
|
74
|
+
'focus-visible:border-ring dark:focus-visible:border-ring focus-visible:ring-ring/50 dark:focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
|
75
|
+
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive'
|
|
76
|
+
),
|
|
77
|
+
native:
|
|
78
|
+
'placeholder:text-muted-foreground/50 dark:placeholder:text-muted-foreground/50',
|
|
79
|
+
}),
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
placeholderTextColor={undefined}
|
|
83
|
+
editable={editable}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { Input };
|
|
90
|
+
export type { InputProps };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
import { Text } from './Text';
|
|
4
|
+
import type { SlottableTextProps, TextRef } from '@rn-primitives/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Accessible label component for form controls
|
|
8
|
+
*
|
|
9
|
+
* Provides semantic labeling for form inputs with proper styling and accessibility support.
|
|
10
|
+
* Automatically associates with form controls and respects disabled states.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Label with input
|
|
16
|
+
* <View className="gap-2">
|
|
17
|
+
* <Label htmlFor="email">Email Address</Label>
|
|
18
|
+
* <Input id="email" placeholder="Enter email" />
|
|
19
|
+
* </View>
|
|
20
|
+
*
|
|
21
|
+
* // Label for checkbox
|
|
22
|
+
* <View className="flex-row items-center gap-2">
|
|
23
|
+
* <Checkbox id="terms" />
|
|
24
|
+
* <Label htmlFor="terms">I agree to the terms and conditions</Label>
|
|
25
|
+
* </View>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @accessibility
|
|
29
|
+
* - Properly associates with form controls via htmlFor/id
|
|
30
|
+
* - Reduced opacity when associated control is disabled
|
|
31
|
+
* - Screen reader compatible
|
|
32
|
+
*/
|
|
33
|
+
const Label = React.forwardRef<
|
|
34
|
+
TextRef,
|
|
35
|
+
SlottableTextProps & { className?: string }
|
|
36
|
+
>(({ className, ...props }, ref) => (
|
|
37
|
+
<Text
|
|
38
|
+
className={cn(
|
|
39
|
+
'text-sm native:text-base font-medium leading-none text-foreground dark:text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
ref={ref}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
));
|
|
46
|
+
Label.displayName = 'Label';
|
|
47
|
+
|
|
48
|
+
export { Label };
|
|
@@ -0,0 +1,509 @@
|
|
|
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 MenubarPrimitive from '@rn-primitives/menubar';
|
|
6
|
+
import { Portal } from '@rn-primitives/portal';
|
|
7
|
+
import {
|
|
8
|
+
Check,
|
|
9
|
+
ChevronDown,
|
|
10
|
+
ChevronRight,
|
|
11
|
+
ChevronUp,
|
|
12
|
+
} from 'lucide-react-native';
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import {
|
|
15
|
+
Platform,
|
|
16
|
+
Pressable,
|
|
17
|
+
type StyleProp,
|
|
18
|
+
StyleSheet,
|
|
19
|
+
Text,
|
|
20
|
+
type TextProps,
|
|
21
|
+
View,
|
|
22
|
+
type ViewStyle,
|
|
23
|
+
} from 'react-native';
|
|
24
|
+
import { cssInterop } from 'nativewind';
|
|
25
|
+
import { FadeIn } from 'react-native-reanimated';
|
|
26
|
+
import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
|
|
27
|
+
|
|
28
|
+
cssInterop(View, { className: 'style' });
|
|
29
|
+
cssInterop(Text, { className: 'style' });
|
|
30
|
+
cssInterop(Pressable, { className: 'style' });
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Individual menu within the menubar
|
|
34
|
+
*
|
|
35
|
+
* @component
|
|
36
|
+
*/
|
|
37
|
+
const MenubarMenu = MenubarPrimitive.Menu;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Groups related menu items together
|
|
41
|
+
*
|
|
42
|
+
* @component
|
|
43
|
+
*/
|
|
44
|
+
const MenubarGroup = MenubarPrimitive.Group;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Portal component to render menu content outside the DOM hierarchy
|
|
48
|
+
*
|
|
49
|
+
* @component
|
|
50
|
+
*/
|
|
51
|
+
const MenubarPortal = MenubarPrimitive.Portal;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Root component for nested submenu
|
|
55
|
+
*
|
|
56
|
+
* @component
|
|
57
|
+
*/
|
|
58
|
+
const MenubarSub = MenubarPrimitive.Sub;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Groups radio items together with single selection
|
|
62
|
+
*
|
|
63
|
+
* @component
|
|
64
|
+
*/
|
|
65
|
+
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Full window overlay wrapper for iOS, Fragment for other platforms
|
|
69
|
+
*/
|
|
70
|
+
const FullWindowOverlay =
|
|
71
|
+
Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Root menubar component for application-level menu navigation
|
|
75
|
+
*
|
|
76
|
+
* Provides horizontal menu bar similar to desktop applications, with support
|
|
77
|
+
* for nested menus and automatic close on outside press.
|
|
78
|
+
*
|
|
79
|
+
* @component
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* <Menubar>
|
|
83
|
+
* <MenubarMenu>
|
|
84
|
+
* <MenubarTrigger>
|
|
85
|
+
* <Text>File</Text>
|
|
86
|
+
* </MenubarTrigger>
|
|
87
|
+
* <MenubarContent>
|
|
88
|
+
* <MenubarItem>
|
|
89
|
+
* <Text>New File</Text>
|
|
90
|
+
* </MenubarItem>
|
|
91
|
+
* <MenubarItem>
|
|
92
|
+
* <Text>Open</Text>
|
|
93
|
+
* </MenubarItem>
|
|
94
|
+
* </MenubarContent>
|
|
95
|
+
* </MenubarMenu>
|
|
96
|
+
* </Menubar>
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @accessibility
|
|
100
|
+
* - Keyboard navigation support
|
|
101
|
+
* - Proper ARIA attributes for menu structure
|
|
102
|
+
*/
|
|
103
|
+
function Menubar({
|
|
104
|
+
className,
|
|
105
|
+
value: valueProp,
|
|
106
|
+
onValueChange: onValueChangeProp,
|
|
107
|
+
...props
|
|
108
|
+
}: MenubarPrimitive.RootProps & React.RefAttributes<MenubarPrimitive.RootRef>) {
|
|
109
|
+
const id = React.useId();
|
|
110
|
+
const [value, setValue] = React.useState<string | undefined>(undefined);
|
|
111
|
+
|
|
112
|
+
function closeMenu() {
|
|
113
|
+
if (onValueChangeProp) {
|
|
114
|
+
onValueChangeProp(undefined);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setValue(undefined);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
{Platform.OS !== 'web' && (value || valueProp) ? (
|
|
123
|
+
<Portal name={`menubar-overlay-${id}`}>
|
|
124
|
+
<Pressable onPress={closeMenu} style={StyleSheet.absoluteFill} />
|
|
125
|
+
</Portal>
|
|
126
|
+
) : null}
|
|
127
|
+
<MenubarPrimitive.Root
|
|
128
|
+
className={cn(
|
|
129
|
+
'bg-background border-border flex h-10 flex-row items-center gap-1 rounded-md border p-1 shadow-sm shadow-black/5 sm:h-9',
|
|
130
|
+
className
|
|
131
|
+
)}
|
|
132
|
+
value={value ?? valueProp}
|
|
133
|
+
onValueChange={onValueChangeProp ?? setValue}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Trigger button for a menubar menu
|
|
142
|
+
*
|
|
143
|
+
* @component
|
|
144
|
+
* @example
|
|
145
|
+
* ```tsx
|
|
146
|
+
* <MenubarTrigger>
|
|
147
|
+
* <Text>Edit</Text>
|
|
148
|
+
* </MenubarTrigger>
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
function MenubarTrigger({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: MenubarPrimitive.TriggerProps &
|
|
155
|
+
React.RefAttributes<MenubarPrimitive.TriggerRef>) {
|
|
156
|
+
const { value } = MenubarPrimitive.useRootContext();
|
|
157
|
+
const { value: itemValue } = MenubarPrimitive.useMenuContext();
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<TextClassContext.Provider
|
|
161
|
+
value={cn(
|
|
162
|
+
'text-sm font-medium select-none group-active:text-accent-foreground',
|
|
163
|
+
value === itemValue && 'text-accent-foreground'
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
<MenubarPrimitive.Trigger
|
|
167
|
+
className={cn(
|
|
168
|
+
'group flex items-center rounded-md px-2 py-1.5 sm:py-1',
|
|
169
|
+
Platform.select({
|
|
170
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none',
|
|
171
|
+
}),
|
|
172
|
+
value === itemValue && 'bg-accent',
|
|
173
|
+
className
|
|
174
|
+
)}
|
|
175
|
+
{...props}
|
|
176
|
+
/>
|
|
177
|
+
</TextClassContext.Provider>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Trigger for opening a nested submenu
|
|
183
|
+
* @component
|
|
184
|
+
*/
|
|
185
|
+
function MenubarSubTrigger({
|
|
186
|
+
className: _className,
|
|
187
|
+
inset,
|
|
188
|
+
children,
|
|
189
|
+
iconClassName,
|
|
190
|
+
...props
|
|
191
|
+
}: MenubarPrimitive.SubTriggerProps &
|
|
192
|
+
React.RefAttributes<MenubarPrimitive.SubTriggerRef> & {
|
|
193
|
+
children?: React.ReactNode;
|
|
194
|
+
iconClassName?: string;
|
|
195
|
+
inset?: boolean;
|
|
196
|
+
}) {
|
|
197
|
+
const { open } = MenubarPrimitive.useSubContext();
|
|
198
|
+
const icon =
|
|
199
|
+
Platform.OS === 'web' ? ChevronRight : open ? ChevronUp : ChevronDown;
|
|
200
|
+
return (
|
|
201
|
+
<TextClassContext.Provider
|
|
202
|
+
value={cn(
|
|
203
|
+
'text-sm select-none group-active:text-accent-foreground',
|
|
204
|
+
open && 'text-accent-foreground'
|
|
205
|
+
)}
|
|
206
|
+
>
|
|
207
|
+
<MenubarPrimitive.SubTrigger
|
|
208
|
+
className={cn(
|
|
209
|
+
'active:bg-accent group flex flex-row items-center rounded-sm px-2 py-2 sm:py-1.5',
|
|
210
|
+
Platform.select({
|
|
211
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none [&_svg]:pointer-events-none',
|
|
212
|
+
}),
|
|
213
|
+
open && 'bg-accent',
|
|
214
|
+
inset && 'pl-8'
|
|
215
|
+
)}
|
|
216
|
+
{...props}
|
|
217
|
+
>
|
|
218
|
+
<>{children}</>
|
|
219
|
+
<Icon
|
|
220
|
+
as={icon}
|
|
221
|
+
className={cn(
|
|
222
|
+
'text-foreground ml-auto size-4 shrink-0',
|
|
223
|
+
iconClassName
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
</MenubarPrimitive.SubTrigger>
|
|
227
|
+
</TextClassContext.Provider>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Content container for nested submenu items
|
|
233
|
+
* @component
|
|
234
|
+
*/
|
|
235
|
+
function MenubarSubContent({
|
|
236
|
+
className,
|
|
237
|
+
...props
|
|
238
|
+
}: MenubarPrimitive.SubContentProps &
|
|
239
|
+
React.RefAttributes<MenubarPrimitive.SubContentRef>) {
|
|
240
|
+
return (
|
|
241
|
+
<NativeOnlyAnimatedView entering={FadeIn}>
|
|
242
|
+
<MenubarPrimitive.SubContent
|
|
243
|
+
className={cn(
|
|
244
|
+
'bg-popover border-border overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
|
|
245
|
+
Platform.select({
|
|
246
|
+
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]',
|
|
247
|
+
}),
|
|
248
|
+
className
|
|
249
|
+
)}
|
|
250
|
+
{...props}
|
|
251
|
+
/>
|
|
252
|
+
</NativeOnlyAnimatedView>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Main content container for menu items
|
|
258
|
+
* @component
|
|
259
|
+
*/
|
|
260
|
+
function MenubarContent({
|
|
261
|
+
className,
|
|
262
|
+
overlayClassName: _overlayClassName,
|
|
263
|
+
overlayStyle: _overlayStyle,
|
|
264
|
+
portalHost,
|
|
265
|
+
align = 'start',
|
|
266
|
+
alignOffset = -4,
|
|
267
|
+
sideOffset = 8,
|
|
268
|
+
...props
|
|
269
|
+
}: MenubarPrimitive.ContentProps &
|
|
270
|
+
React.RefAttributes<MenubarPrimitive.ContentRef> & {
|
|
271
|
+
overlayStyle?: StyleProp<ViewStyle>;
|
|
272
|
+
overlayClassName?: string;
|
|
273
|
+
portalHost?: string;
|
|
274
|
+
}) {
|
|
275
|
+
return (
|
|
276
|
+
<MenubarPrimitive.Portal hostName={portalHost}>
|
|
277
|
+
<FullWindowOverlay>
|
|
278
|
+
<NativeOnlyAnimatedView
|
|
279
|
+
entering={FadeIn}
|
|
280
|
+
style={StyleSheet.absoluteFill}
|
|
281
|
+
pointerEvents="box-none"
|
|
282
|
+
>
|
|
283
|
+
<TextClassContext.Provider value="text-popover-foreground">
|
|
284
|
+
<MenubarPrimitive.Content
|
|
285
|
+
className={cn(
|
|
286
|
+
'bg-popover border-border min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-lg shadow-black/5',
|
|
287
|
+
Platform.select({
|
|
288
|
+
web: cn(
|
|
289
|
+
'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',
|
|
290
|
+
props.side === 'bottom' && 'slide-in-from-top-2',
|
|
291
|
+
props.side === 'top' && 'slide-in-from-bottom-2'
|
|
292
|
+
),
|
|
293
|
+
}),
|
|
294
|
+
className
|
|
295
|
+
)}
|
|
296
|
+
align={align}
|
|
297
|
+
alignOffset={alignOffset}
|
|
298
|
+
sideOffset={sideOffset}
|
|
299
|
+
{...props}
|
|
300
|
+
/>
|
|
301
|
+
</TextClassContext.Provider>
|
|
302
|
+
</NativeOnlyAnimatedView>
|
|
303
|
+
</FullWindowOverlay>
|
|
304
|
+
</MenubarPrimitive.Portal>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Individual menu item component
|
|
310
|
+
* @component
|
|
311
|
+
*/
|
|
312
|
+
function MenubarItem({
|
|
313
|
+
className,
|
|
314
|
+
inset,
|
|
315
|
+
variant,
|
|
316
|
+
...props
|
|
317
|
+
}: MenubarPrimitive.ItemProps &
|
|
318
|
+
React.RefAttributes<MenubarPrimitive.ItemRef> & {
|
|
319
|
+
className?: string;
|
|
320
|
+
inset?: boolean;
|
|
321
|
+
variant?: 'default' | 'destructive';
|
|
322
|
+
}) {
|
|
323
|
+
return (
|
|
324
|
+
<TextClassContext.Provider
|
|
325
|
+
value={cn(
|
|
326
|
+
'select-none text-sm text-popover-foreground group-active:text-popover-foreground',
|
|
327
|
+
variant === 'destructive' &&
|
|
328
|
+
'text-destructive group-active:text-destructive'
|
|
329
|
+
)}
|
|
330
|
+
>
|
|
331
|
+
<MenubarPrimitive.Item
|
|
332
|
+
className={cn(
|
|
333
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm px-2 py-2 sm:py-1.5',
|
|
334
|
+
Platform.select({
|
|
335
|
+
web: cn(
|
|
336
|
+
'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
337
|
+
variant === 'destructive' &&
|
|
338
|
+
'focus:bg-destructive/10 dark:focus:bg-destructive/20'
|
|
339
|
+
),
|
|
340
|
+
}),
|
|
341
|
+
variant === 'destructive' &&
|
|
342
|
+
'active:bg-destructive/10 dark:active:bg-destructive/20',
|
|
343
|
+
props.disabled && 'opacity-50',
|
|
344
|
+
inset && 'pl-8',
|
|
345
|
+
className
|
|
346
|
+
)}
|
|
347
|
+
{...props}
|
|
348
|
+
/>
|
|
349
|
+
</TextClassContext.Provider>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Menu item with checkbox for multi-selection
|
|
355
|
+
* @component
|
|
356
|
+
*/
|
|
357
|
+
function MenubarCheckboxItem({
|
|
358
|
+
className,
|
|
359
|
+
children,
|
|
360
|
+
...props
|
|
361
|
+
}: MenubarPrimitive.CheckboxItemProps &
|
|
362
|
+
React.RefAttributes<MenubarPrimitive.CheckboxItemRef> & {
|
|
363
|
+
children?: React.ReactNode;
|
|
364
|
+
}) {
|
|
365
|
+
return (
|
|
366
|
+
<TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
|
|
367
|
+
<MenubarPrimitive.CheckboxItem
|
|
368
|
+
className={cn(
|
|
369
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
|
|
370
|
+
Platform.select({
|
|
371
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
372
|
+
}),
|
|
373
|
+
props.disabled && 'opacity-50',
|
|
374
|
+
className
|
|
375
|
+
)}
|
|
376
|
+
{...props}
|
|
377
|
+
>
|
|
378
|
+
<View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
379
|
+
<MenubarPrimitive.ItemIndicator>
|
|
380
|
+
<Icon
|
|
381
|
+
as={Check}
|
|
382
|
+
className={cn(
|
|
383
|
+
'text-foreground size-4',
|
|
384
|
+
Platform.select({ web: 'pointer-events-none' })
|
|
385
|
+
)}
|
|
386
|
+
/>
|
|
387
|
+
</MenubarPrimitive.ItemIndicator>
|
|
388
|
+
</View>
|
|
389
|
+
<>{children}</>
|
|
390
|
+
</MenubarPrimitive.CheckboxItem>
|
|
391
|
+
</TextClassContext.Provider>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Menu item with radio button for single selection within a group
|
|
397
|
+
* @component
|
|
398
|
+
*/
|
|
399
|
+
function MenubarRadioItem({
|
|
400
|
+
className,
|
|
401
|
+
children,
|
|
402
|
+
...props
|
|
403
|
+
}: MenubarPrimitive.RadioItemProps &
|
|
404
|
+
React.RefAttributes<MenubarPrimitive.RadioItemRef> & {
|
|
405
|
+
children?: React.ReactNode;
|
|
406
|
+
}) {
|
|
407
|
+
return (
|
|
408
|
+
<TextClassContext.Provider value="text-sm text-popover-foreground select-none group-active:text-accent-foreground">
|
|
409
|
+
<MenubarPrimitive.RadioItem
|
|
410
|
+
className={cn(
|
|
411
|
+
'active:bg-accent group relative flex flex-row items-center gap-2 rounded-sm py-2 pl-8 pr-2 sm:py-1.5',
|
|
412
|
+
Platform.select({
|
|
413
|
+
web: 'focus:bg-accent focus:text-accent-foreground cursor-default outline-none data-[disabled]:pointer-events-none',
|
|
414
|
+
}),
|
|
415
|
+
props.disabled && 'opacity-50',
|
|
416
|
+
className
|
|
417
|
+
)}
|
|
418
|
+
{...props}
|
|
419
|
+
>
|
|
420
|
+
<View className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
421
|
+
<MenubarPrimitive.ItemIndicator>
|
|
422
|
+
<View className="bg-foreground h-2 w-2 rounded-full" />
|
|
423
|
+
</MenubarPrimitive.ItemIndicator>
|
|
424
|
+
</View>
|
|
425
|
+
<>{children}</>
|
|
426
|
+
</MenubarPrimitive.RadioItem>
|
|
427
|
+
</TextClassContext.Provider>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Label component for grouping and describing menu sections
|
|
433
|
+
* @component
|
|
434
|
+
*/
|
|
435
|
+
function MenubarLabel({
|
|
436
|
+
className,
|
|
437
|
+
inset,
|
|
438
|
+
...props
|
|
439
|
+
}: MenubarPrimitive.LabelProps &
|
|
440
|
+
React.RefAttributes<MenubarPrimitive.LabelRef> & {
|
|
441
|
+
className?: string;
|
|
442
|
+
inset?: boolean;
|
|
443
|
+
}) {
|
|
444
|
+
return (
|
|
445
|
+
<MenubarPrimitive.Label
|
|
446
|
+
className={cn(
|
|
447
|
+
'text-foreground px-2 py-2 text-sm font-medium sm:py-1.5',
|
|
448
|
+
inset && 'pl-8',
|
|
449
|
+
className
|
|
450
|
+
)}
|
|
451
|
+
{...props}
|
|
452
|
+
/>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Visual separator between menu items or sections
|
|
458
|
+
* @component
|
|
459
|
+
*/
|
|
460
|
+
function MenubarSeparator({
|
|
461
|
+
className,
|
|
462
|
+
...props
|
|
463
|
+
}: MenubarPrimitive.SeparatorProps &
|
|
464
|
+
React.RefAttributes<MenubarPrimitive.SeparatorRef>) {
|
|
465
|
+
return (
|
|
466
|
+
<MenubarPrimitive.Separator
|
|
467
|
+
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
|
468
|
+
{...props}
|
|
469
|
+
/>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Displays keyboard shortcut hint for menu items
|
|
475
|
+
* @component
|
|
476
|
+
*/
|
|
477
|
+
function MenubarShortcut({
|
|
478
|
+
className,
|
|
479
|
+
...props
|
|
480
|
+
}: TextProps & React.RefAttributes<Text>) {
|
|
481
|
+
return (
|
|
482
|
+
<Text
|
|
483
|
+
className={cn(
|
|
484
|
+
'text-muted-foreground ml-auto text-xs tracking-widest',
|
|
485
|
+
className
|
|
486
|
+
)}
|
|
487
|
+
{...props}
|
|
488
|
+
/>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export {
|
|
493
|
+
Menubar,
|
|
494
|
+
MenubarCheckboxItem,
|
|
495
|
+
MenubarContent,
|
|
496
|
+
MenubarGroup,
|
|
497
|
+
MenubarItem,
|
|
498
|
+
MenubarLabel,
|
|
499
|
+
MenubarMenu,
|
|
500
|
+
MenubarPortal,
|
|
501
|
+
MenubarRadioGroup,
|
|
502
|
+
MenubarRadioItem,
|
|
503
|
+
MenubarSeparator,
|
|
504
|
+
MenubarShortcut,
|
|
505
|
+
MenubarSub,
|
|
506
|
+
MenubarSubContent,
|
|
507
|
+
MenubarSubTrigger,
|
|
508
|
+
MenubarTrigger,
|
|
509
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import Animated from 'react-native-reanimated';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Platform-conditional animated view component
|
|
6
|
+
*
|
|
7
|
+
* Renders an Animated.View on native platforms (iOS/Android) and a React Fragment
|
|
8
|
+
* on web. This utility helps maintain consistent animation APIs across platforms
|
|
9
|
+
* while avoiding web-incompatible animations.
|
|
10
|
+
*
|
|
11
|
+
* Used internally by other components like Dialog, Popover, Tooltip, etc. to provide
|
|
12
|
+
* smooth entrance/exit animations on native while gracefully degrading to no animation
|
|
13
|
+
* on web where certain Reanimated features are not supported.
|
|
14
|
+
*
|
|
15
|
+
* @component
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <NativeOnlyAnimatedView entering={FadeIn} exiting={FadeOut}>
|
|
19
|
+
* <View><Text>Animated content on native, static on web</Text></View>
|
|
20
|
+
* </NativeOnlyAnimatedView>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @platform iOS, Android - Full animation support
|
|
24
|
+
* @platform web - Renders as Fragment without animations
|
|
25
|
+
*/
|
|
26
|
+
function NativeOnlyAnimatedView(
|
|
27
|
+
props: React.ComponentProps<typeof Animated.View> &
|
|
28
|
+
React.RefAttributes<Animated.View>
|
|
29
|
+
) {
|
|
30
|
+
if (Platform.OS === 'web') {
|
|
31
|
+
return <>{props.children as React.ReactNode}</>;
|
|
32
|
+
} else {
|
|
33
|
+
return <Animated.View {...props} />;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { NativeOnlyAnimatedView };
|