From 8f8113c12a11fea2a8d4f172dac8ff5fa02bd3e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:25:31 +0000 Subject: [PATCH 1/5] Initial plan From 8e327fa42e41969d11e6938b0aeee197f538a478 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:32:10 +0000 Subject: [PATCH 2/5] Add editor component based on Shadcn UI and Edkit Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- package.json | 1 + pnpm-lock.yaml | 54 ++++++++ registry.json | 57 ++++++++ registry/new-york/blocks/editor/editor.tsx | 127 ++++++++++++++++++ registry/new-york/blocks/editor/example.tsx | 43 ++++++ registry/new-york/blocks/editor/index.ts | 3 + registry/new-york/blocks/editor/tool.tsx | 62 +++++++++ .../new-york/blocks/editor/tools/color.tsx | 90 +++++++++++++ .../new-york/blocks/editor/tools/control.ts | 28 ++++ .../new-york/blocks/editor/tools/extra.ts | 8 ++ .../new-york/blocks/editor/tools/index.ts | 100 ++++++++++++++ .../new-york/blocks/editor/tools/layout.ts | 46 +++++++ .../new-york/blocks/editor/tools/media.ts | 28 ++++ registry/new-york/blocks/editor/tools/text.ts | 76 +++++++++++ 14 files changed, 723 insertions(+) create mode 100644 registry/new-york/blocks/editor/editor.tsx create mode 100644 registry/new-york/blocks/editor/example.tsx create mode 100644 registry/new-york/blocks/editor/index.ts create mode 100644 registry/new-york/blocks/editor/tool.tsx create mode 100644 registry/new-york/blocks/editor/tools/color.tsx create mode 100644 registry/new-york/blocks/editor/tools/control.ts create mode 100644 registry/new-york/blocks/editor/tools/extra.ts create mode 100644 registry/new-york/blocks/editor/tools/index.ts create mode 100644 registry/new-york/blocks/editor/tools/layout.ts create mode 100644 registry/new-york/blocks/editor/tools/media.ts create mode 100644 registry/new-york/blocks/editor/tools/text.ts diff --git a/package.json b/package.json index a5f2fc5..4000e1f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "edkit": "^1.2.7", "lodash.debounce": "^4.0.8", "lucide-react": "^0.562.0", "mobx": "^6.15.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a69a97..2f64019 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + edkit: + specifier: ^1.2.7 + version: 1.2.7(typescript@5.9.3) lodash.debounce: specifier: ^4.0.8 version: 4.0.8 @@ -581,6 +584,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@modelcontextprotocol/sdk@1.25.1': resolution: {integrity: sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==} engines: {node: '>=18'} @@ -1082,6 +1088,9 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + '@typescript-eslint/eslint-plugin@8.50.0': resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1376,6 +1385,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browser-fs-access@0.37.0: + resolution: {integrity: sha512-MKpvZrKtv6pBJ2ACd+VwfS9XauBKTMVZg2UBibypuK1gfiXM7euZjbdKmvRsyxeQRhfzNVQrzCSVGXs19/LP8Q==} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1619,6 +1631,9 @@ packages: resolution: {integrity: sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} + edkit@1.2.7: + resolution: {integrity: sha512-dCOBN9MMbCaCdSqhnZTSHPe7lu53TQttttjVBxLE/TehsQasuxmqW3ckimVODFaJVci1A6w429j9bebpiU3zKg==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2490,6 +2505,11 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -3267,6 +3287,12 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turndown-plugin-gfm@1.0.2: + resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} + + turndown@7.2.2: + resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} @@ -3983,6 +4009,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mixmark-io/domino@2.2.0': {} + '@modelcontextprotocol/sdk@1.25.1(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.7 @@ -4419,6 +4447,8 @@ snapshots: '@types/statuses@2.0.6': {} + '@types/turndown@5.0.6': {} + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4740,6 +4770,8 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-fs-access@0.37.0: {} + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.10 @@ -4952,6 +4984,20 @@ snapshots: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 + edkit@1.2.7(typescript@5.9.3): + dependencies: + '@swc/helpers': 0.5.17 + '@types/turndown': 5.0.6 + browser-fs-access: 0.37.0 + marked: 15.0.12 + regenerator-runtime: 0.14.1 + turndown: 7.2.2 + turndown-plugin-gfm: 1.0.2 + web-utility: 4.6.4(typescript@5.9.3) + transitivePeerDependencies: + - element-internals-polyfill + - typescript + ee-first@1.1.1: {} electron-to-chromium@1.5.267: {} @@ -5940,6 +5986,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + marked@15.0.12: {} + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} @@ -6858,6 +6906,12 @@ snapshots: tslib@2.8.1: {} + turndown-plugin-gfm@1.0.2: {} + + turndown@7.2.2: + dependencies: + '@mixmark-io/domino': 2.2.0 + tw-animate-css@1.4.0: {} type-check@0.4.0: diff --git a/registry.json b/registry.json index 4cb3e20..3bf4920 100644 --- a/registry.json +++ b/registry.json @@ -342,6 +342,63 @@ "type": "registry:component" } ] + }, + { + "name": "editor", + "type": "registry:component", + "title": "Editor", + "description": "A lightweight rich text editor based on Edkit and Shadcn UI with various formatting tools.", + "registryDependencies": ["button"], + "dependencies": [ + "edkit", + "lucide-react", + "mobx", + "mobx-react", + "mobx-react-helper", + "web-utility" + ], + "files": [ + { + "path": "registry/new-york/blocks/editor/editor.tsx", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tool.tsx", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/index.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/text.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/layout.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/control.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/media.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/color.tsx", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/tools/extra.ts", + "type": "registry:component" + }, + { + "path": "registry/new-york/blocks/editor/index.ts", + "type": "registry:component" + } + ] } ] } diff --git a/registry/new-york/blocks/editor/editor.tsx b/registry/new-york/blocks/editor/editor.tsx new file mode 100644 index 0000000..a187c9d --- /dev/null +++ b/registry/new-york/blocks/editor/editor.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { EditorComponent, ImageTool, Tool, editor } from "edkit"; +import { computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { FormComponent, FormComponentProps } from "mobx-react-helper"; +import { createRef } from "react"; +import { Constructor } from "web-utility"; + +import { AudioTool, DefaultTools, VideoTool } from "./tools"; +import { cn } from "@/lib/utils"; + +export interface EditorProps extends FormComponentProps { + tools?: Constructor[]; + className?: string; +} + +export interface Editor extends EditorComponent {} + +@observer +@editor +export class Editor + extends FormComponent + implements EditorComponent +{ + static displayName = "Editor"; + + box = createRef(); + + @observable + accessor cursorPoint = ""; + + @computed + get toolList(): Tool[] { + return (this.observedProps.tools || DefaultTools).map( + (ToolButton) => new ToolButton() + ); + } + + @computed + get imageTool() { + return this.toolList.find( + (tool) => tool instanceof ImageTool + ) as ImageTool; + } + + @computed + get audioTool() { + return this.toolList.find( + (tool) => tool instanceof AudioTool + ) as AudioTool; + } + + @computed + get videoTool() { + return this.toolList.find( + (tool) => tool instanceof VideoTool + ) as VideoTool; + } + + componentDidMount() { + super.componentDidMount(); + + const { defaultValue } = this.props; + + if (defaultValue != null) this.box.current!.innerHTML = defaultValue; + + document.addEventListener("selectionchange", this.updateTools); + } + + componentWillUnmount() { + super.componentWillUnmount(); + + document.removeEventListener("selectionchange", this.updateTools); + } + + updateTools = () => { + if (this.box.current !== document.activeElement) return; + + const selection = getSelection(); + if (!selection || selection.rangeCount === 0) return; + + const { endContainer } = selection.getRangeAt(0) || {}; + const element = + endContainer instanceof Element + ? endContainer + : endContainer?.parentElement; + + const rect = element?.getBoundingClientRect(); + if (rect) { + this.cursorPoint = [rect.x, rect.y].join(""); + } + }; + + updateValue = (markup: string) => (this.innerValue = markup.trim()); + + render() { + // Don't remove unused variable `cursorPoint`, which is used for triggering updates + const { cursorPoint, toolList, innerValue } = this; + const { name, className } = this.props; + + return ( + <> +
+ {toolList.map((tool) => tool.render(this.box))} +
+
+ this.updateValue(innerHTML) + } + onPaste={this.handlePasteDrop} + onDrop={this.handlePasteDrop} + /> + + + ); + } +} diff --git a/registry/new-york/blocks/editor/example.tsx b/registry/new-york/blocks/editor/example.tsx new file mode 100644 index 0000000..c27fcca --- /dev/null +++ b/registry/new-york/blocks/editor/example.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { configure } from "mobx"; +import { formToJSON } from "web-utility"; +import { Editor, OriginalTools, ExtraTools } from "./index"; + +configure({ enforceActions: "never" }); + +export default function EditorExample() { + return ( +
{ + event.preventDefault(); + const { content } = formToJSON(event.currentTarget); + alert(content); + }} + > +
+
+

Rich Text Editor

+

+ A lightweight rich text editor based on Edkit and Shadcn UI +

+
+ + + + +
+
+ ); +} diff --git a/registry/new-york/blocks/editor/index.ts b/registry/new-york/blocks/editor/index.ts new file mode 100644 index 0000000..8cf5f0a --- /dev/null +++ b/registry/new-york/blocks/editor/index.ts @@ -0,0 +1,3 @@ +export * from "./tool"; +export * from "./tools"; +export * from "./editor"; diff --git a/registry/new-york/blocks/editor/tool.tsx b/registry/new-york/blocks/editor/tool.tsx new file mode 100644 index 0000000..f64236d --- /dev/null +++ b/registry/new-york/blocks/editor/tool.tsx @@ -0,0 +1,62 @@ +import { RefObject } from "react"; +import { Tool } from "edkit"; +import { Button } from "@/components/ui/button"; +import * as Icons from "lucide-react"; + +export function renderTool(this: Tool, editor: RefObject) { + const { title, active, icon, usable } = this; + + // Map Bootstrap icons to Lucide icons + const iconMap: Record = { + "type-bold": "Bold", + "type-italic": "Italic", + "type-underline": "Underline", + "type-strikethrough": "Strikethrough", + "type-h1": "Heading1", + "type-h2": "Heading2", + "type-h3": "Heading3", + "sort-alpha-down": "ArrowDownAZ", + "sort-alpha-up": "ArrowUpAZ", + "box-arrow-down-right": "ArrowDownRight", + "box-arrow-up-right": "ArrowUpRight", + link: "Link", + "text-left": "AlignLeft", + "text-center": "AlignCenter", + "text-right": "AlignRight", + justify: "AlignJustify", + "list-ol": "ListOrdered", + "list-ul": "List", + "reception-0": "Minus", + window: "Frame", + image: "Image", + voicemail: "Mic", + "camera-video": "Video", + "arrow-counterclockwise": "Undo", + "arrow-clockwise": "Redo", + eraser: "Eraser", + "file-earmark-x": "X", + markdown: "FileText", + }; + + const IconComponent = Icons[iconMap[icon] || "Circle"] as React.ComponentType< + React.SVGProps + >; + + return ( + + ); +} diff --git a/registry/new-york/blocks/editor/tools/color.tsx b/registry/new-york/blocks/editor/tools/color.tsx new file mode 100644 index 0000000..03edefc --- /dev/null +++ b/registry/new-york/blocks/editor/tools/color.tsx @@ -0,0 +1,90 @@ +import { FC, RefObject } from "react"; +import { + ColorName, + ColorTool, + ForeColorTool as FCT, + BackColorTool as BCT, +} from "edkit"; +import { Button } from "@/components/ui/button"; +import { Type, FileText } from "lucide-react"; + +export interface ColorSelectorProps + extends Partial> { + icon: "file-earmark-font" | "file-earmark-font-fill"; + type: ColorName; + onChange?: (color: string) => any; +} + +export const ColorSelector: FC = ({ + className = "", + title, + type, + value, + onChange, + icon, +}) => { + const IconComponent = icon === "file-earmark-font" ? Type : FileText; + + return ( + + onChange?.(value)} + /> + + + ); +}; + +export function renderColorTool( + this: ColorTool, + editor: RefObject +) { + const { icon, name, colorName } = this; + + return ( + + editor.current && this.execute(editor.current, color) + } + /> + ); +} + +export class ForeColorTool extends FCT { + icon = "file-earmark-font"; + render = renderColorTool; +} + +export class BackColorTool extends BCT { + icon = "file-earmark-font-fill"; + render = renderColorTool; +} diff --git a/registry/new-york/blocks/editor/tools/control.ts b/registry/new-york/blocks/editor/tools/control.ts new file mode 100644 index 0000000..f39375e --- /dev/null +++ b/registry/new-york/blocks/editor/tools/control.ts @@ -0,0 +1,28 @@ +import { + UndoTool as UDT, + RedoTool as RDT, + ResetTool as RST, + ClearTool as CT, +} from "edkit"; + +import { renderTool } from "../tool"; + +export class UndoTool extends UDT { + icon = "arrow-counterclockwise"; + render = renderTool; +} + +export class RedoTool extends RDT { + icon = "arrow-clockwise"; + render = renderTool; +} + +export class ResetTool extends RST { + icon = "eraser"; + render = renderTool; +} + +export class ClearTool extends CT { + icon = "file-earmark-x"; + render = renderTool; +} diff --git a/registry/new-york/blocks/editor/tools/extra.ts b/registry/new-york/blocks/editor/tools/extra.ts new file mode 100644 index 0000000..97b67ac --- /dev/null +++ b/registry/new-york/blocks/editor/tools/extra.ts @@ -0,0 +1,8 @@ +import { CopyMarkdownTool as CMDT } from "edkit"; + +import { renderTool } from "../tool"; + +export class CopyMarkdownTool extends CMDT { + icon = "markdown"; + render = renderTool; +} diff --git a/registry/new-york/blocks/editor/tools/index.ts b/registry/new-york/blocks/editor/tools/index.ts new file mode 100644 index 0000000..8c304b2 --- /dev/null +++ b/registry/new-york/blocks/editor/tools/index.ts @@ -0,0 +1,100 @@ +import { + BoldTool, + ItalicTool, + UnderlineTool, + StrikeThroughTool, + H1Tool, + H2Tool, + H3Tool, + FontSizeDownTool, + FontSizeUpTool, + SubscriptTool, + SuperscriptTool, + LinkTool, +} from "./text"; +import { ForeColorTool, BackColorTool } from "./color"; +import { + AlignLeftTool, + AlignCenterTool, + AlignRightTool, + AlignFullTool, + OrderedListTool, + UnorderedListTool, + HorizontalRuleTool, +} from "./layout"; +import { IFrameTool, ImageTool, AudioTool, VideoTool } from "./media"; +import { UndoTool, RedoTool, ResetTool, ClearTool } from "./control"; +import { CopyMarkdownTool } from "./extra"; + +export * from "./text"; +export * from "./color"; +export * from "./layout"; +export * from "./media"; +export * from "./control"; +export * from "./extra"; + +export const TextTools = [ + BoldTool, + ItalicTool, + UnderlineTool, + StrikeThroughTool, + H1Tool, + H2Tool, + H3Tool, + FontSizeDownTool, + FontSizeUpTool, + SubscriptTool, + SuperscriptTool, + LinkTool, +]; + +export const ColorTools = [ForeColorTool, BackColorTool]; + +export const LayoutTools = [ + AlignLeftTool, + AlignCenterTool, + AlignRightTool, + AlignFullTool, + OrderedListTool, + UnorderedListTool, + HorizontalRuleTool, +]; + +export const MediaTools = [IFrameTool, ImageTool, AudioTool, VideoTool]; + +export const ControlTools = [UndoTool, RedoTool, ResetTool, ClearTool]; + +export const ExtraTools = [CopyMarkdownTool]; + +export const OriginalTools = [ + ...TextTools, + ...ColorTools, + ...LayoutTools, + ...MediaTools, + ...ControlTools, +]; + +export const DefaultTools = [ + BoldTool, + ItalicTool, + UnderlineTool, + StrikeThroughTool, + H1Tool, + H2Tool, + H3Tool, + SubscriptTool, + SuperscriptTool, + ForeColorTool, + BackColorTool, + AlignLeftTool, + AlignCenterTool, + AlignRightTool, + AlignFullTool, + OrderedListTool, + UnorderedListTool, + HorizontalRuleTool, + ImageTool, + UndoTool, + RedoTool, + ClearTool, +]; diff --git a/registry/new-york/blocks/editor/tools/layout.ts b/registry/new-york/blocks/editor/tools/layout.ts new file mode 100644 index 0000000..c2fbd02 --- /dev/null +++ b/registry/new-york/blocks/editor/tools/layout.ts @@ -0,0 +1,46 @@ +import { + AlignLeftTool as ALT, + AlignCenterTool as ACT, + AlignRightTool as ART, + AlignFullTool as AFT, + OrderedListTool as OLT, + UnorderedListTool as ULT, + HorizontalRuleTool as HRT, +} from "edkit"; + +import { renderTool } from "../tool"; + +export class AlignLeftTool extends ALT { + icon = "text-left"; + render = renderTool; +} + +export class AlignCenterTool extends ACT { + icon = "text-center"; + render = renderTool; +} + +export class AlignRightTool extends ART { + icon = "text-right"; + render = renderTool; +} + +export class AlignFullTool extends AFT { + icon = "justify"; + render = renderTool; +} + +export class OrderedListTool extends OLT { + icon = "list-ol"; + render = renderTool; +} + +export class UnorderedListTool extends ULT { + icon = "list-ul"; + render = renderTool; +} + +export class HorizontalRuleTool extends HRT { + icon = "reception-0"; + render = renderTool; +} diff --git a/registry/new-york/blocks/editor/tools/media.ts b/registry/new-york/blocks/editor/tools/media.ts new file mode 100644 index 0000000..1904965 --- /dev/null +++ b/registry/new-york/blocks/editor/tools/media.ts @@ -0,0 +1,28 @@ +import { + IFrameTool as FT, + ImageTool as IT, + AudioTool as AT, + VideoTool as VT, +} from "edkit"; + +import { renderTool } from "../tool"; + +export class IFrameTool extends FT { + icon = "window"; + render = renderTool; +} + +export class ImageTool extends IT { + icon = "image"; + render = renderTool; +} + +export class AudioTool extends AT { + icon = "voicemail"; + render = renderTool; +} + +export class VideoTool extends VT { + icon = "camera-video"; + render = renderTool; +} diff --git a/registry/new-york/blocks/editor/tools/text.ts b/registry/new-york/blocks/editor/tools/text.ts new file mode 100644 index 0000000..e9ed8fe --- /dev/null +++ b/registry/new-york/blocks/editor/tools/text.ts @@ -0,0 +1,76 @@ +import { + BoldTool as BT, + ItalicTool as IT, + UnderlineTool as UT, + StrikeThroughTool as STT, + H1Tool as H1T, + H2Tool as H2T, + H3Tool as H3T, + FontSizeDownTool as FSDT, + FontSizeUpTool as FSUT, + SubscriptTool as SubST, + SuperscriptTool as SupST, + LinkTool as LT, +} from "edkit"; + +import { renderTool } from "../tool"; + +export class BoldTool extends BT { + icon = "type-bold"; + render = renderTool; +} + +export class ItalicTool extends IT { + icon = "type-italic"; + render = renderTool; +} + +export class UnderlineTool extends UT { + icon = "type-underline"; + render = renderTool; +} + +export class StrikeThroughTool extends STT { + icon = "type-strikethrough"; + render = renderTool; +} + +export class H1Tool extends H1T { + icon = "type-h1"; + render = renderTool; +} + +export class H2Tool extends H2T { + icon = "type-h2"; + render = renderTool; +} + +export class H3Tool extends H3T { + icon = "type-h3"; + render = renderTool; +} + +export class FontSizeDownTool extends FSDT { + icon = "sort-alpha-down"; + render = renderTool; +} + +export class FontSizeUpTool extends FSUT { + icon = "sort-alpha-up"; + render = renderTool; +} + +export class SubscriptTool extends SubST { + icon = "box-arrow-down-right"; + render = renderTool; +} + +export class SuperscriptTool extends SupST { + icon = "box-arrow-up-right"; + render = renderTool; +} + +export class LinkTool extends LT { + icon = "link"; + render = renderTool; +} From fa013443e39cd2b9329a2a512a1953fbbc961a58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:39:03 +0000 Subject: [PATCH 3/5] Update editor example to use Shadcn UI Button component and improve README documentation Co-authored-by: TechQuery <19969570+TechQuery@users.noreply.github.com> --- README.md | 29 +++++++++++++++++++++ app/page.tsx | 9 +++++++ registry/new-york/blocks/editor/example.tsx | 8 ++---- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 571687a..f5e608a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ A **Pagination Table** & **Scroll List** component suite for [CRUD operation][1] 14. [Scroll Boundary](https://mobx-restful-shadcn.idea2.app/) 15. [Scroll List](https://mobx-restful-shadcn.idea2.app/) 16. [Searchable Input](https://mobx-restful-shadcn.idea2.app/) +17. [Editor](https://mobx-restful-shadcn.idea2.app/) ## Installation @@ -195,6 +196,34 @@ export const EditorPage = () => ( ); ``` +### Editor + +```tsx +import { configure } from "mobx"; +import { formToJSON } from "web-utility"; +import { Editor, OriginalTools, ExtraTools } from "@/components/ui/editor"; + +configure({ enforceActions: "never" }); + +export const EditorPage = () => ( +
{ + event.preventDefault(); + const { content } = formToJSON(event.currentTarget); + alert(content); + }} + > + + + +); +``` + ## Development This is a custom component registry built with Next.js and compatible with the `shadcn` CLI. diff --git a/app/page.tsx b/app/page.tsx index a6e9175..65e21eb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -15,6 +15,7 @@ import { RangeInputExample } from "@/registry/new-york/blocks/range-input/exampl import { FilePickerExample } from "@/registry/new-york/blocks/file-picker/example"; import { FormFieldExample } from "@/registry/new-york/blocks/form-field/example"; import { RestTableExample } from "@/registry/new-york/blocks/rest-table/example"; +import EditorExample from "@/registry/new-york/blocks/editor/example"; export default function Home() { return ( @@ -139,6 +140,14 @@ export default function Home() { > + + + +
); diff --git a/registry/new-york/blocks/editor/example.tsx b/registry/new-york/blocks/editor/example.tsx index c27fcca..c23b46f 100644 --- a/registry/new-york/blocks/editor/example.tsx +++ b/registry/new-york/blocks/editor/example.tsx @@ -3,6 +3,7 @@ import { configure } from "mobx"; import { formToJSON } from "web-utility"; import { Editor, OriginalTools, ExtraTools } from "./index"; +import { Button } from "@/components/ui/button"; configure({ enforceActions: "never" }); @@ -31,12 +32,7 @@ export default function EditorExample() { onChange={console.log} /> - + ); From 6c332d59a5a0221e447f8c351f69c4d25b9f9d96 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sun, 21 Dec 2025 03:39:48 +0800 Subject: [PATCH 4/5] [optimize] simplify Copilot's source codes [remove] Example components --- README.md | 3 + app/page.tsx | 39 +---- package.json | 2 +- pnpm-lock.yaml | 20 +-- registry.json | 69 ++------ .../components/pokemon-card.tsx | 25 --- .../components/pokemon-image.tsx | 20 --- .../complex-component/hooks/use-pokemon.ts | 7 - .../blocks/complex-component/lib/pokemon.ts | 48 ----- .../blocks/complex-component/page.tsx | 22 --- registry/new-york/blocks/editor/editor.tsx | 33 ++-- registry/new-york/blocks/editor/example.tsx | 58 +++---- registry/new-york/blocks/editor/tool.tsx | 45 +---- .../new-york/blocks/editor/tools/color.tsx | 73 ++++---- .../new-york/blocks/editor/tools/control.ts | 8 +- .../new-york/blocks/editor/tools/extra.ts | 2 +- .../new-york/blocks/editor/tools/index.ts | 5 - .../new-york/blocks/editor/tools/layout.ts | 14 +- .../new-york/blocks/editor/tools/media.ts | 8 +- registry/new-york/blocks/editor/tools/text.ts | 24 +-- .../blocks/example-form/example-form.tsx | 164 ------------------ .../blocks/example-with-css/example-card.css | 134 -------------- .../blocks/example-with-css/example-card.tsx | 44 ----- .../blocks/hello-world/hello-world.tsx | 3 - 24 files changed, 144 insertions(+), 726 deletions(-) delete mode 100644 registry/new-york/blocks/complex-component/components/pokemon-card.tsx delete mode 100644 registry/new-york/blocks/complex-component/components/pokemon-image.tsx delete mode 100644 registry/new-york/blocks/complex-component/hooks/use-pokemon.ts delete mode 100644 registry/new-york/blocks/complex-component/lib/pokemon.ts delete mode 100644 registry/new-york/blocks/complex-component/page.tsx delete mode 100644 registry/new-york/blocks/example-form/example-form.tsx delete mode 100644 registry/new-york/blocks/example-with-css/example-card.css delete mode 100644 registry/new-york/blocks/example-with-css/example-card.tsx delete mode 100644 registry/new-york/blocks/hello-world/hello-world.tsx diff --git a/README.md b/README.md index f5e608a..d889cfc 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ export const EditorPage = () => ( ```tsx import { configure } from "mobx"; import { formToJSON } from "web-utility"; + import { Editor, OriginalTools, ExtraTools } from "@/components/ui/editor"; configure({ enforceActions: "never" }); @@ -209,7 +210,9 @@ export const EditorPage = () => (
{ event.preventDefault(); + const { content } = formToJSON(event.currentTarget); + alert(content); }} > diff --git a/app/page.tsx b/app/page.tsx index 65e21eb..46e69fa 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,8 +1,4 @@ import { ComponentCard } from "@/components/example/component-card"; -import { HelloWorld } from "@/registry/new-york/blocks/hello-world/hello-world"; -import { ExampleForm } from "@/registry/new-york/blocks/example-form/example-form"; -import PokemonPage from "@/registry/new-york/blocks/complex-component/page"; -import { ExampleCard } from "@/registry/new-york/blocks/example-with-css/example-card"; import { BadgeBarExample } from "@/registry/new-york/blocks/badge-bar/example"; import { PagerExample } from "@/registry/new-york/blocks/pager/example"; import { ImagePreviewExample } from "@/registry/new-york/blocks/image-preview/example"; @@ -15,47 +11,20 @@ import { RangeInputExample } from "@/registry/new-york/blocks/range-input/exampl import { FilePickerExample } from "@/registry/new-york/blocks/file-picker/example"; import { FormFieldExample } from "@/registry/new-york/blocks/form-field/example"; import { RestTableExample } from "@/registry/new-york/blocks/rest-table/example"; -import EditorExample from "@/registry/new-york/blocks/editor/example"; +import { EditorExample } from "@/registry/new-york/blocks/editor/example"; export default function Home() { return (
-

Custom Registry

+

+ MobX-RESTful-Shadcn Registry +

A custom registry for distributing code using shadcn.

- - - - - - - - - - - - - - - - =6'} - caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -4741,7 +4741,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.10: {} + baseline-browser-mapping@2.9.11: {} body-parser@2.2.1: dependencies: @@ -4774,8 +4774,8 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.10 - caniuse-lite: 1.0.30001760 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001761 electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -4810,7 +4810,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001761: {} chalk@4.1.2: dependencies: @@ -6140,8 +6140,8 @@ snapshots: dependencies: '@next/env': 16.1.0 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.9.10 - caniuse-lite: 1.0.30001760 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001761 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) diff --git a/registry.json b/registry.json index 3bf4920..097e3d2 100644 --- a/registry.json +++ b/registry.json @@ -1,60 +1,14 @@ { "$schema": "https://ui.shadcn.com/schema/registry.json", - "name": "MobX RESTful Shadcn", + "name": "mobx-restful-shadcn", "homepage": "https://mobx-restful-shadcn.idea2.app", "items": [ - { - "name": "complex-component", - "type": "registry:component", - "title": "Complex Component", - "description": "A complex component showing hooks, libs and components.", - "registryDependencies": ["card"], - "files": [ - { - "path": "registry/new-york/blocks/complex-component/page.tsx", - "type": "registry:page", - "target": "app/pokemon/page.tsx" - }, - { - "path": "registry/new-york/blocks/complex-component/components/pokemon-card.tsx", - "type": "registry:component" - }, - { - "path": "registry/new-york/blocks/complex-component/components/pokemon-image.tsx", - "type": "registry:component" - }, - { - "path": "registry/new-york/blocks/complex-component/lib/pokemon.ts", - "type": "registry:lib" - }, - { - "path": "registry/new-york/blocks/complex-component/hooks/use-pokemon.ts", - "type": "registry:hook" - } - ] - }, - { - "name": "example-with-css", - "type": "registry:component", - "title": "Example with CSS", - "description": "A login form with a CSS file.", - "files": [ - { - "path": "registry/new-york/blocks/example-with-css/example-card.tsx", - "type": "registry:component" - }, - { - "path": "registry/new-york/blocks/example-with-css/example-card.css", - "type": "registry:component" - } - ] - }, { "name": "badge-bar", "type": "registry:component", "title": "Badge Bar", "description": "A component for displaying a list of badges with optional click and delete handlers.", - "registryDependencies": ["badge"], + "registryDependencies": ["badge", "@mobx-restful-shadcn/badge-bar"], "dependencies": ["lucide-react", "web-utility"], "files": [ { @@ -101,7 +55,7 @@ "type": "registry:component", "title": "File Preview", "description": "A file preview component supporting images, audio, video, and documents.", - "registryDependencies": ["dialog"], + "registryDependencies": ["@mobx-restful-shadcn/image-preview"], "dependencies": ["lucide-react"], "files": [ { @@ -134,6 +88,7 @@ "mobx-restful", "lodash.debounce" ], + "registryDependencies": ["@mobx-restful-shadcn/scroll-boundary"], "files": [ { "path": "registry/new-york/blocks/scroll-list/scroll-list.tsx", @@ -167,7 +122,7 @@ "type": "registry:component", "title": "Badge Input", "description": "An input component that displays values as removable badges, supporting multiple entries.", - "registryDependencies": ["badge"], + "registryDependencies": ["@mobx-restful-shadcn/badge-bar"], "dependencies": ["mobx-react", "mobx-react-helper", "web-utility"], "files": [ { @@ -194,7 +149,7 @@ "type": "registry:component", "title": "File Picker", "description": "A file picker component with preview and remove functionality.", - "registryDependencies": ["button"], + "registryDependencies": ["button", "@mobx-restful-shadcn/file-preview"], "dependencies": [ "lucide-react", "mobx", @@ -228,7 +183,7 @@ "type": "registry:component", "title": "File Uploader", "description": "A file uploader component with drag-and-drop support for managing multiple files using MobX.", - "registryDependencies": ["file-picker"], + "registryDependencies": ["@mobx-restful-shadcn/file-picker"], "dependencies": [ "mobx", "mobx-react", @@ -346,7 +301,7 @@ { "name": "editor", "type": "registry:component", - "title": "Editor", + "title": "HTML Editor", "description": "A lightweight rich text editor based on Edkit and Shadcn UI with various formatting tools.", "registryDependencies": ["button"], "dependencies": [ @@ -358,6 +313,10 @@ "web-utility" ], "files": [ + { + "path": "registry/new-york/blocks/editor/index.ts", + "type": "registry:component" + }, { "path": "registry/new-york/blocks/editor/editor.tsx", "type": "registry:component" @@ -393,10 +352,6 @@ { "path": "registry/new-york/blocks/editor/tools/extra.ts", "type": "registry:component" - }, - { - "path": "registry/new-york/blocks/editor/index.ts", - "type": "registry:component" } ] } diff --git a/registry/new-york/blocks/complex-component/components/pokemon-card.tsx b/registry/new-york/blocks/complex-component/components/pokemon-card.tsx deleted file mode 100644 index 2eed576..0000000 --- a/registry/new-york/blocks/complex-component/components/pokemon-card.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { cache } from "react" -import { getPokemon } from "@/registry/new-york/blocks/complex-component/lib/pokemon" -import { Card, CardContent } from "@/registry/new-york/ui/card" -import { PokemonImage } from "@/registry/new-york/blocks/complex-component/components/pokemon-image" - -const cachedGetPokemon = cache(getPokemon) - -export async function PokemonCard({ name }: { name: string }) { - const pokemon = await cachedGetPokemon(name) - - if (!pokemon) { - return null - } - - return ( - - -
- -
-
{pokemon.name}
-
-
- ) -} diff --git a/registry/new-york/blocks/complex-component/components/pokemon-image.tsx b/registry/new-york/blocks/complex-component/components/pokemon-image.tsx deleted file mode 100644 index a3d2461..0000000 --- a/registry/new-york/blocks/complex-component/components/pokemon-image.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client" - -/* eslint-disable @next/next/no-img-element */ -import { usePokemonImage } from "@/registry/new-york/blocks/complex-component/hooks/use-pokemon" - -export function PokemonImage({ - name, - number, -}: { - name: string - number: number -}) { - const imageUrl = usePokemonImage(number) - - if (!imageUrl) { - return null - } - - return {name} -} diff --git a/registry/new-york/blocks/complex-component/hooks/use-pokemon.ts b/registry/new-york/blocks/complex-component/hooks/use-pokemon.ts deleted file mode 100644 index 0201b96..0000000 --- a/registry/new-york/blocks/complex-component/hooks/use-pokemon.ts +++ /dev/null @@ -1,7 +0,0 @@ -"use client" - -// Totally unnecessary hook, but it's a good example of how to use a hook in a custom registry. - -export function usePokemonImage(number: number) { - return `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png` -} diff --git a/registry/new-york/blocks/complex-component/lib/pokemon.ts b/registry/new-york/blocks/complex-component/lib/pokemon.ts deleted file mode 100644 index 9aa12f9..0000000 --- a/registry/new-york/blocks/complex-component/lib/pokemon.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { z } from "zod" - -export async function getPokemonList({ limit = 10 }: { limit?: number }) { - try { - const response = await fetch( - `https://pokeapi.co/api/v2/pokemon?limit=${limit}` - ) - return z - .object({ - results: z.array(z.object({ name: z.string() })), - }) - .parse(await response.json()) - } catch (error) { - console.error(error) - return null - } -} - -export async function getPokemon(name: string) { - try { - const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`) - - if (!response.ok) { - throw new Error("Failed to fetch pokemon") - } - - return z - .object({ - name: z.string(), - id: z.number(), - sprites: z.object({ - front_default: z.string(), - }), - stats: z.array( - z.object({ - base_stat: z.number(), - stat: z.object({ - name: z.string(), - }), - }) - ), - }) - .parse(await response.json()) - } catch (error) { - console.error(error) - return null - } -} diff --git a/registry/new-york/blocks/complex-component/page.tsx b/registry/new-york/blocks/complex-component/page.tsx deleted file mode 100644 index bd202d3..0000000 --- a/registry/new-york/blocks/complex-component/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { cache } from "react" -import { PokemonCard } from "@/registry/new-york/blocks/complex-component/components/pokemon-card" -import { getPokemonList } from "@/registry/new-york/blocks/complex-component/lib/pokemon" -const getCachedPokemonList = cache(getPokemonList) - -export default async function Page() { - const pokemons = await getCachedPokemonList({ limit: 12 }) - - if (!pokemons) { - return null - } - - return ( -
-
- {pokemons.results.map((p) => ( - - ))} -
-
- ) -} diff --git a/registry/new-york/blocks/editor/editor.tsx b/registry/new-york/blocks/editor/editor.tsx index a187c9d..8c4841a 100644 --- a/registry/new-york/blocks/editor/editor.tsx +++ b/registry/new-york/blocks/editor/editor.tsx @@ -7,12 +7,12 @@ import { FormComponent, FormComponentProps } from "mobx-react-helper"; import { createRef } from "react"; import { Constructor } from "web-utility"; -import { AudioTool, DefaultTools, VideoTool } from "./tools"; import { cn } from "@/lib/utils"; +import { AudioTool, DefaultTools, VideoTool } from "./tools"; export interface EditorProps extends FormComponentProps { - tools?: Constructor[]; className?: string; + tools?: Constructor[]; } export interface Editor extends EditorComponent {} @@ -39,23 +39,17 @@ export class Editor @computed get imageTool() { - return this.toolList.find( - (tool) => tool instanceof ImageTool - ) as ImageTool; + return this.toolList.find((tool) => tool instanceof ImageTool) as ImageTool; } @computed get audioTool() { - return this.toolList.find( - (tool) => tool instanceof AudioTool - ) as AudioTool; + return this.toolList.find((tool) => tool instanceof AudioTool) as AudioTool; } @computed get videoTool() { - return this.toolList.find( - (tool) => tool instanceof VideoTool - ) as VideoTool; + return this.toolList.find((tool) => tool instanceof VideoTool) as VideoTool; } componentDidMount() { @@ -77,19 +71,14 @@ export class Editor updateTools = () => { if (this.box.current !== document.activeElement) return; - const selection = getSelection(); - if (!selection || selection.rangeCount === 0) return; - - const { endContainer } = selection.getRangeAt(0) || {}; - const element = - endContainer instanceof Element + const { endContainer } = getSelection()?.getRangeAt(0) || {}; + const { x, y } = + (endContainer instanceof Element ? endContainer - : endContainer?.parentElement; + : endContainer?.parentElement + )?.getBoundingClientRect() || {}; - const rect = element?.getBoundingClientRect(); - if (rect) { - this.cursorPoint = [rect.x, rect.y].join(""); - } + this.cursorPoint = [x, y] + ""; }; updateValue = (markup: string) => (this.innerValue = markup.trim()); diff --git a/registry/new-york/blocks/editor/example.tsx b/registry/new-york/blocks/editor/example.tsx index c23b46f..e715fa5 100644 --- a/registry/new-york/blocks/editor/example.tsx +++ b/registry/new-york/blocks/editor/example.tsx @@ -2,38 +2,38 @@ import { configure } from "mobx"; import { formToJSON } from "web-utility"; -import { Editor, OriginalTools, ExtraTools } from "./index"; + import { Button } from "@/components/ui/button"; +import { Editor, OriginalTools, ExtraTools } from "./index"; configure({ enforceActions: "never" }); -export default function EditorExample() { - return ( - { - event.preventDefault(); - const { content } = formToJSON(event.currentTarget); - alert(content); - }} - > -
-
-

Rich Text Editor

-

- A lightweight rich text editor based on Edkit and Shadcn UI -

-
+export const EditorExample = () => ( + { + event.preventDefault(); + + const { content } = formToJSON(event.currentTarget); - + alert(content); + }} + > +
+
+

HTML Editor

+

+ A lightweight rich text editor based on Edkit and Shadcn UI +

+
- -
- - ); -} + + +
+ +); diff --git a/registry/new-york/blocks/editor/tool.tsx b/registry/new-york/blocks/editor/tool.tsx index f64236d..656901a 100644 --- a/registry/new-york/blocks/editor/tool.tsx +++ b/registry/new-york/blocks/editor/tool.tsx @@ -1,46 +1,16 @@ -import { RefObject } from "react"; +import { ComponentType, RefObject, SVGProps } from "react"; import { Tool } from "edkit"; -import { Button } from "@/components/ui/button"; import * as Icons from "lucide-react"; +import { Button } from "@/components/ui/button"; + export function renderTool(this: Tool, editor: RefObject) { const { title, active, icon, usable } = this; - // Map Bootstrap icons to Lucide icons - const iconMap: Record = { - "type-bold": "Bold", - "type-italic": "Italic", - "type-underline": "Underline", - "type-strikethrough": "Strikethrough", - "type-h1": "Heading1", - "type-h2": "Heading2", - "type-h3": "Heading3", - "sort-alpha-down": "ArrowDownAZ", - "sort-alpha-up": "ArrowUpAZ", - "box-arrow-down-right": "ArrowDownRight", - "box-arrow-up-right": "ArrowUpRight", - link: "Link", - "text-left": "AlignLeft", - "text-center": "AlignCenter", - "text-right": "AlignRight", - justify: "AlignJustify", - "list-ol": "ListOrdered", - "list-ul": "List", - "reception-0": "Minus", - window: "Frame", - image: "Image", - voicemail: "Mic", - "camera-video": "Video", - "arrow-counterclockwise": "Undo", - "arrow-clockwise": "Redo", - eraser: "Eraser", - "file-earmark-x": "X", - markdown: "FileText", - }; - - const IconComponent = Icons[iconMap[icon] || "Circle"] as React.ComponentType< - React.SVGProps - >; + const IconComponent = + (Icons[icon as keyof typeof Icons] as ComponentType< + SVGProps + >) || Icons.Circle; return ( - - ); -}; + {icon === "file-earmark-font" ? ( + + ) : ( + + )} + + +); export function renderColorTool( this: ColorTool, @@ -69,7 +72,7 @@ export function renderColorTool( className="mr-2 mb-2" key={icon} title={name} - icon={icon as "file-earmark-font" | "file-earmark-font-fill"} + icon={icon as ColorSelectorProps["icon"]} type={colorName} value={this.getColor()} onChange={(color) => diff --git a/registry/new-york/blocks/editor/tools/control.ts b/registry/new-york/blocks/editor/tools/control.ts index f39375e..46b2e61 100644 --- a/registry/new-york/blocks/editor/tools/control.ts +++ b/registry/new-york/blocks/editor/tools/control.ts @@ -8,21 +8,21 @@ import { import { renderTool } from "../tool"; export class UndoTool extends UDT { - icon = "arrow-counterclockwise"; + icon = "Undo"; render = renderTool; } export class RedoTool extends RDT { - icon = "arrow-clockwise"; + icon = "Redo"; render = renderTool; } export class ResetTool extends RST { - icon = "eraser"; + icon = "Eraser"; render = renderTool; } export class ClearTool extends CT { - icon = "file-earmark-x"; + icon = "X"; render = renderTool; } diff --git a/registry/new-york/blocks/editor/tools/extra.ts b/registry/new-york/blocks/editor/tools/extra.ts index 97b67ac..c488da7 100644 --- a/registry/new-york/blocks/editor/tools/extra.ts +++ b/registry/new-york/blocks/editor/tools/extra.ts @@ -3,6 +3,6 @@ import { CopyMarkdownTool as CMDT } from "edkit"; import { renderTool } from "../tool"; export class CopyMarkdownTool extends CMDT { - icon = "markdown"; + icon = "FileText"; render = renderTool; } diff --git a/registry/new-york/blocks/editor/tools/index.ts b/registry/new-york/blocks/editor/tools/index.ts index 8c304b2..a20f282 100644 --- a/registry/new-york/blocks/editor/tools/index.ts +++ b/registry/new-york/blocks/editor/tools/index.ts @@ -47,9 +47,7 @@ export const TextTools = [ SuperscriptTool, LinkTool, ]; - export const ColorTools = [ForeColorTool, BackColorTool]; - export const LayoutTools = [ AlignLeftTool, AlignCenterTool, @@ -59,11 +57,8 @@ export const LayoutTools = [ UnorderedListTool, HorizontalRuleTool, ]; - export const MediaTools = [IFrameTool, ImageTool, AudioTool, VideoTool]; - export const ControlTools = [UndoTool, RedoTool, ResetTool, ClearTool]; - export const ExtraTools = [CopyMarkdownTool]; export const OriginalTools = [ diff --git a/registry/new-york/blocks/editor/tools/layout.ts b/registry/new-york/blocks/editor/tools/layout.ts index c2fbd02..bab0d11 100644 --- a/registry/new-york/blocks/editor/tools/layout.ts +++ b/registry/new-york/blocks/editor/tools/layout.ts @@ -11,36 +11,36 @@ import { import { renderTool } from "../tool"; export class AlignLeftTool extends ALT { - icon = "text-left"; + icon = "AlignLeft"; render = renderTool; } export class AlignCenterTool extends ACT { - icon = "text-center"; + icon = "AlignCenter"; render = renderTool; } export class AlignRightTool extends ART { - icon = "text-right"; + icon = "AlignRight"; render = renderTool; } export class AlignFullTool extends AFT { - icon = "justify"; + icon = "AlignJustify"; render = renderTool; } export class OrderedListTool extends OLT { - icon = "list-ol"; + icon = "ListOrdered"; render = renderTool; } export class UnorderedListTool extends ULT { - icon = "list-ul"; + icon = "List"; render = renderTool; } export class HorizontalRuleTool extends HRT { - icon = "reception-0"; + icon = "Minus"; render = renderTool; } diff --git a/registry/new-york/blocks/editor/tools/media.ts b/registry/new-york/blocks/editor/tools/media.ts index 1904965..6585aa3 100644 --- a/registry/new-york/blocks/editor/tools/media.ts +++ b/registry/new-york/blocks/editor/tools/media.ts @@ -8,21 +8,21 @@ import { import { renderTool } from "../tool"; export class IFrameTool extends FT { - icon = "window"; + icon = "Frame"; render = renderTool; } export class ImageTool extends IT { - icon = "image"; + icon = "Image"; render = renderTool; } export class AudioTool extends AT { - icon = "voicemail"; + icon = "Mic"; render = renderTool; } export class VideoTool extends VT { - icon = "camera-video"; + icon = "Video"; render = renderTool; } diff --git a/registry/new-york/blocks/editor/tools/text.ts b/registry/new-york/blocks/editor/tools/text.ts index e9ed8fe..1f3d345 100644 --- a/registry/new-york/blocks/editor/tools/text.ts +++ b/registry/new-york/blocks/editor/tools/text.ts @@ -16,61 +16,61 @@ import { import { renderTool } from "../tool"; export class BoldTool extends BT { - icon = "type-bold"; + icon = "Bold"; render = renderTool; } export class ItalicTool extends IT { - icon = "type-italic"; + icon = "Italic"; render = renderTool; } export class UnderlineTool extends UT { - icon = "type-underline"; + icon = "Underline"; render = renderTool; } export class StrikeThroughTool extends STT { - icon = "type-strikethrough"; + icon = "Strikethrough"; render = renderTool; } export class H1Tool extends H1T { - icon = "type-h1"; + icon = "Heading1"; render = renderTool; } export class H2Tool extends H2T { - icon = "type-h2"; + icon = "Heading2"; render = renderTool; } export class H3Tool extends H3T { - icon = "type-h3"; + icon = "Heading3"; render = renderTool; } export class FontSizeDownTool extends FSDT { - icon = "sort-alpha-down"; + icon = "ArrowDownAZ"; render = renderTool; } export class FontSizeUpTool extends FSUT { - icon = "sort-alpha-up"; + icon = "ArrowUpAZ"; render = renderTool; } export class SubscriptTool extends SubST { - icon = "box-arrow-down-right"; + icon = "ArrowDownRight"; render = renderTool; } export class SuperscriptTool extends SupST { - icon = "box-arrow-up-right"; + icon = "ArrowUpRight"; render = renderTool; } export class LinkTool extends LT { - icon = "link"; + icon = "Link"; render = renderTool; } diff --git a/registry/new-york/blocks/example-form/example-form.tsx b/registry/new-york/blocks/example-form/example-form.tsx deleted file mode 100644 index 6b6bb9f..0000000 --- a/registry/new-york/blocks/example-form/example-form.tsx +++ /dev/null @@ -1,164 +0,0 @@ -"use client" - -import * as React from "react" -import { - Card, - CardTitle, - CardHeader, - CardDescription, - CardContent, - CardFooter, -} from "@/registry/new-york/ui/card" -import { Input } from "@/registry/new-york/ui/input" -import { Label } from "@/registry/new-york/ui/label" -import { Button } from "@/registry/new-york/ui/button" -import { Textarea } from "@/registry/new-york/ui/textarea" -import { z } from "zod" - -const exampleFormSchema = z.object({ - name: z.string().min(1), - email: z.string().email(), - message: z.string().min(1), -}) - -export function ExampleForm() { - const [pending, setPending] = React.useState(false) - const [state, setState] = React.useState({ - defaultValues: { - name: "", - email: "", - message: "", - }, - success: false, - errors: { - name: "", - email: "", - message: "", - }, - }) - - const handleSubmit = React.useCallback( - (e: React.FormEvent) => { - e.preventDefault() - setPending(true) - - const formData = new FormData(e.target as HTMLFormElement) - const data = Object.fromEntries(formData.entries()) - const result = exampleFormSchema.safeParse(data) - - if (!result.success) { - setState({ - ...state, - errors: Object.fromEntries( - Object.entries(result.error.flatten().fieldErrors).map( - ([key, value]) => [key, value?.[0] ?? ""] - ) - ) as Record, - }) - setPending(false) - return - } - - setPending(false) - }, - [state] - ) - - return ( -
- - - How can we help? - - Need help with your project? We're here to assist you. - - - -
- - - {state.errors?.name && ( -

- {state.errors.name} -

- )} -
-
- - - {state.errors?.email && ( -

- {state.errors.email} -

- )} -
-
- -