@gentleduck/registry-ui 0.2.1

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 (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. package/tsconfig.json +25 -0
@@ -0,0 +1,44 @@
1
+ import { cva } from '@gentleduck/variants'
2
+
3
+ export const buttonVariants = cva(
4
+ "relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
5
+
6
+ {
7
+ defaultVariants: {
8
+ border: 'default',
9
+ size: 'default',
10
+ variant: 'default',
11
+ },
12
+ variants: {
13
+ border: {
14
+ default: '',
15
+ destructive: 'border border-destructive/40 bg-destructive/40 hover:border-destructive hover:bg-destructive/65',
16
+ primary: 'border border-border/40 hover:border-border/80',
17
+ secondary: 'border border-secondary/40 bg-secondary/40 hover:border-secondary hover:bg-secondary/65',
18
+ warning: 'border border-warning/40 bg-warning/40 hover:border-warning hover:bg-warning/65',
19
+ },
20
+ size: {
21
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
22
+ icon: 'size-9',
23
+ 'icon-lg': 'size-10',
24
+ 'icon-sm': 'size-8',
25
+ lg: 'h-10 px-6 has-[>svg]:px-4',
26
+ sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
27
+ },
28
+ variant: {
29
+ dashed:
30
+ 'border border-input border-dashed bg-background text-accent-foreground shadow-xs hover:bg-accent/50 hover:text-accent-foreground',
31
+ default: 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',
32
+ destructive: 'bg-destructive/90 text-destructive-foreground shadow-xs hover:bg-destructive/70',
33
+ expand_icon: 'group relative bg-primary text-primary-foreground hover:bg-primary/90',
34
+ ghost: 'text-accent-foreground hover:bg-accent hover:text-accent-foreground',
35
+ link: 'text-primary underline-offset-4 hover:underline',
36
+ nothing: '',
37
+ outline:
38
+ 'border border-input bg-background text-accent-foreground shadow-xs hover:bg-accent hover:text-accent-foreground',
39
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
40
+ warning: 'bg-warning/90 text-warning-foreground shadow-xs hover:bg-warning/70',
41
+ },
42
+ },
43
+ },
44
+ )
@@ -0,0 +1,79 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { Slot, Slottable } from '@gentleduck/primitives/slot'
3
+ import { Loader } from 'lucide-react'
4
+ import * as React from 'react'
5
+ import { buttonVariants } from './button.constants'
6
+ import type { AnimationIconProps, ButtonProps } from './button.types'
7
+
8
+ /**
9
+ * Renders a customizable button component, supporting various styles and behaviors.
10
+ */
11
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
12
+ (
13
+ {
14
+ children,
15
+ variant = 'default',
16
+ size = 'default',
17
+ border = 'default',
18
+ asChild,
19
+ className,
20
+ loading,
21
+ isCollapsed,
22
+ icon,
23
+ secondIcon,
24
+ type = 'button',
25
+ disabled,
26
+ ...props
27
+ },
28
+ ref,
29
+ ) => {
30
+ const Component = (asChild ? Slot : 'button') as React.ElementType
31
+
32
+ return (
33
+ <Component
34
+ data-slot="button"
35
+ {...props}
36
+ aria-busy={loading ? true : undefined}
37
+ className={cn(
38
+ buttonVariants({
39
+ border,
40
+ className,
41
+ size: isCollapsed ? 'icon' : size,
42
+ variant,
43
+ }),
44
+ )}
45
+ disabled={loading ?? disabled}
46
+ ref={ref}
47
+ type={type}>
48
+ {loading ? <Loader aria-hidden="true" className="animate-spin" /> : icon}
49
+ <Slottable>{!isCollapsed && children}</Slottable>
50
+ {!isCollapsed && secondIcon && secondIcon}
51
+ </Component>
52
+ )
53
+ },
54
+ )
55
+ Button.displayName = 'Button'
56
+
57
+ /**
58
+ * Renders an animation icon component.
59
+ */
60
+ function AnimationIcon({ children, animationIcon }: AnimationIconProps): React.JSX.Element {
61
+ return (
62
+ <>
63
+ {animationIcon?.icon && animationIcon.iconPlacement === 'left' && (
64
+ <div className="w-0 pe-0 opacity-0 transition-all duration-200 group-hover:w-5 group-hover:pe-2 group-hover:opacity-100 ltr:translate-x-[-1.3em] ltr:group-hover:-translate-x-1 rtl:translate-x-[1.3em] rtl:group-hover:translate-x-1">
65
+ {animationIcon?.icon}
66
+ </div>
67
+ )}
68
+ {children}
69
+ {animationIcon?.icon && animationIcon.iconPlacement === 'right' && (
70
+ <div className="w-0 ps-0 opacity-0 transition-all duration-200 group-hover:w-5 group-hover:translate-x-0 group-hover:ps-2 group-hover:opacity-100 ltr:translate-x-[1.3em] rtl:translate-x-[-1.3em]">
71
+ {animationIcon?.icon}
72
+ </div>
73
+ )}
74
+ </>
75
+ )
76
+ }
77
+ AnimationIcon.displayName = 'AnimationIcon'
78
+
79
+ export { Button, AnimationIcon }
@@ -0,0 +1,38 @@
1
+ import type { VariantProps } from '@gentleduck/variants'
2
+ import type { buttonVariants } from './button.constants'
3
+
4
+ /**
5
+ * Props for the Button component, combining native button attributes, variant styles, and custom options.
6
+ */
7
+ export interface ButtonProps
8
+ extends Omit<React.HTMLProps<HTMLButtonElement>, 'size'>,
9
+ VariantProps<typeof buttonVariants> {
10
+ /** Render as child component using Slot (e.g., for custom wrappers) */
11
+ asChild?: boolean
12
+ /** Controls collapsed state for buttons like sidebar toggles */
13
+ isCollapsed?: boolean
14
+ /** Shows loading state/spinner in the button */
15
+ loading?: boolean
16
+ /** Primary icon to display in the button */
17
+ icon?: React.ReactNode
18
+ /** Secondary icon (e.g., for split actions or toggles) */
19
+ secondIcon?: React.ReactNode
20
+ }
21
+
22
+ /**
23
+ * Props for components that support optional animated icons.
24
+ */
25
+ export type AnimationIconProps = {
26
+ /** The content inside the icon wrapper */
27
+ children: React.ReactNode
28
+ /**
29
+ * Optional animated icon configuration.
30
+ * Modify the variant to use animation styles.
31
+ */
32
+ animationIcon?: {
33
+ /** Icon to animate (if applicable) */
34
+ icon?: React.ReactNode
35
+ /** Determines icon position relative to the children */
36
+ iconPlacement?: 'left' | 'right'
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ export * from './button'
2
+ export * from './button.constants'
3
+ export * from './button.types'
@@ -0,0 +1,26 @@
1
+ import { cva } from '@gentleduck/variants'
2
+
3
+ export const buttonGroupVariants = cva(
4
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:relative [&>*]:hover:z-[1] [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
5
+ {
6
+ defaultVariants: {
7
+ orientation: 'horizontal',
8
+ },
9
+ variants: {
10
+ orientation: {
11
+ horizontal: `
12
+ [&>*:not(:first-child)]:rounded-s-none
13
+ [&>*:not(:last-child)]:rounded-e-none
14
+ [&>*:nth-last-child(2):has(+span[aria-hidden])]:!rounded-e-md
15
+ [&>*:not(:first-child)]:-ms-px
16
+ `,
17
+ vertical: `
18
+ flex-col
19
+ [&>*:not(:first-child)]:rounded-t-none
20
+ [&>*:not(:last-child)]:rounded-b-none
21
+ [&>*:not(:first-child)]:-mt-px
22
+ `,
23
+ },
24
+ },
25
+ },
26
+ )
@@ -0,0 +1,65 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import { Slot } from '@gentleduck/primitives/slot'
4
+ import type { VariantProps } from '@gentleduck/variants'
5
+ import * as React from 'react'
6
+ import { Separator } from '../separator'
7
+ import { buttonGroupVariants } from './button-group.constants'
8
+
9
+ const ButtonGroup = React.forwardRef<
10
+ HTMLDivElement,
11
+ React.ComponentPropsWithoutRef<'div'> & VariantProps<typeof buttonGroupVariants>
12
+ >(({ className, orientation = 'horizontal', dir, ...props }, ref) => {
13
+ const direction = useDirection(dir as Direction)
14
+ return (
15
+ <div
16
+ className={cn(buttonGroupVariants({ orientation }), className)}
17
+ data-orientation={orientation}
18
+ data-slot="button-group"
19
+ dir={direction}
20
+ ref={ref}
21
+ role="group"
22
+ {...props}
23
+ />
24
+ )
25
+ })
26
+ ButtonGroup.displayName = 'ButtonGroup'
27
+
28
+ const ButtonGroupText = React.forwardRef<
29
+ HTMLDivElement,
30
+ React.ComponentPropsWithoutRef<'div'> & {
31
+ asChild?: boolean
32
+ }
33
+ >(({ className, asChild = false, ...props }, ref) => {
34
+ const Comp = asChild ? Slot : 'div'
35
+
36
+ return (
37
+ <Comp
38
+ className={cn(
39
+ "flex items-center gap-2 rounded-md border bg-muted px-4 font-medium text-sm shadow-xs [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
40
+ className,
41
+ )}
42
+ ref={ref}
43
+ {...props}
44
+ />
45
+ )
46
+ })
47
+ ButtonGroupText.displayName = 'ButtonGroupText'
48
+
49
+ const ButtonGroupSeparator = React.forwardRef<
50
+ React.ComponentRef<typeof Separator>,
51
+ React.ComponentPropsWithoutRef<typeof Separator>
52
+ >(({ className, orientation = 'vertical', ...props }, ref) => {
53
+ return (
54
+ <Separator
55
+ className={cn('!m-0 relative self-stretch bg-input data-[orientation=vertical]:h-auto', className)}
56
+ data-slot="button-group-separator"
57
+ orientation={orientation}
58
+ ref={ref}
59
+ {...props}
60
+ />
61
+ )
62
+ })
63
+ ButtonGroupSeparator.displayName = 'ButtonGroupSeparator'
64
+
65
+ export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText }
@@ -0,0 +1,2 @@
1
+ export * from './button-group'
2
+ export * from './button-group.constants'
@@ -0,0 +1,191 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
5
+ import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
6
+ import * as React from 'react'
7
+ import { type DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
8
+ import { Button, buttonVariants } from '../button'
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ captionLayout = 'label',
15
+ buttonVariant = 'ghost',
16
+ formatters,
17
+ components,
18
+ dir,
19
+ ...props
20
+ }: React.ComponentProps<typeof DayPicker> & {
21
+ buttonVariant?: React.ComponentProps<typeof Button>['variant']
22
+ }) {
23
+ const direction = useDirection(dir as Direction)
24
+ const defaultClassNames = getDefaultClassNames()
25
+ const localeTag = React.useMemo(() => {
26
+ const code = props.locale?.code
27
+ if (!code) return undefined
28
+ return code.startsWith('ar') ? `${code}-u-nu-arab` : code
29
+ }, [props.locale])
30
+
31
+ const monthFormatter = React.useMemo(() => {
32
+ return new Intl.DateTimeFormat(localeTag, { month: 'short' })
33
+ }, [localeTag])
34
+
35
+ const captionFormatter = React.useMemo(() => {
36
+ return new Intl.DateTimeFormat(localeTag, { month: 'long', year: 'numeric' })
37
+ }, [localeTag])
38
+
39
+ const numberFormatter = React.useMemo(() => {
40
+ return new Intl.NumberFormat(localeTag)
41
+ }, [localeTag])
42
+
43
+ const formatLocalizedNumber = React.useCallback(
44
+ (value: number) => {
45
+ return numberFormatter.format(value)
46
+ },
47
+ [numberFormatter],
48
+ )
49
+
50
+ return (
51
+ <DayPicker
52
+ dir={direction}
53
+ captionLayout={captionLayout}
54
+ className={cn(
55
+ 'group/calendar bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent p-3 [--cell-size:--spacing(8)]',
56
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
57
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
58
+ className,
59
+ )}
60
+ classNames={{
61
+ button_next: cn(
62
+ buttonVariants({ variant: buttonVariant }),
63
+ 'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',
64
+ defaultClassNames.button_next,
65
+ ),
66
+ button_previous: cn(
67
+ buttonVariants({ variant: buttonVariant }),
68
+ 'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',
69
+ defaultClassNames.button_previous,
70
+ ),
71
+ caption_label: cn(
72
+ 'select-none font-medium',
73
+ captionLayout === 'label'
74
+ ? 'text-sm'
75
+ : 'flex h-8 items-center gap-1 rounded-md ps-2 pe-1 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',
76
+ defaultClassNames.caption_label,
77
+ ),
78
+ day: cn(
79
+ 'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-s-md [&:last-child[data-selected=true]_button]:rounded-e-md',
80
+ defaultClassNames.day,
81
+ ),
82
+ disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),
83
+ dropdown: cn('absolute inset-0 bg-popover opacity-0', defaultClassNames.dropdown),
84
+ dropdown_root: cn(
85
+ 'relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50',
86
+ defaultClassNames.dropdown_root,
87
+ ),
88
+ dropdowns: cn(
89
+ 'flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm',
90
+ defaultClassNames.dropdowns,
91
+ ),
92
+ hidden: cn('invisible', defaultClassNames.hidden),
93
+ month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
94
+ month_caption: cn(
95
+ 'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',
96
+ defaultClassNames.month_caption,
97
+ ),
98
+ months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),
99
+ nav: cn('absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', defaultClassNames.nav),
100
+ outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),
101
+ range_end: cn('rounded-e-md bg-accent', defaultClassNames.range_end),
102
+ range_middle: cn('rounded-none', defaultClassNames.range_middle),
103
+ range_start: cn('rounded-s-md bg-accent', defaultClassNames.range_start),
104
+ root: cn('w-fit', defaultClassNames.root),
105
+ table: 'w-full border-collapse',
106
+ today: cn(
107
+ 'rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none',
108
+ defaultClassNames.today,
109
+ ),
110
+ week: cn('mt-2 flex w-full', defaultClassNames.week),
111
+ week_number: cn('select-none text-[0.8rem] text-muted-foreground', defaultClassNames.week_number),
112
+ week_number_header: cn('w-(--cell-size) select-none', defaultClassNames.week_number_header),
113
+ weekday: cn(
114
+ 'flex-1 select-none rounded-md font-normal text-[0.8rem] text-muted-foreground',
115
+ defaultClassNames.weekday,
116
+ ),
117
+ weekdays: cn('flex', defaultClassNames.weekdays),
118
+ ...classNames,
119
+ }}
120
+ components={{
121
+ Chevron: ({ className, orientation, ...props }) => {
122
+ if (orientation === 'left') {
123
+ return <ChevronLeftIcon className={cn('size-4', className)} {...props} />
124
+ }
125
+
126
+ if (orientation === 'right') {
127
+ return <ChevronRightIcon className={cn('size-4', className)} {...props} />
128
+ }
129
+
130
+ return <ChevronDownIcon className={cn('size-4', className)} {...props} />
131
+ },
132
+ DayButton: CalendarDayButton,
133
+ Root: ({ className, rootRef, ...props }) => {
134
+ return <div className={cn(className)} data-slot="calendar" ref={rootRef} {...props} />
135
+ },
136
+ WeekNumber: ({ children, ...props }) => {
137
+ return (
138
+ <td {...props}>
139
+ <div className="flex size-(--cell-size) items-center justify-center text-center">{children}</div>
140
+ </td>
141
+ )
142
+ },
143
+ ...components,
144
+ }}
145
+ formatters={{
146
+ formatCaption: (date) => captionFormatter.format(date),
147
+ formatDay: (date) => formatLocalizedNumber(date.getDate()),
148
+ formatMonthDropdown: (date) => monthFormatter.format(date),
149
+ formatWeekNumber: (weekNumber) => formatLocalizedNumber(weekNumber),
150
+ formatYearDropdown: (date) => String(date.getFullYear()),
151
+ ...formatters,
152
+ }}
153
+ showOutsideDays={showOutsideDays}
154
+ {...props}
155
+ />
156
+ )
157
+ }
158
+ Calendar.displayName = 'Calendar'
159
+
160
+ function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>) {
161
+ const defaultClassNames = getDefaultClassNames()
162
+
163
+ const ref = React.useRef<HTMLButtonElement>(null)
164
+ React.useEffect(() => {
165
+ if (modifiers.focused) ref.current?.focus()
166
+ }, [modifiers.focused])
167
+
168
+ return (
169
+ <Button
170
+ className={cn(
171
+ 'flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-s-md data-[range-end=true]:rounded-e-md data-[range-end=true]:bg-primary data-[range-middle=true]:bg-accent data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-accent-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 [&>span]:text-xs [&>span]:opacity-70',
172
+ defaultClassNames.day,
173
+ className,
174
+ )}
175
+ data-day={day.date.toLocaleDateString()}
176
+ data-range-end={modifiers.range_end}
177
+ data-range-middle={modifiers.range_middle}
178
+ data-range-start={modifiers.range_start}
179
+ data-selected-single={
180
+ modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle
181
+ }
182
+ ref={ref}
183
+ size="icon"
184
+ variant="ghost"
185
+ {...props}
186
+ />
187
+ )
188
+ }
189
+ CalendarDayButton.displayName = 'CalendarDayButton'
190
+
191
+ export { Calendar, CalendarDayButton }
@@ -0,0 +1 @@
1
+ export * from './calendar'
@@ -0,0 +1,81 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import * as React from 'react'
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, dir, ...props }, ref) => {
7
+ const direction = useDirection(dir as Direction)
8
+ return (
9
+ <div
10
+ ref={ref}
11
+ className={cn('flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm', className)}
12
+ data-card=""
13
+ dir={direction}
14
+ {...props}
15
+ />
16
+ )
17
+ },
18
+ )
19
+ Card.displayName = 'Card'
20
+
21
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
22
+ ({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn(
26
+ '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-card-action:grid-cols-[1fr_auto] [.border-b]:pb-6',
27
+ className,
28
+ )}
29
+ data-slot="card-header"
30
+ {...props}
31
+ />
32
+ ),
33
+ )
34
+ CardHeader.displayName = 'CardHeader'
35
+
36
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
37
+ ({ className, ...props }, ref) => (
38
+ <div ref={ref} className={cn('font-semibold leading-none', className)} data-slot="card-title" {...props} />
39
+ ),
40
+ )
41
+ CardTitle.displayName = 'CardTitle'
42
+
43
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
44
+ ({ className, ...props }, ref) => (
45
+ <div ref={ref} className={cn('text-muted-foreground text-sm', className)} data-slot="card-description" {...props} />
46
+ ),
47
+ )
48
+ CardDescription.displayName = 'CardDescription'
49
+
50
+ const CardAction = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
51
+ ({ className, ...props }, ref) => (
52
+ <div
53
+ ref={ref}
54
+ className={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}
55
+ data-slot="card-action"
56
+ {...props}
57
+ />
58
+ ),
59
+ )
60
+ CardAction.displayName = 'CardAction'
61
+
62
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
63
+ ({ className, ...props }, ref) => (
64
+ <div ref={ref} className={cn('px-6', className)} data-slot="card-content" {...props} />
65
+ ),
66
+ )
67
+ CardContent.displayName = 'CardContent'
68
+
69
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
70
+ ({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn('flex items-center justify-end gap-2 px-6 [.border-t]:pt-6', className)}
74
+ data-slot="card-footer"
75
+ {...props}
76
+ />
77
+ ),
78
+ )
79
+ CardFooter.displayName = 'CardFooter'
80
+
81
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }
@@ -0,0 +1 @@
1
+ export * from './card'