@handled-ai/design-system 0.20.17 → 0.20.18

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.
@@ -10,7 +10,8 @@ interface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
10
10
  fontOptions?: RichTextFontOption[];
11
11
  selectedFontFamily?: string;
12
12
  onFontFamilyChange?: (fontFamily: string) => void;
13
+ showDelete?: boolean;
13
14
  }
14
- declare function RichTextToolbar({ onAction, className, fontOptions, selectedFontFamily, onFontFamilyChange, ...rest }: RichTextToolbarProps): React.JSX.Element;
15
+ declare function RichTextToolbar({ onAction, className, fontOptions, selectedFontFamily, onFontFamilyChange, showDelete, ...rest }: RichTextToolbarProps): React.JSX.Element;
15
16
 
16
17
  export { type RichTextAction, type RichTextFontOption, RichTextToolbar, type RichTextToolbarProps };
@@ -33,9 +33,15 @@ var __objRest = (source, exclude) => {
33
33
  return target;
34
34
  };
35
35
  import { jsx, jsxs } from "react/jsx-runtime";
36
- import * as React from "react";
37
36
  import { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from "lucide-react";
38
37
  import { cn } from "../lib/utils.js";
38
+ import {
39
+ DropdownMenu,
40
+ DropdownMenuContent,
41
+ DropdownMenuRadioGroup,
42
+ DropdownMenuRadioItem,
43
+ DropdownMenuTrigger
44
+ } from "./dropdown-menu.js";
39
45
  function ToolbarButton({
40
46
  action,
41
47
  icon: Icon,
@@ -61,30 +67,20 @@ function RichTextToolbar(_a) {
61
67
  className,
62
68
  fontOptions,
63
69
  selectedFontFamily,
64
- onFontFamilyChange
70
+ onFontFamilyChange,
71
+ showDelete = true
65
72
  } = _b, rest = __objRest(_b, [
66
73
  "onAction",
67
74
  "className",
68
75
  "fontOptions",
69
76
  "selectedFontFamily",
70
- "onFontFamilyChange"
77
+ "onFontFamilyChange",
78
+ "showDelete"
71
79
  ]);
72
- var _a2, _b2;
73
- const [fontMenuOpen, setFontMenuOpen] = React.useState(false);
74
- const fontMenuRef = React.useRef(null);
80
+ var _a2, _b2, _c;
75
81
  const selectedFontOption = (_a2 = fontOptions == null ? void 0 : fontOptions.find((option) => option.value === selectedFontFamily)) != null ? _a2 : fontOptions == null ? void 0 : fontOptions[0];
76
82
  const fontLabel = (_b2 = selectedFontOption == null ? void 0 : selectedFontOption.label) != null ? _b2 : "Sans Serif";
77
83
  const hasFontMenu = Boolean((fontOptions == null ? void 0 : fontOptions.length) && onFontFamilyChange);
78
- React.useEffect(() => {
79
- if (!fontMenuOpen) return;
80
- function handleDocumentMouseDown(event) {
81
- var _a3;
82
- if ((_a3 = fontMenuRef.current) == null ? void 0 : _a3.contains(event.target)) return;
83
- setFontMenuOpen(false);
84
- }
85
- document.addEventListener("mousedown", handleDocumentMouseDown);
86
- return () => document.removeEventListener("mousedown", handleDocumentMouseDown);
87
- }, [fontMenuOpen]);
88
84
  return /* @__PURE__ */ jsxs(
89
85
  "div",
90
86
  __spreadProps(__spreadValues({
@@ -98,51 +94,64 @@ function RichTextToolbar(_a) {
98
94
  /* @__PURE__ */ jsx(ToolbarButton, { action: "undo", icon: Undo2, label: "Undo", onAction }),
99
95
  /* @__PURE__ */ jsx(ToolbarButton, { action: "redo", icon: Redo2, label: "Redo", onAction }),
100
96
  /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1", "aria-hidden": "true" }),
101
- /* @__PURE__ */ jsxs("div", { className: "relative", ref: fontMenuRef, children: [
102
- /* @__PURE__ */ jsxs(
97
+ hasFontMenu ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
98
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
103
99
  "button",
104
100
  {
105
101
  type: "button",
106
102
  "data-slot": "rich-text-toolbar-button",
107
- onClick: () => {
108
- onAction == null ? void 0 : onAction("font");
109
- if (hasFontMenu) setFontMenuOpen((open) => !open);
110
- },
103
+ onClick: () => onAction == null ? void 0 : onAction("font"),
111
104
  "aria-label": "Font family",
112
105
  "aria-haspopup": "menu",
113
- "aria-expanded": hasFontMenu ? fontMenuOpen : void 0,
114
106
  className: "text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1",
115
107
  children: [
116
108
  fontLabel,
117
109
  /* @__PURE__ */ jsx(ChevronDown, { size: 10 })
118
110
  ]
119
111
  }
120
- ),
121
- hasFontMenu && fontMenuOpen ? /* @__PURE__ */ jsx(
122
- "div",
112
+ ) }),
113
+ /* @__PURE__ */ jsx(
114
+ DropdownMenuContent,
123
115
  {
124
- role: "menu",
125
- "aria-label": "Font family",
126
- className: "absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md",
127
- children: fontOptions.map((option) => /* @__PURE__ */ jsx(
128
- "button",
116
+ side: "top",
117
+ align: "start",
118
+ className: "min-w-32",
119
+ onMouseDown: (event) => event.preventDefault(),
120
+ children: /* @__PURE__ */ jsx(
121
+ DropdownMenuRadioGroup,
129
122
  {
130
- type: "button",
131
- role: "menuitemradio",
132
- "aria-checked": option.value === selectedFontFamily,
133
- className: "block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60",
134
- style: { fontFamily: option.value },
135
- onClick: () => {
136
- onFontFamilyChange == null ? void 0 : onFontFamilyChange(option.value);
137
- setFontMenuOpen(false);
138
- },
139
- children: option.label
140
- },
141
- option.value
142
- ))
123
+ "aria-label": "Font family",
124
+ value: (_c = selectedFontOption == null ? void 0 : selectedFontOption.value) != null ? _c : "",
125
+ onValueChange: (value) => onFontFamilyChange == null ? void 0 : onFontFamilyChange(value),
126
+ children: fontOptions == null ? void 0 : fontOptions.map((option) => /* @__PURE__ */ jsx(
127
+ DropdownMenuRadioItem,
128
+ {
129
+ value: option.value,
130
+ className: "text-xs",
131
+ style: { fontFamily: option.value },
132
+ children: option.label
133
+ },
134
+ option.value
135
+ ))
136
+ }
137
+ )
143
138
  }
144
- ) : null
145
- ] }),
139
+ )
140
+ ] }) : /* @__PURE__ */ jsxs(
141
+ "button",
142
+ {
143
+ type: "button",
144
+ "data-slot": "rich-text-toolbar-button",
145
+ onClick: () => onAction == null ? void 0 : onAction("font"),
146
+ "aria-label": "Font family",
147
+ "aria-haspopup": "menu",
148
+ className: "text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1",
149
+ children: [
150
+ fontLabel,
151
+ /* @__PURE__ */ jsx(ChevronDown, { size: 10 })
152
+ ]
153
+ }
154
+ ),
146
155
  /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1", "aria-hidden": "true" }),
147
156
  /* @__PURE__ */ jsx(ToolbarButton, { action: "bold", icon: Bold, label: "Bold", onAction }),
148
157
  /* @__PURE__ */ jsx(ToolbarButton, { action: "italic", icon: Italic, label: "Italic", onAction }),
@@ -151,7 +160,7 @@ function RichTextToolbar(_a) {
151
160
  /* @__PURE__ */ jsx(ToolbarButton, { action: "align", icon: AlignLeft, label: "Align left", onAction }),
152
161
  /* @__PURE__ */ jsx(ToolbarButton, { action: "list", icon: List, label: "List", onAction })
153
162
  ] }),
154
- /* @__PURE__ */ jsx(ToolbarButton, { action: "delete", icon: Trash2, label: "Delete", extraClassName: "hover:text-destructive", onAction })
163
+ showDelete ? /* @__PURE__ */ jsx(ToolbarButton, { action: "delete", icon: Trash2, label: "Delete", extraClassName: "hover:text-destructive", onAction }) : null
155
164
  ]
156
165
  })
157
166
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/rich-text-toolbar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\ntype RichTextAction =\n | \"undo\" | \"redo\"\n | \"font\"\n | \"bold\" | \"italic\" | \"underline\"\n | \"align\" | \"list\"\n | \"delete\"\n\ninterface RichTextFontOption {\n label: string\n value: string\n}\n\ninterface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {\n onAction?: (action: RichTextAction) => void\n fontOptions?: RichTextFontOption[]\n selectedFontFamily?: string\n onFontFamilyChange?: (fontFamily: string) => void\n}\n\nfunction ToolbarButton({\n action,\n icon: Icon,\n label,\n extraClassName,\n onAction,\n}: {\n action: RichTextAction\n icon: LucideIcon\n label: string\n extraClassName?: string\n onAction?: (action: RichTextAction) => void\n}) {\n return (\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(action)}\n aria-label={label}\n className={cn(\"p-1.5 rounded hover:bg-muted/50 cursor-pointer text-muted-foreground\", extraClassName)}\n >\n <Icon size={14} />\n </button>\n )\n}\n\nfunction RichTextToolbar({\n onAction,\n className,\n fontOptions,\n selectedFontFamily,\n onFontFamilyChange,\n ...rest\n}: RichTextToolbarProps) {\n const [fontMenuOpen, setFontMenuOpen] = React.useState(false)\n const fontMenuRef = React.useRef<HTMLDivElement | null>(null)\n const selectedFontOption = fontOptions?.find((option) => option.value === selectedFontFamily) ?? fontOptions?.[0]\n const fontLabel = selectedFontOption?.label ?? \"Sans Serif\"\n const hasFontMenu = Boolean(fontOptions?.length && onFontFamilyChange)\n\n React.useEffect(() => {\n if (!fontMenuOpen) return\n\n function handleDocumentMouseDown(event: MouseEvent) {\n if (fontMenuRef.current?.contains(event.target as Node)) return\n setFontMenuOpen(false)\n }\n\n document.addEventListener(\"mousedown\", handleDocumentMouseDown)\n return () => document.removeEventListener(\"mousedown\", handleDocumentMouseDown)\n }, [fontMenuOpen])\n\n return (\n <div\n data-slot=\"rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\"px-3 py-1.5 flex items-center justify-between\", className)}\n {...rest}\n >\n <div className=\"flex items-center gap-0.5\">\n <ToolbarButton action=\"undo\" icon={Undo2} label=\"Undo\" onAction={onAction} />\n <ToolbarButton action=\"redo\" icon={Redo2} label=\"Redo\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <div className=\"relative\" ref={fontMenuRef}>\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => {\n onAction?.(\"font\")\n if (hasFontMenu) setFontMenuOpen((open) => !open)\n }}\n aria-label=\"Font family\"\n aria-haspopup=\"menu\"\n aria-expanded={hasFontMenu ? fontMenuOpen : undefined}\n className=\"text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1\"\n >\n {fontLabel}\n <ChevronDown size={10} />\n </button>\n\n {hasFontMenu && fontMenuOpen ? (\n <div\n role=\"menu\"\n aria-label=\"Font family\"\n className=\"absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md\"\n >\n {fontOptions!.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={option.value === selectedFontFamily}\n className=\"block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60\"\n style={{ fontFamily: option.value }}\n onClick={() => {\n onFontFamilyChange?.(option.value)\n setFontMenuOpen(false)\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"bold\" icon={Bold} label=\"Bold\" onAction={onAction} />\n <ToolbarButton action=\"italic\" icon={Italic} label=\"Italic\" onAction={onAction} />\n <ToolbarButton action=\"underline\" icon={Underline} label=\"Underline\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"align\" icon={AlignLeft} label=\"Align left\" onAction={onAction} />\n <ToolbarButton action=\"list\" icon={List} label=\"List\" onAction={onAction} />\n </div>\n\n <ToolbarButton action=\"delete\" icon={Trash2} label=\"Delete\" extraClassName=\"hover:text-destructive\" onAction={onAction} />\n </div>\n )\n}\n\nexport { RichTextToolbar, type RichTextToolbarProps, type RichTextAction, type RichTextFontOption }\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../src/components/rich-text-toolbar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\n\ntype RichTextAction =\n | \"undo\" | \"redo\"\n | \"font\"\n | \"bold\" | \"italic\" | \"underline\"\n | \"align\" | \"list\"\n | \"delete\"\n\ninterface RichTextFontOption {\n label: string\n value: string\n}\n\ninterface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {\n onAction?: (action: RichTextAction) => void\n fontOptions?: RichTextFontOption[]\n selectedFontFamily?: string\n onFontFamilyChange?: (fontFamily: string) => void\n showDelete?: boolean\n}\n\nfunction ToolbarButton({\n action,\n icon: Icon,\n label,\n extraClassName,\n onAction,\n}: {\n action: RichTextAction\n icon: LucideIcon\n label: string\n extraClassName?: string\n onAction?: (action: RichTextAction) => void\n}) {\n return (\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(action)}\n aria-label={label}\n className={cn(\"p-1.5 rounded hover:bg-muted/50 cursor-pointer text-muted-foreground\", extraClassName)}\n >\n <Icon size={14} />\n </button>\n )\n}\n\nfunction RichTextToolbar({\n onAction,\n className,\n fontOptions,\n selectedFontFamily,\n onFontFamilyChange,\n showDelete = true,\n ...rest\n}: RichTextToolbarProps) {\n const selectedFontOption = fontOptions?.find((option) => option.value === selectedFontFamily) ?? fontOptions?.[0]\n const fontLabel = selectedFontOption?.label ?? \"Sans Serif\"\n const hasFontMenu = Boolean(fontOptions?.length && onFontFamilyChange)\n\n return (\n <div\n data-slot=\"rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\"px-3 py-1.5 flex items-center justify-between\", className)}\n {...rest}\n >\n <div className=\"flex items-center gap-0.5\">\n <ToolbarButton action=\"undo\" icon={Undo2} label=\"Undo\" onAction={onAction} />\n <ToolbarButton action=\"redo\" icon={Redo2} label=\"Redo\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n {hasFontMenu ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(\"font\")}\n aria-label=\"Font family\"\n aria-haspopup=\"menu\"\n className=\"text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1\"\n >\n {fontLabel}\n <ChevronDown size={10} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side=\"top\"\n align=\"start\"\n className=\"min-w-32\"\n onMouseDown={(event) => event.preventDefault()}\n >\n <DropdownMenuRadioGroup\n aria-label=\"Font family\"\n value={selectedFontOption?.value ?? \"\"}\n onValueChange={(value) => onFontFamilyChange?.(value)}\n >\n {fontOptions?.map((option) => (\n <DropdownMenuRadioItem\n key={option.value}\n value={option.value}\n className=\"text-xs\"\n style={{ fontFamily: option.value }}\n >\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(\"font\")}\n aria-label=\"Font family\"\n aria-haspopup=\"menu\"\n className=\"text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1\"\n >\n {fontLabel}\n <ChevronDown size={10} />\n </button>\n )}\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"bold\" icon={Bold} label=\"Bold\" onAction={onAction} />\n <ToolbarButton action=\"italic\" icon={Italic} label=\"Italic\" onAction={onAction} />\n <ToolbarButton action=\"underline\" icon={Underline} label=\"Underline\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"align\" icon={AlignLeft} label=\"Align left\" onAction={onAction} />\n <ToolbarButton action=\"list\" icon={List} label=\"List\" onAction={onAction} />\n </div>\n\n {showDelete ? (\n <ToolbarButton action=\"delete\" icon={Trash2} label=\"Delete\" extraClassName=\"hover:text-destructive\" onAction={onAction} />\n ) : null}\n </div>\n )\n}\n\nexport { RichTextToolbar, type RichTextToolbarProps, type RichTextAction, type RichTextFontOption }\n"],"mappings":"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","names":["_a","_b"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.20.17",
3
+ "version": "0.20.18",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -54,7 +54,7 @@ describe("RichTextToolbar", () => {
54
54
  expect(handler).toHaveBeenCalledWith("font");
55
55
  });
56
56
 
57
- it("opens a font menu and emits the selected font family when options are provided", () => {
57
+ it("opens a font menu and emits the selected font family when options are provided", async () => {
58
58
  const handler = vi.fn();
59
59
  render(
60
60
  <RichTextToolbar
@@ -67,18 +67,23 @@ describe("RichTextToolbar", () => {
67
67
  />,
68
68
  );
69
69
 
70
- fireEvent.click(screen.getByLabelText("Font family"));
71
- expect(screen.getByRole("menu", { name: "Font family" })).toBeDefined();
70
+ fireEvent.pointerDown(screen.getByLabelText("Font family"), { button: 0, ctrlKey: false });
71
+ expect(await screen.findByRole("menu")).toBeDefined();
72
72
  fireEvent.click(screen.getByRole("menuitemradio", { name: "Serif" }));
73
73
 
74
74
  expect(handler).toHaveBeenCalledWith("Georgia, serif");
75
- expect(screen.queryByRole("menu", { name: "Font family" })).toBeNull();
75
+ expect(screen.queryByRole("menu")).toBeNull();
76
76
  });
77
77
 
78
78
  it("keeps the font button backward compatible when no menu options are supplied", () => {
79
79
  render(<RichTextToolbar />);
80
80
  fireEvent.click(screen.getByLabelText("Font family"));
81
- expect(screen.queryByRole("menu", { name: "Font family" })).toBeNull();
81
+ expect(screen.queryByRole("menu")).toBeNull();
82
+ });
83
+
84
+ it("can hide the delete action", () => {
85
+ render(<RichTextToolbar showDelete={false} />);
86
+ expect(screen.queryByLabelText("Delete")).toBeNull();
82
87
  });
83
88
 
84
89
  it("fires onAction with 'delete' when Delete button clicked", () => {
@@ -5,6 +5,13 @@ import type { LucideIcon } from "lucide-react"
5
5
  import { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from "lucide-react"
6
6
 
7
7
  import { cn } from "../lib/utils"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuRadioGroup,
12
+ DropdownMenuRadioItem,
13
+ DropdownMenuTrigger,
14
+ } from "./dropdown-menu"
8
15
 
9
16
  type RichTextAction =
10
17
  | "undo" | "redo"
@@ -23,6 +30,7 @@ interface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
23
30
  fontOptions?: RichTextFontOption[]
24
31
  selectedFontFamily?: string
25
32
  onFontFamilyChange?: (fontFamily: string) => void
33
+ showDelete?: boolean
26
34
  }
27
35
 
28
36
  function ToolbarButton({
@@ -57,26 +65,13 @@ function RichTextToolbar({
57
65
  fontOptions,
58
66
  selectedFontFamily,
59
67
  onFontFamilyChange,
68
+ showDelete = true,
60
69
  ...rest
61
70
  }: RichTextToolbarProps) {
62
- const [fontMenuOpen, setFontMenuOpen] = React.useState(false)
63
- const fontMenuRef = React.useRef<HTMLDivElement | null>(null)
64
71
  const selectedFontOption = fontOptions?.find((option) => option.value === selectedFontFamily) ?? fontOptions?.[0]
65
72
  const fontLabel = selectedFontOption?.label ?? "Sans Serif"
66
73
  const hasFontMenu = Boolean(fontOptions?.length && onFontFamilyChange)
67
74
 
68
- React.useEffect(() => {
69
- if (!fontMenuOpen) return
70
-
71
- function handleDocumentMouseDown(event: MouseEvent) {
72
- if (fontMenuRef.current?.contains(event.target as Node)) return
73
- setFontMenuOpen(false)
74
- }
75
-
76
- document.addEventListener("mousedown", handleDocumentMouseDown)
77
- return () => document.removeEventListener("mousedown", handleDocumentMouseDown)
78
- }, [fontMenuOpen])
79
-
80
75
  return (
81
76
  <div
82
77
  data-slot="rich-text-toolbar"
@@ -91,48 +86,58 @@ function RichTextToolbar({
91
86
 
92
87
  <div className="w-px h-4 bg-border mx-1" aria-hidden="true" />
93
88
 
94
- <div className="relative" ref={fontMenuRef}>
89
+ {hasFontMenu ? (
90
+ <DropdownMenu>
91
+ <DropdownMenuTrigger asChild>
92
+ <button
93
+ type="button"
94
+ data-slot="rich-text-toolbar-button"
95
+ onClick={() => onAction?.("font")}
96
+ aria-label="Font family"
97
+ aria-haspopup="menu"
98
+ className="text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1"
99
+ >
100
+ {fontLabel}
101
+ <ChevronDown size={10} />
102
+ </button>
103
+ </DropdownMenuTrigger>
104
+ <DropdownMenuContent
105
+ side="top"
106
+ align="start"
107
+ className="min-w-32"
108
+ onMouseDown={(event) => event.preventDefault()}
109
+ >
110
+ <DropdownMenuRadioGroup
111
+ aria-label="Font family"
112
+ value={selectedFontOption?.value ?? ""}
113
+ onValueChange={(value) => onFontFamilyChange?.(value)}
114
+ >
115
+ {fontOptions?.map((option) => (
116
+ <DropdownMenuRadioItem
117
+ key={option.value}
118
+ value={option.value}
119
+ className="text-xs"
120
+ style={{ fontFamily: option.value }}
121
+ >
122
+ {option.label}
123
+ </DropdownMenuRadioItem>
124
+ ))}
125
+ </DropdownMenuRadioGroup>
126
+ </DropdownMenuContent>
127
+ </DropdownMenu>
128
+ ) : (
95
129
  <button
96
130
  type="button"
97
131
  data-slot="rich-text-toolbar-button"
98
- onClick={() => {
99
- onAction?.("font")
100
- if (hasFontMenu) setFontMenuOpen((open) => !open)
101
- }}
132
+ onClick={() => onAction?.("font")}
102
133
  aria-label="Font family"
103
134
  aria-haspopup="menu"
104
- aria-expanded={hasFontMenu ? fontMenuOpen : undefined}
105
135
  className="text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1"
106
136
  >
107
137
  {fontLabel}
108
138
  <ChevronDown size={10} />
109
139
  </button>
110
-
111
- {hasFontMenu && fontMenuOpen ? (
112
- <div
113
- role="menu"
114
- aria-label="Font family"
115
- className="absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md"
116
- >
117
- {fontOptions!.map((option) => (
118
- <button
119
- key={option.value}
120
- type="button"
121
- role="menuitemradio"
122
- aria-checked={option.value === selectedFontFamily}
123
- className="block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60"
124
- style={{ fontFamily: option.value }}
125
- onClick={() => {
126
- onFontFamilyChange?.(option.value)
127
- setFontMenuOpen(false)
128
- }}
129
- >
130
- {option.label}
131
- </button>
132
- ))}
133
- </div>
134
- ) : null}
135
- </div>
140
+ )}
136
141
 
137
142
  <div className="w-px h-4 bg-border mx-1" aria-hidden="true" />
138
143
 
@@ -146,7 +151,9 @@ function RichTextToolbar({
146
151
  <ToolbarButton action="list" icon={List} label="List" onAction={onAction} />
147
152
  </div>
148
153
 
149
- <ToolbarButton action="delete" icon={Trash2} label="Delete" extraClassName="hover:text-destructive" onAction={onAction} />
154
+ {showDelete ? (
155
+ <ToolbarButton action="delete" icon={Trash2} label="Delete" extraClassName="hover:text-destructive" onAction={onAction} />
156
+ ) : null}
150
157
  </div>
151
158
  )
152
159
  }