@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.
- package/README.md +129 -0
- package/design/atoms-metadata.json +82 -0
- package/design/molecules-metadata.json +130 -0
- package/design/organisms-metadata.json +38 -0
- package/design/templates-metadata.json +38 -0
- package/package.json +158 -0
- package/src/components/atoms/Alert.tsx +63 -0
- package/src/components/atoms/Avatar.tsx +57 -0
- package/src/components/atoms/Badge.tsx +30 -0
- package/src/components/atoms/Button.tsx +29 -0
- package/src/components/atoms/ButtonVariants.ts +37 -0
- package/src/components/atoms/Checkbox.tsx +52 -0
- package/src/components/atoms/Img.tsx +102 -0
- package/src/components/atoms/Input.tsx +37 -0
- package/src/components/atoms/Kbd.tsx +22 -0
- package/src/components/atoms/Label.tsx +22 -0
- package/src/components/atoms/Progress.tsx +38 -0
- package/src/components/atoms/RadioGroup.tsx +86 -0
- package/src/components/atoms/Select.tsx +28 -0
- package/src/components/atoms/Separator.tsx +33 -0
- package/src/components/atoms/Skeleton.tsx +36 -0
- package/src/components/atoms/Slider.tsx +26 -0
- package/src/components/atoms/Spinner.tsx +34 -0
- package/src/components/atoms/Switch.tsx +47 -0
- package/src/components/atoms/Textarea.tsx +34 -0
- package/src/components/atoms/ToggleGroup.tsx +60 -0
- package/src/components/atoms/ToolPill.tsx +77 -0
- package/src/components/atoms/generated/default-variant-keys.ts +36 -0
- package/src/components/atoms/generated/variant-keys.ts +61 -0
- package/src/components/generated/component-manifest.ts +741 -0
- package/src/components/generated/component-style-hints.ts +1262 -0
- package/src/components/molecules/AIChatInput.tsx +140 -0
- package/src/components/molecules/AIChatMessage.tsx +109 -0
- package/src/components/molecules/Accordion.tsx +99 -0
- package/src/components/molecules/Breadcrumb.tsx +115 -0
- package/src/components/molecules/Calendar.tsx +60 -0
- package/src/components/molecules/Card.tsx +78 -0
- package/src/components/molecules/Carousel.tsx +261 -0
- package/src/components/molecules/Command.tsx +152 -0
- package/src/components/molecules/ContextMenu.tsx +200 -0
- package/src/components/molecules/Dialog.tsx +122 -0
- package/src/components/molecules/DropdownMenu.tsx +200 -0
- package/src/components/molecules/FilterButton.tsx +133 -0
- package/src/components/molecules/Form.tsx +90 -0
- package/src/components/molecules/HoverCard.tsx +29 -0
- package/src/components/molecules/List.tsx +120 -0
- package/src/components/molecules/Menubar.tsx +231 -0
- package/src/components/molecules/Modal.tsx +66 -0
- package/src/components/molecules/NotificationCenter.tsx +118 -0
- package/src/components/molecules/Pagination.tsx +118 -0
- package/src/components/molecules/Popover.tsx +31 -0
- package/src/components/molecules/ProgressWidget.tsx +40 -0
- package/src/components/molecules/Resizable.tsx +47 -0
- package/src/components/molecules/ScrollArea.tsx +48 -0
- package/src/components/molecules/Sheet.tsx +140 -0
- package/src/components/molecules/SidebarItem.tsx +134 -0
- package/src/components/molecules/SortButton.tsx +56 -0
- package/src/components/molecules/StatusBar.tsx +41 -0
- package/src/components/molecules/Stepper.tsx +108 -0
- package/src/components/molecules/Table.tsx +117 -0
- package/src/components/molecules/Tabs.tsx +64 -0
- package/src/components/molecules/Toast.tsx +57 -0
- package/src/components/molecules/Tooltip.tsx +30 -0
- package/src/components/molecules/generated/default-variant-keys.ts +22 -0
- package/src/components/molecules/generated/variant-keys.ts +33 -0
- package/src/components/organisms/AppRail.tsx +28 -0
- package/src/components/organisms/CommandPalette.tsx +58 -0
- package/src/components/organisms/FileUploader.tsx +151 -0
- package/src/components/organisms/FloatingPanel.tsx +46 -0
- package/src/components/organisms/InspectorPanel.tsx +65 -0
- package/src/components/organisms/RightRail.tsx +29 -0
- package/src/components/organisms/ShareModal.tsx +182 -0
- package/src/components/organisms/SpatialCanvas.tsx +36 -0
- package/src/components/organisms/ToastProvider.tsx +49 -0
- package/src/components/templates/AuthTemplate.tsx +58 -0
- package/src/components/templates/BannalyzeTemplate.tsx +55 -0
- package/src/components/templates/ChatTemplate.tsx +55 -0
- package/src/components/templates/DashboardTemplate.tsx +34 -0
- package/src/components/templates/EditorTemplate.tsx +46 -0
- package/src/components/templates/KanbanTemplate.tsx +38 -0
- package/src/components/templates/LandingTemplate.tsx +53 -0
- package/src/components/templates/MediaLibraryTemplate.tsx +55 -0
- package/src/components/templates/SettingsTemplate.tsx +48 -0
- package/src/globals.css +108 -0
- package/src/index.ts +89 -0
- package/src/lib/utils.ts +6 -0
- package/tailwind-preset.js +11 -0
- 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 }
|