Skip to content
Merged
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
34 changes: 33 additions & 1 deletion src/components/ui/RichTextEditor.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { WandSparkles } from "lucide-react";
import { fn } from "storybook/test";
import { RichTextEditor } from "./rich-text-editor";
import { Label } from "./label";

Expand All @@ -14,6 +16,7 @@ const meta = {
},
placeholder: { control: "text" },
defaultValue: { control: "text" },
contentAction: { control: false },
},
decorators: [
(Story) => (
Expand All @@ -28,7 +31,7 @@ export default meta;

type Story = StoryObj<typeof meta>;

/** Full toolbar variant with all formatting options including font size, image, undo/redo, and more. */
/** Full toolbar variant with all formatting options. Content action is hidden by default. */
export const Default: Story = {
args: {
variant: "full",
Expand Down Expand Up @@ -75,3 +78,32 @@ export const WithLabel: Story = {
placeholder: "Enter your description here...",
},
};

/** Show the bottom-right content action button with default sparkles icon (no text). */
export const ContentActionIconOnly: Story = {
args: {
variant: "full",
placeholder: "Icon-only content action...",
contentAction: {
show: true,
onClick: fn(),
},
},
};

/** Replace the default sparkles button with custom icon, label, and click handler. */
export const ContentActionCustom: Story = {
args: {
variant: "full",
placeholder: "Try the custom AI action...",
contentAction: {
show: true,
title: "Rewrite with AI",
showContent: true,
icon: <WandSparkles className="size-4 text-sky-600" />,
content: <span className="text-xs">Rewrite</span>,
className: "border border-border bg-background px-2",
onClick: fn(),
},
},
};
6 changes: 5 additions & 1 deletion src/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export {
} from "./sortable";
export { Switch, LabeledSwitch, SwitchCard, type SwitchProps, type LabeledSwitchProps, type SwitchCardProps } from "./switch";
export { Textarea } from "./textarea";
export { RichTextEditor, type RichTextEditorProps } from "./rich-text-editor";
export {
RichTextEditor,
type RichTextEditorProps,
type RichTextEditorContentAction,
} from "./rich-text-editor";
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
export { Toggle } from './toggle'
export { ToggleGroup, ToggleGroupItem } from './toggle-group'
Expand Down
44 changes: 37 additions & 7 deletions src/components/ui/rich-text-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,24 @@ import {

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

interface RichTextEditorContentAction {
show?: boolean;
showContent?: boolean;
title?: string;
icon?: React.ReactNode;
content?: React.ReactNode;
onClick?: () => void;
className?: string;
}

interface RichTextEditorProps {
value?: string;
defaultValue?: string;
onChange?: (value: string) => void;
placeholder?: string;
className?: string;
variant?: "full" | "simple";
contentAction?: RichTextEditorContentAction;
}

function RichTextEditor({
Expand All @@ -34,6 +45,7 @@ function RichTextEditor({
placeholder = "Start typing...",
className = "",
variant = "full",
contentAction,
}: RichTextEditorProps) {
const editorRef = React.useRef<HTMLDivElement>(null);
const [fontSize, setFontSize] = React.useState("14 px");
Expand Down Expand Up @@ -90,6 +102,12 @@ function RichTextEditor({
executeCommand("formatBlock", formatMap[e.target.value] || "p");
};

const showContentAction = contentAction?.show ?? false;
const showContentActionContent = contentAction?.showContent ?? false;
const contentActionIcon = contentAction?.icon === undefined
? <Sparkles className="size-4 text-muted-foreground" />
: contentAction.icon;

return (
<div
className={cn(
Expand Down Expand Up @@ -312,12 +330,20 @@ function RichTextEditor({
minHeight: variant === "full" ? "100px" : "120px",
}}
/>
<button
type="button"
className="absolute bottom-2 right-2 p-1.5 hover:bg-muted rounded"
>
<Sparkles className="size-4 text-muted-foreground" />
</button>
{showContentAction && (
<button
type="button"
className={cn(
"absolute bottom-2 right-2 inline-flex items-center gap-1.5 p-1.5 hover:bg-muted rounded",
contentAction?.className,
)}
title={contentAction?.title ?? "AI Assist"}
onClick={contentAction?.onClick}
>
{contentActionIcon}
{showContentActionContent ? contentAction?.content : null}
</button>
)}
</div>

<style>{`
Expand All @@ -334,4 +360,8 @@ function RichTextEditor({
);
}

export { RichTextEditor, type RichTextEditorProps };
export {
RichTextEditor,
type RichTextEditorProps,
type RichTextEditorContentAction,
};
Loading