feat(editor): implement US-EDITOR-MOBILE with fixed premium toolbar (44px), action sheet (bottom sheet) for block and AI actions, select all block text, and performance fallbacks
This commit is contained in:
143
memento-note/components/mobile-editor-toolbar.tsx
Normal file
143
memento-note/components/mobile-editor-toolbar.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { Editor } from '@tiptap/core'
|
||||
import {
|
||||
Bold, Italic, Highlighter, Link2, List, CheckSquare,
|
||||
Heading, Code, Sparkles, MessageSquare, Quote, AlignLeft
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export type MobileEditorToolbarProps = {
|
||||
editor: Editor | null
|
||||
onOpenActionSheet: () => void
|
||||
onInsertImage?: () => void
|
||||
}
|
||||
|
||||
export function MobileEditorToolbar({
|
||||
editor,
|
||||
onOpenActionSheet,
|
||||
onInsertImage,
|
||||
}: MobileEditorToolbarProps) {
|
||||
if (!editor) return null
|
||||
|
||||
// Format states
|
||||
const isBold = editor.isActive('bold')
|
||||
const isItalic = editor.isActive('italic')
|
||||
const isHighlight = editor.isActive('highlight')
|
||||
const isLink = editor.isActive('link')
|
||||
const isBulletList = editor.isActive('bulletList')
|
||||
const isTaskList = editor.isActive('taskList')
|
||||
const isCodeBlock = editor.isActive('codeBlock')
|
||||
const isHeading = editor.isActive('heading')
|
||||
|
||||
const toggleHeadingCycle = () => {
|
||||
if (editor.isActive('heading', { level: 1 })) {
|
||||
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
} else if (editor.isActive('heading', { level: 2 })) {
|
||||
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
} else if (editor.isActive('heading', { level: 3 })) {
|
||||
editor.chain().focus().setParagraph().run()
|
||||
} else {
|
||||
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||
}
|
||||
}
|
||||
|
||||
const handleLinkPress = () => {
|
||||
if (isLink) {
|
||||
editor.chain().focus().unsetLink().run()
|
||||
} else {
|
||||
const url = window.prompt('URL:')
|
||||
if (url && url.trim()) {
|
||||
editor.chain().focus().setLink({ href: url.trim() }).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mobile-editor-toolbar-container">
|
||||
<div className="mobile-editor-toolbar-scroll">
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isBold && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
aria-label="Bold"
|
||||
>
|
||||
<Bold size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isItalic && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
aria-label="Italic"
|
||||
>
|
||||
<Italic size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isHighlight && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleHighlight().run()}
|
||||
aria-label="Highlight"
|
||||
>
|
||||
<Highlighter size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isLink && 'active')}
|
||||
onClick={handleLinkPress}
|
||||
aria-label="Link"
|
||||
>
|
||||
<Link2 size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isBulletList && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
aria-label="Bullet List"
|
||||
>
|
||||
<List size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isTaskList && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleTaskList().run()}
|
||||
aria-label="Task List"
|
||||
>
|
||||
<CheckSquare size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isHeading && 'active')}
|
||||
onClick={toggleHeadingCycle}
|
||||
aria-label="Heading"
|
||||
>
|
||||
<Heading size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={cn('mobile-toolbar-btn', isCodeBlock && 'active')}
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
aria-label="Code Block"
|
||||
>
|
||||
<Code size={18} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="mobile-toolbar-btn highlight-btn"
|
||||
onClick={onOpenActionSheet}
|
||||
aria-label="Plus"
|
||||
>
|
||||
<Sparkles size={18} className="text-amber-500 animate-pulse" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user