-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#264 editor component #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
133ee6d
0932890
332d2d4
d976b4b
9bbb32b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Page; | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||
| import { RichTextEditor } from "@repo/ui/components/editor/editor"; | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify import path consistency The import path 🏁 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/srcLength 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: 📝 Committable suggestion
Suggested change
|
||||||||
| 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", | ||||||||
| }, | ||||||||
| }; | ||||||||
| 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", | ||
|
|
@@ -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/*" | ||
|
|
||
| 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" | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Missing import for +import type { DialogProps } from "@repo/ui/components/ui/dialog"
|
||||||
| 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" | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Missing imports for +import { Dialog, DialogContent } from "@repo/ui/components/ui/dialog"Or, if the dialog wrapper is no longer needed, remove/replace the 📝 Committable suggestion
Suggested change
|
||||||
|
|
||||||
| const Command = React.forwardRef< | ||||||
| React.ElementRef<typeof CommandPrimitive>, | ||||||
|
|
||||||
| 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"> | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typo in class name and improve className readability There's a typo in the class name: - <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
Suggested change
|
||||||||||||||||||||
| {children} | ||||||||||||||||||||
| </div> | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
Comment on lines
+3
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
This duplication makes maintenance more difficult and could confuse developers. Consider:
|
||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use a more specific type instead of Using - icon: any;
+ icon: React.ComponentType<{ size?: number }>;📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve accessibility for the button component Consider enhancing accessibility by:
<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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null check before accessing editor methods There's no protection against 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toast.error(error.message ?? "Can't upload image"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </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> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mixing controlled and uncontrolled props can cause unpredictable behavior
The
RichTextEditorcomponent is currently receiving bothvalue(controlled) anddefaultValue(uncontrolled) props simultaneously. React components should typically be either fully controlled or uncontrolled, not both.Choose one approach:
valueandonChangedefaultValue<RichTextEditor onChange={(html, text) => console.log(html, text)} defaultValue="hello" - value="salam" label="badry" helperText="ali" />📝 Committable suggestion