@lunar-kit/core 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/dist/index.d.ts +5 -0
- package/dist/index.js +14 -0
- package/package.json +31 -0
- package/src/components/ui/accordion.tsx +334 -0
- package/src/components/ui/avatar.tsx +326 -0
- package/src/components/ui/badge.tsx +84 -0
- package/src/components/ui/banner.tsx +151 -0
- package/src/components/ui/bottom-sheet.tsx +579 -0
- package/src/components/ui/button.tsx +142 -0
- package/src/components/ui/calendar.tsx +502 -0
- package/src/components/ui/card.tsx +163 -0
- package/src/components/ui/checkbox.tsx +129 -0
- package/src/components/ui/date-picker.tsx +190 -0
- package/src/components/ui/date-range-picker.tsx +262 -0
- package/src/components/ui/dialog.tsx +204 -0
- package/src/components/ui/form.tsx +139 -0
- package/src/components/ui/input.tsx +107 -0
- package/src/components/ui/radio-group.tsx +123 -0
- package/src/components/ui/radio.tsx +109 -0
- package/src/components/ui/select-sheet.tsx +814 -0
- package/src/components/ui/select.tsx +547 -0
- package/src/components/ui/tabs.tsx +254 -0
- package/src/components/ui/text.tsx +229 -0
- package/src/components/ui/textarea.tsx +77 -0
- package/src/components/v0/accordion.tsx +199 -0
- package/src/components/v1/accordion.tsx +234 -0
- package/src/components/v1/avatar.tsx +259 -0
- package/src/components/v1/bottom-sheet.tsx +1090 -0
- package/src/components/v1/button.tsx +61 -0
- package/src/components/v1/calendar.tsx +498 -0
- package/src/components/v1/card.tsx +86 -0
- package/src/components/v1/checkbox.tsx +46 -0
- package/src/components/v1/date-picker.tsx +135 -0
- package/src/components/v1/date-range-picker.tsx +218 -0
- package/src/components/v1/dialog.tsx +211 -0
- package/src/components/v1/radio-group.tsx +76 -0
- package/src/components/v1/select.tsx +217 -0
- package/src/components/v1/tabs.tsx +253 -0
- package/src/registry/ui/accordion.json +30 -0
- package/src/registry/ui/avatar.json +41 -0
- package/src/registry/ui/badge.json +26 -0
- package/src/registry/ui/banner.json +27 -0
- package/src/registry/ui/bottom-sheet.json +29 -0
- package/src/registry/ui/button.json +24 -0
- package/src/registry/ui/calendar.json +29 -0
- package/src/registry/ui/card.json +25 -0
- package/src/registry/ui/checkbox.json +25 -0
- package/src/registry/ui/date-picker.json +30 -0
- package/src/registry/ui/date-range-picker.json +33 -0
- package/src/registry/ui/dialog.json +25 -0
- package/src/registry/ui/form.json +27 -0
- package/src/registry/ui/input.json +22 -0
- package/src/registry/ui/radio-group.json +26 -0
- package/src/registry/ui/radio.json +23 -0
- package/src/registry/ui/select-sheet.json +29 -0
- package/src/registry/ui/select.json +26 -0
- package/src/registry/ui/tabs.json +29 -0
- package/src/registry/ui/text.json +22 -0
- package/src/registry/ui/textarea.json +24 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// components/ui/checkbox.tsx
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { View, Pressable } from 'react-native';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { Text } from './text';
|
|
7
|
+
import { Check, Minus } from 'lucide-react-native';
|
|
8
|
+
import { useThemeColors } from '@/hooks/useThemeColors';
|
|
9
|
+
|
|
10
|
+
// Checkbox Variants
|
|
11
|
+
const checkboxVariants = cva(
|
|
12
|
+
'flex-row items-center',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
size: {
|
|
16
|
+
sm: 'gap-2',
|
|
17
|
+
md: 'gap-3',
|
|
18
|
+
lg: 'gap-4',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: 'md',
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Checkbox Box Variants
|
|
28
|
+
const checkboxBoxVariants = cva(
|
|
29
|
+
'rounded border-2 items-center justify-center',
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
size: {
|
|
33
|
+
sm: 'h-4 w-4 border',
|
|
34
|
+
md: 'h-5 w-5 border-2',
|
|
35
|
+
lg: 'h-6 w-6 border-2',
|
|
36
|
+
},
|
|
37
|
+
checked: {
|
|
38
|
+
true: 'bg-primary border-primary',
|
|
39
|
+
false: 'border-input',
|
|
40
|
+
},
|
|
41
|
+
disabled: {
|
|
42
|
+
true: 'opacity-50',
|
|
43
|
+
false: '',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
size: 'md',
|
|
48
|
+
checked: false,
|
|
49
|
+
disabled: false,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
interface CheckboxProps extends VariantProps<typeof checkboxVariants> {
|
|
55
|
+
checked?: boolean;
|
|
56
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
57
|
+
children?: React.ReactNode;
|
|
58
|
+
className?: string;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
indeterminate?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface CheckboxLabelProps {
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
className?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface CheckboxDescriptionProps {
|
|
69
|
+
children: React.ReactNode;
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function Checkbox({
|
|
74
|
+
checked = false,
|
|
75
|
+
onCheckedChange,
|
|
76
|
+
children,
|
|
77
|
+
className,
|
|
78
|
+
size = 'md',
|
|
79
|
+
disabled = false,
|
|
80
|
+
indeterminate = false,
|
|
81
|
+
}: CheckboxProps) {
|
|
82
|
+
const { colors } = useThemeColors();
|
|
83
|
+
|
|
84
|
+
const iconSize = size === 'sm' ? 12 : size === 'lg' ? 18 : 14;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Pressable
|
|
88
|
+
onPress={() => !disabled && onCheckedChange?.(!checked)}
|
|
89
|
+
className={cn(checkboxVariants({ size }), className)}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
>
|
|
92
|
+
{/* Checkbox Square */}
|
|
93
|
+
<View
|
|
94
|
+
className={cn(
|
|
95
|
+
checkboxBoxVariants({
|
|
96
|
+
size,
|
|
97
|
+
checked: checked || indeterminate,
|
|
98
|
+
disabled
|
|
99
|
+
})
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{indeterminate ? (
|
|
103
|
+
<Minus size={iconSize} color={colors.primaryForeground} strokeWidth={3} />
|
|
104
|
+
) : checked ? (
|
|
105
|
+
<Check size={iconSize} color={colors.primaryForeground} strokeWidth={3} />
|
|
106
|
+
) : null}
|
|
107
|
+
</View>
|
|
108
|
+
|
|
109
|
+
{/* Label */}
|
|
110
|
+
{children}
|
|
111
|
+
</Pressable>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function CheckboxLabel({ children, className }: CheckboxLabelProps) {
|
|
116
|
+
return (
|
|
117
|
+
<Text variant="body" size="sm" className={className}>
|
|
118
|
+
{children}
|
|
119
|
+
</Text>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function CheckboxDescription({ children, className }: CheckboxDescriptionProps) {
|
|
124
|
+
return (
|
|
125
|
+
<Text variant="muted" size="sm" className={cn('mt-0.5', className)}>
|
|
126
|
+
{children}
|
|
127
|
+
</Text>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// components/ui/date-picker.tsx
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Pressable } from 'react-native';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { Text } from './text';
|
|
7
|
+
import { Dialog, DialogContent } from './dialog';
|
|
8
|
+
import { Calendar } from './calendar';
|
|
9
|
+
import { CalendarDays } from 'lucide-react-native';
|
|
10
|
+
import { useThemeColors } from '@/hooks/useThemeColors';
|
|
11
|
+
import dayjs from 'dayjs';
|
|
12
|
+
|
|
13
|
+
type DatePickerVariant = 'date' | 'month' | 'year';
|
|
14
|
+
|
|
15
|
+
// Date Picker Trigger Variants
|
|
16
|
+
const datePickerTriggerVariants = cva(
|
|
17
|
+
'flex-row items-center justify-between rounded-lg border',
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: 'border-input bg-background',
|
|
22
|
+
outline: 'border-input bg-transparent',
|
|
23
|
+
filled: 'border-transparent bg-muted',
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
sm: 'px-3 py-2',
|
|
27
|
+
md: 'px-4 py-3',
|
|
28
|
+
lg: 'px-5 py-4',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: 'default',
|
|
33
|
+
size: 'md',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
type TriggerVariant = 'default' | 'outline' | 'filled';
|
|
39
|
+
type TriggerSize = 'sm' | 'md' | 'lg';
|
|
40
|
+
|
|
41
|
+
interface DatePickerProps {
|
|
42
|
+
value?: Date;
|
|
43
|
+
onValueChange?: (date: Date) => void;
|
|
44
|
+
children: React.ReactNode;
|
|
45
|
+
variant?: DatePickerVariant;
|
|
46
|
+
minDate?: Date;
|
|
47
|
+
maxDate?: Date;
|
|
48
|
+
triggerVariant?: TriggerVariant;
|
|
49
|
+
triggerSize?: TriggerSize;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface DatePickerTriggerProps {
|
|
53
|
+
className?: string;
|
|
54
|
+
children: React.ReactNode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface DatePickerContentProps {
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DatePickerContext = React.createContext<{
|
|
62
|
+
value?: Date;
|
|
63
|
+
onValueChange?: (date: Date) => void;
|
|
64
|
+
open: boolean;
|
|
65
|
+
setOpen: (open: boolean) => void;
|
|
66
|
+
variant: DatePickerVariant;
|
|
67
|
+
minDate?: Date;
|
|
68
|
+
maxDate?: Date;
|
|
69
|
+
triggerVariant: TriggerVariant;
|
|
70
|
+
triggerSize: TriggerSize;
|
|
71
|
+
} | null>(null);
|
|
72
|
+
|
|
73
|
+
function useDatePicker() {
|
|
74
|
+
const context = React.useContext(DatePickerContext);
|
|
75
|
+
if (!context) {
|
|
76
|
+
throw new Error('DatePicker components must be used within DatePicker');
|
|
77
|
+
}
|
|
78
|
+
return context;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function DatePicker({
|
|
82
|
+
value,
|
|
83
|
+
onValueChange,
|
|
84
|
+
children,
|
|
85
|
+
variant = 'date',
|
|
86
|
+
minDate,
|
|
87
|
+
maxDate,
|
|
88
|
+
triggerVariant = 'default',
|
|
89
|
+
triggerSize = 'md',
|
|
90
|
+
}: DatePickerProps) {
|
|
91
|
+
const [open, setOpen] = React.useState(false);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<DatePickerContext.Provider
|
|
95
|
+
value={{
|
|
96
|
+
value,
|
|
97
|
+
onValueChange,
|
|
98
|
+
open,
|
|
99
|
+
setOpen,
|
|
100
|
+
variant,
|
|
101
|
+
minDate,
|
|
102
|
+
maxDate,
|
|
103
|
+
triggerVariant,
|
|
104
|
+
triggerSize,
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
108
|
+
{children}
|
|
109
|
+
</Dialog>
|
|
110
|
+
</DatePickerContext.Provider>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function DatePickerTrigger({ className, children }: DatePickerTriggerProps) {
|
|
115
|
+
const { setOpen, triggerVariant, triggerSize } = useDatePicker();
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Pressable
|
|
119
|
+
onPress={() => setOpen(true)}
|
|
120
|
+
className={cn(
|
|
121
|
+
datePickerTriggerVariants({
|
|
122
|
+
variant: triggerVariant,
|
|
123
|
+
size: triggerSize
|
|
124
|
+
}),
|
|
125
|
+
className
|
|
126
|
+
)}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
</Pressable>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function DatePickerContent({ className }: DatePickerContentProps) {
|
|
134
|
+
const { value, onValueChange, setOpen, variant, minDate, maxDate } = useDatePicker();
|
|
135
|
+
|
|
136
|
+
const handleValueChange = (date: Date) => {
|
|
137
|
+
onValueChange?.(date);
|
|
138
|
+
setOpen(false);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<DialogContent className={cn('p-0', className)}>
|
|
143
|
+
<Calendar
|
|
144
|
+
value={value}
|
|
145
|
+
onValueChange={handleValueChange}
|
|
146
|
+
variant={variant}
|
|
147
|
+
minDate={minDate}
|
|
148
|
+
maxDate={maxDate}
|
|
149
|
+
/>
|
|
150
|
+
</DialogContent>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function DatePickerValue({
|
|
155
|
+
placeholder = 'Select date',
|
|
156
|
+
format = 'MMM DD, YYYY',
|
|
157
|
+
className,
|
|
158
|
+
showIcon = true,
|
|
159
|
+
}: {
|
|
160
|
+
placeholder?: string;
|
|
161
|
+
format?: string;
|
|
162
|
+
className?: string;
|
|
163
|
+
showIcon?: boolean;
|
|
164
|
+
}) {
|
|
165
|
+
const { value } = useDatePicker();
|
|
166
|
+
const { colors } = useThemeColors();
|
|
167
|
+
|
|
168
|
+
const hasValue = !!value;
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<Text
|
|
173
|
+
size="sm"
|
|
174
|
+
className={cn(
|
|
175
|
+
'flex-1',
|
|
176
|
+
hasValue ? 'text-foreground' : 'text-muted-foreground',
|
|
177
|
+
className
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
{value ? dayjs(value).format(format) : placeholder}
|
|
181
|
+
</Text>
|
|
182
|
+
{showIcon && (
|
|
183
|
+
<CalendarDays
|
|
184
|
+
size={18}
|
|
185
|
+
color={hasValue ? colors.foreground : colors.mutedForeground}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// components/ui/date-range-picker.tsx
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { View, Pressable } from 'react-native';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { Text } from './text';
|
|
7
|
+
import { Button } from './button';
|
|
8
|
+
import { Dialog, DialogContent } from './dialog';
|
|
9
|
+
import { Calendar } from './calendar';
|
|
10
|
+
import { CalendarDays } from 'lucide-react-native';
|
|
11
|
+
import { useThemeColors } from '@/hooks/useThemeColors';
|
|
12
|
+
import dayjs from 'dayjs';
|
|
13
|
+
|
|
14
|
+
// Date Range Picker Trigger Variants
|
|
15
|
+
const dateRangePickerTriggerVariants = cva(
|
|
16
|
+
'flex-row items-center justify-between rounded-lg border',
|
|
17
|
+
{
|
|
18
|
+
variants: {
|
|
19
|
+
variant: {
|
|
20
|
+
default: 'border-input bg-background',
|
|
21
|
+
outline: 'border-input bg-transparent',
|
|
22
|
+
filled: 'border-transparent bg-muted',
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
sm: 'px-3 py-2',
|
|
26
|
+
md: 'px-4 py-3',
|
|
27
|
+
lg: 'px-5 py-4',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: 'default',
|
|
32
|
+
size: 'md',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
interface DateRangePickerProps extends VariantProps<typeof dateRangePickerTriggerVariants> {
|
|
38
|
+
startDate?: Date;
|
|
39
|
+
endDate?: Date;
|
|
40
|
+
onRangeChange?: (startDate: Date | undefined, endDate: Date | undefined) => void;
|
|
41
|
+
children: React.ReactNode;
|
|
42
|
+
minDate?: Date;
|
|
43
|
+
maxDate?: Date;
|
|
44
|
+
maxDays?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface DateRangePickerTriggerProps {
|
|
48
|
+
className?: string;
|
|
49
|
+
children: React.ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface DateRangePickerContentProps {
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DateRangePickerContext = React.createContext<{
|
|
57
|
+
startDate?: Date;
|
|
58
|
+
endDate?: Date;
|
|
59
|
+
onRangeChange?: (startDate: Date | undefined, endDate: Date | undefined) => void;
|
|
60
|
+
open: boolean;
|
|
61
|
+
setOpen: (open: boolean) => void;
|
|
62
|
+
minDate?: Date;
|
|
63
|
+
maxDate?: Date;
|
|
64
|
+
maxDays?: number;
|
|
65
|
+
variant: 'default' | 'outline' | 'filled';
|
|
66
|
+
size: 'sm' | 'md' | 'lg';
|
|
67
|
+
} | null>(null);
|
|
68
|
+
|
|
69
|
+
function useDateRangePicker() {
|
|
70
|
+
const context = React.useContext(DateRangePickerContext);
|
|
71
|
+
if (!context) {
|
|
72
|
+
throw new Error('DateRangePicker components must be used within DateRangePicker');
|
|
73
|
+
}
|
|
74
|
+
return context;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function DateRangePicker({
|
|
78
|
+
startDate,
|
|
79
|
+
endDate,
|
|
80
|
+
onRangeChange,
|
|
81
|
+
children,
|
|
82
|
+
minDate,
|
|
83
|
+
maxDate,
|
|
84
|
+
maxDays,
|
|
85
|
+
variant = 'default',
|
|
86
|
+
size = 'md',
|
|
87
|
+
}: DateRangePickerProps) {
|
|
88
|
+
const [open, setOpen] = React.useState(false);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<DateRangePickerContext.Provider
|
|
92
|
+
value={{
|
|
93
|
+
startDate,
|
|
94
|
+
endDate,
|
|
95
|
+
onRangeChange,
|
|
96
|
+
open,
|
|
97
|
+
setOpen,
|
|
98
|
+
minDate,
|
|
99
|
+
maxDate,
|
|
100
|
+
maxDays,
|
|
101
|
+
variant: variant ?? 'default',
|
|
102
|
+
size: size ?? 'md',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
106
|
+
{children}
|
|
107
|
+
</Dialog>
|
|
108
|
+
</DateRangePickerContext.Provider>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function DateRangePickerTrigger({ className, children }: DateRangePickerTriggerProps) {
|
|
113
|
+
const { setOpen, variant, size } = useDateRangePicker();
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Pressable
|
|
117
|
+
onPress={() => setOpen(true)}
|
|
118
|
+
className={cn(
|
|
119
|
+
dateRangePickerTriggerVariants({ variant, size }),
|
|
120
|
+
className
|
|
121
|
+
)}
|
|
122
|
+
>
|
|
123
|
+
{children}
|
|
124
|
+
</Pressable>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function DateRangePickerContent({ className }: DateRangePickerContentProps) {
|
|
129
|
+
const { startDate, endDate, onRangeChange, setOpen, minDate, maxDate, maxDays } =
|
|
130
|
+
useDateRangePicker();
|
|
131
|
+
|
|
132
|
+
const [tempStartDate, setTempStartDate] = React.useState<Date | undefined>(startDate);
|
|
133
|
+
const [tempEndDate, setTempEndDate] = React.useState<Date | undefined>(endDate);
|
|
134
|
+
|
|
135
|
+
const handleRangeChange = (start: Date | undefined, end: Date | undefined) => {
|
|
136
|
+
setTempStartDate(start);
|
|
137
|
+
setTempEndDate(end);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleApply = () => {
|
|
141
|
+
if (tempStartDate && tempEndDate) {
|
|
142
|
+
onRangeChange?.(tempStartDate, tempEndDate);
|
|
143
|
+
setOpen(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handleClear = () => {
|
|
148
|
+
setTempStartDate(undefined);
|
|
149
|
+
setTempEndDate(undefined);
|
|
150
|
+
onRangeChange?.(undefined, undefined);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleCancel = () => {
|
|
154
|
+
setTempStartDate(startDate);
|
|
155
|
+
setTempEndDate(endDate);
|
|
156
|
+
setOpen(false);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<DialogContent className={cn('p-0', className)}>
|
|
161
|
+
<View>
|
|
162
|
+
{/* Calendar with range mode */}
|
|
163
|
+
<Calendar
|
|
164
|
+
mode="range"
|
|
165
|
+
startDate={tempStartDate}
|
|
166
|
+
endDate={tempEndDate}
|
|
167
|
+
onRangeChange={handleRangeChange}
|
|
168
|
+
minDate={minDate}
|
|
169
|
+
maxDate={maxDate}
|
|
170
|
+
maxDays={maxDays}
|
|
171
|
+
/>
|
|
172
|
+
|
|
173
|
+
{/* Selected Range Info */}
|
|
174
|
+
{tempStartDate && (
|
|
175
|
+
<View className="mt-2 mx-4 p-3 bg-accent rounded-lg">
|
|
176
|
+
<Text variant="body" size="sm" className="font-medium">
|
|
177
|
+
{tempEndDate
|
|
178
|
+
? `${dayjs(tempStartDate).format('MMM DD, YYYY')} - ${dayjs(tempEndDate).format('MMM DD, YYYY')}`
|
|
179
|
+
: `Start: ${dayjs(tempStartDate).format('MMM DD, YYYY')} (Select end date)`}
|
|
180
|
+
</Text>
|
|
181
|
+
{tempStartDate && tempEndDate && (
|
|
182
|
+
<Text variant="muted" size="sm" className="mt-1">
|
|
183
|
+
{dayjs(tempEndDate).diff(dayjs(tempStartDate), 'day') + 1} days
|
|
184
|
+
</Text>
|
|
185
|
+
)}
|
|
186
|
+
</View>
|
|
187
|
+
)}
|
|
188
|
+
|
|
189
|
+
{/* Action Buttons */}
|
|
190
|
+
<View className="flex-row gap-2 m-4">
|
|
191
|
+
<Button
|
|
192
|
+
variant="outline"
|
|
193
|
+
size="sm"
|
|
194
|
+
onPress={handleClear}
|
|
195
|
+
className="flex-1"
|
|
196
|
+
>
|
|
197
|
+
Clear
|
|
198
|
+
</Button>
|
|
199
|
+
|
|
200
|
+
<Button
|
|
201
|
+
variant="ghost"
|
|
202
|
+
size="sm"
|
|
203
|
+
onPress={handleCancel}
|
|
204
|
+
className="flex-1"
|
|
205
|
+
>
|
|
206
|
+
Cancel
|
|
207
|
+
</Button>
|
|
208
|
+
|
|
209
|
+
<Button
|
|
210
|
+
variant="default"
|
|
211
|
+
size="sm"
|
|
212
|
+
onPress={handleApply}
|
|
213
|
+
disabled={!tempStartDate || !tempEndDate}
|
|
214
|
+
className="flex-1"
|
|
215
|
+
>
|
|
216
|
+
Apply
|
|
217
|
+
</Button>
|
|
218
|
+
</View>
|
|
219
|
+
</View>
|
|
220
|
+
</DialogContent>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function DateRangePickerValue({
|
|
225
|
+
placeholder = 'Select date range',
|
|
226
|
+
format = 'MMM DD, YYYY',
|
|
227
|
+
className,
|
|
228
|
+
showIcon = true,
|
|
229
|
+
}: {
|
|
230
|
+
placeholder?: string;
|
|
231
|
+
format?: string;
|
|
232
|
+
className?: string;
|
|
233
|
+
showIcon?: boolean;
|
|
234
|
+
}) {
|
|
235
|
+
const { startDate, endDate } = useDateRangePicker();
|
|
236
|
+
const { colors } = useThemeColors();
|
|
237
|
+
|
|
238
|
+
const hasValue = startDate && endDate;
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<>
|
|
242
|
+
<Text
|
|
243
|
+
size="sm"
|
|
244
|
+
className={cn(
|
|
245
|
+
'flex-1',
|
|
246
|
+
hasValue ? 'text-foreground' : 'text-muted-foreground',
|
|
247
|
+
className
|
|
248
|
+
)}
|
|
249
|
+
>
|
|
250
|
+
{hasValue
|
|
251
|
+
? `${dayjs(startDate).format(format)} - ${dayjs(endDate).format(format)}`
|
|
252
|
+
: placeholder}
|
|
253
|
+
</Text>
|
|
254
|
+
{showIcon && (
|
|
255
|
+
<CalendarDays
|
|
256
|
+
size={18}
|
|
257
|
+
color={hasValue ? colors.foreground : colors.mutedForeground}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
</>
|
|
261
|
+
);
|
|
262
|
+
}
|