@spree/dashboard-ui 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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/package.json +86 -0
  4. package/src/hooks/use-copy-to-clipboard.ts +44 -0
  5. package/src/hooks/use-mobile.ts +19 -0
  6. package/src/hooks/use-scrolled.ts +30 -0
  7. package/src/index.ts +99 -0
  8. package/src/lib/date-locale.ts +29 -0
  9. package/src/lib/utils.ts +6 -0
  10. package/src/lib/validation-messages.ts +19 -0
  11. package/src/spree/address-block.tsx +74 -0
  12. package/src/spree/back-button.tsx +48 -0
  13. package/src/spree/bulk-dialog.tsx +71 -0
  14. package/src/spree/bulk-price-table.tsx +254 -0
  15. package/src/spree/calculator-summary.tsx +120 -0
  16. package/src/spree/color-picker.tsx +202 -0
  17. package/src/spree/confirm-dialog.tsx +82 -0
  18. package/src/spree/copy-to-clipboard-button.tsx +90 -0
  19. package/src/spree/country-flag.tsx +30 -0
  20. package/src/spree/data-grid/cells.tsx +415 -0
  21. package/src/spree/data-grid/context.ts +59 -0
  22. package/src/spree/data-grid/data-grid.tsx +214 -0
  23. package/src/spree/data-grid/fill-handle.tsx +153 -0
  24. package/src/spree/data-grid/helpers.ts +18 -0
  25. package/src/spree/data-grid/index.ts +5 -0
  26. package/src/spree/data-grid/types.ts +36 -0
  27. package/src/spree/data-grid/use-data-grid-keyboard.ts +254 -0
  28. package/src/spree/data-grid/use-stable-cell-registration.ts +45 -0
  29. package/src/spree/drag-handle.tsx +51 -0
  30. package/src/spree/form-actions.tsx +140 -0
  31. package/src/spree/json-preview-drawer.tsx +254 -0
  32. package/src/spree/json-value-view.tsx +149 -0
  33. package/src/spree/language-menu-items.tsx +54 -0
  34. package/src/spree/metadata/metadata-card.tsx +62 -0
  35. package/src/spree/relative-time.tsx +49 -0
  36. package/src/spree/resource-combobox.tsx +108 -0
  37. package/src/spree/resource-layout.tsx +31 -0
  38. package/src/spree/resource-multi-autocomplete.tsx +180 -0
  39. package/src/spree/resource-name-cell.tsx +44 -0
  40. package/src/spree/route-error-boundary.tsx +102 -0
  41. package/src/spree/row-actions.tsx +85 -0
  42. package/src/spree/row-click-bridge.tsx +17 -0
  43. package/src/spree/secret-input.tsx +137 -0
  44. package/src/spree/storefront-visible-switch.tsx +58 -0
  45. package/src/spree/tag-list.tsx +26 -0
  46. package/src/spree/theme-provider.tsx +83 -0
  47. package/src/spree/theme-toggle.tsx +35 -0
  48. package/src/styles.css +338 -0
  49. package/src/ui/attachment.tsx +187 -0
  50. package/src/ui/avatar.tsx +94 -0
  51. package/src/ui/badge.tsx +161 -0
  52. package/src/ui/breadcrumb.tsx +109 -0
  53. package/src/ui/button.tsx +60 -0
  54. package/src/ui/calendar.tsx +66 -0
  55. package/src/ui/card.tsx +72 -0
  56. package/src/ui/chart.tsx +94 -0
  57. package/src/ui/checkbox.tsx +50 -0
  58. package/src/ui/collapsible.tsx +18 -0
  59. package/src/ui/combobox.tsx +270 -0
  60. package/src/ui/command.tsx +172 -0
  61. package/src/ui/data-table.tsx +60 -0
  62. package/src/ui/date-picker.tsx +253 -0
  63. package/src/ui/date-range-picker.tsx +150 -0
  64. package/src/ui/dialog.tsx +214 -0
  65. package/src/ui/dropdown-menu.tsx +345 -0
  66. package/src/ui/empty.tsx +94 -0
  67. package/src/ui/field.tsx +221 -0
  68. package/src/ui/input-group.tsx +141 -0
  69. package/src/ui/input.tsx +19 -0
  70. package/src/ui/label.tsx +29 -0
  71. package/src/ui/pagination.tsx +123 -0
  72. package/src/ui/popover.tsx +76 -0
  73. package/src/ui/progress.tsx +73 -0
  74. package/src/ui/radio-group.tsx +42 -0
  75. package/src/ui/rich-text-editor.tsx +263 -0
  76. package/src/ui/select.tsx +213 -0
  77. package/src/ui/separator.tsx +31 -0
  78. package/src/ui/sheet.tsx +208 -0
  79. package/src/ui/sidebar.tsx +679 -0
  80. package/src/ui/skeleton.tsx +13 -0
  81. package/src/ui/slot.tsx +35 -0
  82. package/src/ui/sonner.tsx +47 -0
  83. package/src/ui/switch.tsx +39 -0
  84. package/src/ui/textarea.tsx +18 -0
  85. package/src/ui/tooltip.tsx +100 -0
@@ -0,0 +1,141 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import * as React from 'react'
3
+ import { cn } from '../lib/utils'
4
+ import { Button } from './button'
5
+ import { Input } from './input'
6
+ import { Textarea } from './textarea'
7
+
8
+ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
9
+ return (
10
+ <div
11
+ data-slot="input-group"
12
+ role="group"
13
+ className={cn(
14
+ 'group/input-group relative flex w-full min-w-0 items-center rounded-lg border border-border bg-card text-foreground shadow-xs transition-all duration-100 ease-in-out outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:shadow-none has-disabled:bg-muted has-disabled:border-border has-disabled:opacity-50 has-disabled:shadow-none has-[[data-slot=input-group-control]:focus-visible]:border-blue-500 has-[[data-slot=input-group-control]:focus-visible]:shadow-[0_0_0_3px_rgba(59,130,246,0.15)] has-[[data-slot][aria-invalid=true]]:border-destructive has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ const inputGroupAddonVariants = cva(
23
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
24
+ {
25
+ variants: {
26
+ align: {
27
+ 'inline-start': 'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',
28
+ 'inline-end': 'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',
29
+ 'block-start':
30
+ 'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',
31
+ 'block-end':
32
+ 'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ align: 'inline-start',
37
+ },
38
+ },
39
+ )
40
+
41
+ function InputGroupAddon({
42
+ className,
43
+ align = 'inline-start',
44
+ ...props
45
+ }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
46
+ return (
47
+ <div
48
+ role="group"
49
+ data-slot="input-group-addon"
50
+ data-align={align}
51
+ className={cn(inputGroupAddonVariants({ align }), className)}
52
+ onClick={(e) => {
53
+ if ((e.target as HTMLElement).closest('button')) {
54
+ return
55
+ }
56
+ e.currentTarget.parentElement?.querySelector('input')?.focus()
57
+ }}
58
+ {...props}
59
+ />
60
+ )
61
+ }
62
+
63
+ const inputGroupButtonVariants = cva('flex items-center gap-2 text-sm shadow-none', {
64
+ variants: {
65
+ size: {
66
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
67
+ sm: '',
68
+ 'icon-xs': 'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',
69
+ 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
70
+ },
71
+ },
72
+ defaultVariants: {
73
+ size: 'xs',
74
+ },
75
+ })
76
+
77
+ function InputGroupButton({
78
+ className,
79
+ type = 'button',
80
+ variant = 'ghost',
81
+ size = 'xs',
82
+ ...props
83
+ }: Omit<React.ComponentProps<typeof Button>, 'size'> &
84
+ VariantProps<typeof inputGroupButtonVariants>) {
85
+ return (
86
+ <Button
87
+ type={type}
88
+ data-size={size}
89
+ variant={variant}
90
+ className={cn(inputGroupButtonVariants({ size }), className)}
91
+ {...props}
92
+ />
93
+ )
94
+ }
95
+
96
+ function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
97
+ return (
98
+ <span
99
+ className={cn(
100
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
101
+ className,
102
+ )}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+
108
+ function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
109
+ return (
110
+ <Input
111
+ data-slot="input-group-control"
112
+ className={cn(
113
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus:border-0 focus:shadow-none focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0',
114
+ className,
115
+ )}
116
+ {...props}
117
+ />
118
+ )
119
+ }
120
+
121
+ function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
122
+ return (
123
+ <Textarea
124
+ data-slot="input-group-control"
125
+ className={cn(
126
+ 'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',
127
+ className,
128
+ )}
129
+ {...props}
130
+ />
131
+ )
132
+ }
133
+
134
+ export {
135
+ InputGroup,
136
+ InputGroupAddon,
137
+ InputGroupButton,
138
+ InputGroupInput,
139
+ InputGroupText,
140
+ InputGroupTextarea,
141
+ }
@@ -0,0 +1,19 @@
1
+ import type * as React from 'react'
2
+
3
+ import { cn } from '../lib/utils'
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ 'block w-full min-w-0 min-h-8 rounded-lg border border-border bg-card py-1.5 px-2.5 text-base font-normal leading-normal text-foreground shadow-xs transition-all duration-100 ease-in-out outline-none placeholder:text-muted-foreground focus:border-blue-500 focus:shadow-[0_0_0_3px_rgba(59,130,246,0.15)] disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-muted disabled:border-border disabled:text-muted-foreground disabled:shadow-none aria-invalid:border-destructive file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Input }
@@ -0,0 +1,29 @@
1
+ import { Field } from '@base-ui/react/field'
2
+ import * as React from 'react'
3
+
4
+ import { cn } from '../lib/utils'
5
+
6
+ /**
7
+ * Label component backed by Base UI's Field.Label.
8
+ *
9
+ * Wraps each label in a Field.Root so it can be used standalone
10
+ * without requiring consumers to manually add Field.Root parents.
11
+ */
12
+ function Label({ className, children, ...props }: React.ComponentProps<'label'>) {
13
+ return (
14
+ <Field.Root>
15
+ <Field.Label
16
+ data-slot="label"
17
+ className={cn(
18
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
19
+ className,
20
+ )}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </Field.Label>
25
+ </Field.Root>
26
+ )
27
+ }
28
+
29
+ export { Label }
@@ -0,0 +1,123 @@
1
+ import {
2
+ ChevronLeftIcon,
3
+ ChevronRightIcon,
4
+ ChevronsLeftIcon,
5
+ ChevronsRightIcon,
6
+ } from 'lucide-react'
7
+ import { useTranslation } from 'react-i18next'
8
+ import { Button } from './button'
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select'
10
+
11
+ interface PaginationMeta {
12
+ page: number
13
+ limit: number
14
+ count: number
15
+ pages: number
16
+ from: number
17
+ to: number
18
+ previous: number | null
19
+ next: number | null
20
+ }
21
+
22
+ interface PaginationProps {
23
+ meta: PaginationMeta
24
+ onPageChange: (page: number) => void
25
+ onPageSizeChange?: (size: number) => void
26
+ pageSizeOptions?: number[]
27
+ }
28
+
29
+ export function Pagination({
30
+ meta,
31
+ onPageChange,
32
+ onPageSizeChange,
33
+ pageSizeOptions = [10, 20, 25, 30, 50],
34
+ }: PaginationProps) {
35
+ const { t } = useTranslation()
36
+
37
+ if (meta.pages <= 1 && !onPageSizeChange) return null
38
+
39
+ return (
40
+ <div className="flex items-center justify-between border-t border-border px-4 py-3">
41
+ <div className="text-sm text-muted-foreground">
42
+ {t('admin.components.pagination.range', {
43
+ from: meta.from,
44
+ to: meta.to,
45
+ count: meta.count,
46
+ })}
47
+ </div>
48
+ <div className="flex items-center gap-6">
49
+ {onPageSizeChange && (
50
+ <div className="flex items-center gap-2">
51
+ <span className="text-sm text-muted-foreground">
52
+ {t('admin.components.pagination.rows_per_page')}
53
+ </span>
54
+ <Select
55
+ value={`${meta.limit}`}
56
+ onValueChange={(value) => onPageSizeChange(Number(value))}
57
+ >
58
+ <SelectTrigger className="h-8 w-[70px]">
59
+ <SelectValue />
60
+ </SelectTrigger>
61
+ <SelectContent side="top">
62
+ {pageSizeOptions.map((size) => (
63
+ <SelectItem key={size} value={`${size}`}>
64
+ {size}
65
+ </SelectItem>
66
+ ))}
67
+ </SelectContent>
68
+ </Select>
69
+ </div>
70
+ )}
71
+ {meta.pages > 1 && (
72
+ <>
73
+ <span className="text-sm text-muted-foreground">
74
+ {t('admin.common.page_of', { page: meta.page, total: meta.pages })}
75
+ </span>
76
+ <div className="flex items-center gap-1">
77
+ <Button
78
+ size="icon-sm"
79
+ variant="outline"
80
+ className="hidden lg:flex"
81
+ disabled={!meta.previous}
82
+ onClick={() => onPageChange(1)}
83
+ >
84
+ <span className="sr-only">{t('admin.components.pagination.first_page')}</span>
85
+ <ChevronsLeftIcon />
86
+ </Button>
87
+ <Button
88
+ size="icon-sm"
89
+ variant="outline"
90
+ disabled={!meta.previous}
91
+ onClick={() => meta.previous && onPageChange(meta.previous)}
92
+ >
93
+ <span className="sr-only">{t('admin.components.pagination.previous_page')}</span>
94
+ <ChevronLeftIcon />
95
+ </Button>
96
+ <Button
97
+ size="icon-sm"
98
+ variant="outline"
99
+ disabled={!meta.next}
100
+ onClick={() => meta.next && onPageChange(meta.next)}
101
+ >
102
+ <span className="sr-only">{t('admin.components.pagination.next_page')}</span>
103
+ <ChevronRightIcon />
104
+ </Button>
105
+ <Button
106
+ size="icon-sm"
107
+ className="hidden lg:flex"
108
+ variant="outline"
109
+ disabled={!meta.next}
110
+ onClick={() => onPageChange(meta.pages)}
111
+ >
112
+ <span className="sr-only">{t('admin.components.pagination.last_page')}</span>
113
+ <ChevronsRightIcon />
114
+ </Button>
115
+ </div>
116
+ </>
117
+ )}
118
+ </div>
119
+ </div>
120
+ )
121
+ }
122
+
123
+ export type { PaginationMeta }
@@ -0,0 +1,76 @@
1
+ import { Popover as PopoverPrimitive } from '@base-ui/react/popover'
2
+ import * as React from 'react'
3
+ import { cn } from '../lib/utils'
4
+
5
+ function Popover({
6
+ children,
7
+ ...props
8
+ }: {
9
+ children?: React.ReactNode
10
+ open?: boolean
11
+ defaultOpen?: boolean
12
+ onOpenChange?: (open: boolean, eventDetails: PopoverPrimitive.Root.ChangeEventDetails) => void
13
+ modal?: boolean
14
+ }) {
15
+ return (
16
+ <PopoverPrimitive.Root data-slot="popover" {...props}>
17
+ {children}
18
+ </PopoverPrimitive.Root>
19
+ )
20
+ }
21
+
22
+ function PopoverTrigger({
23
+ asChild,
24
+ children,
25
+ ...props
26
+ }: React.ComponentProps<typeof PopoverPrimitive.Trigger> & {
27
+ asChild?: boolean
28
+ }) {
29
+ if (asChild && React.isValidElement(children)) {
30
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" render={children} {...props} />
31
+ }
32
+ return (
33
+ <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props}>
34
+ {children}
35
+ </PopoverPrimitive.Trigger>
36
+ )
37
+ }
38
+
39
+ function PopoverContent({
40
+ className,
41
+ align = 'center',
42
+ sideOffset = 4,
43
+ side = 'bottom',
44
+ children,
45
+ ...props
46
+ }: {
47
+ className?: string
48
+ align?: 'start' | 'center' | 'end'
49
+ sideOffset?: number
50
+ side?: 'top' | 'bottom' | 'left' | 'right'
51
+ children?: React.ReactNode
52
+ } & Omit<React.ComponentProps<typeof PopoverPrimitive.Popup>, 'className'>) {
53
+ return (
54
+ <PopoverPrimitive.Portal>
55
+ <PopoverPrimitive.Positioner side={side} sideOffset={sideOffset} align={align}>
56
+ <PopoverPrimitive.Popup
57
+ data-slot="popover-content"
58
+ className={cn(
59
+ 'z-50 w-auto rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-md',
60
+ 'duration-100 data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 transition-[opacity,transform]',
61
+ className,
62
+ )}
63
+ {...props}
64
+ >
65
+ {children}
66
+ </PopoverPrimitive.Popup>
67
+ </PopoverPrimitive.Positioner>
68
+ </PopoverPrimitive.Portal>
69
+ )
70
+ }
71
+
72
+ function PopoverAnchor({ ...props }: React.ComponentProps<'div'>) {
73
+ return <div data-slot="popover-anchor" {...props} />
74
+ }
75
+
76
+ export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ import { Progress as ProgressPrimitive } from '@base-ui/react/progress'
4
+
5
+ import { cn } from '../lib/utils'
6
+
7
+ /**
8
+ * Base UI progress bar. `value={null}` renders the indeterminate state
9
+ * (Base UI stamps `data-indeterminate` on every part). Compose
10
+ * `ProgressLabel`/`ProgressValue` as children when a caption is needed —
11
+ * the track always renders last.
12
+ */
13
+ function Progress({ className, children, value, ...props }: ProgressPrimitive.Root.Props) {
14
+ return (
15
+ <ProgressPrimitive.Root
16
+ value={value}
17
+ data-slot="progress"
18
+ className={cn('flex w-full flex-wrap items-center gap-2', className)}
19
+ {...props}
20
+ >
21
+ {children}
22
+ <ProgressTrack>
23
+ <ProgressIndicator />
24
+ </ProgressTrack>
25
+ </ProgressPrimitive.Root>
26
+ )
27
+ }
28
+
29
+ function ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {
30
+ return (
31
+ <ProgressPrimitive.Track
32
+ data-slot="progress-track"
33
+ className={cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function ProgressIndicator({ className, ...props }: ProgressPrimitive.Indicator.Props) {
40
+ return (
41
+ <ProgressPrimitive.Indicator
42
+ data-slot="progress-indicator"
43
+ className={cn(
44
+ 'h-full rounded-full bg-primary transition-all',
45
+ 'data-[indeterminate]:w-1/3 data-[indeterminate]:animate-pulse',
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ function ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {
54
+ return (
55
+ <ProgressPrimitive.Label
56
+ data-slot="progress-label"
57
+ className={cn('font-medium text-sm', className)}
58
+ {...props}
59
+ />
60
+ )
61
+ }
62
+
63
+ function ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {
64
+ return (
65
+ <ProgressPrimitive.Value
66
+ data-slot="progress-value"
67
+ className={cn('ml-auto text-muted-foreground text-sm', className)}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ export { Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue }
@@ -0,0 +1,42 @@
1
+ import { Radio as RadioPrimitive } from '@base-ui/react/radio'
2
+ import { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'
3
+ import type * as React from 'react'
4
+ import { cn } from '../lib/utils'
5
+
6
+ function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive>) {
7
+ return (
8
+ <RadioGroupPrimitive
9
+ data-slot="radio-group"
10
+ className={cn('grid w-full gap-2', className)}
11
+ {...props}
12
+ />
13
+ )
14
+ }
15
+
16
+ function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioPrimitive.Root>) {
17
+ return (
18
+ <RadioPrimitive.Root
19
+ data-slot="radio-group-item"
20
+ className={cn(
21
+ 'peer relative inline-block size-4 shrink-0 rounded-full border border-input align-[-3px] shadow-xs outline-none transition-shadow',
22
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
23
+ 'disabled:cursor-not-allowed disabled:opacity-50',
24
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
25
+ 'data-[checked]:border-blue-500 data-[checked]:bg-blue-500 data-[checked]:text-white',
26
+ 'dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[checked]:bg-blue-500',
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ <RadioPrimitive.Indicator
32
+ keepMounted
33
+ data-slot="radio-group-indicator"
34
+ className="absolute inset-0 flex items-center justify-center text-current transition-none data-[unchecked]:hidden"
35
+ >
36
+ <span className="size-1.5 rounded-full bg-current" />
37
+ </RadioPrimitive.Indicator>
38
+ </RadioPrimitive.Root>
39
+ )
40
+ }
41
+
42
+ export { RadioGroup, RadioGroupItem }