feat: V1 prototype — Vite/React/TS tileset generator
- Scaffold: package.json, tsconfig.json, vite.config.ts, index.html - src/lib/imageProcessor.ts: full pipeline (normalize, offset, seam repair, export, validation) - src/components/UploadPanel.tsx: drag-and-drop, file picker, clipboard paste - src/components/SettingsPanel.tsx: all controls per spec - src/components/PreviewPanel.tsx: Original / Tileable / Repeated tabs - src/components/ErrorBanner.tsx: dismissible error/warning banners - src/App.tsx: root component wiring everything together - src/index.css: dark premium glassmorphism theme w/ Inter font
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
interface Banner {
|
||||
id: string;
|
||||
type: 'error' | 'warning' | 'info';
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
banners: Banner[];
|
||||
onDismiss: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function ErrorBanner({ banners, onDismiss }: Props) {
|
||||
if (banners.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
{banners.map((b) => (
|
||||
<div key={b.id} className={`banner banner-${b.type}`} role="alert">
|
||||
<BannerIcon type={b.type} />
|
||||
<span>{b.message}</span>
|
||||
<button
|
||||
className="banner-close"
|
||||
onClick={() => onDismiss(b.id)}
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BannerIcon({ type }: { type: Banner['type'] }) {
|
||||
if (type === 'error') {
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
|
||||
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
if (type === 'warning') {
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /><line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
|
||||
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export type { Banner };
|
||||
Reference in New Issue
Block a user