feat: add split-action share button with copy shortcut
Add intuitive share UI with dual functionality: PreviewCenter (Dropdown Menu): - Single menu item: "📱 Share" (opens QR modal) | "📋" (quick copy) - Copy button on right side with visual feedback - Shows ✓ checkmark + green highlight for 2 seconds after copy - Loading state (⏳) during share link generation ActionsSidebar (Full Buttons): - Button group: "📱 Share" takes most space | "📋" copy shortcut - Same visual feedback and loading states - Maintains button styling consistency Quick Copy Flow: - Lazy generation: Creates share link only when copy is clicked - Auto-copies to clipboard without opening modal - Visual confirmation: Icon changes to ✓, background turns green - Error handling with console logs Both UIs provide: - Main action: Opens QR code modal for sharing - Shortcut action: Direct clipboard copy - Disabled state during generation - Hover effects on both parts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
224
apps/web/src/app/create/worksheets/components/ActionsSidebar.tsx
Normal file
224
apps/web/src/app/create/worksheets/components/ActionsSidebar.tsx
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { css } from '@styled/css'
|
||||||
|
import { stack } from '@styled/patterns'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { UploadWorksheetModal } from '@/components/worksheets/UploadWorksheetModal'
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
|
import { GenerateButton } from './GenerateButton'
|
||||||
|
import { ShareModal } from './ShareModal'
|
||||||
|
import { useWorksheetConfig } from './WorksheetConfigContext'
|
||||||
|
|
||||||
|
interface ActionsSidebarProps {
|
||||||
|
onGenerate: () => Promise<void>
|
||||||
|
status: 'idle' | 'generating' | 'success' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionsSidebar({ onGenerate, status }: ActionsSidebarProps) {
|
||||||
|
const { resolvedTheme } = useTheme()
|
||||||
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
const router = useRouter()
|
||||||
|
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false)
|
||||||
|
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
|
||||||
|
const [isGeneratingShare, setIsGeneratingShare] = useState(false)
|
||||||
|
const [justCopied, setJustCopied] = useState(false)
|
||||||
|
const { formState } = useWorksheetConfig()
|
||||||
|
|
||||||
|
// Upload complete handler
|
||||||
|
const handleUploadComplete = (attemptId: string) => {
|
||||||
|
router.push(`/worksheets/attempts/${attemptId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick share - copy link to clipboard without showing modal
|
||||||
|
const handleQuickShare = async () => {
|
||||||
|
setIsGeneratingShare(true)
|
||||||
|
setJustCopied(false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/worksheets/share', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
worksheetType: 'addition',
|
||||||
|
config: formState,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to create share link')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
await navigator.clipboard.writeText(data.url)
|
||||||
|
|
||||||
|
setJustCopied(true)
|
||||||
|
setTimeout(() => setJustCopied(false), 2000)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create share link:', err)
|
||||||
|
// TODO: Show error toast
|
||||||
|
} finally {
|
||||||
|
setIsGeneratingShare(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
data-component="actions-sidebar"
|
||||||
|
className={css({
|
||||||
|
h: 'full',
|
||||||
|
bg: isDark ? 'gray.800' : 'white',
|
||||||
|
rounded: 'xl',
|
||||||
|
shadow: 'card',
|
||||||
|
p: '4',
|
||||||
|
overflow: 'auto',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={stack({ gap: '4' })}>
|
||||||
|
{/* Generate Button */}
|
||||||
|
<GenerateButton
|
||||||
|
status={status === 'success' ? 'idle' : status}
|
||||||
|
onGenerate={onGenerate}
|
||||||
|
isDark={isDark}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Share Button with Copy Shortcut */}
|
||||||
|
<div
|
||||||
|
data-component="share-button-group"
|
||||||
|
className={css({
|
||||||
|
w: 'full',
|
||||||
|
display: 'flex',
|
||||||
|
rounded: 'xl',
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadow: 'md',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: 'blue.700',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Main share button - opens QR modal */}
|
||||||
|
<button
|
||||||
|
data-action="show-qr-code"
|
||||||
|
onClick={() => setIsShareModalOpen(true)}
|
||||||
|
className={css({
|
||||||
|
flex: '1',
|
||||||
|
px: '6',
|
||||||
|
py: '4',
|
||||||
|
bg: 'blue.600',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'md',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '2',
|
||||||
|
border: 'none',
|
||||||
|
outline: 'none',
|
||||||
|
_hover: {
|
||||||
|
bg: 'blue.700',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
transform: 'translateY(0)',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={css({ fontSize: 'xl' })}>📱</span>
|
||||||
|
<span>Share</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Copy shortcut */}
|
||||||
|
<button
|
||||||
|
data-action="copy-share-link"
|
||||||
|
onClick={handleQuickShare}
|
||||||
|
disabled={isGeneratingShare}
|
||||||
|
title={justCopied ? 'Copied!' : 'Copy share link'}
|
||||||
|
className={css({
|
||||||
|
px: '4',
|
||||||
|
py: '4',
|
||||||
|
bg: justCopied ? 'green.600' : 'blue.600',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'xl',
|
||||||
|
cursor: isGeneratingShare ? 'wait' : 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderColor: justCopied ? 'green.700' : 'blue.700',
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
opacity: isGeneratingShare ? '0.6' : '1',
|
||||||
|
_hover: isGeneratingShare || justCopied
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
bg: 'blue.700',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
},
|
||||||
|
_active: isGeneratingShare || justCopied
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
transform: 'translateY(0)',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{isGeneratingShare ? '⏳' : justCopied ? '✓' : '📋'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Upload Worksheet Button */}
|
||||||
|
<button
|
||||||
|
data-action="upload-worksheet"
|
||||||
|
onClick={() => setIsUploadModalOpen(true)}
|
||||||
|
className={css({
|
||||||
|
w: 'full',
|
||||||
|
px: '6',
|
||||||
|
py: '4',
|
||||||
|
bg: 'purple.600',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'md',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
rounded: 'xl',
|
||||||
|
shadow: 'md',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '2',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: 'purple.700',
|
||||||
|
_hover: {
|
||||||
|
bg: 'purple.700',
|
||||||
|
borderColor: 'purple.800',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
shadow: 'lg',
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
transform: 'translateY(0)',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={css({ fontSize: 'xl' })}>⬆️</span>
|
||||||
|
<span>Upload</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Share Modal */}
|
||||||
|
<ShareModal
|
||||||
|
isOpen={isShareModalOpen}
|
||||||
|
onClose={() => setIsShareModalOpen(false)}
|
||||||
|
worksheetType="addition"
|
||||||
|
config={formState}
|
||||||
|
isDark={isDark}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Upload Worksheet Modal */}
|
||||||
|
<UploadWorksheetModal
|
||||||
|
isOpen={isUploadModalOpen}
|
||||||
|
onClose={() => setIsUploadModalOpen(false)}
|
||||||
|
onUploadComplete={handleUploadComplete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
367
apps/web/src/app/create/worksheets/components/PreviewCenter.tsx
Normal file
367
apps/web/src/app/create/worksheets/components/PreviewCenter.tsx
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
import { css } from '@styled/css'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import type { WorksheetFormState } from '@/app/create/worksheets/types'
|
||||||
|
import { UploadWorksheetModal } from '@/components/worksheets/UploadWorksheetModal'
|
||||||
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
|
import { ShareModal } from './ShareModal'
|
||||||
|
import { WorksheetPreview } from './WorksheetPreview'
|
||||||
|
|
||||||
|
interface PreviewCenterProps {
|
||||||
|
formState: WorksheetFormState
|
||||||
|
initialPreview?: string[]
|
||||||
|
onGenerate: () => Promise<void>
|
||||||
|
status: 'idle' | 'generating' | 'success' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PreviewCenter({
|
||||||
|
formState,
|
||||||
|
initialPreview,
|
||||||
|
onGenerate,
|
||||||
|
status,
|
||||||
|
}: PreviewCenterProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const { resolvedTheme } = useTheme()
|
||||||
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [isScrolling, setIsScrolling] = useState(false)
|
||||||
|
const scrollTimeoutRef = useRef<NodeJS.Timeout>()
|
||||||
|
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false)
|
||||||
|
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
|
||||||
|
const [isGeneratingShare, setIsGeneratingShare] = useState(false)
|
||||||
|
const [justCopied, setJustCopied] = useState(false)
|
||||||
|
const isGenerating = status === 'generating'
|
||||||
|
|
||||||
|
// Detect scrolling in the scroll container
|
||||||
|
useEffect(() => {
|
||||||
|
const container = scrollContainerRef.current
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
setIsScrolling(true)
|
||||||
|
|
||||||
|
// Clear existing timeout
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new timeout to hide after 1 second of no scrolling
|
||||||
|
scrollTimeoutRef.current = setTimeout(() => {
|
||||||
|
setIsScrolling(false)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
container.removeEventListener('scroll', handleScroll)
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Upload complete handler
|
||||||
|
const handleUploadComplete = (attemptId: string) => {
|
||||||
|
router.push(`/worksheets/attempts/${attemptId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick share - copy link to clipboard without showing modal
|
||||||
|
const handleQuickShare = async () => {
|
||||||
|
setIsGeneratingShare(true)
|
||||||
|
setJustCopied(false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/worksheets/share', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
worksheetType: 'addition',
|
||||||
|
config: formState,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to create share link')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
await navigator.clipboard.writeText(data.url)
|
||||||
|
|
||||||
|
setJustCopied(true)
|
||||||
|
setTimeout(() => setJustCopied(false), 2000)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create share link:', err)
|
||||||
|
// TODO: Show error toast
|
||||||
|
} finally {
|
||||||
|
setIsGeneratingShare(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
data-component="preview-center"
|
||||||
|
className={css({
|
||||||
|
h: 'full',
|
||||||
|
w: 'full',
|
||||||
|
overflow: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Floating Action Button - Top Right */}
|
||||||
|
<div
|
||||||
|
data-component="floating-action-button"
|
||||||
|
className={css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: '24',
|
||||||
|
right: '4',
|
||||||
|
zIndex: 1000,
|
||||||
|
display: 'flex',
|
||||||
|
borderRadius: 'lg',
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadow: 'lg',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: 'brand.700',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Main Download Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-action="download-pdf"
|
||||||
|
onClick={onGenerate}
|
||||||
|
disabled={isGenerating}
|
||||||
|
className={css({
|
||||||
|
px: '4',
|
||||||
|
py: '2.5',
|
||||||
|
bg: 'brand.600',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
cursor: isGenerating ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: isGenerating ? '0.7' : '1',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '2',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
_hover: isGenerating
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
bg: 'brand.700',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{isGenerating ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
w: '4',
|
||||||
|
h: '4',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: 'white',
|
||||||
|
borderTopColor: 'transparent',
|
||||||
|
rounded: 'full',
|
||||||
|
animation: 'spin 1s linear infinite',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<span>Generating...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className={css({ fontSize: 'lg' })}>⬇️</span>
|
||||||
|
<span>Download</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Dropdown Trigger */}
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild>
|
||||||
|
<button
|
||||||
|
data-action="open-actions-dropdown"
|
||||||
|
disabled={isGenerating}
|
||||||
|
className={css({
|
||||||
|
px: '2',
|
||||||
|
bg: 'brand.600',
|
||||||
|
color: 'white',
|
||||||
|
cursor: isGenerating ? 'not-allowed' : 'pointer',
|
||||||
|
opacity: isGenerating ? '0.7' : '1',
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderColor: 'brand.700',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
_hover: isGenerating
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
bg: 'brand.700',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={css({ fontSize: 'xs' })}>▼</span>
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
align="end"
|
||||||
|
sideOffset={4}
|
||||||
|
className={css({
|
||||||
|
bg: 'white',
|
||||||
|
borderRadius: 'lg',
|
||||||
|
shadow: 'lg',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'gray.200',
|
||||||
|
overflow: 'hidden',
|
||||||
|
minW: '160px',
|
||||||
|
zIndex: 10000,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
data-action="share-worksheet"
|
||||||
|
asChild
|
||||||
|
className={css({
|
||||||
|
outline: 'none',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
w: 'full',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Main share button - opens QR modal */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsShareModalOpen(true)}
|
||||||
|
className={css({
|
||||||
|
flex: '1',
|
||||||
|
px: '4',
|
||||||
|
py: '2.5',
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'medium',
|
||||||
|
color: 'gray.700',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '2',
|
||||||
|
outline: 'none',
|
||||||
|
bg: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
_hover: {
|
||||||
|
bg: 'blue.50',
|
||||||
|
color: 'blue.700',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={css({ fontSize: 'lg' })}>📱</span>
|
||||||
|
<span>Share</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Copy shortcut */}
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleQuickShare()
|
||||||
|
}}
|
||||||
|
disabled={isGeneratingShare}
|
||||||
|
className={css({
|
||||||
|
px: '3',
|
||||||
|
py: '2.5',
|
||||||
|
fontSize: 'lg',
|
||||||
|
color: justCopied ? 'green.700' : 'gray.600',
|
||||||
|
cursor: isGeneratingShare ? 'wait' : 'pointer',
|
||||||
|
bg: justCopied ? 'green.50' : 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderColor: 'gray.200',
|
||||||
|
outline: 'none',
|
||||||
|
opacity: isGeneratingShare ? '0.6' : '1',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
_hover: isGeneratingShare || justCopied
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
bg: 'green.50',
|
||||||
|
color: 'green.700',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
title={justCopied ? 'Copied!' : 'Copy share link'}
|
||||||
|
>
|
||||||
|
{isGeneratingShare ? '⏳' : justCopied ? '✓' : '📋'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
|
<DropdownMenu.Item
|
||||||
|
data-action="upload-worksheet"
|
||||||
|
onClick={() => setIsUploadModalOpen(true)}
|
||||||
|
className={css({
|
||||||
|
px: '4',
|
||||||
|
py: '2.5',
|
||||||
|
fontSize: 'sm',
|
||||||
|
fontWeight: 'medium',
|
||||||
|
color: 'gray.700',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '2',
|
||||||
|
outline: 'none',
|
||||||
|
_hover: {
|
||||||
|
bg: 'purple.50',
|
||||||
|
color: 'purple.700',
|
||||||
|
},
|
||||||
|
_focus: {
|
||||||
|
bg: 'purple.50',
|
||||||
|
color: 'purple.700',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={css({ fontSize: 'lg' })}>⬆️</span>
|
||||||
|
<span>Upload</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Share Modal */}
|
||||||
|
<ShareModal
|
||||||
|
isOpen={isShareModalOpen}
|
||||||
|
onClose={() => setIsShareModalOpen(false)}
|
||||||
|
worksheetType="addition"
|
||||||
|
config={formState}
|
||||||
|
isDark={isDark}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Upload Worksheet Modal */}
|
||||||
|
<UploadWorksheetModal
|
||||||
|
isOpen={isUploadModalOpen}
|
||||||
|
onClose={() => setIsUploadModalOpen(false)}
|
||||||
|
onUploadComplete={handleUploadComplete}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
w: 'full',
|
||||||
|
maxW: '1000px',
|
||||||
|
minH: 'full',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<WorksheetPreview
|
||||||
|
formState={formState}
|
||||||
|
initialData={initialPreview}
|
||||||
|
isScrolling={isScrolling}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user