@oppulence/design-system 1.0.4 → 1.0.5
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/hooks/use-resize-observer.ts +24 -0
- package/lib/ai.ts +31 -0
- package/package.json +19 -1
- package/src/components/atoms/animated-size-container.tsx +59 -0
- package/src/components/atoms/currency-input.tsx +16 -0
- package/src/components/atoms/icons.tsx +840 -0
- package/src/components/atoms/image.tsx +23 -0
- package/src/components/atoms/index.ts +10 -0
- package/src/components/atoms/loader.tsx +92 -0
- package/src/components/atoms/quantity-input.tsx +103 -0
- package/src/components/atoms/record-button.tsx +178 -0
- package/src/components/atoms/submit-button.tsx +26 -0
- package/src/components/atoms/text-effect.tsx +251 -0
- package/src/components/atoms/text-shimmer.tsx +74 -0
- package/src/components/molecules/actions.tsx +53 -0
- package/src/components/molecules/branch.tsx +192 -0
- package/src/components/molecules/code-block.tsx +151 -0
- package/src/components/molecules/form.tsx +177 -0
- package/src/components/molecules/index.ts +12 -0
- package/src/components/molecules/inline-citation.tsx +295 -0
- package/src/components/molecules/message.tsx +64 -0
- package/src/components/molecules/sources.tsx +116 -0
- package/src/components/molecules/suggestion.tsx +53 -0
- package/src/components/molecules/task.tsx +74 -0
- package/src/components/molecules/time-range-input.tsx +73 -0
- package/src/components/molecules/tool-call-indicator.tsx +42 -0
- package/src/components/molecules/tool.tsx +130 -0
- package/src/components/organisms/combobox-dropdown.tsx +171 -0
- package/src/components/organisms/conversation.tsx +98 -0
- package/src/components/organisms/date-range-picker.tsx +53 -0
- package/src/components/organisms/editor/extentions/bubble-menu/bubble-item.tsx +30 -0
- package/src/components/organisms/editor/extentions/bubble-menu/bubble-menu-button.tsx +27 -0
- package/src/components/organisms/editor/extentions/bubble-menu/index.tsx +63 -0
- package/src/components/organisms/editor/extentions/bubble-menu/link-item.tsx +104 -0
- package/src/components/organisms/editor/extentions/register.ts +22 -0
- package/src/components/organisms/editor/index.tsx +50 -0
- package/src/components/organisms/editor/styles.css +31 -0
- package/src/components/organisms/editor/utils.ts +19 -0
- package/src/components/organisms/index.ts +11 -0
- package/src/components/organisms/multiple-selector.tsx +632 -0
- package/src/components/organisms/prompt-input.tsx +747 -0
- package/src/components/organisms/reasoning.tsx +170 -0
- package/src/components/organisms/response.tsx +121 -0
- package/src/components/organisms/toast-toaster.tsx +84 -0
- package/src/components/organisms/toast.tsx +124 -0
- package/src/components/organisms/use-toast.tsx +206 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
import React, { useMemo, type JSX } from "react";
|
|
5
|
+
import { cn } from "../../../lib/utils";
|
|
6
|
+
|
|
7
|
+
type TextShimmerSize = "xs" | "sm" | "md" | "lg";
|
|
8
|
+
|
|
9
|
+
export type TextShimmerProps = {
|
|
10
|
+
children: string;
|
|
11
|
+
as?: React.ElementType;
|
|
12
|
+
duration?: number;
|
|
13
|
+
spread?: number;
|
|
14
|
+
size?: TextShimmerSize;
|
|
15
|
+
baseColor?: string;
|
|
16
|
+
gradientColor?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const sizeClasses: Record<TextShimmerSize, string> = {
|
|
20
|
+
xs: "text-xs",
|
|
21
|
+
sm: "text-sm",
|
|
22
|
+
md: "text-base",
|
|
23
|
+
lg: "text-lg",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function TextShimmerComponent({
|
|
27
|
+
children,
|
|
28
|
+
as: Component = "p",
|
|
29
|
+
duration = 2,
|
|
30
|
+
spread = 2,
|
|
31
|
+
size = "sm",
|
|
32
|
+
baseColor,
|
|
33
|
+
gradientColor,
|
|
34
|
+
}: TextShimmerProps) {
|
|
35
|
+
const MotionComponent = motion.create(
|
|
36
|
+
Component as keyof JSX.IntrinsicElements,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const dynamicSpread = useMemo(() => {
|
|
40
|
+
return children.length * spread;
|
|
41
|
+
}, [children, spread]);
|
|
42
|
+
|
|
43
|
+
const style = {
|
|
44
|
+
"--spread": `${dynamicSpread}px`,
|
|
45
|
+
backgroundImage:
|
|
46
|
+
"var(--bg), linear-gradient(var(--base-color), var(--base-color))",
|
|
47
|
+
...(baseColor ? { "--base-color": baseColor } : null),
|
|
48
|
+
...(gradientColor ? { "--base-gradient-color": gradientColor } : null),
|
|
49
|
+
} as React.CSSProperties;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<MotionComponent
|
|
53
|
+
className={cn(
|
|
54
|
+
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text",
|
|
55
|
+
"text-transparent [--base-color:#a1a1aa] [--base-gradient-color:#000]",
|
|
56
|
+
"[background-repeat:no-repeat,padding-box] [--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]",
|
|
57
|
+
"dark:[--base-color:#71717a] dark:[--base-gradient-color:#ffffff] dark:[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--base-gradient-color),#0000_calc(50%+var(--spread)))]",
|
|
58
|
+
sizeClasses[size],
|
|
59
|
+
)}
|
|
60
|
+
initial={{ backgroundPosition: "100% center" }}
|
|
61
|
+
animate={{ backgroundPosition: "0% center" }}
|
|
62
|
+
transition={{
|
|
63
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
64
|
+
duration,
|
|
65
|
+
ease: "linear",
|
|
66
|
+
}}
|
|
67
|
+
style={style}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</MotionComponent>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const TextShimmer = React.memo(TextShimmerComponent);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button, type ButtonProps } from "../atoms/button";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
7
|
+
|
|
8
|
+
export type ActionsProps = Omit<ComponentProps<"div">, "className">;
|
|
9
|
+
|
|
10
|
+
export const Actions = ({ children, ...props }: ActionsProps) => (
|
|
11
|
+
<div className="flex items-center gap-1 text-muted-foreground" {...props}>
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export type ActionProps = Omit<ButtonProps, "className"> & {
|
|
17
|
+
tooltip?: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Action = ({
|
|
22
|
+
tooltip,
|
|
23
|
+
children,
|
|
24
|
+
label,
|
|
25
|
+
variant = "ghost",
|
|
26
|
+
size = "icon-lg",
|
|
27
|
+
...props
|
|
28
|
+
}: ActionProps) => {
|
|
29
|
+
const button = (
|
|
30
|
+
<Button size={size} type="button" variant={variant} {...props}>
|
|
31
|
+
{children}
|
|
32
|
+
<span className="sr-only">{label || tooltip}</span>
|
|
33
|
+
</Button>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (tooltip) {
|
|
37
|
+
return (
|
|
38
|
+
<Tooltip>
|
|
39
|
+
<TooltipTrigger
|
|
40
|
+
render={<Button size={size} type="button" variant={variant} {...props} />}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
<span className="sr-only">{label || tooltip}</span>
|
|
44
|
+
</TooltipTrigger>
|
|
45
|
+
<TooltipContent>
|
|
46
|
+
<p>{tooltip}</p>
|
|
47
|
+
</TooltipContent>
|
|
48
|
+
</Tooltip>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return button;
|
|
53
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ChatRole } from "../../../lib/ai";
|
|
4
|
+
import { cn } from "../../../lib/utils";
|
|
5
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
6
|
+
import type { ButtonHTMLAttributes, HTMLAttributes, ReactElement } from "react";
|
|
7
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
8
|
+
|
|
9
|
+
import { buttonVariants } from "../atoms/button";
|
|
10
|
+
|
|
11
|
+
type BranchContextType = {
|
|
12
|
+
currentBranch: number;
|
|
13
|
+
totalBranches: number;
|
|
14
|
+
goToPrevious: () => void;
|
|
15
|
+
goToNext: () => void;
|
|
16
|
+
branches: ReactElement[];
|
|
17
|
+
setBranches: (branches: ReactElement[]) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const BranchContext = createContext<BranchContextType | null>(null);
|
|
21
|
+
|
|
22
|
+
const useBranch = () => {
|
|
23
|
+
const context = useContext(BranchContext);
|
|
24
|
+
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("Branch components must be used within Branch");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return context;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type BranchProps = Omit<HTMLAttributes<HTMLDivElement>, "className"> & {
|
|
33
|
+
defaultBranch?: number;
|
|
34
|
+
onBranchChange?: (branchIndex: number) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const Branch = ({
|
|
38
|
+
defaultBranch = 0,
|
|
39
|
+
onBranchChange,
|
|
40
|
+
...props
|
|
41
|
+
}: BranchProps) => {
|
|
42
|
+
const [currentBranch, setCurrentBranch] = useState(defaultBranch);
|
|
43
|
+
const [branches, setBranches] = useState<ReactElement[]>([]);
|
|
44
|
+
|
|
45
|
+
const handleBranchChange = (newBranch: number) => {
|
|
46
|
+
setCurrentBranch(newBranch);
|
|
47
|
+
onBranchChange?.(newBranch);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const goToPrevious = () => {
|
|
51
|
+
const newBranch =
|
|
52
|
+
currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
|
53
|
+
handleBranchChange(newBranch);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const goToNext = () => {
|
|
57
|
+
const newBranch =
|
|
58
|
+
currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
|
59
|
+
handleBranchChange(newBranch);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const contextValue: BranchContextType = {
|
|
63
|
+
currentBranch,
|
|
64
|
+
totalBranches: branches.length,
|
|
65
|
+
goToPrevious,
|
|
66
|
+
goToNext,
|
|
67
|
+
branches,
|
|
68
|
+
setBranches,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<BranchContext.Provider value={contextValue}>
|
|
73
|
+
<div className="grid w-full gap-2 [&>div]:pb-0" {...props} />
|
|
74
|
+
</BranchContext.Provider>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type BranchMessagesProps = Omit<HTMLAttributes<HTMLDivElement>, "className">;
|
|
79
|
+
|
|
80
|
+
export const BranchMessages = ({ children, ...props }: BranchMessagesProps) => {
|
|
81
|
+
const { currentBranch, setBranches, branches } = useBranch();
|
|
82
|
+
const childrenArray = Array.isArray(children) ? children : [children];
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (branches.length !== childrenArray.length) {
|
|
86
|
+
setBranches(childrenArray as ReactElement[]);
|
|
87
|
+
}
|
|
88
|
+
}, [childrenArray, branches, setBranches]);
|
|
89
|
+
|
|
90
|
+
return (childrenArray as ReactElement[]).map((branch, index) => (
|
|
91
|
+
<div
|
|
92
|
+
className={cn(
|
|
93
|
+
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
|
94
|
+
index === currentBranch ? "block" : "hidden",
|
|
95
|
+
)}
|
|
96
|
+
key={branch.key}
|
|
97
|
+
{...props}
|
|
98
|
+
>
|
|
99
|
+
{branch}
|
|
100
|
+
</div>
|
|
101
|
+
));
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type BranchSelectorProps = Omit<HTMLAttributes<HTMLDivElement>, "className"> & {
|
|
105
|
+
from: ChatRole;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const BranchSelector = ({ from, ...props }: BranchSelectorProps) => {
|
|
109
|
+
const { totalBranches } = useBranch();
|
|
110
|
+
|
|
111
|
+
if (totalBranches <= 1) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
className={cn(
|
|
118
|
+
"flex items-center gap-2 self-end px-10",
|
|
119
|
+
from === "assistant" ? "justify-start" : "justify-end",
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type BranchPreviousProps = Omit<
|
|
127
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
128
|
+
"className"
|
|
129
|
+
>;
|
|
130
|
+
|
|
131
|
+
export const BranchPrevious = ({ children, ...props }: BranchPreviousProps) => {
|
|
132
|
+
const { goToPrevious, totalBranches } = useBranch();
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<button
|
|
136
|
+
aria-label="Previous branch"
|
|
137
|
+
className={cn(
|
|
138
|
+
buttonVariants({ variant: "ghost", size: "icon-round-sm" }),
|
|
139
|
+
"text-muted-foreground hover:text-foreground",
|
|
140
|
+
)}
|
|
141
|
+
disabled={totalBranches <= 1}
|
|
142
|
+
onClick={goToPrevious}
|
|
143
|
+
type="button"
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
{children ?? <ChevronLeftIcon size={14} />}
|
|
147
|
+
</button>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type BranchNextProps = Omit<
|
|
152
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
153
|
+
"className"
|
|
154
|
+
>;
|
|
155
|
+
|
|
156
|
+
export const BranchNext = ({ children, ...props }: BranchNextProps) => {
|
|
157
|
+
const { goToNext, totalBranches } = useBranch();
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<button
|
|
161
|
+
aria-label="Next branch"
|
|
162
|
+
className={cn(
|
|
163
|
+
buttonVariants({ variant: "ghost", size: "icon-round-sm" }),
|
|
164
|
+
"text-muted-foreground hover:text-foreground",
|
|
165
|
+
)}
|
|
166
|
+
disabled={totalBranches <= 1}
|
|
167
|
+
onClick={goToNext}
|
|
168
|
+
type="button"
|
|
169
|
+
{...props}
|
|
170
|
+
>
|
|
171
|
+
{children ?? <ChevronRightIcon size={14} />}
|
|
172
|
+
</button>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export type BranchPageProps = Omit<
|
|
177
|
+
HTMLAttributes<HTMLSpanElement>,
|
|
178
|
+
"className"
|
|
179
|
+
>;
|
|
180
|
+
|
|
181
|
+
export const BranchPage = ({ ...props }: BranchPageProps) => {
|
|
182
|
+
const { currentBranch, totalBranches } = useBranch();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<span
|
|
186
|
+
className="font-medium text-muted-foreground text-xs tabular-nums"
|
|
187
|
+
{...props}
|
|
188
|
+
>
|
|
189
|
+
{currentBranch + 1} of {totalBranches}
|
|
190
|
+
</span>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
4
|
+
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
5
|
+
import { createContext, useContext, useState } from "react";
|
|
6
|
+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
7
|
+
import {
|
|
8
|
+
oneDark,
|
|
9
|
+
oneLight,
|
|
10
|
+
} from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
11
|
+
|
|
12
|
+
import { cn } from "../../../lib/utils";
|
|
13
|
+
import { buttonVariants } from "../atoms/button";
|
|
14
|
+
|
|
15
|
+
type CodeBlockContextType = {
|
|
16
|
+
code: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const CodeBlockContext = createContext<CodeBlockContextType>({
|
|
20
|
+
code: "",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type CodeBlockProps = Omit<HTMLAttributes<HTMLDivElement>, "className"> & {
|
|
24
|
+
code: string;
|
|
25
|
+
language: string;
|
|
26
|
+
showLineNumbers?: boolean;
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const CodeBlock = ({
|
|
31
|
+
code,
|
|
32
|
+
language,
|
|
33
|
+
showLineNumbers = false,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: CodeBlockProps) => (
|
|
37
|
+
<CodeBlockContext.Provider value={{ code }}>
|
|
38
|
+
<div
|
|
39
|
+
className="relative w-full overflow-hidden rounded-md border bg-background text-foreground"
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
<div className="relative">
|
|
43
|
+
{/* @ts-expect-error - SyntaxHighlighter is not a valid JSX component */}
|
|
44
|
+
<SyntaxHighlighter
|
|
45
|
+
className="overflow-hidden dark:hidden"
|
|
46
|
+
codeTagProps={{
|
|
47
|
+
className: "text-sm",
|
|
48
|
+
}}
|
|
49
|
+
customStyle={{
|
|
50
|
+
margin: 0,
|
|
51
|
+
padding: "1rem",
|
|
52
|
+
fontSize: "0.875rem",
|
|
53
|
+
background: "hsl(var(--background))",
|
|
54
|
+
color: "hsl(var(--foreground))",
|
|
55
|
+
}}
|
|
56
|
+
language={language}
|
|
57
|
+
lineNumberStyle={{
|
|
58
|
+
color: "hsl(var(--muted-foreground))",
|
|
59
|
+
paddingRight: "1rem",
|
|
60
|
+
minWidth: "2.5rem",
|
|
61
|
+
}}
|
|
62
|
+
showLineNumbers={showLineNumbers}
|
|
63
|
+
style={oneLight}
|
|
64
|
+
>
|
|
65
|
+
{code}
|
|
66
|
+
</SyntaxHighlighter>
|
|
67
|
+
{/* @ts-expect-error - SyntaxHighlighter is not a valid JSX component */}
|
|
68
|
+
<SyntaxHighlighter
|
|
69
|
+
className="hidden overflow-hidden dark:block"
|
|
70
|
+
codeTagProps={{
|
|
71
|
+
className: "text-sm",
|
|
72
|
+
}}
|
|
73
|
+
customStyle={{
|
|
74
|
+
margin: 0,
|
|
75
|
+
padding: "1rem",
|
|
76
|
+
fontSize: "0.875rem",
|
|
77
|
+
background: "hsl(var(--background))",
|
|
78
|
+
color: "hsl(var(--foreground))",
|
|
79
|
+
}}
|
|
80
|
+
language={language}
|
|
81
|
+
lineNumberStyle={{
|
|
82
|
+
color: "hsl(var(--muted-foreground))",
|
|
83
|
+
paddingRight: "1rem",
|
|
84
|
+
minWidth: "2.5rem",
|
|
85
|
+
}}
|
|
86
|
+
showLineNumbers={showLineNumbers}
|
|
87
|
+
style={oneDark}
|
|
88
|
+
>
|
|
89
|
+
{code}
|
|
90
|
+
</SyntaxHighlighter>
|
|
91
|
+
{children && (
|
|
92
|
+
<div className="absolute top-2 right-2 flex items-center gap-2">
|
|
93
|
+
{children}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</CodeBlockContext.Provider>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
export type CodeBlockCopyButtonProps = Omit<
|
|
102
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
103
|
+
"className"
|
|
104
|
+
> & {
|
|
105
|
+
onCopy?: () => void;
|
|
106
|
+
onError?: (error: Error) => void;
|
|
107
|
+
timeout?: number;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const CodeBlockCopyButton = ({
|
|
111
|
+
onCopy,
|
|
112
|
+
onError,
|
|
113
|
+
timeout = 2000,
|
|
114
|
+
children,
|
|
115
|
+
...props
|
|
116
|
+
}: CodeBlockCopyButtonProps) => {
|
|
117
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
118
|
+
const { code } = useContext(CodeBlockContext);
|
|
119
|
+
|
|
120
|
+
const copyToClipboard = async () => {
|
|
121
|
+
if (typeof window === "undefined" || !navigator.clipboard.writeText) {
|
|
122
|
+
onError?.(new Error("Clipboard API not available"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await navigator.clipboard.writeText(code);
|
|
128
|
+
setIsCopied(true);
|
|
129
|
+
onCopy?.();
|
|
130
|
+
setTimeout(() => setIsCopied(false), timeout);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
onError?.(error as Error);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const Icon = isCopied ? CheckIcon : CopyIcon;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<button
|
|
140
|
+
className={cn(
|
|
141
|
+
buttonVariants({ variant: "ghost", size: "icon-xs" }),
|
|
142
|
+
"shrink-0",
|
|
143
|
+
)}
|
|
144
|
+
onClick={copyToClipboard}
|
|
145
|
+
type="button"
|
|
146
|
+
{...props}
|
|
147
|
+
>
|
|
148
|
+
{children ?? <Icon size={14} />}
|
|
149
|
+
</button>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
Controller,
|
|
5
|
+
type ControllerProps,
|
|
6
|
+
type FieldPath,
|
|
7
|
+
type FieldValues,
|
|
8
|
+
FormProvider,
|
|
9
|
+
useFormContext,
|
|
10
|
+
} from "react-hook-form";
|
|
11
|
+
|
|
12
|
+
const Form = FormProvider;
|
|
13
|
+
|
|
14
|
+
type FormFieldContextValue<
|
|
15
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
16
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
17
|
+
> = {
|
|
18
|
+
name: TName;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
22
|
+
{} as FormFieldContextValue,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const FormField = <
|
|
26
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
27
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
28
|
+
>({
|
|
29
|
+
...props
|
|
30
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
31
|
+
return (
|
|
32
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
33
|
+
<Controller {...props} />
|
|
34
|
+
</FormFieldContext.Provider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const useFormField = () => {
|
|
39
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
40
|
+
const itemContext = React.useContext(FormItemContext);
|
|
41
|
+
const { getFieldState, formState } = useFormContext();
|
|
42
|
+
|
|
43
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
44
|
+
|
|
45
|
+
if (!fieldContext) {
|
|
46
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { id } = itemContext;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
name: fieldContext.name,
|
|
54
|
+
formItemId: `${id}-form-item`,
|
|
55
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
56
|
+
formMessageId: `${id}-form-item-message`,
|
|
57
|
+
...fieldState,
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type FormItemContextValue = {
|
|
62
|
+
id: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
66
|
+
{} as FormItemContextValue,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const FormItem = React.forwardRef<
|
|
70
|
+
HTMLDivElement,
|
|
71
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, "className">
|
|
72
|
+
>(({ ...props }, ref) => {
|
|
73
|
+
const id = React.useId();
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<FormItemContext.Provider value={{ id }}>
|
|
77
|
+
<div ref={ref} className="space-y-2" {...props} />
|
|
78
|
+
</FormItemContext.Provider>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
FormItem.displayName = "FormItem";
|
|
82
|
+
|
|
83
|
+
const FormLabel = React.forwardRef<
|
|
84
|
+
HTMLLabelElement,
|
|
85
|
+
Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "className">
|
|
86
|
+
>(({ ...props }, ref) => {
|
|
87
|
+
const { error, formItemId } = useFormField();
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<label
|
|
91
|
+
ref={ref}
|
|
92
|
+
htmlFor={formItemId}
|
|
93
|
+
className={
|
|
94
|
+
error
|
|
95
|
+
? "gap-2 text-sm leading-none font-medium text-destructive group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed"
|
|
96
|
+
: "gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed"
|
|
97
|
+
}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
FormLabel.displayName = "FormLabel";
|
|
103
|
+
|
|
104
|
+
const FormControl = React.forwardRef<
|
|
105
|
+
React.ElementRef<typeof Slot>,
|
|
106
|
+
React.ComponentPropsWithoutRef<typeof Slot>
|
|
107
|
+
>(({ ...props }, ref) => {
|
|
108
|
+
const { error, formItemId, formDescriptionId, formMessageId } =
|
|
109
|
+
useFormField();
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Slot
|
|
113
|
+
ref={ref}
|
|
114
|
+
id={formItemId}
|
|
115
|
+
aria-describedby={
|
|
116
|
+
!error
|
|
117
|
+
? `${formDescriptionId}`
|
|
118
|
+
: `${formDescriptionId} ${formMessageId}`
|
|
119
|
+
}
|
|
120
|
+
aria-invalid={!!error}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
FormControl.displayName = "FormControl";
|
|
126
|
+
|
|
127
|
+
const FormDescription = React.forwardRef<
|
|
128
|
+
HTMLParagraphElement,
|
|
129
|
+
Omit<React.HTMLAttributes<HTMLParagraphElement>, "className">
|
|
130
|
+
>(({ ...props }, ref) => {
|
|
131
|
+
const { formDescriptionId } = useFormField();
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<p
|
|
135
|
+
ref={ref}
|
|
136
|
+
id={formDescriptionId}
|
|
137
|
+
className="text-[0.8rem] text-muted-foreground"
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
FormDescription.displayName = "FormDescription";
|
|
143
|
+
|
|
144
|
+
const FormMessage = React.forwardRef<
|
|
145
|
+
HTMLParagraphElement,
|
|
146
|
+
Omit<React.HTMLAttributes<HTMLParagraphElement>, "className">
|
|
147
|
+
>(({ children, ...props }, ref) => {
|
|
148
|
+
const { error, formMessageId } = useFormField();
|
|
149
|
+
const body = error ? String(error?.message) : children;
|
|
150
|
+
|
|
151
|
+
if (!body) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<p
|
|
157
|
+
ref={ref}
|
|
158
|
+
id={formMessageId}
|
|
159
|
+
className="text-[0.8rem] font-medium text-destructive"
|
|
160
|
+
{...props}
|
|
161
|
+
>
|
|
162
|
+
{body}
|
|
163
|
+
</p>
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
FormMessage.displayName = "FormMessage";
|
|
167
|
+
|
|
168
|
+
export {
|
|
169
|
+
useFormField,
|
|
170
|
+
Form,
|
|
171
|
+
FormItem,
|
|
172
|
+
FormLabel,
|
|
173
|
+
FormControl,
|
|
174
|
+
FormDescription,
|
|
175
|
+
FormMessage,
|
|
176
|
+
FormField,
|
|
177
|
+
};
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
export * from "./accordion";
|
|
2
|
+
export * from "./actions";
|
|
2
3
|
export * from "./ai-chat";
|
|
3
4
|
export * from "./alert";
|
|
5
|
+
export * from "./branch";
|
|
4
6
|
export * from "./breadcrumb";
|
|
5
7
|
export * from "./button-group";
|
|
6
8
|
export * from "./card";
|
|
7
9
|
export * from "./collapsible";
|
|
10
|
+
export * from "./code-block";
|
|
8
11
|
export * from "./command-search";
|
|
9
12
|
export * from "./empty";
|
|
10
13
|
export * from "./field";
|
|
14
|
+
export * from "./form";
|
|
11
15
|
export * from "./grid";
|
|
12
16
|
export * from "./hover-card";
|
|
17
|
+
export * from "./inline-citation";
|
|
13
18
|
export * from "./input-group";
|
|
14
19
|
export * from "./input-otp";
|
|
15
20
|
export * from "./item";
|
|
21
|
+
export * from "./message";
|
|
16
22
|
export * from "./page-header";
|
|
17
23
|
export * from "./pagination";
|
|
18
24
|
export * from "./popover";
|
|
@@ -22,8 +28,14 @@ export * from "./scroll-area";
|
|
|
22
28
|
export * from "./section";
|
|
23
29
|
export * from "./select";
|
|
24
30
|
export * from "./settings";
|
|
31
|
+
export * from "./sources";
|
|
32
|
+
export * from "./suggestion";
|
|
33
|
+
export * from "./task";
|
|
25
34
|
export * from "./table";
|
|
26
35
|
export * from "./tabs";
|
|
27
36
|
export * from "./theme-switcher";
|
|
37
|
+
export * from "./time-range-input";
|
|
28
38
|
export * from "./toggle-group";
|
|
39
|
+
export * from "./tool";
|
|
40
|
+
export * from "./tool-call-indicator";
|
|
29
41
|
export * from "./tooltip";
|