@metacells/mcellui-mcp-server 0.1.1 → 0.1.3
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/dist/index.js +14 -3
- package/package.json +5 -3
- package/registry/registry.json +717 -0
- package/registry/ui/accordion.tsx +416 -0
- package/registry/ui/action-sheet.tsx +396 -0
- package/registry/ui/alert-dialog.tsx +355 -0
- package/registry/ui/avatar-stack.tsx +278 -0
- package/registry/ui/avatar.tsx +116 -0
- package/registry/ui/badge.tsx +125 -0
- package/registry/ui/button.tsx +240 -0
- package/registry/ui/card.tsx +675 -0
- package/registry/ui/carousel.tsx +431 -0
- package/registry/ui/checkbox.tsx +252 -0
- package/registry/ui/chip.tsx +271 -0
- package/registry/ui/column.tsx +133 -0
- package/registry/ui/datetime-picker.tsx +578 -0
- package/registry/ui/dialog.tsx +292 -0
- package/registry/ui/fab.tsx +225 -0
- package/registry/ui/form.tsx +323 -0
- package/registry/ui/horizontal-list.tsx +200 -0
- package/registry/ui/icon-button.tsx +244 -0
- package/registry/ui/image-gallery.tsx +455 -0
- package/registry/ui/image.tsx +283 -0
- package/registry/ui/input.tsx +242 -0
- package/registry/ui/label.tsx +99 -0
- package/registry/ui/list.tsx +519 -0
- package/registry/ui/progress.tsx +168 -0
- package/registry/ui/pull-to-refresh.tsx +231 -0
- package/registry/ui/radio-group.tsx +294 -0
- package/registry/ui/rating.tsx +311 -0
- package/registry/ui/row.tsx +136 -0
- package/registry/ui/screen.tsx +153 -0
- package/registry/ui/search-input.tsx +281 -0
- package/registry/ui/section-header.tsx +258 -0
- package/registry/ui/segmented-control.tsx +229 -0
- package/registry/ui/select.tsx +311 -0
- package/registry/ui/separator.tsx +74 -0
- package/registry/ui/sheet.tsx +362 -0
- package/registry/ui/skeleton.tsx +156 -0
- package/registry/ui/slider.tsx +307 -0
- package/registry/ui/spinner.tsx +100 -0
- package/registry/ui/stepper.tsx +314 -0
- package/registry/ui/stories.tsx +463 -0
- package/registry/ui/swipeable-row.tsx +362 -0
- package/registry/ui/switch.tsx +246 -0
- package/registry/ui/tabs.tsx +348 -0
- package/registry/ui/textarea.tsx +265 -0
- package/registry/ui/toast.tsx +316 -0
- package/registry/ui/tooltip.tsx +369 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Segmented Control
|
|
3
|
+
*
|
|
4
|
+
* A native iOS-style segmented control component.
|
|
5
|
+
* Perfect for switching between views or filtering content.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const [selected, setSelected] = useState('daily');
|
|
10
|
+
*
|
|
11
|
+
* <SegmentedControl
|
|
12
|
+
* value={selected}
|
|
13
|
+
* onValueChange={setSelected}
|
|
14
|
+
* segments={[
|
|
15
|
+
* { label: 'Daily', value: 'daily' },
|
|
16
|
+
* { label: 'Weekly', value: 'weekly' },
|
|
17
|
+
* { label: 'Monthly', value: 'monthly' },
|
|
18
|
+
* ]}
|
|
19
|
+
* />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import React, { useCallback, useRef, useEffect } from 'react';
|
|
24
|
+
import {
|
|
25
|
+
View,
|
|
26
|
+
Text,
|
|
27
|
+
Pressable,
|
|
28
|
+
StyleSheet,
|
|
29
|
+
ViewStyle,
|
|
30
|
+
TextStyle,
|
|
31
|
+
LayoutChangeEvent,
|
|
32
|
+
LayoutRectangle,
|
|
33
|
+
} from 'react-native';
|
|
34
|
+
import Animated, {
|
|
35
|
+
useSharedValue,
|
|
36
|
+
useAnimatedStyle,
|
|
37
|
+
withSpring,
|
|
38
|
+
} from 'react-native-reanimated';
|
|
39
|
+
import { useTheme } from '@nativeui/core';
|
|
40
|
+
import { haptic } from '@nativeui/core';
|
|
41
|
+
|
|
42
|
+
export type SegmentedControlSize = 'sm' | 'md' | 'lg';
|
|
43
|
+
|
|
44
|
+
export interface Segment {
|
|
45
|
+
label: string;
|
|
46
|
+
value: string;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SegmentedControlProps {
|
|
51
|
+
/** Array of segment options */
|
|
52
|
+
segments: Segment[];
|
|
53
|
+
/** Current selected value */
|
|
54
|
+
value: string;
|
|
55
|
+
/** Callback when value changes */
|
|
56
|
+
onValueChange: (value: string) => void;
|
|
57
|
+
/** Size preset */
|
|
58
|
+
size?: SegmentedControlSize;
|
|
59
|
+
/** Disabled state */
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
/** Container style */
|
|
62
|
+
style?: ViewStyle;
|
|
63
|
+
/** Segment text style */
|
|
64
|
+
textStyle?: TextStyle;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const heights: Record<SegmentedControlSize, number> = {
|
|
68
|
+
sm: 32,
|
|
69
|
+
md: 40,
|
|
70
|
+
lg: 48,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const fontSizes: Record<SegmentedControlSize, number> = {
|
|
74
|
+
sm: 13,
|
|
75
|
+
md: 14,
|
|
76
|
+
lg: 15,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const SPRING_CONFIG = { damping: 20, stiffness: 200 };
|
|
80
|
+
|
|
81
|
+
export function SegmentedControl({
|
|
82
|
+
segments,
|
|
83
|
+
value,
|
|
84
|
+
onValueChange,
|
|
85
|
+
size = 'md',
|
|
86
|
+
disabled = false,
|
|
87
|
+
style,
|
|
88
|
+
textStyle,
|
|
89
|
+
}: SegmentedControlProps) {
|
|
90
|
+
const { colors, radius } = useTheme();
|
|
91
|
+
const segmentLayouts = useRef(new Map<string, LayoutRectangle>()).current;
|
|
92
|
+
|
|
93
|
+
const indicatorX = useSharedValue(0);
|
|
94
|
+
const indicatorWidth = useSharedValue(0);
|
|
95
|
+
|
|
96
|
+
const height = heights[size];
|
|
97
|
+
const fontSize = fontSizes[size];
|
|
98
|
+
const padding = 4;
|
|
99
|
+
const segmentHeight = height - padding * 2;
|
|
100
|
+
|
|
101
|
+
const handleLayout = (segmentValue: string, event: LayoutChangeEvent) => {
|
|
102
|
+
const layout = event.nativeEvent.layout;
|
|
103
|
+
segmentLayouts.set(segmentValue, layout);
|
|
104
|
+
|
|
105
|
+
// Update indicator if this is the active segment
|
|
106
|
+
if (segmentValue === value) {
|
|
107
|
+
indicatorX.value = withSpring(layout.x, SPRING_CONFIG);
|
|
108
|
+
indicatorWidth.value = withSpring(layout.width, SPRING_CONFIG);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Update indicator when value changes
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const layout = segmentLayouts.get(value);
|
|
115
|
+
if (layout) {
|
|
116
|
+
indicatorX.value = withSpring(layout.x, SPRING_CONFIG);
|
|
117
|
+
indicatorWidth.value = withSpring(layout.width, SPRING_CONFIG);
|
|
118
|
+
}
|
|
119
|
+
}, [value, segmentLayouts]);
|
|
120
|
+
|
|
121
|
+
const handlePress = useCallback(
|
|
122
|
+
(segmentValue: string, segmentDisabled?: boolean) => {
|
|
123
|
+
if (disabled || segmentDisabled) return;
|
|
124
|
+
haptic('selection');
|
|
125
|
+
onValueChange(segmentValue);
|
|
126
|
+
},
|
|
127
|
+
[disabled, onValueChange]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const indicatorStyle = useAnimatedStyle(() => ({
|
|
131
|
+
transform: [{ translateX: indicatorX.value }],
|
|
132
|
+
width: indicatorWidth.value,
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<View
|
|
137
|
+
style={[
|
|
138
|
+
styles.container,
|
|
139
|
+
{
|
|
140
|
+
height,
|
|
141
|
+
backgroundColor: colors.backgroundMuted,
|
|
142
|
+
borderRadius: radius.lg,
|
|
143
|
+
padding,
|
|
144
|
+
opacity: disabled ? 0.5 : 1,
|
|
145
|
+
},
|
|
146
|
+
style,
|
|
147
|
+
]}
|
|
148
|
+
>
|
|
149
|
+
{/* Indicator */}
|
|
150
|
+
<Animated.View
|
|
151
|
+
style={[
|
|
152
|
+
styles.indicator,
|
|
153
|
+
{
|
|
154
|
+
height: segmentHeight,
|
|
155
|
+
backgroundColor: colors.background,
|
|
156
|
+
borderRadius: radius.md,
|
|
157
|
+
},
|
|
158
|
+
indicatorStyle,
|
|
159
|
+
]}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
{/* Segments */}
|
|
163
|
+
{segments.map((segment) => {
|
|
164
|
+
const isActive = value === segment.value;
|
|
165
|
+
const isSegmentDisabled = disabled || segment.disabled;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Pressable
|
|
169
|
+
key={segment.value}
|
|
170
|
+
style={[
|
|
171
|
+
styles.segment,
|
|
172
|
+
{
|
|
173
|
+
height: segmentHeight,
|
|
174
|
+
opacity: isSegmentDisabled ? 0.5 : 1,
|
|
175
|
+
},
|
|
176
|
+
]}
|
|
177
|
+
onLayout={(e) => handleLayout(segment.value, e)}
|
|
178
|
+
onPress={() => handlePress(segment.value, segment.disabled)}
|
|
179
|
+
disabled={isSegmentDisabled}
|
|
180
|
+
accessibilityRole="button"
|
|
181
|
+
accessibilityState={{ selected: isActive, disabled: isSegmentDisabled }}
|
|
182
|
+
>
|
|
183
|
+
<Text
|
|
184
|
+
style={[
|
|
185
|
+
styles.segmentText,
|
|
186
|
+
{
|
|
187
|
+
fontSize,
|
|
188
|
+
color: isActive ? colors.foreground : colors.foregroundMuted,
|
|
189
|
+
fontWeight: isActive ? '600' : '500',
|
|
190
|
+
},
|
|
191
|
+
textStyle,
|
|
192
|
+
]}
|
|
193
|
+
numberOfLines={1}
|
|
194
|
+
>
|
|
195
|
+
{segment.label}
|
|
196
|
+
</Text>
|
|
197
|
+
</Pressable>
|
|
198
|
+
);
|
|
199
|
+
})}
|
|
200
|
+
</View>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const styles = StyleSheet.create({
|
|
205
|
+
container: {
|
|
206
|
+
flexDirection: 'row',
|
|
207
|
+
position: 'relative',
|
|
208
|
+
},
|
|
209
|
+
indicator: {
|
|
210
|
+
position: 'absolute',
|
|
211
|
+
top: 4,
|
|
212
|
+
left: 0,
|
|
213
|
+
shadowColor: '#000',
|
|
214
|
+
shadowOffset: { width: 0, height: 1 },
|
|
215
|
+
shadowOpacity: 0.08,
|
|
216
|
+
shadowRadius: 2,
|
|
217
|
+
elevation: 2,
|
|
218
|
+
},
|
|
219
|
+
segment: {
|
|
220
|
+
flex: 1,
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
justifyContent: 'center',
|
|
223
|
+
zIndex: 1,
|
|
224
|
+
paddingHorizontal: 8,
|
|
225
|
+
},
|
|
226
|
+
segmentText: {
|
|
227
|
+
textAlign: 'center',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select
|
|
3
|
+
*
|
|
4
|
+
* A selection component using a bottom sheet picker.
|
|
5
|
+
* Native-feeling select experience for mobile.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const [value, setValue] = useState('');
|
|
10
|
+
*
|
|
11
|
+
* <Select
|
|
12
|
+
* label="Country"
|
|
13
|
+
* placeholder="Select a country"
|
|
14
|
+
* value={value}
|
|
15
|
+
* onValueChange={setValue}
|
|
16
|
+
* options={[
|
|
17
|
+
* { label: 'United States', value: 'us' },
|
|
18
|
+
* { label: 'Germany', value: 'de' },
|
|
19
|
+
* { label: 'Japan', value: 'jp' },
|
|
20
|
+
* ]}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import React, { useState, useCallback } from 'react';
|
|
26
|
+
import {
|
|
27
|
+
View,
|
|
28
|
+
Text,
|
|
29
|
+
Pressable,
|
|
30
|
+
StyleSheet,
|
|
31
|
+
ViewStyle,
|
|
32
|
+
TextStyle,
|
|
33
|
+
ScrollView,
|
|
34
|
+
} from 'react-native';
|
|
35
|
+
import Animated, {
|
|
36
|
+
useSharedValue,
|
|
37
|
+
useAnimatedStyle,
|
|
38
|
+
withTiming,
|
|
39
|
+
interpolateColor,
|
|
40
|
+
Easing,
|
|
41
|
+
} from 'react-native-reanimated';
|
|
42
|
+
import { useTheme } from '@nativeui/core';
|
|
43
|
+
import { haptic } from '@nativeui/core';
|
|
44
|
+
|
|
45
|
+
const TIMING_CONFIG = { duration: 200, easing: Easing.out(Easing.quad) };
|
|
46
|
+
import {
|
|
47
|
+
Sheet,
|
|
48
|
+
SheetContent,
|
|
49
|
+
SheetHeader,
|
|
50
|
+
SheetTitle,
|
|
51
|
+
} from './sheet';
|
|
52
|
+
|
|
53
|
+
export interface SelectOption {
|
|
54
|
+
label: string;
|
|
55
|
+
value: string;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SelectProps {
|
|
60
|
+
/** Label text above select */
|
|
61
|
+
label?: string;
|
|
62
|
+
/** Placeholder when no value selected */
|
|
63
|
+
placeholder?: string;
|
|
64
|
+
/** Current selected value */
|
|
65
|
+
value?: string;
|
|
66
|
+
/** Callback when value changes */
|
|
67
|
+
onValueChange?: (value: string) => void;
|
|
68
|
+
/** Available options */
|
|
69
|
+
options: SelectOption[];
|
|
70
|
+
/** Error message */
|
|
71
|
+
error?: string;
|
|
72
|
+
/** Helper text */
|
|
73
|
+
helperText?: string;
|
|
74
|
+
/** Disabled state */
|
|
75
|
+
disabled?: boolean;
|
|
76
|
+
/** Sheet title */
|
|
77
|
+
sheetTitle?: string;
|
|
78
|
+
/** Container style */
|
|
79
|
+
containerStyle?: ViewStyle;
|
|
80
|
+
/** Trigger style */
|
|
81
|
+
style?: ViewStyle;
|
|
82
|
+
/** Label style */
|
|
83
|
+
labelStyle?: TextStyle;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
87
|
+
|
|
88
|
+
export function Select({
|
|
89
|
+
label,
|
|
90
|
+
placeholder = 'Select an option',
|
|
91
|
+
value,
|
|
92
|
+
onValueChange,
|
|
93
|
+
options,
|
|
94
|
+
error,
|
|
95
|
+
helperText,
|
|
96
|
+
disabled = false,
|
|
97
|
+
sheetTitle,
|
|
98
|
+
containerStyle,
|
|
99
|
+
style,
|
|
100
|
+
labelStyle,
|
|
101
|
+
}: SelectProps) {
|
|
102
|
+
const { colors, radius, spacing } = useTheme();
|
|
103
|
+
const [open, setOpen] = useState(false);
|
|
104
|
+
const focusProgress = useSharedValue(0);
|
|
105
|
+
|
|
106
|
+
const hasError = !!error;
|
|
107
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
108
|
+
|
|
109
|
+
const handleOpen = useCallback(() => {
|
|
110
|
+
if (disabled) return;
|
|
111
|
+
haptic('light');
|
|
112
|
+
focusProgress.value = withTiming(1, TIMING_CONFIG);
|
|
113
|
+
setOpen(true);
|
|
114
|
+
}, [disabled]);
|
|
115
|
+
|
|
116
|
+
const handleClose = useCallback(() => {
|
|
117
|
+
focusProgress.value = withTiming(0, TIMING_CONFIG);
|
|
118
|
+
setOpen(false);
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const handleSelect = useCallback(
|
|
122
|
+
(optionValue: string) => {
|
|
123
|
+
haptic('selection');
|
|
124
|
+
onValueChange?.(optionValue);
|
|
125
|
+
handleClose();
|
|
126
|
+
},
|
|
127
|
+
[onValueChange, handleClose]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const animatedBorderStyle = useAnimatedStyle(() => {
|
|
131
|
+
if (hasError) {
|
|
132
|
+
return {
|
|
133
|
+
borderColor: colors.destructive,
|
|
134
|
+
borderWidth: 2,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
borderColor: interpolateColor(
|
|
140
|
+
focusProgress.value,
|
|
141
|
+
[0, 1],
|
|
142
|
+
[colors.border, colors.primary]
|
|
143
|
+
),
|
|
144
|
+
borderWidth: focusProgress.value > 0.5 ? 2 : 1,
|
|
145
|
+
};
|
|
146
|
+
}, [hasError, colors]);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<View style={[styles.container, containerStyle]}>
|
|
150
|
+
{label && (
|
|
151
|
+
<Text
|
|
152
|
+
style={[
|
|
153
|
+
styles.label,
|
|
154
|
+
{
|
|
155
|
+
fontSize: 14,
|
|
156
|
+
marginBottom: spacing[1.5],
|
|
157
|
+
color: hasError ? colors.destructive : colors.foreground,
|
|
158
|
+
},
|
|
159
|
+
labelStyle,
|
|
160
|
+
]}
|
|
161
|
+
>
|
|
162
|
+
{label}
|
|
163
|
+
</Text>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
<AnimatedPressable
|
|
167
|
+
style={[
|
|
168
|
+
styles.trigger,
|
|
169
|
+
{
|
|
170
|
+
minHeight: 48,
|
|
171
|
+
paddingHorizontal: 12,
|
|
172
|
+
borderRadius: radius.md,
|
|
173
|
+
backgroundColor: disabled ? colors.backgroundMuted : colors.background,
|
|
174
|
+
},
|
|
175
|
+
animatedBorderStyle,
|
|
176
|
+
style,
|
|
177
|
+
]}
|
|
178
|
+
onPress={handleOpen}
|
|
179
|
+
disabled={disabled}
|
|
180
|
+
accessibilityRole="combobox"
|
|
181
|
+
accessibilityState={{ disabled, expanded: open }}
|
|
182
|
+
accessibilityLabel={label}
|
|
183
|
+
>
|
|
184
|
+
<Text
|
|
185
|
+
style={[
|
|
186
|
+
styles.triggerText,
|
|
187
|
+
{
|
|
188
|
+
color: selectedOption
|
|
189
|
+
? disabled
|
|
190
|
+
? colors.foregroundMuted
|
|
191
|
+
: colors.foreground
|
|
192
|
+
: colors.foregroundMuted,
|
|
193
|
+
},
|
|
194
|
+
]}
|
|
195
|
+
numberOfLines={1}
|
|
196
|
+
>
|
|
197
|
+
{selectedOption?.label ?? placeholder}
|
|
198
|
+
</Text>
|
|
199
|
+
<Text style={[styles.chevron, { color: colors.foregroundMuted }]}>
|
|
200
|
+
▼
|
|
201
|
+
</Text>
|
|
202
|
+
</AnimatedPressable>
|
|
203
|
+
|
|
204
|
+
{(error || helperText) && (
|
|
205
|
+
<Text
|
|
206
|
+
style={[
|
|
207
|
+
styles.helperText,
|
|
208
|
+
{
|
|
209
|
+
fontSize: 12,
|
|
210
|
+
marginTop: spacing[1],
|
|
211
|
+
color: hasError ? colors.destructive : colors.foregroundMuted,
|
|
212
|
+
},
|
|
213
|
+
]}
|
|
214
|
+
>
|
|
215
|
+
{error || helperText}
|
|
216
|
+
</Text>
|
|
217
|
+
)}
|
|
218
|
+
|
|
219
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
220
|
+
<SheetContent height="50%">
|
|
221
|
+
<SheetHeader>
|
|
222
|
+
<SheetTitle>{sheetTitle ?? label ?? 'Select'}</SheetTitle>
|
|
223
|
+
</SheetHeader>
|
|
224
|
+
<ScrollView style={styles.optionsList} showsVerticalScrollIndicator={false}>
|
|
225
|
+
{options.map((option) => (
|
|
226
|
+
<Pressable
|
|
227
|
+
key={option.value}
|
|
228
|
+
style={({ pressed }) => [
|
|
229
|
+
styles.option,
|
|
230
|
+
{
|
|
231
|
+
backgroundColor:
|
|
232
|
+
option.value === value
|
|
233
|
+
? colors.primary + '15'
|
|
234
|
+
: pressed
|
|
235
|
+
? colors.backgroundMuted
|
|
236
|
+
: 'transparent',
|
|
237
|
+
borderRadius: radius.md,
|
|
238
|
+
paddingVertical: spacing[3],
|
|
239
|
+
paddingHorizontal: spacing[4],
|
|
240
|
+
opacity: option.disabled ? 0.5 : 1,
|
|
241
|
+
},
|
|
242
|
+
]}
|
|
243
|
+
onPress={() => !option.disabled && handleSelect(option.value)}
|
|
244
|
+
disabled={option.disabled}
|
|
245
|
+
>
|
|
246
|
+
<Text
|
|
247
|
+
style={[
|
|
248
|
+
styles.optionText,
|
|
249
|
+
{
|
|
250
|
+
color:
|
|
251
|
+
option.value === value
|
|
252
|
+
? colors.primary
|
|
253
|
+
: colors.foreground,
|
|
254
|
+
fontWeight: option.value === value ? '600' : '400',
|
|
255
|
+
},
|
|
256
|
+
]}
|
|
257
|
+
>
|
|
258
|
+
{option.label}
|
|
259
|
+
</Text>
|
|
260
|
+
{option.value === value && (
|
|
261
|
+
<Text style={[styles.checkmark, { color: colors.primary }]}>
|
|
262
|
+
✓
|
|
263
|
+
</Text>
|
|
264
|
+
)}
|
|
265
|
+
</Pressable>
|
|
266
|
+
))}
|
|
267
|
+
</ScrollView>
|
|
268
|
+
</SheetContent>
|
|
269
|
+
</Sheet>
|
|
270
|
+
</View>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const styles = StyleSheet.create({
|
|
275
|
+
container: {
|
|
276
|
+
width: '100%',
|
|
277
|
+
},
|
|
278
|
+
label: {
|
|
279
|
+
fontWeight: '500',
|
|
280
|
+
},
|
|
281
|
+
trigger: {
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
justifyContent: 'space-between',
|
|
285
|
+
},
|
|
286
|
+
triggerText: {
|
|
287
|
+
flex: 1,
|
|
288
|
+
fontSize: 14,
|
|
289
|
+
},
|
|
290
|
+
chevron: {
|
|
291
|
+
fontSize: 10,
|
|
292
|
+
marginLeft: 8,
|
|
293
|
+
},
|
|
294
|
+
helperText: {},
|
|
295
|
+
optionsList: {
|
|
296
|
+
flex: 1,
|
|
297
|
+
marginTop: 8,
|
|
298
|
+
},
|
|
299
|
+
option: {
|
|
300
|
+
flexDirection: 'row',
|
|
301
|
+
alignItems: 'center',
|
|
302
|
+
justifyContent: 'space-between',
|
|
303
|
+
},
|
|
304
|
+
optionText: {
|
|
305
|
+
fontSize: 16,
|
|
306
|
+
},
|
|
307
|
+
checkmark: {
|
|
308
|
+
fontSize: 16,
|
|
309
|
+
fontWeight: '600',
|
|
310
|
+
},
|
|
311
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator
|
|
3
|
+
*
|
|
4
|
+
* A visual divider component for separating content.
|
|
5
|
+
* Supports horizontal and vertical orientations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <Separator />
|
|
10
|
+
* <Separator orientation="vertical" />
|
|
11
|
+
* <Separator decorative={false} />
|
|
12
|
+
* <Separator style={{ marginVertical: 16 }} />
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import {
|
|
18
|
+
View,
|
|
19
|
+
StyleSheet,
|
|
20
|
+
ViewStyle,
|
|
21
|
+
ViewProps,
|
|
22
|
+
} from 'react-native';
|
|
23
|
+
import { useTheme } from '@nativeui/core';
|
|
24
|
+
|
|
25
|
+
export type SeparatorOrientation = 'horizontal' | 'vertical';
|
|
26
|
+
|
|
27
|
+
export interface SeparatorProps extends Omit<ViewProps, 'style'> {
|
|
28
|
+
/** Direction of the separator */
|
|
29
|
+
orientation?: SeparatorOrientation;
|
|
30
|
+
/** Whether the separator is purely decorative (affects accessibility) */
|
|
31
|
+
decorative?: boolean;
|
|
32
|
+
/** Additional styles */
|
|
33
|
+
style?: ViewStyle;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Separator({
|
|
37
|
+
orientation = 'horizontal',
|
|
38
|
+
decorative = true,
|
|
39
|
+
style,
|
|
40
|
+
...props
|
|
41
|
+
}: SeparatorProps) {
|
|
42
|
+
const { colors } = useTheme();
|
|
43
|
+
|
|
44
|
+
const isHorizontal = orientation === 'horizontal';
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<View
|
|
48
|
+
style={[
|
|
49
|
+
styles.base,
|
|
50
|
+
isHorizontal ? styles.horizontal : styles.vertical,
|
|
51
|
+
{ backgroundColor: colors.border },
|
|
52
|
+
style,
|
|
53
|
+
]}
|
|
54
|
+
accessibilityRole={decorative ? 'none' : undefined}
|
|
55
|
+
accessible={!decorative}
|
|
56
|
+
accessibilityLabel={decorative ? undefined : 'Separator'}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const styles = StyleSheet.create({
|
|
63
|
+
base: {
|
|
64
|
+
flexShrink: 0,
|
|
65
|
+
},
|
|
66
|
+
horizontal: {
|
|
67
|
+
height: StyleSheet.hairlineWidth,
|
|
68
|
+
width: '100%',
|
|
69
|
+
},
|
|
70
|
+
vertical: {
|
|
71
|
+
width: StyleSheet.hairlineWidth,
|
|
72
|
+
alignSelf: 'stretch',
|
|
73
|
+
},
|
|
74
|
+
});
|