diff --git a/packages/akiradocs/src/components/blocks/ButtonBlock.tsx b/packages/akiradocs/src/components/blocks/ButtonBlock.tsx new file mode 100644 index 0000000..a54e8b1 --- /dev/null +++ b/packages/akiradocs/src/components/blocks/ButtonBlock.tsx @@ -0,0 +1,103 @@ +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/components/ui/button" +import { useState } from "react" + +interface ButtonBlockProps { + content: string + metadata?: { + buttonUrl?: string + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' + size?: 'default' | 'sm' | 'lg' + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' + } + } + isEditing?: boolean + onUpdate?: (content: string) => void + align?: 'left' | 'center' | 'right' +} + +export function ButtonBlock({ + content, + metadata, + isEditing, + onUpdate, + align = 'left' +}: ButtonBlockProps) { + const [isFocused, setIsFocused] = useState(false); + const buttonStyle = metadata?.buttonStyle || {} + const url = metadata?.buttonUrl || '#' + + const buttonClasses = cn( + "relative", + { + 'rounded-none': buttonStyle.radius === 'none', + 'rounded-sm': buttonStyle.radius === 'sm', + 'rounded-md': buttonStyle.radius === 'md', + 'rounded-lg': buttonStyle.radius === 'lg', + 'rounded-full': buttonStyle.radius === 'full', + 'w-full': buttonStyle.size === 'lg', + 'w-24': buttonStyle.size === 'sm', + 'w-auto': buttonStyle.size === 'default' + } + ) + + const wrapperClasses = cn( + 'w-full', + { + 'text-left': align === 'left', + 'text-center': align === 'center', + 'text-right': align === 'right' + } + ) + + if (isEditing) { + return ( +
+
+ + onUpdate?.(e.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + className={cn( + buttonVariants({ + variant: buttonStyle.variant || 'default', + size: buttonStyle.size || 'default' + }), + buttonClasses, + "absolute inset-0 border-0 outline-none", + !isFocused && "opacity-0" + )} + placeholder="Button text..." + /> +
+
+ ) + } + + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/packages/akiradocs/src/components/blocks/SortableBlock.tsx b/packages/akiradocs/src/components/blocks/SortableBlock.tsx index 9afa175..020e810 100644 --- a/packages/akiradocs/src/components/blocks/SortableBlock.tsx +++ b/packages/akiradocs/src/components/blocks/SortableBlock.tsx @@ -35,6 +35,12 @@ interface SortableBlockProps { align?: 'left' | 'center' | 'right' type?: 'info' | 'warning' | 'success' | 'error' title?: string + buttonUrl?: string + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' + size?: 'default' | 'sm' | 'lg' + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' + } } } updateBlock: (id: string, content: string) => void @@ -514,6 +520,21 @@ export function SortableBlock({ updateBlock(block.id, JSON.stringify(updatedContent)); } }} + showButtonControls={block.type === 'button'} + buttonMetadata={{ + url: block.metadata?.buttonUrl, + style: block.metadata?.buttonStyle + }} + onButtonMetadataChange={(metadata) => { + updateBlockMetadata(block.id, { + ...block.metadata, + buttonUrl: metadata.buttonUrl, + buttonStyle: { + ...block.metadata?.buttonStyle, + ...metadata.buttonStyle + } + }) + }} /> )} void +} + +const spacingSizes = { + small: 'h-4', + medium: 'h-8', + large: 'h-16', + xlarge: 'h-24' +} + +export function SpacerBlock({ size = 'medium', isEditing, onUpdate }: SpacerBlockProps) { + const [isFocused, setIsFocused] = useState(false) + + if (!isEditing) { + return
+ } + + return ( +
setIsFocused(true)} + onBlur={(e) => { + if (!e.currentTarget.contains(e.relatedTarget)) { + setIsFocused(false) + } + }} + > +
+ {isFocused && ( + + )} +
+ ) +} \ No newline at end of file diff --git a/packages/akiradocs/src/components/editor/AIRewriteButton.tsx b/packages/akiradocs/src/components/editor/AIRewriteButton.tsx index eb1c654..589ef86 100644 --- a/packages/akiradocs/src/components/editor/AIRewriteButton.tsx +++ b/packages/akiradocs/src/components/editor/AIRewriteButton.tsx @@ -193,6 +193,25 @@ const blockStyles = { label: 'Descriptive', prompt: 'Rewrite the API reference to be more descriptive and engaging.' } + ], + button: [ + { + value: 'descriptive', + label: 'Descriptive', + prompt: 'Rewrite the button text to be more descriptive and engaging.' + }, + { + value: 'concise', + label: 'Concise', + prompt: 'Rewrite the button text to be more concise and clear.' + } + ], + spacer: [ + { + value: 'default', + label: 'Default', + prompt: 'No rewriting options available for spacer' + } ] } as const; @@ -213,6 +232,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite }; const handleRewrite = async () => { + if (blockType === 'spacer') { + return; + } + try { await onRewrite(style) } catch (error) { @@ -224,6 +247,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite } } + if (blockType === 'spacer') { + return null; + } + return ( diff --git a/packages/akiradocs/src/components/editor/AddBlockButton.tsx b/packages/akiradocs/src/components/editor/AddBlockButton.tsx index 00a1770..ec5998f 100644 --- a/packages/akiradocs/src/components/editor/AddBlockButton.tsx +++ b/packages/akiradocs/src/components/editor/AddBlockButton.tsx @@ -16,19 +16,14 @@ import { Minus, Table, Quote, - // ToggleLeft, - // CheckSquare, - // Video, - // Music, - // File, - // Smile, AlertCircle, Plus, - // Search, Video, Music, File, CheckSquare, + ArrowUpDown, + Link2, } from 'lucide-react' interface AddBlockButtonProps { @@ -83,6 +78,8 @@ export const AddBlockButton = forwardRef< { type: 'audio', icon: , label: 'Audio', description: 'Embed audio content.', group: 'Media' }, { type: 'file', icon: , label: 'File', description: 'Upload or link to a file.', group: 'Media' }, { type: 'checkList', icon: , label: 'To-do list', description: 'Track tasks with a to-do list.', group: 'Basic' }, + { type: 'spacer', icon: , label: 'Spacing', description: 'Add vertical space between blocks.', group: 'Basic' }, + { type: 'button', icon: , label: 'Button', description: 'Add a clickable button with link.', group: 'Basic' }, ] const filteredOptions = blockOptions.filter((option) => diff --git a/packages/akiradocs/src/components/editor/BlockFormatToolbar.tsx b/packages/akiradocs/src/components/editor/BlockFormatToolbar.tsx index 8a70ee5..e575d98 100644 --- a/packages/akiradocs/src/components/editor/BlockFormatToolbar.tsx +++ b/packages/akiradocs/src/components/editor/BlockFormatToolbar.tsx @@ -76,6 +76,23 @@ interface BlockFormatToolbarProps { caption: string; alignment: 'left' | 'center' | 'right'; }>) => void; + showButtonControls?: boolean; + buttonMetadata?: { + url?: string; + style?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; + }; + onButtonMetadataChange?: (metadata: Partial<{ + buttonUrl: string; + buttonStyle: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; + }>) => void; } export function BlockFormatToolbar({ @@ -119,8 +136,11 @@ export function BlockFormatToolbar({ showAudioControls = false, audioContent, onAudioMetadataChange, + showButtonControls = false, + buttonMetadata, + onButtonMetadataChange, }: BlockFormatToolbarProps) { - if (blockType === 'file') { + if (blockType === 'file' || blockType === 'spacer') { return null; } @@ -133,7 +153,7 @@ export function BlockFormatToolbar({ "bg-popover border shadow-md rounded-md", className )}> - {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && blockType !== 'table' && ( + {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && !showButtonControls && blockType !== 'table' && ( <> )} + {showButtonControls && ( + <> + + + onButtonMetadataChange?.({ buttonUrl: e.target.value })} + placeholder="URL" + className="h-7 w-32 text-xs" + /> + + + + + + + + )} + {!showImageControls && !showVideoControls && !showAudioControls && ( <> {!showCalloutControls && blockType !== 'table' && } diff --git a/packages/akiradocs/src/lib/renderers/BlockRenderer.tsx b/packages/akiradocs/src/lib/renderers/BlockRenderer.tsx index b640646..6247349 100644 --- a/packages/akiradocs/src/lib/renderers/BlockRenderer.tsx +++ b/packages/akiradocs/src/lib/renderers/BlockRenderer.tsx @@ -6,22 +6,15 @@ import { List } from "@/components/blocks/ListBlock" import { Blockquote } from "@/components/blocks/BlockquoteBlock" import { Divider } from "@/components/blocks/DividerBlock" import { CodeBlock } from "@/components/blocks/CodeBlock" -// import { Image } from '../blocks/Image' -// import { Table } from '../blocks/Table' -// import { ToggleList } from '../blocks/ToggleList' import { CheckList } from "@/components/blocks/CheckListBlock" -// import { Video } from '../blocks/Video' -// import { Audio } from '../blocks/Audio' -// import { File } from '../blocks/File' -// import { Emoji } from '../blocks/Emoji' import { Callout } from "@/components/blocks/CalloutBlock" -import { cn } from '@/lib/utils' -import { ErrorBoundary } from 'react-error-boundary' import { ImageBlock } from "@/components/blocks/ImageBlock" import { Table } from '@/components/blocks/TableBlock' import { VideoBlock } from "@/components/blocks/VideoBlock" import { AudioBlock } from "@/components/blocks/AudioBlock" import { FileBlock } from "@/components/blocks/FileBlock" +import { SpacerBlock } from "@/components/blocks/SpacerBlock" +import { ButtonBlock } from "@/components/blocks/ButtonBlock" interface ImageBlockContent { url: string; @@ -64,20 +57,16 @@ export function BlockRenderer({ block, isEditing, onUpdate }: BlockRendererProps ); case 'heading': - const hasStrong = block.content.includes(''); return ( onUpdate?.(block.id, content)} > - {hasStrong ? block.content.replace(/<\/?strong>/g, '') : block.content} + {block.content} ); case 'list': @@ -234,6 +223,24 @@ export function BlockRenderer({ block, isEditing, onUpdate }: BlockRendererProps onUpdate={(content) => onUpdate?.(block.id, content)} /> ); + case 'spacer': + return ( + onUpdate?.(block.id, size)} + /> + ); + case 'button': + return ( + onUpdate?.(block.id, content)} + align={block.metadata?.align} + /> + ); default: return null } diff --git a/packages/akiradocs/src/types/Block.ts b/packages/akiradocs/src/types/Block.ts index 61f1b7a..044cc58 100644 --- a/packages/akiradocs/src/types/Block.ts +++ b/packages/akiradocs/src/types/Block.ts @@ -12,9 +12,10 @@ export type BlockType = | 'video' | 'audio' | 'file' - // | 'emoji' | 'callout' - | 'apiReference'; + | 'spacer' + | 'apiReference' + | 'button'; export interface Block { id: string; @@ -44,6 +45,12 @@ export interface Block { align?: 'left' | 'center' | 'right'; // For alignment type?: 'info' | 'warning' | 'success' | 'error'; // For callouts title?: string; // For callouts + buttonUrl?: string; + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; }; } diff --git a/packages/editor/src/app/editMode/[...slug]/page.tsx b/packages/editor/src/app/editMode/[...slug]/page.tsx index 116dbff..d43e4b1 100644 --- a/packages/editor/src/app/editMode/[...slug]/page.tsx +++ b/packages/editor/src/app/editMode/[...slug]/page.tsx @@ -157,6 +157,11 @@ export default function ArticleEditorContent({ params }: { params: Promise<{ slu metadata: {} } + // Add default content for spacer blocks + if (newBlock.type === 'spacer') { + newBlock.content = 'medium' + } + if (newBlock.type === 'table') { newBlock.metadata = { headers: ['Column 1', 'Column 2'], @@ -168,6 +173,18 @@ export default function ArticleEditorContent({ params }: { params: Promise<{ slu newBlock.content = JSON.stringify([{ text: '', checked: false }]); } + if (newBlock.type === 'button') { + newBlock.content = 'Click me' + newBlock.metadata = { + buttonUrl: '#', + buttonStyle: { + variant: 'default', + size: 'default', + radius: 'md' + } + } + } + if (afterId === 'new') { setBlocks([newBlock]) } else { diff --git a/packages/editor/src/components/blocks/ButtonBlock.tsx b/packages/editor/src/components/blocks/ButtonBlock.tsx new file mode 100644 index 0000000..a54e8b1 --- /dev/null +++ b/packages/editor/src/components/blocks/ButtonBlock.tsx @@ -0,0 +1,103 @@ +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/components/ui/button" +import { useState } from "react" + +interface ButtonBlockProps { + content: string + metadata?: { + buttonUrl?: string + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' + size?: 'default' | 'sm' | 'lg' + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' + } + } + isEditing?: boolean + onUpdate?: (content: string) => void + align?: 'left' | 'center' | 'right' +} + +export function ButtonBlock({ + content, + metadata, + isEditing, + onUpdate, + align = 'left' +}: ButtonBlockProps) { + const [isFocused, setIsFocused] = useState(false); + const buttonStyle = metadata?.buttonStyle || {} + const url = metadata?.buttonUrl || '#' + + const buttonClasses = cn( + "relative", + { + 'rounded-none': buttonStyle.radius === 'none', + 'rounded-sm': buttonStyle.radius === 'sm', + 'rounded-md': buttonStyle.radius === 'md', + 'rounded-lg': buttonStyle.radius === 'lg', + 'rounded-full': buttonStyle.radius === 'full', + 'w-full': buttonStyle.size === 'lg', + 'w-24': buttonStyle.size === 'sm', + 'w-auto': buttonStyle.size === 'default' + } + ) + + const wrapperClasses = cn( + 'w-full', + { + 'text-left': align === 'left', + 'text-center': align === 'center', + 'text-right': align === 'right' + } + ) + + if (isEditing) { + return ( +
+
+ + onUpdate?.(e.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + className={cn( + buttonVariants({ + variant: buttonStyle.variant || 'default', + size: buttonStyle.size || 'default' + }), + buttonClasses, + "absolute inset-0 border-0 outline-none", + !isFocused && "opacity-0" + )} + placeholder="Button text..." + /> +
+
+ ) + } + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/packages/editor/src/components/blocks/SortableBlock.tsx b/packages/editor/src/components/blocks/SortableBlock.tsx index 9afa175..020e810 100644 --- a/packages/editor/src/components/blocks/SortableBlock.tsx +++ b/packages/editor/src/components/blocks/SortableBlock.tsx @@ -35,6 +35,12 @@ interface SortableBlockProps { align?: 'left' | 'center' | 'right' type?: 'info' | 'warning' | 'success' | 'error' title?: string + buttonUrl?: string + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' + size?: 'default' | 'sm' | 'lg' + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' + } } } updateBlock: (id: string, content: string) => void @@ -514,6 +520,21 @@ export function SortableBlock({ updateBlock(block.id, JSON.stringify(updatedContent)); } }} + showButtonControls={block.type === 'button'} + buttonMetadata={{ + url: block.metadata?.buttonUrl, + style: block.metadata?.buttonStyle + }} + onButtonMetadataChange={(metadata) => { + updateBlockMetadata(block.id, { + ...block.metadata, + buttonUrl: metadata.buttonUrl, + buttonStyle: { + ...block.metadata?.buttonStyle, + ...metadata.buttonStyle + } + }) + }} /> )} void +} + +const spacingSizes = { + small: 'h-4', + medium: 'h-8', + large: 'h-16', + xlarge: 'h-24' +} + +export function SpacerBlock({ size = 'medium', isEditing, onUpdate }: SpacerBlockProps) { + const [isFocused, setIsFocused] = useState(false) + + if (!isEditing) { + return
+ } + + return ( +
setIsFocused(true)} + onBlur={(e) => { + if (!e.currentTarget.contains(e.relatedTarget)) { + setIsFocused(false) + } + }} + > +
+ {isFocused && ( + + )} +
+ ) +} \ No newline at end of file diff --git a/packages/editor/src/components/editor/AIRewriteButton.tsx b/packages/editor/src/components/editor/AIRewriteButton.tsx index eb1c654..589ef86 100644 --- a/packages/editor/src/components/editor/AIRewriteButton.tsx +++ b/packages/editor/src/components/editor/AIRewriteButton.tsx @@ -193,6 +193,25 @@ const blockStyles = { label: 'Descriptive', prompt: 'Rewrite the API reference to be more descriptive and engaging.' } + ], + button: [ + { + value: 'descriptive', + label: 'Descriptive', + prompt: 'Rewrite the button text to be more descriptive and engaging.' + }, + { + value: 'concise', + label: 'Concise', + prompt: 'Rewrite the button text to be more concise and clear.' + } + ], + spacer: [ + { + value: 'default', + label: 'Default', + prompt: 'No rewriting options available for spacer' + } ] } as const; @@ -213,6 +232,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite }; const handleRewrite = async () => { + if (blockType === 'spacer') { + return; + } + try { await onRewrite(style) } catch (error) { @@ -224,6 +247,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite } } + if (blockType === 'spacer') { + return null; + } + return ( diff --git a/packages/editor/src/components/editor/AddBlockButton.tsx b/packages/editor/src/components/editor/AddBlockButton.tsx index 00a1770..ec5998f 100644 --- a/packages/editor/src/components/editor/AddBlockButton.tsx +++ b/packages/editor/src/components/editor/AddBlockButton.tsx @@ -16,19 +16,14 @@ import { Minus, Table, Quote, - // ToggleLeft, - // CheckSquare, - // Video, - // Music, - // File, - // Smile, AlertCircle, Plus, - // Search, Video, Music, File, CheckSquare, + ArrowUpDown, + Link2, } from 'lucide-react' interface AddBlockButtonProps { @@ -83,6 +78,8 @@ export const AddBlockButton = forwardRef< { type: 'audio', icon: , label: 'Audio', description: 'Embed audio content.', group: 'Media' }, { type: 'file', icon: , label: 'File', description: 'Upload or link to a file.', group: 'Media' }, { type: 'checkList', icon: , label: 'To-do list', description: 'Track tasks with a to-do list.', group: 'Basic' }, + { type: 'spacer', icon: , label: 'Spacing', description: 'Add vertical space between blocks.', group: 'Basic' }, + { type: 'button', icon: , label: 'Button', description: 'Add a clickable button with link.', group: 'Basic' }, ] const filteredOptions = blockOptions.filter((option) => diff --git a/packages/editor/src/components/editor/BlockFormatToolbar.tsx b/packages/editor/src/components/editor/BlockFormatToolbar.tsx index 8a70ee5..e575d98 100644 --- a/packages/editor/src/components/editor/BlockFormatToolbar.tsx +++ b/packages/editor/src/components/editor/BlockFormatToolbar.tsx @@ -76,6 +76,23 @@ interface BlockFormatToolbarProps { caption: string; alignment: 'left' | 'center' | 'right'; }>) => void; + showButtonControls?: boolean; + buttonMetadata?: { + url?: string; + style?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; + }; + onButtonMetadataChange?: (metadata: Partial<{ + buttonUrl: string; + buttonStyle: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; + }>) => void; } export function BlockFormatToolbar({ @@ -119,8 +136,11 @@ export function BlockFormatToolbar({ showAudioControls = false, audioContent, onAudioMetadataChange, + showButtonControls = false, + buttonMetadata, + onButtonMetadataChange, }: BlockFormatToolbarProps) { - if (blockType === 'file') { + if (blockType === 'file' || blockType === 'spacer') { return null; } @@ -133,7 +153,7 @@ export function BlockFormatToolbar({ "bg-popover border shadow-md rounded-md", className )}> - {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && blockType !== 'table' && ( + {!showImageControls && !showCodeControls && !showVideoControls && !showAudioControls && !showButtonControls && blockType !== 'table' && ( <> )} + {showButtonControls && ( + <> + + + onButtonMetadataChange?.({ buttonUrl: e.target.value })} + placeholder="URL" + className="h-7 w-32 text-xs" + /> + + + + + + + + )} + {!showImageControls && !showVideoControls && !showAudioControls && ( <> {!showCalloutControls && blockType !== 'table' && } diff --git a/packages/editor/src/lib/renderers/BlockRenderer.tsx b/packages/editor/src/lib/renderers/BlockRenderer.tsx index ba5d2a9..6247349 100644 --- a/packages/editor/src/lib/renderers/BlockRenderer.tsx +++ b/packages/editor/src/lib/renderers/BlockRenderer.tsx @@ -6,22 +6,15 @@ import { List } from "@/components/blocks/ListBlock" import { Blockquote } from "@/components/blocks/BlockquoteBlock" import { Divider } from "@/components/blocks/DividerBlock" import { CodeBlock } from "@/components/blocks/CodeBlock" -// import { Image } from '../blocks/Image' -// import { Table } from '../blocks/Table' -// import { ToggleList } from '../blocks/ToggleList' import { CheckList } from "@/components/blocks/CheckListBlock" -// import { Video } from '../blocks/Video' -// import { Audio } from '../blocks/Audio' -// import { File } from '../blocks/File' -// import { Emoji } from '../blocks/Emoji' import { Callout } from "@/components/blocks/CalloutBlock" -import { cn } from '@/lib/utils' -import { ErrorBoundary } from 'react-error-boundary' import { ImageBlock } from "@/components/blocks/ImageBlock" import { Table } from '@/components/blocks/TableBlock' import { VideoBlock } from "@/components/blocks/VideoBlock" import { AudioBlock } from "@/components/blocks/AudioBlock" import { FileBlock } from "@/components/blocks/FileBlock" +import { SpacerBlock } from "@/components/blocks/SpacerBlock" +import { ButtonBlock } from "@/components/blocks/ButtonBlock" interface ImageBlockContent { url: string; @@ -230,6 +223,24 @@ export function BlockRenderer({ block, isEditing, onUpdate }: BlockRendererProps onUpdate={(content) => onUpdate?.(block.id, content)} /> ); + case 'spacer': + return ( + onUpdate?.(block.id, size)} + /> + ); + case 'button': + return ( + onUpdate?.(block.id, content)} + align={block.metadata?.align} + /> + ); default: return null } diff --git a/packages/editor/src/types/Block.ts b/packages/editor/src/types/Block.ts index 61f1b7a..044cc58 100644 --- a/packages/editor/src/types/Block.ts +++ b/packages/editor/src/types/Block.ts @@ -12,9 +12,10 @@ export type BlockType = | 'video' | 'audio' | 'file' - // | 'emoji' | 'callout' - | 'apiReference'; + | 'spacer' + | 'apiReference' + | 'button'; export interface Block { id: string; @@ -44,6 +45,12 @@ export interface Block { align?: 'left' | 'center' | 'right'; // For alignment type?: 'info' | 'warning' | 'success' | 'error'; // For callouts title?: string; // For callouts + buttonUrl?: string; + buttonStyle?: { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg'; + radius?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + }; }; }