@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.
- package/CHANGELOG.md +62 -0
- package/index.css +3 -0
- package/package.json +59 -0
- package/src/_old/_table/index.ts +5 -0
- package/src/_old/_table/table-advanced.constants.tsx +24 -0
- package/src/_old/_table/table-advanced.tsx +311 -0
- package/src/_old/_table/table-advanced.types.ts +272 -0
- package/src/_old/_table/table.constants.ts +2 -0
- package/src/_old/_table/table.hook.tsx +115 -0
- package/src/_old/_table/table.lib.ts +85 -0
- package/src/_old/_table/table.tsx +916 -0
- package/src/_old/_table/table.types.ts +118 -0
- package/src/_old/_table/todo.md +11 -0
- package/src/_old/_upload/index.ts +9 -0
- package/src/_old/_upload/todo.md +38 -0
- package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
- package/src/_old/_upload/upload-advanced.tsx +507 -0
- package/src/_old/_upload/upload-sonner.tsx +58 -0
- package/src/_old/_upload/upload.assets.tsx +239 -0
- package/src/_old/_upload/upload.constants.tsx +75 -0
- package/src/_old/_upload/upload.dto.ts +19 -0
- package/src/_old/_upload/upload.lib.tsx +630 -0
- package/src/_old/_upload/upload.tsx +491 -0
- package/src/_old/_upload/upload.types.ts +436 -0
- package/src/accordion/accordion.tsx +247 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/alert.constants.ts +17 -0
- package/src/alert/alert.tsx +52 -0
- package/src/alert/index.ts +2 -0
- package/src/alert-dialog/alert-dialog.tsx +107 -0
- package/src/alert-dialog/index.ts +1 -0
- package/src/aspect-ratio/aspect-ratio.tsx +33 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/audio/audio-record.tsx +776 -0
- package/src/audio/audio-visualizer.tsx +377 -0
- package/src/audio/audio.libs.ts +5 -0
- package/src/audio/audio.types.ts +50 -0
- package/src/audio/index.ts +2 -0
- package/src/avatar/avatar.tsx +78 -0
- package/src/avatar/index.ts +1 -0
- package/src/badge/badge.constants.ts +38 -0
- package/src/badge/badge.tsx +19 -0
- package/src/badge/index.ts +2 -0
- package/src/breadcrumb/breadcrumb.tsx +119 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/button.constants.ts +44 -0
- package/src/button/button.tsx +79 -0
- package/src/button/button.types.ts +38 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.constants.ts +26 -0
- package/src/button-group/button-group.tsx +65 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar.tsx +191 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/card.tsx +81 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/carousel.tsx +211 -0
- package/src/carousel/carousel.types.ts +23 -0
- package/src/carousel/index.ts +2 -0
- package/src/chart/chart.libs.ts +27 -0
- package/src/chart/chart.tsx +260 -0
- package/src/chart/chart.types.ts +38 -0
- package/src/chart/index.ts +3 -0
- package/src/checkbox/checkbox.tsx +144 -0
- package/src/checkbox/checkbox.types.ts +24 -0
- package/src/checkbox/index.ts +2 -0
- package/src/collapsible/collapsible.tsx +151 -0
- package/src/collapsible/index.ts +1 -0
- package/src/combobox/combobox.tsx +132 -0
- package/src/combobox/index.ts +1 -0
- package/src/command/command.tsx +192 -0
- package/src/command/command.types.ts +11 -0
- package/src/command/index.ts +2 -0
- package/src/context-menu/context-menu.tsx +178 -0
- package/src/context-menu/index.ts +1 -0
- package/src/dialog/dialog-responsive.tsx +137 -0
- package/src/dialog/dialog.tsx +97 -0
- package/src/dialog/index.ts +2 -0
- package/src/direction/direction.tsx +13 -0
- package/src/direction/index.ts +1 -0
- package/src/drawer/drawer.tsx +185 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/dropdown-menu.tsx +181 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty/empty.constants.ts +15 -0
- package/src/empty/empty.tsx +73 -0
- package/src/empty/index.ts +2 -0
- package/src/field/field.constants.ts +22 -0
- package/src/field/field.tsx +203 -0
- package/src/field/index.ts +2 -0
- package/src/hover-card/hover-card.tsx +79 -0
- package/src/hover-card/index.ts +1 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +45 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.tsx +170 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.tsx +66 -0
- package/src/item/index.ts +2 -0
- package/src/item/item.constants.ts +22 -0
- package/src/item/item.tsx +185 -0
- package/src/json-editor/index.ts +4 -0
- package/src/json-editor/json-editor.hooks.ts +21 -0
- package/src/json-editor/json-editor.libs.ts +34 -0
- package/src/json-editor/json-editor.tsx +425 -0
- package/src/json-editor/json-editor.types.ts +80 -0
- package/src/json-editor/json-editor.view.tsx +110 -0
- package/src/json-editor/json-text-area.tsx +7 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.tsx +39 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.tsx +28 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.tsx +213 -0
- package/src/navigation-menu/index.ts +1 -0
- package/src/navigation-menu/navigation-menu.tsx +152 -0
- package/src/pagination/index.ts +2 -0
- package/src/pagination/pagination.tsx +191 -0
- package/src/pagination/pagination.types.ts +17 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.tsx +35 -0
- package/src/preview-panel/index.ts +3 -0
- package/src/preview-panel/preview-panel-dialog.tsx +99 -0
- package/src/preview-panel/preview-panel.tsx +389 -0
- package/src/preview-panel/preview-panel.types.ts +49 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.tsx +32 -0
- package/src/radio-group/index.ts +1 -0
- package/src/radio-group/radio-group.tsx +92 -0
- package/src/resizable/index.ts +1 -0
- package/src/resizable/resizable.tsx +52 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +30 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.tsx +138 -0
- package/src/separator/index.ts +1 -0
- package/src/separator/separator.tsx +28 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.constants.tsx +20 -0
- package/src/sheet/sheet.tsx +92 -0
- package/src/sidebar/index.ts +4 -0
- package/src/sidebar/sidebar.constants.ts +30 -0
- package/src/sidebar/sidebar.hooks.ts +13 -0
- package/src/sidebar/sidebar.tsx +676 -0
- package/src/sidebar/sidebar.types.ts +28 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.tsx +22 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +57 -0
- package/src/sonner/index.ts +4 -0
- package/src/sonner/sonner.chunks.tsx +80 -0
- package/src/sonner/sonner.libs.ts +13 -0
- package/src/sonner/sonner.tsx +31 -0
- package/src/sonner/sonner.types.ts +9 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +63 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +95 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.tsx +151 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +24 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.constants.ts +22 -0
- package/src/toggle/toggle.tsx +24 -0
- package/src/toggle-group/index.ts +1 -0
- package/src/toggle-group/toggle-group.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +32 -0
- package/src/upload/index.ts +1 -0
- package/src/upload/upload.constants.tsx +19 -0
- package/src/upload/upload.libs.ts +97 -0
- package/src/upload/upload.tsx +340 -0
- package/src/upload/upload.types.ts +44 -0
- 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,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,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'
|