* chore: run Tailwind v4 upgrade tool (Phase 1) - Upgraded tailwindcss v3 → v4.2.1, postcss plugin to @tailwindcss/postcss - Deleted tailwind.config.js, migrated theme to CSS @theme block in index.css - Replaced @tailwind directives with @import 'tailwindcss' - Added @custom-variant dark, @utility blocks for custom utilities - Updated class names across 128 files (shadow-sm → shadow-xs, etc.) - Removed autoprefixer (built into v4) - Added migration plan doc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: switch from @tailwindcss/postcss to @tailwindcss/vite (Phase 2) - Replaced @tailwindcss/postcss with @tailwindcss/vite plugin - Deleted postcss.config.js (no longer needed) - Tailwind now runs as a native Vite plugin for faster HMR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert to OKLCH colors, move keyframes into @theme (Phase 3-4) - Replaced all HSL color indirection with direct OKLCH values in @theme - Moved all keyframes inside @theme block (v4 pattern) - Eliminated hsl(var(--x)) double-indirection across 17 component files - Replaced hsl() inline styles with var(--color-*) theme references - Cleaned up redundant rdp-* utility blocks - Fixed @custom-variant dark syntax to use :where() - Added sidebar/glass/shadow vars as OKLCH in :root Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
import { useState } from 'react'
|
|
import { X } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import { usePermissions } from '@/hooks/usePermissions'
|
|
import { StepForm } from './StepForm'
|
|
import { StepLibraryBrowser } from './StepLibraryBrowser'
|
|
import type { Step, StepCreate } from '@/types/step'
|
|
import { Spinner } from '@/components/common/Spinner'
|
|
|
|
export interface CustomStepDraft {
|
|
title: string
|
|
step_type: 'decision' | 'action' | 'solution'
|
|
content: {
|
|
instructions: string
|
|
help_text?: string
|
|
commands?: Array<{
|
|
label: string
|
|
command: string
|
|
command_type?: string
|
|
}>
|
|
}
|
|
category_id?: string
|
|
tags?: string[]
|
|
}
|
|
|
|
interface CustomStepModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
onInsertStep: (step: Step | CustomStepDraft, isFromLibrary: boolean) => void
|
|
}
|
|
|
|
type Tab = 'create' | 'browse'
|
|
|
|
export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepModalProps) {
|
|
const { canCreateSteps } = usePermissions()
|
|
const [activeTab, setActiveTab] = useState<Tab>(canCreateSteps ? 'create' : 'browse')
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
if (!isOpen) return null
|
|
|
|
const handleFormSubmit = async (data: StepCreate) => {
|
|
setIsSubmitting(true)
|
|
setError(null)
|
|
|
|
try {
|
|
// Always create a draft - saving to library is handled by PostStepActionModal
|
|
const draft: CustomStepDraft = {
|
|
title: data.title,
|
|
step_type: data.step_type,
|
|
content: data.content,
|
|
category_id: data.category_id,
|
|
tags: data.tags
|
|
}
|
|
onInsertStep(draft, false) // false = not from library (user typed it)
|
|
} catch (err) {
|
|
console.error('Failed to create step:', err)
|
|
setError('Failed to create step. Please try again.')
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const handleBrowserInsert = (step: Step) => {
|
|
onInsertStep(step, true) // true = from library (already saved)
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-xs sm:items-center sm:p-4">
|
|
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between border-b border-border p-4">
|
|
<h2 className="text-lg font-semibold text-foreground">Add Custom Step</h2>
|
|
<button
|
|
onClick={onClose}
|
|
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
aria-label="Close"
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="flex border-b border-border">
|
|
{canCreateSteps && (
|
|
<button
|
|
onClick={() => setActiveTab('create')}
|
|
className={cn(
|
|
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
|
activeTab === 'create'
|
|
? 'border-b-2 border-primary bg-primary/5 text-foreground'
|
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
)}
|
|
>
|
|
Type My Own
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={() => setActiveTab('browse')}
|
|
className={cn(
|
|
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
|
activeTab === 'browse'
|
|
? 'border-b-2 border-primary bg-primary/5 text-primary'
|
|
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
|
)}
|
|
>
|
|
Browse Library
|
|
</button>
|
|
</div>
|
|
|
|
{/* Error Display */}
|
|
{error && (
|
|
<div className="mx-4 mt-4 rounded-lg border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab Content */}
|
|
<div className="flex-1 overflow-hidden">
|
|
{activeTab === 'create' ? (
|
|
<div className="h-full overflow-y-auto p-6">
|
|
<StepForm
|
|
onSubmit={handleFormSubmit}
|
|
onCancel={onClose}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<StepLibraryBrowser
|
|
onInsert={handleBrowserInsert}
|
|
showCreateButton={false}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Loading Overlay */}
|
|
{isSubmitting && (
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
|
<div className="flex flex-col items-center gap-3">
|
|
<Spinner className="border-t-foreground" />
|
|
<p className="text-sm text-muted-foreground">Creating step...</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|