a11y: add landmarks, semantic structure, skip nav, iframe lang
This commit is contained in:
26
src/App.tsx
26
src/App.tsx
@@ -194,7 +194,7 @@ const App: React.FC = () => {
|
|||||||
style={{ fontSize: `${uiZoom}%` }}
|
style={{ fontSize: `${uiZoom}%` }}
|
||||||
>
|
>
|
||||||
<div role="status" aria-live="polite" className="sr-only">{statusMessage}</div>
|
<div role="status" aria-live="polite" className="sr-only">{statusMessage}</div>
|
||||||
<div className="h-full w-full flex flex-col">
|
<main id="main-content" className="h-full w-full flex flex-col">
|
||||||
<Preview
|
<Preview
|
||||||
htmlContent={generatedHtml}
|
htmlContent={generatedHtml}
|
||||||
onBack={handleBackToConfig}
|
onBack={handleBackToConfig}
|
||||||
@@ -205,7 +205,7 @@ const App: React.FC = () => {
|
|||||||
onZoomChange={setUiZoom}
|
onZoomChange={setUiZoom}
|
||||||
templates={templates}
|
templates={templates}
|
||||||
/>
|
/>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -278,18 +278,20 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{appState !== AppState.UPLOAD && (
|
{appState !== AppState.UPLOAD && (
|
||||||
<motion.div
|
<motion.nav
|
||||||
|
aria-label="Progress"
|
||||||
initial={{ opacity: 0, x: 20 }}
|
initial={{ opacity: 0, x: 20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
exit={{ opacity: 0, x: -20 }}
|
exit={{ opacity: 0, x: -20 }}
|
||||||
className="flex items-center gap-4 text-sm text-zinc-400"
|
|
||||||
>
|
>
|
||||||
<span className={appState === AppState.CONFIG ? "text-indigo-400 font-medium" : ""}>Configure</span>
|
<ol className="flex items-center gap-4 text-sm text-zinc-400">
|
||||||
<span>/</span>
|
<li className={appState === AppState.CONFIG ? "text-indigo-400 font-medium" : ""} aria-current={appState === AppState.CONFIG ? "step" : undefined}>Configure</li>
|
||||||
<span className={appState === AppState.GENERATING ? "text-indigo-400 font-medium" : ""}>Generate</span>
|
<li aria-hidden="true">/</li>
|
||||||
<span>/</span>
|
<li className={appState === AppState.GENERATING ? "text-indigo-400 font-medium" : ""} aria-current={appState === AppState.GENERATING ? "step" : undefined}>Generate</li>
|
||||||
<span>Preview</span>
|
<li aria-hidden="true">/</li>
|
||||||
</motion.div>
|
<li className={appState === AppState.PREVIEW ? "text-indigo-400 font-medium" : ""} aria-current={appState === AppState.PREVIEW ? "step" : undefined}>Preview</li>
|
||||||
|
</ol>
|
||||||
|
</motion.nav>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,9 +299,9 @@ const App: React.FC = () => {
|
|||||||
</motion.header>
|
</motion.header>
|
||||||
|
|
||||||
{/* Main Content - Takes remaining space */}
|
{/* Main Content - Takes remaining space */}
|
||||||
<main className="flex-1 relative overflow-hidden">
|
<main id="main-content" className="flex-1 relative overflow-hidden">
|
||||||
{/* Animated background blobs */}
|
{/* Animated background blobs */}
|
||||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
<div className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] bg-indigo-900/10 rounded-full blur-3xl"
|
className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] bg-indigo-900/10 rounded-full blur-3xl"
|
||||||
animate={{
|
animate={{
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ export const Preview: React.FC<PreviewProps> = ({
|
|||||||
// Inject CSS directly as inline style tag
|
// Inject CSS directly as inline style tag
|
||||||
const html = `
|
const html = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
@@ -390,9 +390,9 @@ export const Preview: React.FC<PreviewProps> = ({
|
|||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center gap-6">
|
<div className="flex flex-col sm:flex-row items-center gap-6">
|
||||||
<FontList fonts={usedFonts} />
|
<FontList fonts={usedFonts} />
|
||||||
<div className="h-4 w-px bg-zinc-800 hidden sm:block" />
|
<div className="h-4 w-px bg-zinc-800 hidden sm:block" aria-hidden="true" />
|
||||||
<ZoomControl zoom={uiZoom} onZoomChange={onZoomChange} />
|
<ZoomControl zoom={uiZoom} onZoomChange={onZoomChange} />
|
||||||
<div className="h-4 w-px bg-zinc-800 hidden sm:block" />
|
<div className="h-4 w-px bg-zinc-800 hidden sm:block" aria-hidden="true" />
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-zinc-400 text-sm hidden sm:inline">Format: {paperSize}</span>
|
<span className="text-zinc-400 text-sm hidden sm:inline">Format: {paperSize}</span>
|
||||||
<motion.button
|
<motion.button
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
@@ -318,7 +318,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|||||||
<div className="lg:col-span-4 flex flex-col gap-4 min-h-0 bg-zinc-900/30 rounded-2xl border border-zinc-800/50 overflow-hidden">
|
<div className="lg:col-span-4 flex flex-col gap-4 min-h-0 bg-zinc-900/30 rounded-2xl border border-zinc-800/50 overflow-hidden">
|
||||||
|
|
||||||
{/* Category Filter Tabs */}
|
{/* Category Filter Tabs */}
|
||||||
<div className="flex flex-col border-b border-zinc-800/50 bg-zinc-900/20">
|
<nav aria-label="Style filters" className="flex flex-col border-b border-zinc-800/50 bg-zinc-900/20">
|
||||||
<div className="p-4 overflow-x-auto no-scrollbar pb-2">
|
<div className="p-4 overflow-x-auto no-scrollbar pb-2">
|
||||||
<div className="flex gap-2" role="group" aria-label="Filter by category">
|
<div className="flex gap-2" role="group" aria-label="Filter by category">
|
||||||
<button
|
<button
|
||||||
@@ -376,11 +376,11 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|||||||
{searchQuery.trim() ? `${filteredStyles.length} template${filteredStyles.length !== 1 ? 's' : ''} found` : ''}
|
{searchQuery.trim() ? `${filteredStyles.length} template${filteredStyles.length !== 1 ? 's' : ''} found` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
|
|
||||||
{/* Scrollable List */}
|
{/* Scrollable List */}
|
||||||
|
<section aria-label="Style list" className="flex-1 overflow-y-auto p-4 space-y-3 custom-scrollbar">
|
||||||
<div
|
<div
|
||||||
className="flex-1 overflow-y-auto p-4 space-y-3 custom-scrollbar"
|
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-label="Typography styles"
|
aria-label="Typography styles"
|
||||||
aria-activedescendant={selectedStyle ? `style-${selectedStyle}` : undefined}
|
aria-activedescendant={selectedStyle ? `style-${selectedStyle}` : undefined}
|
||||||
@@ -441,20 +441,21 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* RIGHT COLUMN: Preview Window */}
|
{/* RIGHT COLUMN: Preview Window */}
|
||||||
<div className="lg:col-span-8 flex flex-col min-h-0 bg-zinc-950 rounded-2xl border border-zinc-800 shadow-2xl relative overflow-hidden">
|
<section aria-label="Style preview" className="lg:col-span-8 flex flex-col min-h-0 bg-zinc-950 rounded-2xl border border-zinc-800 shadow-2xl relative overflow-hidden">
|
||||||
|
|
||||||
{/* Preview Header */}
|
{/* Preview Header */}
|
||||||
<div className="h-12 border-b border-zinc-800 bg-zinc-900/50 flex items-center px-4 justify-between">
|
<div className="h-12 border-b border-zinc-800 bg-zinc-900/50 flex items-center px-4 justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5" aria-hidden="true">
|
||||||
<div className="w-3 h-3 rounded-full bg-red-500/20 border border-red-500/50"></div>
|
<div className="w-3 h-3 rounded-full bg-red-500/20 border border-red-500/50"></div>
|
||||||
<div className="w-3 h-3 rounded-full bg-amber-500/20 border border-amber-500/50"></div>
|
<div className="w-3 h-3 rounded-full bg-amber-500/20 border border-amber-500/50"></div>
|
||||||
<div className="w-3 h-3 rounded-full bg-emerald-500/20 border border-emerald-500/50"></div>
|
<div className="w-3 h-3 rounded-full bg-emerald-500/20 border border-emerald-500/50"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-4 w-px bg-zinc-800 mx-2"></div>
|
<div className="h-4 w-px bg-zinc-800 mx-2" aria-hidden="true"></div>
|
||||||
<span className="text-xs text-zinc-400 font-medium">Live Preview</span>
|
<span className="text-xs text-zinc-400 font-medium">Live Preview</span>
|
||||||
</div>
|
</div>
|
||||||
{currentStyleObj && (
|
{currentStyleObj && (
|
||||||
@@ -491,7 +492,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|||||||
<Printer size={18} aria-hidden="true" />
|
<Printer size={18} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user