@oppulence/design-system 1.0.4 → 1.0.6

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 (46) hide show
  1. package/hooks/use-resize-observer.ts +24 -0
  2. package/lib/ai.ts +31 -0
  3. package/package.json +19 -1
  4. package/src/components/atoms/animated-size-container.tsx +59 -0
  5. package/src/components/atoms/currency-input.tsx +16 -0
  6. package/src/components/atoms/icons.tsx +840 -0
  7. package/src/components/atoms/image.tsx +23 -0
  8. package/src/components/atoms/index.ts +10 -0
  9. package/src/components/atoms/loader.tsx +92 -0
  10. package/src/components/atoms/quantity-input.tsx +103 -0
  11. package/src/components/atoms/record-button.tsx +178 -0
  12. package/src/components/atoms/submit-button.tsx +26 -0
  13. package/src/components/atoms/text-effect.tsx +251 -0
  14. package/src/components/atoms/text-shimmer.tsx +74 -0
  15. package/src/components/molecules/actions.tsx +53 -0
  16. package/src/components/molecules/branch.tsx +192 -0
  17. package/src/components/molecules/code-block.tsx +151 -0
  18. package/src/components/molecules/form.tsx +177 -0
  19. package/src/components/molecules/index.ts +12 -0
  20. package/src/components/molecules/inline-citation.tsx +295 -0
  21. package/src/components/molecules/message.tsx +64 -0
  22. package/src/components/molecules/sources.tsx +116 -0
  23. package/src/components/molecules/suggestion.tsx +53 -0
  24. package/src/components/molecules/task.tsx +74 -0
  25. package/src/components/molecules/time-range-input.tsx +73 -0
  26. package/src/components/molecules/tool-call-indicator.tsx +42 -0
  27. package/src/components/molecules/tool.tsx +130 -0
  28. package/src/components/organisms/combobox-dropdown.tsx +171 -0
  29. package/src/components/organisms/conversation.tsx +98 -0
  30. package/src/components/organisms/date-range-picker.tsx +53 -0
  31. package/src/components/organisms/editor/extentions/bubble-menu/bubble-item.tsx +30 -0
  32. package/src/components/organisms/editor/extentions/bubble-menu/bubble-menu-button.tsx +27 -0
  33. package/src/components/organisms/editor/extentions/bubble-menu/index.tsx +63 -0
  34. package/src/components/organisms/editor/extentions/bubble-menu/link-item.tsx +104 -0
  35. package/src/components/organisms/editor/extentions/register.ts +22 -0
  36. package/src/components/organisms/editor/index.tsx +50 -0
  37. package/src/components/organisms/editor/styles.css +31 -0
  38. package/src/components/organisms/editor/utils.ts +19 -0
  39. package/src/components/organisms/index.ts +11 -0
  40. package/src/components/organisms/multiple-selector.tsx +632 -0
  41. package/src/components/organisms/prompt-input.tsx +747 -0
  42. package/src/components/organisms/reasoning.tsx +170 -0
  43. package/src/components/organisms/response.tsx +121 -0
  44. package/src/components/organisms/toast-toaster.tsx +84 -0
  45. package/src/components/organisms/toast.tsx +124 -0
  46. package/src/components/organisms/use-toast.tsx +206 -0
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import { Icons } from "../atoms/icons";
4
+ import { TextShimmer } from "../atoms/text-shimmer";
5
+
6
+ export const toolDisplayConfig = {
7
+ getBurnRate: {
8
+ displayText: "Getting Burn Rate Data",
9
+ icon: Icons.TrendingUp,
10
+ },
11
+ web_search: {
12
+ displayText: "Searching the Web",
13
+ icon: Icons.Search,
14
+ },
15
+ } as const;
16
+
17
+ export type SupportedToolName = keyof typeof toolDisplayConfig;
18
+
19
+ export interface ToolCallIndicatorProps {
20
+ toolName: SupportedToolName;
21
+ }
22
+
23
+ export function ToolCallIndicator({ toolName }: ToolCallIndicatorProps) {
24
+ const config = toolDisplayConfig[toolName];
25
+
26
+ if (!config) {
27
+ return null;
28
+ }
29
+
30
+ return (
31
+ <div className="flex justify-start mt-3 animate-fade-in">
32
+ <div className="border px-3 py-1 flex items-center gap-2 w-fit">
33
+ <div className="flex items-center justify-center size-3.5">
34
+ <config.icon size={14} />
35
+ </div>
36
+ <TextShimmer size="xs" baseColor="#707070" gradientColor="#111111" duration={1}>
37
+ {config.displayText}
38
+ </TextShimmer>
39
+ </div>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,130 @@
1
+ "use client";
2
+
3
+ import type { ToolUIPart } from "../../../lib/ai";
4
+ import { cn } from "../../../lib/utils";
5
+ import {
6
+ CheckCircleIcon,
7
+ ChevronDownIcon,
8
+ CircleIcon,
9
+ ClockIcon,
10
+ WrenchIcon,
11
+ XCircleIcon,
12
+ } from "lucide-react";
13
+ import type { ComponentProps, ReactNode } from "react";
14
+
15
+ import { badgeVariants } from "../atoms/badge";
16
+ import { CodeBlock } from "./code-block";
17
+ import {
18
+ Collapsible,
19
+ CollapsibleContent,
20
+ CollapsibleTrigger,
21
+ } from "./collapsible";
22
+
23
+ export type ToolProps = Omit<ComponentProps<typeof Collapsible>, "className">;
24
+
25
+ export const Tool = ({ ...props }: ToolProps) => (
26
+ <Collapsible className="not-prose mb-4 w-full rounded-md border" {...props} />
27
+ );
28
+
29
+ export type ToolHeaderProps = {
30
+ type: ToolUIPart["type"];
31
+ state: ToolUIPart["state"];
32
+ };
33
+
34
+ const getStatusBadge = (status: ToolUIPart["state"]) => {
35
+ const labels = {
36
+ "input-streaming": "Pending",
37
+ "input-available": "Running",
38
+ "output-available": "Completed",
39
+ "output-error": "Error",
40
+ } as const;
41
+
42
+ const icons = {
43
+ "input-streaming": <CircleIcon className="size-4" />,
44
+ "input-available": <ClockIcon className="size-4 animate-pulse" />,
45
+ "output-available": <CheckCircleIcon className="size-4 text-green-600" />,
46
+ "output-error": <XCircleIcon className="size-4 text-red-600" />,
47
+ } as const;
48
+
49
+ return (
50
+ <span className={cn(badgeVariants({ variant: "secondary" }), "gap-1.5 rounded-full text-xs")}>
51
+ {icons[status]}
52
+ {labels[status]}
53
+ </span>
54
+ );
55
+ };
56
+
57
+ export const ToolHeader = ({ type, state, ...props }: ToolHeaderProps) => (
58
+ <CollapsibleTrigger
59
+ className="flex w-full items-center justify-between gap-4 p-3"
60
+ {...props}
61
+ >
62
+ <div className="flex items-center gap-2">
63
+ <WrenchIcon className="size-4 text-muted-foreground" />
64
+ <span className="font-medium text-sm">{type}</span>
65
+ {getStatusBadge(state)}
66
+ </div>
67
+ <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
68
+ </CollapsibleTrigger>
69
+ );
70
+
71
+ export type ToolContentProps = Omit<
72
+ ComponentProps<typeof CollapsibleContent>,
73
+ "className"
74
+ >;
75
+
76
+ export const ToolContent = ({ ...props }: ToolContentProps) => (
77
+ <CollapsibleContent
78
+ className="data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in"
79
+ {...props}
80
+ />
81
+ );
82
+
83
+ export type ToolInputProps = Omit<ComponentProps<"div">, "className"> & {
84
+ input: ToolUIPart["input"];
85
+ };
86
+
87
+ export const ToolInput = ({ input, ...props }: ToolInputProps) => (
88
+ <div className="space-y-2 overflow-hidden p-4" {...props}>
89
+ <h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
90
+ Parameters
91
+ </h4>
92
+ <div className="rounded-md bg-muted/50">
93
+ <CodeBlock code={JSON.stringify(input, null, 2)} language="json" />
94
+ </div>
95
+ </div>
96
+ );
97
+
98
+ export type ToolOutputProps = Omit<ComponentProps<"div">, "className"> & {
99
+ output: ReactNode;
100
+ errorText: ToolUIPart["errorText"];
101
+ };
102
+
103
+ export const ToolOutput = ({
104
+ output,
105
+ errorText,
106
+ ...props
107
+ }: ToolOutputProps) => {
108
+ if (!(output || errorText)) {
109
+ return null;
110
+ }
111
+
112
+ return (
113
+ <div className="space-y-2 p-4" {...props}>
114
+ <h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
115
+ {errorText ? "Error" : "Result"}
116
+ </h4>
117
+ <div
118
+ className={cn(
119
+ "overflow-x-auto rounded-md text-xs [&_table]:w-full",
120
+ errorText
121
+ ? "bg-destructive/10 text-destructive"
122
+ : "bg-muted/50 text-foreground",
123
+ )}
124
+ >
125
+ {errorText && <div>{errorText}</div>}
126
+ {output && <div>{output}</div>}
127
+ </div>
128
+ </div>
129
+ );
130
+ };
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import { Check, ChevronsUpDown } from "lucide-react";
4
+ import * as React from "react";
5
+
6
+ import { Command } from "./command";
7
+ import { CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./command";
8
+ import { Button } from "../atoms/button";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "../molecules/popover";
10
+
11
+ export type ComboboxItem = {
12
+ id: string;
13
+ label: string;
14
+ disabled?: boolean;
15
+ };
16
+
17
+ type Props<T> = {
18
+ placeholder?: React.ReactNode;
19
+ searchPlaceholder?: string;
20
+ items: T[];
21
+ onSelect: (item: T) => void;
22
+ selectedItem?: T;
23
+ renderSelectedItem?: (selectedItem: T) => React.ReactNode;
24
+ renderOnCreate?: (value: string) => React.ReactNode;
25
+ renderListItem?: (listItem: { isChecked: boolean; item: T }) => React.ReactNode;
26
+ emptyResults?: React.ReactNode;
27
+ popoverProps?: Omit<React.ComponentProps<typeof PopoverContent>, "className">;
28
+ disabled?: boolean;
29
+ onCreate?: (value: string) => void;
30
+ headless?: boolean;
31
+ modal?: boolean;
32
+ };
33
+
34
+ export function ComboboxDropdown<T extends ComboboxItem>({
35
+ headless,
36
+ placeholder,
37
+ searchPlaceholder,
38
+ items,
39
+ onSelect,
40
+ selectedItem: incomingSelectedItem,
41
+ renderSelectedItem = (item) => item.label,
42
+ renderListItem,
43
+ renderOnCreate,
44
+ emptyResults,
45
+ popoverProps,
46
+ disabled,
47
+ onCreate,
48
+ modal = true,
49
+ }: Props<T>) {
50
+ const [open, setOpen] = React.useState(false);
51
+ const [internalSelectedItem, setInternalSelectedItem] = React.useState<
52
+ T | undefined
53
+ >();
54
+ const [inputValue, setInputValue] = React.useState("");
55
+
56
+ const selectedItem = incomingSelectedItem ?? internalSelectedItem;
57
+
58
+ const filteredItems = items.filter((item) =>
59
+ item.label.toLowerCase().includes(inputValue.toLowerCase()),
60
+ );
61
+
62
+ const showCreate = onCreate && Boolean(inputValue) && !filteredItems.length;
63
+
64
+ const Component = (
65
+ <Command loop shouldFilter={false}>
66
+ <CommandInput
67
+ value={inputValue}
68
+ onValueChange={setInputValue}
69
+ placeholder={searchPlaceholder ?? "Search item..."}
70
+ />
71
+
72
+ <CommandGroup>
73
+ <CommandList>
74
+ {filteredItems.map((item) => {
75
+ const isChecked = selectedItem?.id === item.id;
76
+
77
+ return (
78
+ <CommandItem
79
+ disabled={item.disabled}
80
+ key={item.id}
81
+ value={item.id}
82
+ onSelect={(id) => {
83
+ const foundItem = items.find((item) => item.id === id);
84
+
85
+ if (!foundItem) {
86
+ return;
87
+ }
88
+
89
+ onSelect(foundItem);
90
+ setInternalSelectedItem(foundItem);
91
+ setOpen(false);
92
+ }}
93
+ >
94
+ {renderListItem ? (
95
+ renderListItem({ isChecked, item })
96
+ ) : (
97
+ <>
98
+ <Check
99
+ className={
100
+ isChecked ? "mr-2 h-4 w-4 opacity-100" : "mr-2 h-4 w-4 opacity-0"
101
+ }
102
+ />
103
+ {item.label}
104
+ </>
105
+ )}
106
+ </CommandItem>
107
+ );
108
+ })}
109
+
110
+ <CommandEmpty>{emptyResults ?? "No item found"}</CommandEmpty>
111
+
112
+ {showCreate && (
113
+ <CommandItem
114
+ key={inputValue}
115
+ value={inputValue}
116
+ onSelect={() => {
117
+ onCreate(inputValue);
118
+ setOpen(false);
119
+ setInputValue("");
120
+ }}
121
+ onMouseDown={(event) => {
122
+ event.preventDefault();
123
+ event.stopPropagation();
124
+ }}
125
+ >
126
+ {renderOnCreate ? renderOnCreate(inputValue) : null}
127
+ </CommandItem>
128
+ )}
129
+ </CommandList>
130
+ </CommandGroup>
131
+ </Command>
132
+ );
133
+
134
+ if (headless) {
135
+ return Component;
136
+ }
137
+
138
+ return (
139
+ <Popover open={open} onOpenChange={setOpen} modal={modal}>
140
+ <PopoverTrigger
141
+ render={<Button variant="outline" width="full" aria-expanded={open} />}
142
+ disabled={disabled}
143
+ >
144
+ <span className="flex w-full items-center justify-between gap-2">
145
+ <span className="truncate text-ellipsis">
146
+ {selectedItem ? (
147
+ <span className="items-center overflow-hidden whitespace-nowrap text-ellipsis block">
148
+ {renderSelectedItem
149
+ ? renderSelectedItem(selectedItem)
150
+ : selectedItem.label}
151
+ </span>
152
+ ) : (
153
+ placeholder ?? "Select item..."
154
+ )}
155
+ </span>
156
+ <ChevronsUpDown className="size-4 opacity-50" />
157
+ </span>
158
+ </PopoverTrigger>
159
+
160
+ <PopoverContent
161
+ {...popoverProps}
162
+ style={{
163
+ width: "var(--anchor-width)",
164
+ ...popoverProps?.style,
165
+ }}
166
+ >
167
+ {Component}
168
+ </PopoverContent>
169
+ </Popover>
170
+ );
171
+ }
@@ -0,0 +1,98 @@
1
+ "use client";
2
+
3
+ import { ArrowDownIcon } from "lucide-react";
4
+ import type { ButtonHTMLAttributes, ComponentProps, ReactNode } from "react";
5
+ import { useCallback } from "react";
6
+ import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
7
+
8
+ import { cn } from "../../../lib/utils";
9
+ import { buttonVariants } from "../atoms/button";
10
+
11
+ export type ConversationProps = Omit<
12
+ ComponentProps<typeof StickToBottom>,
13
+ "className"
14
+ >;
15
+
16
+ export const Conversation = ({ ...props }: ConversationProps) => (
17
+ <StickToBottom
18
+ className="relative flex-1 overflow-y-auto [&_div::-webkit-scrollbar]:hidden [&_div]:[scrollbar-width:none] [&_div]:[-ms-overflow-style:none]"
19
+ initial="smooth"
20
+ resize="smooth"
21
+ role="log"
22
+ {...props}
23
+ />
24
+ );
25
+
26
+ export type ConversationContentProps = Omit<
27
+ ComponentProps<typeof StickToBottom.Content>,
28
+ "className"
29
+ >;
30
+
31
+ export const ConversationContent = ({ ...props }: ConversationContentProps) => (
32
+ <StickToBottom.Content className="p-4" {...props} />
33
+ );
34
+
35
+ export type ConversationEmptyStateProps = Omit<
36
+ ComponentProps<"div">,
37
+ "className"
38
+ > & {
39
+ title?: string;
40
+ description?: string;
41
+ icon?: ReactNode;
42
+ };
43
+
44
+ export const ConversationEmptyState = ({
45
+ title = "No messages yet",
46
+ description = "Start a conversation to see messages here",
47
+ icon,
48
+ children,
49
+ ...props
50
+ }: ConversationEmptyStateProps) => (
51
+ <div
52
+ className="flex size-full flex-col items-center justify-center gap-3 p-8 text-center"
53
+ {...props}
54
+ >
55
+ {children ?? (
56
+ <>
57
+ {icon && <div className="text-muted-foreground">{icon}</div>}
58
+ <div className="space-y-1">
59
+ <h3 className="font-medium text-sm">{title}</h3>
60
+ {description && (
61
+ <p className="text-muted-foreground text-sm">{description}</p>
62
+ )}
63
+ </div>
64
+ </>
65
+ )}
66
+ </div>
67
+ );
68
+
69
+ export type ConversationScrollButtonProps = Omit<
70
+ ButtonHTMLAttributes<HTMLButtonElement>,
71
+ "className"
72
+ >;
73
+
74
+ export const ConversationScrollButton = ({
75
+ ...props
76
+ }: ConversationScrollButtonProps) => {
77
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
78
+
79
+ const handleScrollToBottom = useCallback(() => {
80
+ scrollToBottom();
81
+ }, [scrollToBottom]);
82
+
83
+ return (
84
+ !isAtBottom && (
85
+ <button
86
+ className={cn(
87
+ buttonVariants({ variant: "outline", size: "icon" }),
88
+ "absolute bottom-[10.5rem] left-1/2 -translate-x-1/2 rounded-full backdrop-filter backdrop-blur-lg dark:bg-[#1A1A1A]/80 bg-[#F6F6F3]/80",
89
+ )}
90
+ onClick={handleScrollToBottom}
91
+ type="button"
92
+ {...props}
93
+ >
94
+ <ArrowDownIcon className="size-4" />
95
+ </button>
96
+ )
97
+ );
98
+ };
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import type { HTMLAttributes } from "react";
4
+ import type { DateRange } from "react-day-picker";
5
+
6
+ import { ChevronDownIcon } from "lucide-react";
7
+ import { Button } from "../atoms/button";
8
+ import { Calendar } from "./calendar";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "../molecules/popover";
10
+
11
+ export type DateRangePickerProps = Omit<
12
+ HTMLAttributes<HTMLDivElement>,
13
+ "className"
14
+ > & {
15
+ range: DateRange;
16
+ onSelect: (range?: DateRange) => void;
17
+ placeholder: string;
18
+ disabled?: boolean;
19
+ };
20
+
21
+ export function DateRangePicker({
22
+ range,
23
+ disabled,
24
+ onSelect,
25
+ placeholder,
26
+ ...props
27
+ }: DateRangePickerProps) {
28
+ return (
29
+ <div className="grid gap-2" {...props}>
30
+ <Popover>
31
+ <PopoverTrigger
32
+ render={<Button variant="outline" width="full" />}
33
+ disabled={disabled}
34
+ >
35
+ <span className="flex w-full items-center justify-between gap-2 font-medium">
36
+ <span>{placeholder}</span>
37
+ <ChevronDownIcon className="size-4" />
38
+ </span>
39
+ </PopoverTrigger>
40
+ <PopoverContent align="end" sideOffset={8}>
41
+ <Calendar
42
+ initialFocus
43
+ mode="range"
44
+ defaultMonth={range?.from}
45
+ selected={range}
46
+ onSelect={onSelect}
47
+ numberOfMonths={2}
48
+ />
49
+ </PopoverContent>
50
+ </Popover>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import type { Editor } from "@tiptap/react";
4
+ import { BubbleMenuButton } from "./bubble-menu-button";
5
+
6
+ interface BubbleItemProps {
7
+ editor: Editor;
8
+ action: () => void;
9
+ isActive: boolean;
10
+ children: React.ReactNode;
11
+ }
12
+
13
+ export function BubbleMenuItem({
14
+ editor,
15
+ action,
16
+ isActive,
17
+ children,
18
+ }: BubbleItemProps) {
19
+ return (
20
+ <BubbleMenuButton
21
+ action={() => {
22
+ editor.chain().focus();
23
+ action();
24
+ }}
25
+ isActive={isActive}
26
+ >
27
+ {children}
28
+ </BubbleMenuButton>
29
+ );
30
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ interface BubbleMenuButtonProps {
4
+ action: () => void;
5
+ isActive: boolean;
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ export function BubbleMenuButton({
10
+ action,
11
+ isActive,
12
+ children,
13
+ }: BubbleMenuButtonProps) {
14
+ return (
15
+ <button
16
+ type="button"
17
+ onClick={action}
18
+ className={
19
+ isActive
20
+ ? "px-2.5 py-1.5 text-[11px] transition-colors bg-white dark:bg-stone-900 text-primary"
21
+ : "px-2.5 py-1.5 text-[11px] transition-colors bg-transparent hover:bg-muted"
22
+ }
23
+ >
24
+ {children}
25
+ </button>
26
+ );
27
+ }
@@ -0,0 +1,63 @@
1
+ import { type Editor, BubbleMenu as TiptapBubbleMenu } from "@tiptap/react";
2
+ import { useState } from "react";
3
+ import {
4
+ MdOutlineFormatBold,
5
+ MdOutlineFormatItalic,
6
+ MdOutlineFormatStrikethrough,
7
+ } from "react-icons/md";
8
+ import type { Props as TippyOptions } from "tippy.js";
9
+ import { BubbleMenuItem } from "./bubble-item";
10
+ import { LinkItem } from "./link-item";
11
+
12
+ export function BubbleMenu({
13
+ editor,
14
+ tippyOptions,
15
+ }: {
16
+ editor: Editor;
17
+ tippyOptions?: TippyOptions;
18
+ }) {
19
+ const [openLink, setOpenLink] = useState(false);
20
+
21
+ if (!editor) {
22
+ return null;
23
+ }
24
+
25
+ return (
26
+ <div>
27
+ <TiptapBubbleMenu editor={editor} tippyOptions={tippyOptions}>
28
+ <div className="flex w-fit max-w-[90vw] overflow-hidden rounded-full border border-border bg-background text-mono font-regular">
29
+ <>
30
+ <BubbleMenuItem
31
+ editor={editor}
32
+ action={() => editor.chain().focus().toggleBold().run()}
33
+ isActive={editor.isActive("bold")}
34
+ >
35
+ <MdOutlineFormatBold className="size-4" />
36
+ <span className="sr-only">Bold</span>
37
+ </BubbleMenuItem>
38
+
39
+ <BubbleMenuItem
40
+ editor={editor}
41
+ action={() => editor.chain().focus().toggleItalic().run()}
42
+ isActive={editor.isActive("italic")}
43
+ >
44
+ <MdOutlineFormatItalic className="size-4" />
45
+ <span className="sr-only">Italic</span>
46
+ </BubbleMenuItem>
47
+
48
+ <BubbleMenuItem
49
+ editor={editor}
50
+ action={() => editor.chain().focus().toggleStrike().run()}
51
+ isActive={editor.isActive("strike")}
52
+ >
53
+ <MdOutlineFormatStrikethrough className="size-4" />
54
+ <span className="sr-only">Strike</span>
55
+ </BubbleMenuItem>
56
+
57
+ <LinkItem editor={editor} open={openLink} setOpen={setOpenLink} />
58
+ </>
59
+ </div>
60
+ </TiptapBubbleMenu>
61
+ </div>
62
+ );
63
+ }