@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,140 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Send, Paperclip, Loader2, Square } from "lucide-react";
5
+ import { Button } from "../atoms/Button";
6
+ import { Textarea } from "../atoms/Textarea";
7
+ import { cn } from "../../lib/utils";
8
+ import { buttonDefaultVariantKey } from "../atoms/generated/default-variant-keys";
9
+
10
+ export interface AIChatInputProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
11
+ onSend: (message: string, files?: File[]) => void;
12
+ onStop?: () => void;
13
+ isProcessing?: boolean;
14
+ placeholder?: string;
15
+ enableAttachments?: boolean;
16
+ }
17
+
18
+ export function AIChatInput({
19
+ onSend,
20
+ onStop,
21
+ isProcessing = false,
22
+ placeholder = "メッセージを入力...",
23
+ enableAttachments = true,
24
+ className,
25
+ disabled,
26
+ ...props
27
+ }: AIChatInputProps) {
28
+ const [message, setMessage] = React.useState("");
29
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null);
30
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
31
+
32
+ const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
33
+ setMessage(e.target.value);
34
+ adjustHeight();
35
+ };
36
+
37
+ const adjustHeight = () => {
38
+ const textarea = textareaRef.current;
39
+ if (textarea) {
40
+ textarea.style.height = "auto";
41
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
42
+ }
43
+ };
44
+
45
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
46
+ if (e.key === "Enter" && !e.shiftKey) {
47
+ e.preventDefault();
48
+ handleSend();
49
+ }
50
+ };
51
+
52
+ const handleSend = () => {
53
+ if (!message.trim() || isProcessing) return;
54
+ onSend(message);
55
+ setMessage("");
56
+ if (textareaRef.current) {
57
+ textareaRef.current.style.height = "auto";
58
+ }
59
+ };
60
+
61
+ const handleUploadClick = () => {
62
+ fileInputRef.current?.click();
63
+ };
64
+
65
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
66
+ if (e.target.files && e.target.files.length > 0) {
67
+ // For now, we just pass the files to onSend alongside an empty message or handle it differently
68
+ // In a real app, we might stage files. Here we'll just expose the capability.
69
+ onSend(message, Array.from(e.target.files));
70
+ setMessage(""); // Clear message if sent with file
71
+
72
+ if (textareaRef.current) {
73
+ textareaRef.current.value = ""; // Reset input
74
+ textareaRef.current.style.height = "auto";
75
+ }
76
+ if (fileInputRef.current) {
77
+ fileInputRef.current.value = ""; // Reset file input
78
+ }
79
+ }
80
+ };
81
+
82
+ return (
83
+ <div className={cn("relative flex flex-col flex-row items-center items-end w-[640px] max-w-full gap-2 p-4 bg-background border border-t border-border", className)}>
84
+ {enableAttachments && (
85
+ <>
86
+ <input
87
+ type="file"
88
+ multiple
89
+ className="hidden"
90
+ ref={fileInputRef}
91
+ onChange={handleFileChange}
92
+ />
93
+ <Button
94
+ variant="ghost"
95
+ size="icon"
96
+ className="flex-shrink-0 text-muted-foreground hover:text-foreground mb-0.5"
97
+ onClick={handleUploadClick}
98
+ disabled={disabled || isProcessing}
99
+ >
100
+ <Paperclip className="h-5 w-5" />
101
+ </Button>
102
+ </>
103
+ )}
104
+
105
+ <div className="flex-1 relative">
106
+ <Textarea
107
+ ref={textareaRef}
108
+ value={message}
109
+ onChange={handleInput}
110
+ onKeyDown={handleKeyDown}
111
+ placeholder={placeholder}
112
+ disabled={disabled || isProcessing}
113
+ className="min-h-[44px] max-h-[200px] py-3 pr-4 resize-none rounded-xl border-border focus:ring-ring bg-muted"
114
+ rows={1}
115
+ />
116
+ </div>
117
+
118
+ {isProcessing ? (
119
+ <Button
120
+ variant="destructive"
121
+ size="icon"
122
+ className="flex-shrink-0 rounded-xl mb-0.5"
123
+ onClick={onStop}
124
+ >
125
+ <Square className="h-4 w-4 fill-current" />
126
+ </Button>
127
+ ) : (
128
+ <Button
129
+ variant={buttonDefaultVariantKey}
130
+ size="icon"
131
+ className="mb-0.5 flex-shrink-0 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90"
132
+ onClick={handleSend}
133
+ disabled={!message.trim() || disabled}
134
+ >
135
+ <Send className="h-4 w-4" />
136
+ </Button>
137
+ )}
138
+ </div>
139
+ );
140
+ }
@@ -0,0 +1,109 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { User, Sparkles, Copy, ThumbsUp, ThumbsDown } from "lucide-react";
5
+ import { Avatar, AvatarFallback, AvatarImage } from "../atoms/Avatar";
6
+ import { Button } from "../atoms/Button";
7
+ import { cn } from "../../lib/utils";
8
+ import type { AiChatMessageVariantKey } from "./generated/variant-keys";
9
+ import { aiChatMessageDefaultVariantKey } from "./generated/default-variant-keys";
10
+
11
+ type AIChatMessageRole = AiChatMessageVariantKey | "system";
12
+
13
+ export interface AIChatMessageProps {
14
+ role: AIChatMessageRole;
15
+ content: string;
16
+ avatarSrc?: string;
17
+ userName?: string;
18
+ timestamp?: string;
19
+ isTyping?: boolean;
20
+ className?: string;
21
+ }
22
+
23
+ export function AIChatMessage({
24
+ role,
25
+ content,
26
+ avatarSrc,
27
+ userName = role === "user" ? "You" : "AI Assistant",
28
+ timestamp,
29
+ isTyping = false,
30
+ className
31
+ }: AIChatMessageProps) {
32
+ if (role === "system") {
33
+ return (
34
+ <div className={cn("flex justify-center my-4", className)}>
35
+ <span className="text-xs text-muted-foreground bg-muted px-3 py-1 rounded-full">
36
+ {content}
37
+ </span>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ const messageRole: AiChatMessageVariantKey =
43
+ role === "user" ? "user" : aiChatMessageDefaultVariantKey;
44
+ const isUser = messageRole === "user";
45
+
46
+ const rowDirectionClasses: Record<AiChatMessageVariantKey, string> = {
47
+ assistant: "flex-row",
48
+ user: "flex-row-reverse",
49
+ };
50
+
51
+ const bubbleClasses: Record<AiChatMessageVariantKey, string> = {
52
+ assistant: "bg-background border border-border text-foreground rounded-tl-none",
53
+ user: "bg-primary text-primary-foreground rounded-tr-none",
54
+ };
55
+
56
+ return (
57
+ <div className={cn(
58
+ "flex gap-4 p-4 w-[720px] max-w-full transition-colors hover:bg-muted/50 group",
59
+ rowDirectionClasses[messageRole],
60
+ className
61
+ )}>
62
+ {/* Avatar */}
63
+ <div className="flex-shrink-0 mt-1">
64
+ <Avatar className={cn("h-8 w-8", isUser ? "ring-2 ring-primary/20" : "bg-primary text-primary-foreground")}>
65
+ {avatarSrc ? (
66
+ <AvatarImage src={avatarSrc} alt={userName} />
67
+ ) : (
68
+ <AvatarFallback className={cn(isUser ? "bg-background text-muted-foreground" : "bg-transparent text-primary-foreground")}>
69
+ {isUser ? <User className="h-4 w-4" /> : <Sparkles className="h-4 w-4" />}
70
+ </AvatarFallback>
71
+ )}
72
+ </Avatar>
73
+ </div>
74
+
75
+ {/* Content Body */}
76
+ <div className={cn("flex flex-col max-w-[80%]", isUser ? "items-end" : "items-start")}>
77
+ <div className="flex items-center gap-2 mb-1">
78
+ <span className="text-sm font-semibold text-foreground">{userName}</span>
79
+ {timestamp && <span className="text-xs text-muted-foreground">{timestamp}</span>}
80
+ </div>
81
+
82
+ <div className={cn(
83
+ "text-sm leading-relaxed rounded-2xl px-4 py-3 shadow-sm whitespace-pre-wrap",
84
+ bubbleClasses[messageRole]
85
+ )}>
86
+ {content}
87
+ {isTyping && (
88
+ <span className="inline-block w-1.5 h-4 ml-1 align-middle bg-current animate-pulse" />
89
+ )}
90
+ </div>
91
+
92
+ {/* Actions (Only for assistant messages usually, but logic allows both) */}
93
+ {!isUser && !isTyping && (
94
+ <div className="flex items-center gap-1 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">
95
+ <Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground hover:text-foreground">
96
+ <Copy className="h-3 w-3" />
97
+ </Button>
98
+ <Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground hover:text-foreground">
99
+ <ThumbsUp className="h-3 w-3" />
100
+ </Button>
101
+ <Button variant="ghost" size="icon" className="h-6 w-6 text-muted-foreground hover:text-foreground">
102
+ <ThumbsDown className="h-3 w-3" />
103
+ </Button>
104
+ </div>
105
+ )}
106
+ </div>
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,99 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+ import type { AccordionVariantKey } from "./generated/variant-keys"
9
+ import { accordionDefaultVariantKey } from "./generated/default-variant-keys"
10
+
11
+ function buildVariantStateMap(
12
+ defaultVariantKey: AccordionVariantKey,
13
+ defaultStateClass: string,
14
+ alternateStateClass: string
15
+ ): Record<AccordionVariantKey, string> {
16
+ return defaultVariantKey === "collapsed"
17
+ ? { collapsed: defaultStateClass, expanded: alternateStateClass }
18
+ : { collapsed: alternateStateClass, expanded: defaultStateClass }
19
+ }
20
+
21
+ const expandedAccordionVariantKey: AccordionVariantKey =
22
+ accordionDefaultVariantKey === "collapsed" ? "expanded" : "collapsed"
23
+
24
+ const triggerStateClasses = buildVariantStateMap(
25
+ accordionDefaultVariantKey,
26
+ "",
27
+ "[&[data-state=open]>svg]:rotate-180"
28
+ )
29
+
30
+ const contentStateClasses = buildVariantStateMap(
31
+ accordionDefaultVariantKey,
32
+ "data-[state=closed]:animate-accordion-up",
33
+ "data-[state=open]:animate-accordion-down"
34
+ )
35
+
36
+ const Accordion = React.forwardRef<
37
+ React.ElementRef<typeof AccordionPrimitive.Root>,
38
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>
39
+ >(({ className, ...props }, ref) => (
40
+ <AccordionPrimitive.Root
41
+ ref={ref}
42
+ className={cn("flex flex-col w-[400px] border", className)}
43
+ {...props}
44
+ />
45
+ ))
46
+ Accordion.displayName = AccordionPrimitive.Root.displayName
47
+
48
+ const AccordionItem = React.forwardRef<
49
+ React.ElementRef<typeof AccordionPrimitive.Item>,
50
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
51
+ >(({ className, ...props }, ref) => (
52
+ <AccordionPrimitive.Item
53
+ ref={ref}
54
+ className={cn("border-b", className)}
55
+ {...props}
56
+ />
57
+ ))
58
+ AccordionItem.displayName = "AccordionItem"
59
+
60
+ const AccordionTrigger = React.forwardRef<
61
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
62
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
63
+ >(({ className, children, ...props }, ref) => (
64
+ <AccordionPrimitive.Header className="flex">
65
+ <AccordionPrimitive.Trigger
66
+ ref={ref}
67
+ className={cn(
68
+ "flex flex-1 items-center justify-between px-4 py-4 text-sm font-medium transition-all hover:underline",
69
+ triggerStateClasses[expandedAccordionVariantKey],
70
+ className
71
+ )}
72
+ {...props}
73
+ >
74
+ {children}
75
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
76
+ </AccordionPrimitive.Trigger>
77
+ </AccordionPrimitive.Header>
78
+ ))
79
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
80
+
81
+ const AccordionContent = React.forwardRef<
82
+ React.ElementRef<typeof AccordionPrimitive.Content>,
83
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
84
+ >(({ className, children, ...props }, ref) => (
85
+ <AccordionPrimitive.Content
86
+ ref={ref}
87
+ className={cn(
88
+ "overflow-hidden text-sm text-muted-foreground transition-all",
89
+ contentStateClasses[accordionDefaultVariantKey],
90
+ contentStateClasses[expandedAccordionVariantKey]
91
+ )}
92
+ {...props}
93
+ >
94
+ <div className={cn("px-4 pb-4 pt-0", className)}>{children}</div>
95
+ </AccordionPrimitive.Content>
96
+ ))
97
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
98
+
99
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,115 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-col flex-row flex-wrap items-center w-fit gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("underline underline-offset-2 transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-medium text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:size-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
@@ -0,0 +1,60 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { DayPicker } from "react-day-picker"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ ...props
15
+ }: CalendarProps) {
16
+ return (
17
+ <DayPicker
18
+ showOutsideDays={showOutsideDays}
19
+ className={cn("flex flex-col w-[320px] rounded-md rounded-lg border bg-card p-4", className)}
20
+ classNames={{
21
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0 relative",
22
+ month: "space-y-4",
23
+ caption: "flex justify-center pt-1 relative items-center w-full",
24
+ month_caption: "flex justify-center pt-1 relative items-center w-full",
25
+ caption_label: "text-sm font-semibold",
26
+ nav: "absolute left-0 top-0 right-0 flex items-center justify-between px-1 pointer-events-none",
27
+ nav_button: cn(
28
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 text-foreground pointer-events-auto hover:bg-accent rounded-md flex items-center justify-center transition-colors"
29
+ ),
30
+ nav_button_previous: "absolute left-1 top-1",
31
+ nav_button_next: "absolute right-1 top-1",
32
+ table: "w-full border-collapse space-y-1",
33
+ head_row: "",
34
+ head_cell:
35
+ "text-muted-foreground rounded-md w-9 font-normal text-xs",
36
+ row: "w-full mt-2",
37
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
38
+ day: "h-9 w-9 p-0 font-normal aria-selected:opacity-100",
39
+ day_button: cn(
40
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100 hover:bg-accent/50 rounded-md flex items-center justify-center transition-colors"
41
+ ),
42
+ day_range_end: "day-range-end",
43
+ day_selected:
44
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45
+ day_today: "bg-accent text-accent-foreground",
46
+ day_outside:
47
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
48
+ day_disabled: "text-muted-foreground opacity-50",
49
+ day_range_middle:
50
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+ Calendar.displayName = "Calendar"
59
+
60
+ export { Calendar }
@@ -0,0 +1,78 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../lib/utils"
3
+
4
+ const Card = React.forwardRef<
5
+ HTMLDivElement,
6
+ React.HTMLAttributes<HTMLDivElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div
9
+ ref={ref}
10
+ className={cn(
11
+ "w-[350px] rounded-lg border bg-card text-card-foreground shadow-sm",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ ))
17
+ Card.displayName = "Card"
18
+
19
+ const CardHeader = React.forwardRef<
20
+ HTMLDivElement,
21
+ React.HTMLAttributes<HTMLDivElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
26
+ {...props}
27
+ />
28
+ ))
29
+ CardHeader.displayName = "CardHeader"
30
+
31
+ const CardTitle = React.forwardRef<
32
+ HTMLParagraphElement,
33
+ React.HTMLAttributes<HTMLHeadingElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <h3
36
+ ref={ref}
37
+ className={cn(
38
+ "text-xl font-semibold leading-none tracking-tight",
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ ))
44
+ CardTitle.displayName = "CardTitle"
45
+
46
+ const CardDescription = React.forwardRef<
47
+ HTMLParagraphElement,
48
+ React.HTMLAttributes<HTMLParagraphElement>
49
+ >(({ className, ...props }, ref) => (
50
+ <p
51
+ ref={ref}
52
+ className={cn("text-sm text-muted-foreground", className)}
53
+ {...props}
54
+ />
55
+ ))
56
+ CardDescription.displayName = "CardDescription"
57
+
58
+ const CardContent = React.forwardRef<
59
+ HTMLDivElement,
60
+ React.HTMLAttributes<HTMLDivElement>
61
+ >(({ className, ...props }, ref) => (
62
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
63
+ ))
64
+ CardContent.displayName = "CardContent"
65
+
66
+ const CardFooter = React.forwardRef<
67
+ HTMLDivElement,
68
+ React.HTMLAttributes<HTMLDivElement>
69
+ >(({ className, ...props }, ref) => (
70
+ <div
71
+ ref={ref}
72
+ className={cn("flex items-center justify-between gap-3 p-6 pt-0", className)}
73
+ {...props}
74
+ />
75
+ ))
76
+ CardFooter.displayName = "CardFooter"
77
+
78
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }