@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.
Files changed (59) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +14 -0
  3. package/package.json +31 -0
  4. package/src/components/ui/accordion.tsx +334 -0
  5. package/src/components/ui/avatar.tsx +326 -0
  6. package/src/components/ui/badge.tsx +84 -0
  7. package/src/components/ui/banner.tsx +151 -0
  8. package/src/components/ui/bottom-sheet.tsx +579 -0
  9. package/src/components/ui/button.tsx +142 -0
  10. package/src/components/ui/calendar.tsx +502 -0
  11. package/src/components/ui/card.tsx +163 -0
  12. package/src/components/ui/checkbox.tsx +129 -0
  13. package/src/components/ui/date-picker.tsx +190 -0
  14. package/src/components/ui/date-range-picker.tsx +262 -0
  15. package/src/components/ui/dialog.tsx +204 -0
  16. package/src/components/ui/form.tsx +139 -0
  17. package/src/components/ui/input.tsx +107 -0
  18. package/src/components/ui/radio-group.tsx +123 -0
  19. package/src/components/ui/radio.tsx +109 -0
  20. package/src/components/ui/select-sheet.tsx +814 -0
  21. package/src/components/ui/select.tsx +547 -0
  22. package/src/components/ui/tabs.tsx +254 -0
  23. package/src/components/ui/text.tsx +229 -0
  24. package/src/components/ui/textarea.tsx +77 -0
  25. package/src/components/v0/accordion.tsx +199 -0
  26. package/src/components/v1/accordion.tsx +234 -0
  27. package/src/components/v1/avatar.tsx +259 -0
  28. package/src/components/v1/bottom-sheet.tsx +1090 -0
  29. package/src/components/v1/button.tsx +61 -0
  30. package/src/components/v1/calendar.tsx +498 -0
  31. package/src/components/v1/card.tsx +86 -0
  32. package/src/components/v1/checkbox.tsx +46 -0
  33. package/src/components/v1/date-picker.tsx +135 -0
  34. package/src/components/v1/date-range-picker.tsx +218 -0
  35. package/src/components/v1/dialog.tsx +211 -0
  36. package/src/components/v1/radio-group.tsx +76 -0
  37. package/src/components/v1/select.tsx +217 -0
  38. package/src/components/v1/tabs.tsx +253 -0
  39. package/src/registry/ui/accordion.json +30 -0
  40. package/src/registry/ui/avatar.json +41 -0
  41. package/src/registry/ui/badge.json +26 -0
  42. package/src/registry/ui/banner.json +27 -0
  43. package/src/registry/ui/bottom-sheet.json +29 -0
  44. package/src/registry/ui/button.json +24 -0
  45. package/src/registry/ui/calendar.json +29 -0
  46. package/src/registry/ui/card.json +25 -0
  47. package/src/registry/ui/checkbox.json +25 -0
  48. package/src/registry/ui/date-picker.json +30 -0
  49. package/src/registry/ui/date-range-picker.json +33 -0
  50. package/src/registry/ui/dialog.json +25 -0
  51. package/src/registry/ui/form.json +27 -0
  52. package/src/registry/ui/input.json +22 -0
  53. package/src/registry/ui/radio-group.json +26 -0
  54. package/src/registry/ui/radio.json +23 -0
  55. package/src/registry/ui/select-sheet.json +29 -0
  56. package/src/registry/ui/select.json +26 -0
  57. package/src/registry/ui/tabs.json +29 -0
  58. package/src/registry/ui/text.json +22 -0
  59. 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
+ }