@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,547 @@
1
+ // components/ui/select.tsx
2
+ import * as React from 'react';
3
+ import { Pressable, ScrollView, View } from 'react-native';
4
+ import { cn } from '@/lib/utils';
5
+ import { Text } from './text';
6
+ import { Dialog, DialogContent } from './dialog';
7
+ import { useThemeColors } from '@/hooks/useThemeColors';
8
+ import { ChevronDown, Check } from 'lucide-react-native';
9
+
10
+ interface SelectOption {
11
+ label: string;
12
+ value: string;
13
+ }
14
+
15
+ interface SelectProps {
16
+ value?: string;
17
+ onValueChange?: (value: string) => void;
18
+ children: React.ReactNode;
19
+ disabled?: boolean;
20
+ }
21
+
22
+ interface SelectTriggerProps {
23
+ className?: string;
24
+ containerClassName?: string;
25
+ children?: React.ReactNode;
26
+ size?: 'sm' | 'md' | 'lg';
27
+ variant?: 'outline' | 'underline';
28
+ prefix?: React.ReactNode;
29
+ suffix?: React.ReactNode;
30
+ error?: boolean;
31
+ }
32
+
33
+ interface SelectValueProps {
34
+ placeholder?: string;
35
+ className?: string;
36
+ }
37
+
38
+ interface SelectContentProps {
39
+ className?: string;
40
+ children: React.ReactNode;
41
+ }
42
+
43
+ interface SelectItemProps {
44
+ value: string;
45
+ label: string;
46
+ className?: string;
47
+ disabled?: boolean;
48
+ }
49
+
50
+ interface SelectGroupProps {
51
+ className?: string;
52
+ children: React.ReactNode;
53
+ }
54
+
55
+ interface SelectLabelProps {
56
+ className?: string;
57
+ children: React.ReactNode;
58
+ }
59
+
60
+ interface SelectSeparatorProps {
61
+ className?: string;
62
+ }
63
+
64
+ const SelectContext = React.createContext<{
65
+ value?: string;
66
+ onValueChange?: (value: string) => void;
67
+ open: boolean;
68
+ setOpen: (open: boolean) => void;
69
+ disabled?: boolean;
70
+ options: Map<string, string>;
71
+ registerOption: (value: string, label: string) => void;
72
+ } | null>(null);
73
+
74
+ function useSelect() {
75
+ const context = React.useContext(SelectContext);
76
+ if (!context) {
77
+ throw new Error('Select components must be used within Select');
78
+ }
79
+ return context;
80
+ }
81
+
82
+ export function Select({ value, onValueChange, children, disabled = false }: SelectProps) {
83
+ const [open, setOpen] = React.useState(false);
84
+ const [options] = React.useState(() => new Map<string, string>());
85
+
86
+ const registerOption = React.useCallback((val: string, label: string) => {
87
+ options.set(val, label);
88
+ }, [options]);
89
+
90
+ return (
91
+ <SelectContext.Provider
92
+ value={{
93
+ value,
94
+ onValueChange,
95
+ open,
96
+ setOpen: (val) => !disabled && setOpen(val),
97
+ disabled,
98
+ options,
99
+ registerOption,
100
+ }}
101
+ >
102
+ <Dialog open={open} onOpenChange={setOpen}>
103
+ {children}
104
+ </Dialog>
105
+ </SelectContext.Provider>
106
+ );
107
+ }
108
+
109
+ export function SelectTrigger({
110
+ className,
111
+ containerClassName,
112
+ children,
113
+ size = 'md',
114
+ variant = 'outline',
115
+ prefix,
116
+ suffix,
117
+ error = false,
118
+ }: SelectTriggerProps) {
119
+ const { setOpen, disabled, value, options } = useSelect();
120
+ const { colors } = useThemeColors();
121
+ const isOutline = variant === 'outline';
122
+
123
+ const sizeStyles = {
124
+ sm: 'web:h-9 h-10',
125
+ md: 'web:h-10 h-11',
126
+ lg: 'web:h-12 h-13',
127
+ };
128
+
129
+ const displayValue = value ? options.get(value) : null;
130
+
131
+ return (
132
+ <View className={cn('w-full', containerClassName)}>
133
+ <Pressable
134
+ onPress={() => setOpen(true)}
135
+ disabled={disabled}
136
+ className={cn(
137
+ 'flex-row items-center bg-background',
138
+ isOutline && `${sizeStyles[size]} border rounded-lg px-3`,
139
+ !isOutline && `${sizeStyles[size]} border-b`,
140
+ error ? 'border-destructive' : 'border-input',
141
+ 'focus:border-ring',
142
+ disabled && 'opacity-50',
143
+ className
144
+ )}
145
+ >
146
+ {prefix && <View className="mr-3">{prefix}</View>}
147
+
148
+ <View className="flex-1 flex-row items-center justify-between gap-2">
149
+ <Text
150
+ size="md"
151
+ className={cn(
152
+ 'flex-1',
153
+ displayValue ? 'text-foreground' : 'text-muted-foreground'
154
+ )}
155
+ >
156
+ {displayValue || children}
157
+ </Text>
158
+
159
+ <ChevronDown size={16} color={colors.mutedForeground} />
160
+ </View>
161
+
162
+ {suffix && <View className="ml-3">{suffix}</View>}
163
+ </Pressable>
164
+
165
+ {error && (
166
+ <Text size="sm" className="text-destructive mt-2">
167
+ {error}
168
+ </Text>
169
+ )}
170
+ </View>
171
+ );
172
+ }
173
+
174
+ export function SelectValue({ placeholder = 'Select...', className }: SelectValueProps) {
175
+ const { value, options } = useSelect();
176
+ const displayValue = value ? options.get(value) : null;
177
+
178
+ return (
179
+ <Text
180
+ size="sm"
181
+ className={cn(
182
+ 'flex-1',
183
+ displayValue ? 'text-foreground' : 'text-muted-foreground',
184
+ className
185
+ )}
186
+ >
187
+ {displayValue || placeholder}
188
+ </Text>
189
+ );
190
+ }
191
+
192
+ export function SelectContent({ className, children }: SelectContentProps) {
193
+ return (
194
+ <DialogContent className={cn('p-0 max-h-96', className)}>
195
+ <ScrollView
196
+ showsVerticalScrollIndicator={false}
197
+ bounces={false}
198
+ className="max-h-96"
199
+ >
200
+ <View className="py-1">{children}</View>
201
+ </ScrollView>
202
+ </DialogContent>
203
+ );
204
+ }
205
+
206
+ export function SelectItem({ value, label, className, disabled = false }: SelectItemProps) {
207
+ const { value: selectedValue, onValueChange, setOpen, registerOption } = useSelect();
208
+ const { colors } = useThemeColors();
209
+ const isSelected = selectedValue === value;
210
+
211
+ React.useEffect(() => {
212
+ registerOption(value, label);
213
+ }, [value, label, registerOption]);
214
+
215
+ const handleSelect = () => {
216
+ if (disabled) return;
217
+ onValueChange?.(value);
218
+ setOpen(false);
219
+ };
220
+
221
+ return (
222
+ <Pressable
223
+ onPress={handleSelect}
224
+ disabled={disabled}
225
+ className={cn(
226
+ 'flex-row items-center justify-between px-4 py-3 border-b border-border active:bg-accent',
227
+ isSelected && 'bg-accent',
228
+ disabled && 'opacity-50',
229
+ className
230
+ )}
231
+ >
232
+ <Text
233
+ size="sm"
234
+ variant={'body'}
235
+ className={cn(
236
+ 'flex-1',
237
+ isSelected ? 'text-accent-foreground' : 'text-foreground'
238
+ )}
239
+ >
240
+ {label}
241
+ </Text>
242
+
243
+ {isSelected && (
244
+ <Check size={16} color={colors.primary} className="ml-2" />
245
+ )}
246
+ </Pressable>
247
+ );
248
+ }
249
+
250
+ export function SelectGroup({ className, children }: SelectGroupProps) {
251
+ return <View className={cn('py-1', className)}>{children}</View>;
252
+ }
253
+
254
+ export function SelectLabel({ className, children }: SelectLabelProps) {
255
+ return (
256
+ <View className={cn('px-4 py-2', className)}>
257
+ <Text size="sm" variant="body" className="uppercase tracking-wide text-muted-foreground">
258
+ {children}
259
+ </Text>
260
+ </View>
261
+ );
262
+ }
263
+
264
+ export function SelectSeparator({ className }: SelectSeparatorProps) {
265
+ return <View className={cn('h-px bg-border my-1 mx-2', className)} />;
266
+ }
267
+
268
+ export type { SelectOption };
269
+
270
+ // // components/ui/select.tsx
271
+ // import * as React from 'react';
272
+ // import { Pressable, ScrollView, View } from 'react-native';
273
+ // import { cn } from '@/lib/utils';
274
+ // import { Text } from './text';
275
+ // import { Dialog, DialogContent } from './dialog';
276
+ // import { useThemeColors } from '@/hooks/useThemeColors';
277
+ // import { ChevronDown, Check } from 'lucide-react-native';
278
+
279
+ // interface SelectOption {
280
+ // label: string;
281
+ // value: string;
282
+ // }
283
+
284
+ // interface SelectProps {
285
+ // value?: string;
286
+ // onValueChange?: (value: string) => void;
287
+ // children: React.ReactNode;
288
+ // disabled?: boolean;
289
+ // }
290
+
291
+ // interface SelectTriggerProps {
292
+ // className?: string;
293
+ // containerClassName?: string;
294
+ // children?: React.ReactNode;
295
+ // size?: 'sm' | 'md' | 'lg';
296
+ // variant?: 'outline' | 'underline';
297
+ // prefix?: React.ReactNode;
298
+ // suffix?: React.ReactNode;
299
+ // error?: boolean;
300
+ // }
301
+
302
+ // interface SelectValueProps {
303
+ // placeholder?: string;
304
+ // className?: string;
305
+ // }
306
+
307
+ // interface SelectContentProps {
308
+ // className?: string;
309
+ // children: React.ReactNode;
310
+ // }
311
+
312
+ // interface SelectItemProps {
313
+ // value: string;
314
+ // label: string;
315
+ // className?: string;
316
+ // disabled?: boolean;
317
+ // }
318
+
319
+ // interface SelectGroupProps {
320
+ // className?: string;
321
+ // children: React.ReactNode;
322
+ // }
323
+
324
+ // interface SelectLabelProps {
325
+ // className?: string;
326
+ // children: React.ReactNode;
327
+ // }
328
+
329
+ // interface SelectSeparatorProps {
330
+ // className?: string;
331
+ // }
332
+
333
+ // const SelectContext = React.createContext<{
334
+ // value?: string;
335
+ // onValueChange?: (value: string) => void;
336
+ // open: boolean;
337
+ // setOpen: (open: boolean) => void;
338
+ // disabled?: boolean;
339
+ // options: Map<string, string>;
340
+ // registerOption: (value: string, label: string) => void;
341
+ // } | null>(null);
342
+
343
+ // function useSelect() {
344
+ // const context = React.useContext(SelectContext);
345
+ // if (!context) {
346
+ // throw new Error('Select components must be used within Select');
347
+ // }
348
+ // return context;
349
+ // }
350
+
351
+ // export function Select({ value, onValueChange, children, disabled = false }: SelectProps) {
352
+ // const [open, setOpen] = React.useState(false);
353
+ // const [options] = React.useState(() => new Map<string, string>());
354
+
355
+ // const registerOption = React.useCallback((val: string, label: string) => {
356
+ // options.set(val, label);
357
+ // }, [options]);
358
+
359
+ // return (
360
+ // <SelectContext.Provider
361
+ // value={{
362
+ // value,
363
+ // onValueChange,
364
+ // open,
365
+ // setOpen: (val) => !disabled && setOpen(val),
366
+ // disabled,
367
+ // options,
368
+ // registerOption,
369
+ // }}
370
+ // >
371
+ // <Dialog open={open} onOpenChange={setOpen}>
372
+ // {children}
373
+ // </Dialog>
374
+ // </SelectContext.Provider>
375
+ // );
376
+ // }
377
+
378
+ // export function SelectTrigger({
379
+ // className,
380
+ // containerClassName,
381
+ // children,
382
+ // size = 'md',
383
+ // variant = 'outline',
384
+ // prefix,
385
+ // suffix,
386
+ // error = false,
387
+ // }: SelectTriggerProps) {
388
+ // const { setOpen, disabled, value, options } = useSelect();
389
+ // const { colors } = useThemeColors();
390
+ // const [isFocused, setIsFocused] = React.useState(false);
391
+ // const isOutline = variant === 'outline';
392
+
393
+ // const sizeStyles = {
394
+ // sm: 'web:h-9 h-10',
395
+ // md: 'web:h-10 h-11',
396
+ // lg: 'web:h-12 h-13',
397
+ // };
398
+
399
+ // const displayValue = value ? options.get(value) : null;
400
+
401
+ // return (
402
+ // <View className={cn('w-full', containerClassName)}>
403
+ // <View
404
+ // className={cn(
405
+ // 'flex-row items-center bg-background',
406
+ // isOutline && `${sizeStyles[size]} border rounded-lg px-3`,
407
+ // !isOutline && `${sizeStyles[size]} border-b`,
408
+ // isOutline && !error && !isFocused && 'border-input',
409
+ // isOutline && !error && isFocused && 'border-ring',
410
+ // isOutline && error && 'border-destructive',
411
+ // !isOutline && !error && !isFocused && 'border-input',
412
+ // !isOutline && !error && isFocused && 'border-ring',
413
+ // !isOutline && error && 'border-destructive',
414
+ // disabled && 'opacity-50',
415
+ // className
416
+ // )}
417
+ // >
418
+ // {prefix && <View className="mr-3">{prefix}</View>}
419
+
420
+ // <Pressable
421
+ // onPress={() => {
422
+ // setIsFocused(true);
423
+ // setOpen(true);
424
+ // }}
425
+ // disabled={disabled}
426
+ // className="flex-1 flex-row items-center justify-between gap-2"
427
+ // >
428
+ // <Text
429
+ // size="md"
430
+ // className={cn(
431
+ // 'flex-1',
432
+ // displayValue ? 'text-foreground' : 'text-muted-foreground'
433
+ // )}
434
+ // >
435
+ // {displayValue || children}
436
+ // </Text>
437
+
438
+ // <ChevronDown size={16} color={colors.mutedForeground} />
439
+ // </Pressable>
440
+
441
+ // {suffix && <View className="ml-3">{suffix}</View>}
442
+ // </View>
443
+
444
+ // {error && (
445
+ // <Text size="sm" className="text-destructive mt-2">
446
+ // {error}
447
+ // </Text>
448
+ // )}
449
+ // </View>
450
+ // );
451
+ // }
452
+
453
+ // export function SelectValue({ placeholder = 'Select...', className }: SelectValueProps) {
454
+ // const { value, options } = useSelect();
455
+ // const displayValue = value ? options.get(value) : null;
456
+
457
+ // return (
458
+ // <Text
459
+ // size="sm"
460
+ // className={cn(
461
+ // 'flex-1',
462
+ // displayValue ? 'text-foreground' : 'text-muted-foreground',
463
+ // className
464
+ // )}
465
+ // >
466
+ // {displayValue || placeholder}
467
+ // </Text>
468
+ // );
469
+ // }
470
+
471
+ // export function SelectContent({ className, children }: SelectContentProps) {
472
+ // return (
473
+ // <DialogContent className={cn('p-0 max-h-96', className)}>
474
+ // <ScrollView
475
+ // showsVerticalScrollIndicator={false}
476
+ // bounces={false}
477
+ // className="max-h-96"
478
+ // >
479
+ // <View className="py-1">{children}</View>
480
+ // </ScrollView>
481
+ // </DialogContent>
482
+ // );
483
+ // }
484
+
485
+ // export function SelectItem({ value, label, className, disabled = false }: SelectItemProps) {
486
+ // const { value: selectedValue, onValueChange, setOpen, registerOption } = useSelect();
487
+ // const { colors } = useThemeColors();
488
+ // const isSelected = selectedValue === value;
489
+
490
+ // React.useEffect(() => {
491
+ // registerOption(value, label);
492
+ // }, [value, label, registerOption]);
493
+
494
+ // const handleSelect = () => {
495
+ // if (disabled) return;
496
+ // onValueChange?.(value);
497
+ // setOpen(false);
498
+ // };
499
+
500
+ // return (
501
+ // <Pressable
502
+ // onPress={handleSelect}
503
+ // disabled={disabled}
504
+ // className={cn(
505
+ // 'flex-row items-center justify-between px-4 py-3 border-b border-border active:bg-accent',
506
+ // isSelected && 'bg-accent',
507
+ // disabled && 'opacity-50',
508
+ // className
509
+ // )}
510
+ // >
511
+ // <Text
512
+ // size="sm"
513
+ // variant={'body'}
514
+ // className={cn(
515
+ // 'flex-1',
516
+ // isSelected ? 'text-accent-foreground' : 'text-foreground'
517
+ // )}
518
+ // >
519
+ // {label}
520
+ // </Text>
521
+
522
+ // {isSelected && (
523
+ // <Check size={16} color={colors.primary} className="ml-2" />
524
+ // )}
525
+ // </Pressable>
526
+ // );
527
+ // }
528
+
529
+ // export function SelectGroup({ className, children }: SelectGroupProps) {
530
+ // return <View className={cn('py-1', className)}>{children}</View>;
531
+ // }
532
+
533
+ // export function SelectLabel({ className, children }: SelectLabelProps) {
534
+ // return (
535
+ // <View className={cn('px-4 py-2', className)}>
536
+ // <Text size="sm" variant='title' className="uppercase tracking-wide text-muted-foreground">
537
+ // {children}
538
+ // </Text>
539
+ // </View>
540
+ // );
541
+ // }
542
+
543
+ // export function SelectSeparator({ className }: SelectSeparatorProps) {
544
+ // return <View className={cn('h-px bg-border my-1 mx-2', className)} />;
545
+ // }
546
+
547
+ // export type { SelectOption };