Skip to content

Commit

Permalink
Merge pull request #214 from Cloud-Code-AI/201-custom-component-block
Browse files Browse the repository at this point in the history
201 custom component block
  • Loading branch information
sauravpanda authored Dec 12, 2024
2 parents d710430 + 35b3c9a commit a329244
Show file tree
Hide file tree
Showing 17 changed files with 699 additions and 46 deletions.
103 changes: 103 additions & 0 deletions packages/akiradocs/src/components/blocks/ButtonBlock.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={wrapperClasses}>
<div className="relative inline-block">
<Button
variant={buttonStyle.variant || 'default'}
size={buttonStyle.size || 'default'}
className={cn(buttonClasses, isFocused && "invisible")}
type="button"
>
{content || 'Button text...'}
</Button>
<input
value={content}
onChange={(e) => 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..."
/>
</div>
</div>
)
}

return (
<div className={wrapperClasses}>
<div className="relative inline-block">
<Button
variant={buttonStyle.variant || 'default'}
size={buttonStyle.size || 'default'}
className={buttonClasses}
asChild
>
<a href={url} target="_blank" rel="noopener noreferrer">
{content}
</a>
</Button>
</div>
</div>
)
}
21 changes: 21 additions & 0 deletions packages/akiradocs/src/components/blocks/SortableBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
})
}}
/>
)}
<BlockRenderer
Expand Down
59 changes: 59 additions & 0 deletions packages/akiradocs/src/components/blocks/SpacerBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { cn } from '@/lib/utils'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { useState } from 'react'

interface SpacerBlockProps {
size?: 'small' | 'medium' | 'large' | 'xlarge'
isEditing?: boolean
onUpdate?: (size: string) => 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 <div className={cn('w-full', spacingSizes[size])} />
}

return (
<div
className="flex items-center gap-2 py-2"
tabIndex={0}
onFocus={() => setIsFocused(true)}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
setIsFocused(false)
}
}}
>
<div className={cn(
'flex-grow border-2 border-dashed rounded transition-colors duration-200',
spacingSizes[size],
isFocused ? 'border-muted-foreground/20' : 'border-transparent'
)} />
{isFocused && (
<Select
value={size}
onValueChange={(value) => onUpdate?.(value)}
>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="Select size" />
</SelectTrigger>
<SelectContent>
<SelectItem value="small">Small</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="large">Large</SelectItem>
<SelectItem value="xlarge">Extra Large</SelectItem>
</SelectContent>
</Select>
)}
</div>
)
}
27 changes: 27 additions & 0 deletions packages/akiradocs/src/components/editor/AIRewriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -213,6 +232,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite
};

const handleRewrite = async () => {
if (blockType === 'spacer') {
return;
}

try {
await onRewrite(style)
} catch (error) {
Expand All @@ -224,6 +247,10 @@ export function AIRewriteButton({ onRewrite, blockType, isRewriting }: AIRewrite
}
}

if (blockType === 'spacer') {
return null;
}

return (
<Popover>
<PopoverTrigger asChild>
Expand Down
11 changes: 4 additions & 7 deletions packages/akiradocs/src/components/editor/AddBlockButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -83,6 +78,8 @@ export const AddBlockButton = forwardRef<
{ type: 'audio', icon: <Music size={18} />, label: 'Audio', description: 'Embed audio content.', group: 'Media' },
{ type: 'file', icon: <File size={18} />, label: 'File', description: 'Upload or link to a file.', group: 'Media' },
{ type: 'checkList', icon: <CheckSquare size={18} />, label: 'To-do list', description: 'Track tasks with a to-do list.', group: 'Basic' },
{ type: 'spacer', icon: <ArrowUpDown size={18} />, label: 'Spacing', description: 'Add vertical space between blocks.', group: 'Basic' },
{ type: 'button', icon: <Link2 size={18} />, label: 'Button', description: 'Add a clickable button with link.', group: 'Basic' },
]

const filteredOptions = blockOptions.filter((option) =>
Expand Down
Loading

0 comments on commit a329244

Please sign in to comment.