@gunjo/ui 0.0.1-alpha.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 (88) hide show
  1. package/README.md +129 -0
  2. package/design/atoms-metadata.json +82 -0
  3. package/design/molecules-metadata.json +130 -0
  4. package/design/organisms-metadata.json +38 -0
  5. package/design/templates-metadata.json +38 -0
  6. package/package.json +158 -0
  7. package/src/components/atoms/Alert.tsx +63 -0
  8. package/src/components/atoms/Avatar.tsx +57 -0
  9. package/src/components/atoms/Badge.tsx +30 -0
  10. package/src/components/atoms/Button.tsx +29 -0
  11. package/src/components/atoms/ButtonVariants.ts +37 -0
  12. package/src/components/atoms/Checkbox.tsx +52 -0
  13. package/src/components/atoms/Img.tsx +102 -0
  14. package/src/components/atoms/Input.tsx +37 -0
  15. package/src/components/atoms/Kbd.tsx +22 -0
  16. package/src/components/atoms/Label.tsx +22 -0
  17. package/src/components/atoms/Progress.tsx +38 -0
  18. package/src/components/atoms/RadioGroup.tsx +86 -0
  19. package/src/components/atoms/Select.tsx +28 -0
  20. package/src/components/atoms/Separator.tsx +33 -0
  21. package/src/components/atoms/Skeleton.tsx +36 -0
  22. package/src/components/atoms/Slider.tsx +26 -0
  23. package/src/components/atoms/Spinner.tsx +34 -0
  24. package/src/components/atoms/Switch.tsx +47 -0
  25. package/src/components/atoms/Textarea.tsx +34 -0
  26. package/src/components/atoms/ToggleGroup.tsx +60 -0
  27. package/src/components/atoms/ToolPill.tsx +77 -0
  28. package/src/components/atoms/generated/default-variant-keys.ts +36 -0
  29. package/src/components/atoms/generated/variant-keys.ts +61 -0
  30. package/src/components/generated/component-manifest.ts +741 -0
  31. package/src/components/generated/component-style-hints.ts +1262 -0
  32. package/src/components/molecules/AIChatInput.tsx +140 -0
  33. package/src/components/molecules/AIChatMessage.tsx +109 -0
  34. package/src/components/molecules/Accordion.tsx +99 -0
  35. package/src/components/molecules/Breadcrumb.tsx +115 -0
  36. package/src/components/molecules/Calendar.tsx +60 -0
  37. package/src/components/molecules/Card.tsx +78 -0
  38. package/src/components/molecules/Carousel.tsx +261 -0
  39. package/src/components/molecules/Command.tsx +152 -0
  40. package/src/components/molecules/ContextMenu.tsx +200 -0
  41. package/src/components/molecules/Dialog.tsx +122 -0
  42. package/src/components/molecules/DropdownMenu.tsx +200 -0
  43. package/src/components/molecules/FilterButton.tsx +133 -0
  44. package/src/components/molecules/Form.tsx +90 -0
  45. package/src/components/molecules/HoverCard.tsx +29 -0
  46. package/src/components/molecules/List.tsx +120 -0
  47. package/src/components/molecules/Menubar.tsx +231 -0
  48. package/src/components/molecules/Modal.tsx +66 -0
  49. package/src/components/molecules/NotificationCenter.tsx +118 -0
  50. package/src/components/molecules/Pagination.tsx +118 -0
  51. package/src/components/molecules/Popover.tsx +31 -0
  52. package/src/components/molecules/ProgressWidget.tsx +40 -0
  53. package/src/components/molecules/Resizable.tsx +47 -0
  54. package/src/components/molecules/ScrollArea.tsx +48 -0
  55. package/src/components/molecules/Sheet.tsx +140 -0
  56. package/src/components/molecules/SidebarItem.tsx +134 -0
  57. package/src/components/molecules/SortButton.tsx +56 -0
  58. package/src/components/molecules/StatusBar.tsx +41 -0
  59. package/src/components/molecules/Stepper.tsx +108 -0
  60. package/src/components/molecules/Table.tsx +117 -0
  61. package/src/components/molecules/Tabs.tsx +64 -0
  62. package/src/components/molecules/Toast.tsx +57 -0
  63. package/src/components/molecules/Tooltip.tsx +30 -0
  64. package/src/components/molecules/generated/default-variant-keys.ts +22 -0
  65. package/src/components/molecules/generated/variant-keys.ts +33 -0
  66. package/src/components/organisms/AppRail.tsx +28 -0
  67. package/src/components/organisms/CommandPalette.tsx +58 -0
  68. package/src/components/organisms/FileUploader.tsx +151 -0
  69. package/src/components/organisms/FloatingPanel.tsx +46 -0
  70. package/src/components/organisms/InspectorPanel.tsx +65 -0
  71. package/src/components/organisms/RightRail.tsx +29 -0
  72. package/src/components/organisms/ShareModal.tsx +182 -0
  73. package/src/components/organisms/SpatialCanvas.tsx +36 -0
  74. package/src/components/organisms/ToastProvider.tsx +49 -0
  75. package/src/components/templates/AuthTemplate.tsx +58 -0
  76. package/src/components/templates/BannalyzeTemplate.tsx +55 -0
  77. package/src/components/templates/ChatTemplate.tsx +55 -0
  78. package/src/components/templates/DashboardTemplate.tsx +34 -0
  79. package/src/components/templates/EditorTemplate.tsx +46 -0
  80. package/src/components/templates/KanbanTemplate.tsx +38 -0
  81. package/src/components/templates/LandingTemplate.tsx +53 -0
  82. package/src/components/templates/MediaLibraryTemplate.tsx +55 -0
  83. package/src/components/templates/SettingsTemplate.tsx +48 -0
  84. package/src/globals.css +108 -0
  85. package/src/index.ts +89 -0
  86. package/src/lib/utils.ts +6 -0
  87. package/tailwind-preset.js +11 -0
  88. package/tailwind-theme-extend.cjs +86 -0
@@ -0,0 +1,57 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import type { AvatarVariantKey } from "./generated/variant-keys"
8
+ import { avatarDefaultVariantKey } from "./generated/default-variant-keys"
9
+
10
+ const avatarSlotClasses: Record<AvatarVariantKey, string> = {
11
+ fallback: "flex h-full w-full items-center justify-center rounded-full bg-secondary text-sm font-medium text-muted-foreground",
12
+ image: "aspect-square h-full w-full",
13
+ }
14
+
15
+ const Avatar = React.forwardRef<
16
+ React.ElementRef<typeof AvatarPrimitive.Root>,
17
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
18
+ >(({ className, ...props }, ref) => (
19
+ <AvatarPrimitive.Root
20
+ ref={ref}
21
+ className={cn(
22
+ "relative inline-flex h-10 w-10 shrink-0 items-center overflow-hidden rounded-full",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ Avatar.displayName = AvatarPrimitive.Root.displayName
29
+
30
+ const AvatarImage = React.forwardRef<
31
+ React.ElementRef<typeof AvatarPrimitive.Image>,
32
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
33
+ >(({ className, ...props }, ref) => (
34
+ <AvatarPrimitive.Image
35
+ ref={ref}
36
+ className={cn(avatarSlotClasses.image, className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
41
+
42
+ const AvatarFallback = React.forwardRef<
43
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
44
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
45
+ >(({ className, ...props }, ref) => (
46
+ <AvatarPrimitive.Fallback
47
+ ref={ref}
48
+ className={cn(
49
+ avatarSlotClasses[avatarDefaultVariantKey],
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ ))
55
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
56
+
57
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,30 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+ import type { BadgeVariantKey } from "./generated/variant-keys"
4
+ import { badgeDefaultVariantKey } from "./generated/default-variant-keys"
5
+
6
+ const badgeVariantClasses: Record<BadgeVariantKey, string> = {
7
+ default: "bg-foreground text-primary-foreground hover:bg-foreground/80",
8
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
9
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/80",
10
+ outline: "border-border bg-transparent text-foreground",
11
+ }
12
+
13
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ variant?: BadgeVariantKey
15
+ }
16
+
17
+ function Badge({ className, variant = badgeDefaultVariantKey, ...props }: BadgeProps) {
18
+ return (
19
+ <div
20
+ className={cn(
21
+ "inline-flex items-center w-fit h-5 rounded-full border border-transparent px-2.5 py-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
22
+ badgeVariantClasses[variant],
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ export { Badge }
@@ -0,0 +1,29 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Slot } from "@radix-ui/react-slot"
5
+ import { type VariantProps } from "class-variance-authority"
6
+ import { cn } from "../../lib/utils"
7
+ import { buttonVariants } from "./ButtonVariants"
8
+
9
+ export interface ButtonProps
10
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
11
+ VariantProps<typeof buttonVariants> {
12
+ asChild?: boolean
13
+ }
14
+
15
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
16
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
17
+ const Comp = asChild ? Slot : "button"
18
+ return (
19
+ <Comp
20
+ className={cn(buttonVariants({ variant, size, className }))}
21
+ ref={ref}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+ )
27
+ Button.displayName = "Button"
28
+
29
+ export { Button }
@@ -0,0 +1,37 @@
1
+ import { cva, type VariantProps } from "class-variance-authority"
2
+ import type { ButtonVariantKey } from "./generated/variant-keys"
3
+ import { buttonDefaultVariantKey } from "./generated/default-variant-keys"
4
+
5
+ const buttonVariantClasses: Record<ButtonVariantKey, string> = {
6
+ default:
7
+ "bg-foreground text-primary-foreground shadow hover:bg-foreground/90",
8
+ destructive:
9
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
10
+ outline:
11
+ "border border-border bg-transparent shadow-sm hover:bg-muted hover:text-foreground",
12
+ secondary:
13
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
14
+ ghost: "bg-transparent hover:bg-muted hover:text-foreground",
15
+ link: "bg-transparent text-foreground underline underline-offset-4 hover:underline",
16
+ }
17
+
18
+ export const buttonVariants = cva(
19
+ "inline-flex items-center justify-center w-fit gap-2 rounded-lg rounded-[var(--radius)] text-sm font-medium transition-colors duration-150 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
20
+ {
21
+ variants: {
22
+ variant: buttonVariantClasses,
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: buttonDefaultVariantKey,
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export type ButtonVariantsProps = VariantProps<typeof buttonVariants>
@@ -0,0 +1,52 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check } from "lucide-react"
5
+ import { cn } from "../../lib/utils"
6
+ import type { CheckboxVariantKey } from "./generated/variant-keys"
7
+ import { checkboxDefaultVariantKey } from "./generated/default-variant-keys"
8
+
9
+ const checkboxStateClasses: Record<CheckboxVariantKey, string> = {
10
+ checked: "border-transparent bg-foreground text-primary-foreground text-xs font-semibold",
11
+ disabled: "bg-transparent disabled:bg-muted disabled:opacity-50",
12
+ unchecked: "bg-transparent",
13
+ }
14
+
15
+ export interface CheckboxProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
16
+ checked?: boolean
17
+ onCheckedChange?: (checked: boolean) => void
18
+ }
19
+
20
+ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
21
+ ({ className, checked, onCheckedChange, disabled, ...props }, ref) => {
22
+ const checkboxState: CheckboxVariantKey = disabled
23
+ ? "disabled"
24
+ : checked
25
+ ? "checked"
26
+ : checkboxDefaultVariantKey
27
+
28
+ return (
29
+ <button
30
+ type="button"
31
+ role="checkbox"
32
+ aria-checked={checked}
33
+ onClick={() => onCheckedChange?.(!checked)}
34
+ ref={ref}
35
+ className={cn(
36
+ "peer inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-[4px] border border-input bg-transparent ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed",
37
+ checkboxStateClasses[checkboxState],
38
+ className
39
+ )}
40
+ disabled={disabled}
41
+ {...props}
42
+ >
43
+ {checked && (
44
+ <Check className="h-3 w-3" strokeWidth={3} />
45
+ )}
46
+ </button>
47
+ )
48
+ }
49
+ )
50
+ Checkbox.displayName = "Checkbox"
51
+
52
+ export { Checkbox }
@@ -0,0 +1,102 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+ import { Image as ImageIcon } from "lucide-react"
6
+ import type { ImgVariantKey } from "./generated/variant-keys"
7
+ import { imgDefaultVariantKey } from "./generated/default-variant-keys"
8
+
9
+ const imgOpacityClasses: Record<ImgVariantKey, string> = {
10
+ error: "opacity-100",
11
+ loaded: "opacity-100",
12
+ loading: "opacity-0",
13
+ }
14
+
15
+ const imgVariantClasses: Record<ImgVariantKey, string> = {
16
+ error: "inline-flex items-center w-[256px] h-[256px] gap-2 rounded-lg text-xs font-normal",
17
+ loaded: "w-[256px] h-[256px] rounded-lg",
18
+ loading: "w-[256px] h-[256px] rounded-lg",
19
+ }
20
+
21
+ export interface ImgProps extends React.ImgHTMLAttributes<HTMLImageElement> {
22
+ fallback?: React.ReactNode
23
+ aspectRatio?: "square" | "video" | "auto" | "portrait" | "wide"
24
+ objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down"
25
+ showSkeleton?: boolean
26
+ }
27
+
28
+ const Img = React.forwardRef<HTMLImageElement, ImgProps>(
29
+ ({ className, src, alt, fallback, aspectRatio = "auto", objectFit = "cover", showSkeleton = true, onLoad, onError, ...props }, ref) => {
30
+ const [status, setStatus] = React.useState<ImgVariantKey>(imgDefaultVariantKey)
31
+
32
+ const handleLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
33
+ setStatus("loaded")
34
+ onLoad?.(e)
35
+ }
36
+
37
+ const handleError = (e: React.SyntheticEvent<HTMLImageElement>) => {
38
+ setStatus("error")
39
+ onError?.(e)
40
+ }
41
+
42
+ // Reset status if src changes
43
+ React.useEffect(() => {
44
+ setStatus(imgDefaultVariantKey)
45
+ }, [src])
46
+
47
+ const aspectRatioClass = {
48
+ "square": "aspect-square",
49
+ "video": "aspect-video",
50
+ "portrait": "aspect-[3/4]",
51
+ "wide": "aspect-[21/9]",
52
+ "auto": "aspect-auto"
53
+ }[aspectRatio]
54
+
55
+ const objectFitClass = {
56
+ "cover": "object-cover",
57
+ "contain": "object-contain",
58
+ "fill": "object-fill",
59
+ "none": "object-none",
60
+ "scale-down": "object-scale-down"
61
+ }[objectFit]
62
+
63
+ return (
64
+ <div className={cn("relative overflow-hidden bg-secondary", imgVariantClasses[status], aspectRatioClass, className)}>
65
+ {showSkeleton && status === "loading" && (
66
+ <div className="absolute inset-0 z-10 flex animate-pulse items-center justify-center bg-secondary">
67
+ {/* Optional spinner or just pulse */}
68
+ {/* <Loader2 className="w-6 h-6 text-muted-foreground animate-spin" /> */}
69
+ </div>
70
+ )}
71
+
72
+ {status === "error" ? (
73
+ <div className="absolute inset-0 inline-flex flex-col items-center justify-center bg-secondary p-4 text-center text-muted-foreground">
74
+ {fallback || (
75
+ <>
76
+ <ImageIcon className="mb-2 h-8 w-8 opacity-80" />
77
+ <span className="text-xs">Image not found</span>
78
+ </>
79
+ )}
80
+ </div>
81
+ ) : (
82
+ <img
83
+ ref={ref}
84
+ src={src}
85
+ alt={alt}
86
+ onLoad={handleLoad}
87
+ onError={handleError}
88
+ className={cn(
89
+ "w-full h-full transition-opacity duration-300",
90
+ objectFitClass,
91
+ imgOpacityClasses[status]
92
+ )}
93
+ {...props}
94
+ />
95
+ )}
96
+ </div>
97
+ )
98
+ }
99
+ )
100
+ Img.displayName = "Img"
101
+
102
+ export { Img }
@@ -0,0 +1,37 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+ import type { InputVariantKey } from "./generated/variant-keys"
4
+ import { inputDefaultVariantKey } from "./generated/default-variant-keys"
5
+
6
+ const inputVariantClasses: Record<InputVariantKey, string> = {
7
+ default: "",
8
+ disabled: "disabled:bg-muted disabled:text-muted-foreground disabled:opacity-50",
9
+ placeholder: "placeholder:text-muted-foreground",
10
+ }
11
+
12
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { }
13
+
14
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
15
+ ({ className, type, disabled, ...props }, ref) => {
16
+ const inputState: InputVariantKey = disabled ? "disabled" : inputDefaultVariantKey
17
+
18
+ return (
19
+ <input
20
+ type={type}
21
+ className={cn(
22
+ "inline-flex h-9 w-[280px] max-w-full items-center rounded-lg border border-input bg-transparent px-3 py-2 text-sm font-normal shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed",
23
+ inputVariantClasses[inputState],
24
+ inputVariantClasses.placeholder,
25
+ className
26
+ )}
27
+ ref={ref}
28
+ suppressHydrationWarning
29
+ disabled={disabled}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+ )
35
+ Input.displayName = "Input"
36
+
37
+ export { Input }
@@ -0,0 +1,22 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+
4
+ export interface KbdProps extends React.HTMLAttributes<HTMLSpanElement> { }
5
+
6
+ const Kbd = React.forwardRef<HTMLSpanElement, KbdProps>(
7
+ ({ className, ...props }, ref) => {
8
+ return (
9
+ <kbd
10
+ className={cn(
11
+ "pointer-events-none inline-flex h-5 w-fit select-none items-center gap-1 rounded-[4px] border border-input bg-secondary py-1 px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Kbd.displayName = "Kbd"
21
+
22
+ export { Kbd }
@@ -0,0 +1,22 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> { }
7
+
8
+ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
9
+ ({ className, ...props }, ref) => (
10
+ <label
11
+ ref={ref}
12
+ className={cn(
13
+ "text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ )
20
+ Label.displayName = "Label"
21
+
22
+ export { Label }
@@ -0,0 +1,38 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ value?: number
8
+ max?: number
9
+ }
10
+
11
+ const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
12
+ ({ className, value = 0, max = 100, ...props }, ref) => {
13
+ const percentage = Math.min(100, Math.max(0, ((value || 0) / max) * 100))
14
+
15
+ return (
16
+ <div
17
+ ref={ref}
18
+ role="progressbar"
19
+ aria-valuemin={0}
20
+ aria-valuemax={max}
21
+ aria-valuenow={value}
22
+ className={cn(
23
+ "relative h-4 w-[200px] overflow-hidden rounded-full bg-secondary",
24
+ className
25
+ )}
26
+ {...props}
27
+ >
28
+ <div
29
+ className="h-full w-full flex-1 bg-foreground transition-all"
30
+ style={{ transform: `translateX(-${100 - percentage}%)` }}
31
+ />
32
+ </div>
33
+ )
34
+ }
35
+ )
36
+ Progress.displayName = "Progress"
37
+
38
+ export { Progress }
@@ -0,0 +1,86 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+ import type { RadioGroupVariantKey } from "./generated/variant-keys"
6
+ import { radioGroupDefaultVariantKey } from "./generated/default-variant-keys"
7
+
8
+ // Simple Context-less Radio for batch 1, or Context-based?
9
+ // Let's go Simple but composable.
10
+ // Actually standard standard is RadioGroup + RadioGroupItem.
11
+
12
+ const RadioGroupContext = React.createContext<{
13
+ value?: string,
14
+ onValueChange?: (value: string) => void,
15
+ name?: string
16
+ } | undefined>(undefined);
17
+
18
+ const radioGroupItemStateClasses: Record<RadioGroupVariantKey, string> = {
19
+ checked: "border-foreground bg-transparent",
20
+ unchecked: "border-input bg-transparent",
21
+ }
22
+
23
+ export interface RadioGroupProps extends React.HTMLAttributes<HTMLDivElement> {
24
+ value?: string
25
+ defaultValue?: string
26
+ onValueChange?: (value: string) => void
27
+ name?: string
28
+ }
29
+
30
+ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
31
+ ({ className, value, onValueChange, defaultValue, name, children, ...props }, ref) => {
32
+ const [internalValue, setInternalValue] = React.useState(defaultValue)
33
+ const activeValue = value !== undefined ? value : internalValue
34
+
35
+ const handleValueChange = (v: string) => {
36
+ if (value === undefined) setInternalValue(v);
37
+ onValueChange?.(v);
38
+ }
39
+
40
+ return (
41
+ <RadioGroupContext.Provider value={{ value: activeValue, onValueChange: handleValueChange, name }}>
42
+ <div className={cn("grid gap-2", className)} ref={ref} {...props}>
43
+ {children}
44
+ </div>
45
+ </RadioGroupContext.Provider>
46
+ )
47
+ }
48
+ )
49
+ RadioGroup.displayName = "RadioGroup"
50
+
51
+ export interface RadioGroupItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
52
+ value: string
53
+ }
54
+
55
+ const RadioGroupItem = React.forwardRef<HTMLButtonElement, RadioGroupItemProps>(
56
+ ({ className, value, ...props }, ref) => {
57
+ const context = React.useContext(RadioGroupContext)
58
+ const checked = context?.value === value
59
+ const itemState: RadioGroupVariantKey = checked ? "checked" : radioGroupDefaultVariantKey
60
+
61
+ return (
62
+ <button
63
+ type="button"
64
+ role="radio"
65
+ aria-checked={checked}
66
+ onClick={() => context?.onValueChange?.(value)}
67
+ ref={ref}
68
+ className={cn(
69
+ "aspect-square h-4 w-4 rounded-lg border text-foreground ring-offset-background focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
70
+ radioGroupItemStateClasses[itemState],
71
+ className
72
+ )}
73
+ {...props}
74
+ >
75
+ {checked && (
76
+ <div className="flex items-center justify-center">
77
+ <div className="h-2.5 w-2.5 rounded-full bg-foreground" />
78
+ </div>
79
+ )}
80
+ </button>
81
+ )
82
+ }
83
+ )
84
+ RadioGroupItem.displayName = "RadioGroupItem"
85
+
86
+ export { RadioGroup, RadioGroupItem }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> { }
6
+
7
+ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
8
+ ({ className, children, ...props }, ref) => {
9
+ return (
10
+ <div className="relative">
11
+ <select
12
+ className={cn(
13
+ "inline-flex h-10 w-[200px] appearance-none items-center justify-between rounded-lg border border-input bg-transparent px-3 py-2 text-sm font-normal text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground disabled:opacity-50",
14
+ className
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ >
19
+ {children}
20
+ </select>
21
+ <ChevronDown className="pointer-events-none absolute right-3 top-3 h-4 w-4 text-muted-foreground" />
22
+ </div>
23
+ )
24
+ }
25
+ )
26
+ Select.displayName = "Select"
27
+
28
+ export { Select }
@@ -0,0 +1,33 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+ import type { SeparatorVariantKey } from "./generated/variant-keys"
6
+ import { separatorDefaultVariantKey } from "./generated/default-variant-keys"
7
+
8
+ const separatorOrientationClasses: Record<SeparatorVariantKey, string> = {
9
+ horizontal: "w-[200px] h-[1px]",
10
+ vertical: "w-[1px] h-6",
11
+ }
12
+
13
+ export interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ orientation?: SeparatorVariantKey
15
+ className?: string
16
+ }
17
+
18
+ const Separator = React.forwardRef<HTMLDivElement, SeparatorProps>(
19
+ ({ className, orientation = separatorDefaultVariantKey, ...props }, ref) => (
20
+ <div
21
+ ref={ref}
22
+ className={cn(
23
+ "shrink-0 bg-border",
24
+ separatorOrientationClasses[orientation],
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ )
30
+ )
31
+ Separator.displayName = "Separator"
32
+
33
+ export { Separator }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const skeletonVariants = cva(
6
+ "animate-pulse rounded-md bg-muted",
7
+ {
8
+ variants: {
9
+ shape: {
10
+ rectangle: "h-4 w-[200px]",
11
+ circle: "h-10 w-10 rounded-full",
12
+ text: "h-3.5 w-[160px] rounded-full",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ shape: "rectangle",
17
+ },
18
+ }
19
+ )
20
+
21
+ export interface SkeletonProps
22
+ extends React.HTMLAttributes<HTMLDivElement>,
23
+ VariantProps<typeof skeletonVariants> {}
24
+
25
+ const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
26
+ ({ className, shape, ...props }, ref) => (
27
+ <div
28
+ ref={ref}
29
+ className={cn(skeletonVariants({ shape }), className)}
30
+ {...props}
31
+ />
32
+ )
33
+ )
34
+ Skeleton.displayName = "Skeleton"
35
+
36
+ export { Skeleton, skeletonVariants }
@@ -0,0 +1,26 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ export interface SliderProps extends React.InputHTMLAttributes<HTMLInputElement> { }
7
+
8
+ const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
9
+ ({ className, ...props }, ref) => (
10
+ <div className="relative flex w-[200px] touch-none select-none items-center">
11
+ <input
12
+ type="range"
13
+ ref={ref}
14
+ className={cn(
15
+ "h-5 w-full cursor-pointer appearance-none rounded-[10px] bg-input disabled:cursor-not-allowed disabled:opacity-50",
16
+ "[&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-foreground [&::-webkit-slider-thumb]:bg-background [&::-webkit-slider-thumb]:transition-colors [&::-webkit-slider-thumb]:focus-visible:outline-none [&::-webkit-slider-thumb]:focus-visible:ring-1 [&::-webkit-slider-thumb]:focus-visible:ring-ring [&::-webkit-slider-thumb]:focus-visible:ring-offset-1 [&::-webkit-slider-thumb]:focus-visible:ring-offset-background [&::-webkit-slider-thumb]:disabled:pointer-events-none [&::-webkit-slider-thumb]:disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ </div>
22
+ )
23
+ )
24
+ Slider.displayName = "Slider"
25
+
26
+ export { Slider }