Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions apps/core/app/test/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
"use client";

import React from "react";
import { FileFormatField } from "../dashboard/products/create/_components/file-formate-field";
import { FileFormatSection } from "../dashboard/products/create/_components/file-formate-section";
import { RichTextEditor } from "@repo/ui/components/editor/editor";

const Page = () => {
return <FileFormatSection />;
return (
<div className="p-5">
<RichTextEditor
onChange={(html, text) => console.log(html, text)}
defaultValue="hello"
value="salam"
label="badry"
helperText="ali"
/>
Comment on lines +8 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Mixing controlled and uncontrolled props can cause unpredictable behavior

The RichTextEditor component is currently receiving both value (controlled) and defaultValue (uncontrolled) props simultaneously. React components should typically be either fully controlled or uncontrolled, not both.

Choose one approach:

  • For controlled: Provide value and onChange
  • For uncontrolled: Provide only defaultValue
 <RichTextEditor
   onChange={(html, text) => console.log(html, text)}
   defaultValue="hello"
-  value="salam"
   label="badry"
   helperText="ali"
 />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<RichTextEditor
onChange={(html, text) => console.log(html, text)}
defaultValue="hello"
value="salam"
label="badry"
helperText="ali"
/>
<RichTextEditor
onChange={(html, text) => console.log(html, text)}
defaultValue="hello"
label="badry"
helperText="ali"
/>

</div>
);
};

export default Page;
47 changes: 47 additions & 0 deletions apps/storybook/src/stories/editor.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { RichTextEditor } from "@repo/ui/components/editor/editor";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify import path consistency

The import path @repo/ui/components/editor/editor seems inconsistent with the actual file location in the project structure (packages/ui/src/components/molecules/editor/editor.tsx).


🏁 Script executed:

#!/bin/bash
# Verify the import path and actual file locations

echo "Checking for editor component file location:"
fd --type file editor.tsx --search-path ./packages/ui/src

echo "Checking tsconfig paths in ui package:"
cat ./packages/ui/tsconfig.json | grep -A 10 "paths"

Length of output: 529


🏁 Script executed:

#!/bin/bash
# Check path aliases in root tsconfig.json
echo "Root tsconfig.json paths mapping:"
grep -R '"paths"' -n tsconfig.json -A10

# Find where RichTextEditor is declared/exported in the UI package
echo "Searching for RichTextEditor in packages/ui/src:"
grep -R "RichTextEditor" -n packages/ui/src

Length of output: 584


Fix incorrect import path

The import in apps/storybook/src/stories/editor.stories.tsx doesn’t match the real file location under packages/ui/src. Update it to include the molecules segment:

- import { RichTextEditor } from "@repo/ui/components/editor/editor";
+ import { RichTextEditor } from "@repo/ui/components/molecules/editor/editor";

• File needing update:
– apps/storybook/src/stories/editor.stories.tsx (line 1)
• Verify your root/consumer tsconfig paths so that @repo/ui points to packages/ui/src (or add a custom alias for @repo/ui/components/editor if you prefer)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { RichTextEditor } from "@repo/ui/components/editor/editor";
-import { RichTextEditor } from "@repo/ui/components/editor/editor";
+import { RichTextEditor } from "@repo/ui/components/molecules/editor/editor";

import type { Meta, StoryObj } from "@storybook/react";

const meta: Meta<typeof RichTextEditor> = {
title: "Components/RichTextEditor",
component: RichTextEditor,
tags: ["autodocs"],
argTypes: {
value: { control: "text" },
defaultValue: { control: "text" },
onChange: { action: "onChange" },
className: { control: "text" },
label: { control: "text" },
helperText: { control: "text" },
error: { control: "text" },
id: { control: "text" },
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
label: "Rich Text Editor",
helperText: "You can enter rich text here.",
defaultValue: "<p>Initial content</p>",
},
};

export const WithError: Story = {
args: {
label: "Rich Text Editor",
helperText: "This editor has an error.",
defaultValue: "<p>Initial content with error</p>",
error: "This is an error message.",
},
};

export const CustomClass: Story = {
args: {
label: "Rich Text Editor",
helperText: "This editor has a custom class.",
defaultValue: "<p>Initial content with custom class</p>",
className: "custom-editor-class",
},
};
Binary file modified bun.lockb
Binary file not shown.
24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
{
"name": "turborepo-shadcn-ui",
"version": "1.3.0",
"private": true,
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"husky": "^9.1.7",
"prettier": "^3.3.2",
"turbo": "2.0.6"
},
"engines": {
"node": ">=18"
},
"license": "MIT",
"packageManager": "[email protected]",
"private": true,
"scripts": {
"build": "turbo build",
"type-checks": "turbo build check-types",
Expand All @@ -17,17 +28,6 @@
"deploy": "git pull origin main && git push vercel main",
"rebase:main": "git fetch origin main && git rebase main"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"husky": "^9.1.7",
"prettier": "^3.3.2",
"turbo": "2.0.6"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18"
},
"workspaces": [
"apps/*",
"packages/*"
Expand Down
19 changes: 16 additions & 3 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
"typescript": "^5.4.5"
},
"dependencies": {
"@repo/design-system": "*",
"@repo/icons": "*",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
Expand All @@ -51,8 +49,23 @@
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.0",

"@repo/design-system": "*",
"@repo/icons": "*",
"@tabler/icons-react": "^3.12.0",
"@tailwindcss/typography": "^0.5.16",
"@tiptap/extension-code": "^2.11.7",
"@tiptap/extension-code-block": "^2.11.7",
"@tiptap/extension-color": "^2.11.7",
"@tiptap/extension-highlight": "^2.11.5",
"@tiptap/extension-image": "^2.11.5",
"@tiptap/extension-link": "^2.11.5",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-ordered-list": "^2.11.7",
"@tiptap/extension-text-align": "^2.11.5",
"@tiptap/extension-text-style": "^2.11.7",
"@tiptap/extension-underline": "^2.11.7",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/atoms/command.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client"

import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Missing import for DialogProps.
The CommandDialogProps interface extends DialogProps, but the import for that type was removed in this hunk. This will cause unresolved type errors. Please re-add the correct import, for example:

+import type { DialogProps } from "@repo/ui/components/ui/dialog"

Committable suggestion skipped: line range outside the PR's diff.

import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"

import { cn } from "@repo/ui/lib/utils"
import { Dialog, DialogContent } from "@repo/ui/components/ui/dialog"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Missing imports for Dialog and DialogContent.
The CommandDialog component still references <Dialog> and <DialogContent>, but those components were removed from the import list. This will break the build. Please restore their import from the appropriate module, for example:

+import { Dialog, DialogContent } from "@repo/ui/components/ui/dialog"

Or, if the dialog wrapper is no longer needed, remove/replace the CommandDialog implementation accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Dialog, DialogContent } from "@repo/ui/components/ui/dialog"


const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/components/atoms/rich-text-style-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PropsWithChildren } from "react";

export const RichTextStylesProvider = ({ children }: PropsWithChildren) => {
return (
// INFO: "prose" class has max-width and we should remove it by max-w-none
<div className="prose prose-sm max-w-none prose-h2:mb-1 prose-h2:mt-1 prose-h3:mt-1 prose-h4:mt-1 prose-p:my-0 prose-p:mb-1 prose-ul:my-1 prose-ul:list-none prose-ul:pl-1 prose-hr:my-5 prose:text-froground dark:prose-invert">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in class name and improve className readability

There's a typo in the class name: prose:text-froground should be prose:text-foreground. Also, the lengthy inline class string is hard to maintain. Consider extracting these classes or using the cn utility for better readability:

-    <div className="prose prose-sm max-w-none prose-h2:mb-1 prose-h2:mt-1 prose-h3:mt-1 prose-h4:mt-1 prose-p:my-0 prose-p:mb-1  prose-ul:my-1 prose-ul:list-none prose-ul:pl-1 prose-hr:my-5 prose:text-froground dark:prose-invert">
+    <div className={cn(
+      "prose prose-sm max-w-none",
+      "prose-h2:mb-1 prose-h2:mt-1 prose-h3:mt-1 prose-h4:mt-1",
+      "prose-p:my-0 prose-p:mb-1",
+      "prose-ul:my-1 prose-ul:list-none prose-ul:pl-1",
+      "prose-hr:my-5",
+      "prose:text-foreground dark:prose-invert"
+    )}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="prose prose-sm max-w-none prose-h2:mb-1 prose-h2:mt-1 prose-h3:mt-1 prose-h4:mt-1 prose-p:my-0 prose-p:mb-1 prose-ul:my-1 prose-ul:list-none prose-ul:pl-1 prose-hr:my-5 prose:text-froground dark:prose-invert">
<div className={cn(
"prose prose-sm max-w-none",
"prose-h2:mb-1 prose-h2:mt-1 prose-h3:mt-1 prose-h4:mt-1",
"prose-p:my-0 prose-p:mb-1",
"prose-ul:my-1 prose-ul:list-none prose-ul:pl-1",
"prose-hr:my-5",
"prose:text-foreground dark:prose-invert"
)}>

{children}
</div>
);
};
Comment on lines +3 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Resolve duplication with the molecular component

There are two components named RichTextStylesProvider in different directories with different styling implementations:

  1. This one in atoms with extensive styling
  2. Another in molecules/editor with minimal styling

This duplication makes maintenance more difficult and could confuse developers. Consider:

  1. Merging them into one configurable component
  2. Renaming one of them to better reflect its purpose
  3. Adding JSDoc comments to clarify the intended use case for each

16 changes: 8 additions & 8 deletions packages/ui/src/components/atoms/textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";

import { cn } from "@repo/ui/lib/utils"
import { cn } from "@repo/ui/lib/utils";

export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
Expand All @@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
);
},
);
Textarea.displayName = "Textarea";

export { Textarea }
export { Textarea };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const EditorToolbarButton = ({
onClick,
isActive,
icon: Icon,
label,
disabled,
}: {
onClick: () => void;
isActive: boolean;
icon: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a more specific type instead of any for the icon prop

Using any for the icon prop loses type safety. Consider using a more specific React component type:

-  icon: any;
+  icon: React.ComponentType<{ size?: number }>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
icon: any;
icon: React.ComponentType<{ size?: number }>;

label: string;
disabled?: boolean;
}) => (
<button
disabled={disabled}
onClick={onClick}
className={`flex items-center gap-1 rounded-md px-1 py-1 text-sm transition-colors ${
isActive
? "bg-c1-500 text-white"
: " text-foreground hover:bg-gray-800"
} ${disabled ? "pointer-events-none cursor-not-allowed" : ""}`}
title={label}
>
<Icon size={16} />
</button>
Comment on lines +14 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve accessibility for the button component

Consider enhancing accessibility by:

  1. Adding aria-pressed for toggle buttons
  2. Using aria-disabled in addition to the disabled attribute
  3. Adding keyboard navigation support
 <button
   disabled={disabled}
+  aria-disabled={disabled}
+  aria-pressed={isActive}
   onClick={onClick}
   className={`flex items-center gap-1 rounded-md px-1 py-1 text-sm transition-colors ${
     isActive
       ? "bg-c1-500 text-white"
       : " text-foreground hover:bg-gray-800"
   } ${disabled ? "pointer-events-none cursor-not-allowed" : ""}`}
   title={label}
 >
   <Icon size={16} />
 </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
disabled={disabled}
onClick={onClick}
className={`flex items-center gap-1 rounded-md px-1 py-1 text-sm transition-colors ${
isActive
? "bg-c1-500 text-white"
: " text-foreground hover:bg-gray-800"
} ${disabled ? "pointer-events-none cursor-not-allowed" : ""}`}
title={label}
>
<Icon size={16} />
</button>
<button
disabled={disabled}
aria-disabled={disabled}
aria-pressed={isActive}
onClick={onClick}
className={`flex items-center gap-1 rounded-md px-1 py-1 text-sm transition-colors ${
isActive
? "bg-c1-500 text-white"
: " text-foreground hover:bg-gray-800"
} ${disabled ? "pointer-events-none cursor-not-allowed" : ""}`}
title={label}
>
<Icon size={16} />
</button>

);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-disable no-restricted-imports */
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
import { useRef } from 'react';
import { EditorToolbarButton } from './editor-toolbar-button';
import { ImageIcon } from 'lucide-react';
import { ToolbarProps } from './editor-toolbar';
import { toast } from 'sonner';

const useUploadImage = (editor: any) => {
return useMutation({
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append('file', file);

const response = await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});

return response.data; // Returns { imageUrl }
},
onSuccess: (data) => {
if (data.imageUrl) {
editor?.chain().focus().setImage({ src: data.imageUrl }).run();
}
},
Comment on lines +22 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check before accessing editor methods

There's no protection against editor being null when calling chain methods, which could cause runtime errors.

    onSuccess: (data) => {
      if (data.imageUrl) {
-        editor?.chain().focus().setImage({ src: data.imageUrl }).run();
+        if (editor) {
+          editor.chain().focus().setImage({ src: data.imageUrl }).run();
+        }
      }
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (data) => {
if (data.imageUrl) {
editor?.chain().focus().setImage({ src: data.imageUrl }).run();
}
},
onSuccess: (data) => {
if (data.imageUrl) {
if (editor) {
editor.chain().focus().setImage({ src: data.imageUrl }).run();
}
}
},

onError: (error) => {
toast.error(error.message ?? "Can't upload image");
}
});
};
Comment on lines +10 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract useUploadImage hook to a separate file

The useUploadImage hook should be extracted to a separate file for better maintainability and potential reuse in other components.

This would make the component more focused and the hook more reusable across the application. Consider creating a new file like hooks/use-upload-image.ts with the extracted hook.


export const EditorToolbarImage = ({
editor,
showHtml
}: Pick<ToolbarProps, 'editor' | 'showHtml'>) => {
const fileInputRef = useRef<HTMLInputElement>(null);

const { mutate: uploadImage } = useUploadImage(editor);

return (
<div>
<EditorToolbarButton
disabled={showHtml}
onClick={() => fileInputRef.current?.click()}
isActive={false}
icon={ImageIcon}
label="Image"
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
className="hidden"
onChange={(e) => {
if (e.target.files?.[0]) uploadImage(e.target.files[0]);
}}
/>
Comment on lines +39 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add loading state feedback during image upload

The component doesn't provide visual feedback during the upload process, which might confuse users for larger images.

-  const { mutate: uploadImage } = useUploadImage(editor);
+  const { mutate: uploadImage, isPending } = useUploadImage(editor);

  return (
    <div>
      <EditorToolbarButton
-        disabled={showHtml}
+        disabled={showHtml || isPending}
        onClick={() => fileInputRef.current?.click()}
        isActive={false}
-        icon={ImageIcon}
+        icon={isPending ? LoaderIcon : ImageIcon}
        label="Image"
      />
      <input
        type="file"
        accept="image/*"
        ref={fileInputRef}
        className="hidden"
        onChange={(e) => {
-          if (e.target.files?.[0]) uploadImage(e.target.files[0]);
+          if (e.target.files?.[0]) {
+            uploadImage(e.target.files[0]);
+            e.target.value = ''; // Reset input after upload
+          }
        }}
      />
    </div>

Note: You'll need to import LoaderIcon from 'lucide-react'.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { mutate: uploadImage } = useUploadImage(editor);
return (
<div>
<EditorToolbarButton
disabled={showHtml}
onClick={() => fileInputRef.current?.click()}
isActive={false}
icon={ImageIcon}
label="Image"
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
className="hidden"
onChange={(e) => {
if (e.target.files?.[0]) uploadImage(e.target.files[0]);
}}
/>
const { mutate: uploadImage, isPending } = useUploadImage(editor);
return (
<div>
<EditorToolbarButton
disabled={showHtml || isPending}
onClick={() => fileInputRef.current?.click()}
isActive={false}
icon={isPending ? LoaderIcon : ImageIcon}
label="Image"
/>
<input
type="file"
accept="image/*"
ref={fileInputRef}
className="hidden"
onChange={(e) => {
if (e.target.files?.[0]) {
uploadImage(e.target.files[0]);
e.target.value = ''; // Reset input after upload
}
}}
/>
</div>

</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Eye } from 'lucide-react';
import { ToolbarProps } from './editor-toolbar';
import { EditorToolbarButton } from './editor-toolbar-button';

export const EditorToolbarViewHtml = ({
editor,
showHtml,
setShowHtml,
setHtmlContent
}: ToolbarProps) => {
const toggleHtmlView = () => {
if (!editor) return;
setShowHtml((prev) => !prev);
if (!showHtml) {
setHtmlContent(editor.getHTML()); // Get latest HTML before switching
}
};

return (
<EditorToolbarButton
onClick={toggleHtmlView}
isActive={showHtml}
icon={Eye} // Lucide React Icon
label={showHtml ? 'Show Editor' : 'Show HTML'}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useState } from "react";
import { Link2 } from "lucide-react";
import { EditorToolbarButton } from "./editor-toolbar-button";
import { ToolbarProps } from "./editor-toolbar";
import { Popover, PopoverTrigger, PopoverContent } from "./../../atoms/popover";
import { Input } from "./../input";
import { Button } from "./../../atoms/button";

export const EditorToolbarSetLink = ({
editor,
showHtml,
}: Pick<ToolbarProps, "editor" | "showHtml">) => {
const [url, setUrl] = useState("");
const [open, setOpen] = useState(false);

const onSubmit = () => {
if (!editor || !url) return;
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
setOpen(false);
setUrl("");
};

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger>
<EditorToolbarButton
onClick={() => setOpen(true)}
disabled={showHtml}
isActive={false}
icon={Link2}
label="Set Link"
/>
</PopoverTrigger>
<PopoverContent className="poooopppper w-64 p-2">
<Input
type="url"
placeholder="Enter URL"
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
<Button className="mt-2 w-full" onClick={onSubmit}>
Set Link
</Button>
</PopoverContent>
</Popover>
);
};
Loading