Scaffold CommiTea: yarn workspaces, Electron shell, core label schema, design system
- apps/desktop: electron-vite + React + Tailwind mapped onto design tokens (preflight off; tokens/base.css owns the reset); boots to a Reginald placeholder proving fonts/tokens/core wiring - packages/core: pure TS; gitea label schema (est/*, p/*, deadline/hard) with pessimistic conflict resolution + 15 unit tests - docs/design: full design handoff (tokens, 16 component contracts, interactive 14-screen prototype, Reginald voice rules) - docs/PLAN.md: product plan (purity rule, pm-state repo, deterministic scheduler + Monte Carlo, directive log) - Deliberate deviation from novelpad stack: no ElectricSQL/PGlite — local store is a rebuildable cache over gitea REST/webhooks (better-sqlite3 in main process, arriving in P1) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
68
docs/design/components/feedback/Dialog.js.txt
Normal file
68
docs/design/components/feedback/Dialog.js.txt
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
|
||||
const CSS = `
|
||||
.ct-dialog-scrim {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(32, 38, 29, 0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 100;
|
||||
animation: ct-dialog-fade var(--duration-base) var(--ease-out);
|
||||
}
|
||||
.ct-dialog {
|
||||
background: var(--surface-card);
|
||||
border: 1px solid var(--border-hairline);
|
||||
border-radius: var(--radius-3);
|
||||
box-shadow: var(--shadow-3);
|
||||
width: min(480px, calc(100vw - 48px));
|
||||
max-height: calc(100vh - 96px);
|
||||
display: flex; flex-direction: column;
|
||||
animation: ct-dialog-rise var(--duration-base) var(--ease-out);
|
||||
}
|
||||
.ct-dialog__header {
|
||||
display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
|
||||
padding: 20px 20px 12px;
|
||||
border-bottom: 3px double var(--border-strong);
|
||||
margin: 0 20px; padding-left: 0; padding-right: 0;
|
||||
}
|
||||
.ct-dialog__title { font: var(--text-title); color: var(--ink-1); margin: 0; }
|
||||
.ct-dialog__body { padding: 16px 20px; overflow-y: auto; font: var(--text-body); color: var(--ink-1); }
|
||||
.ct-dialog__footer {
|
||||
display: flex; justify-content: flex-end; gap: 8px;
|
||||
padding: 12px 20px 20px;
|
||||
}
|
||||
@keyframes ct-dialog-fade { from { opacity: 0; } }
|
||||
@keyframes ct-dialog-rise { from { opacity: 0; transform: translateY(8px); } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.ct-dialog-scrim, .ct-dialog { animation: none; }
|
||||
}
|
||||
`;
|
||||
(function inject() {
|
||||
if (typeof document !== 'undefined' && !document.getElementById('ct-dialog-css')) {
|
||||
const s = document.createElement('style'); s.id = 'ct-dialog-css'; s.textContent = CSS;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
})();
|
||||
|
||||
export function Dialog({ open, onClose, title, footer, children, style }) {
|
||||
React.useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e) => { if (e.key === 'Escape' && onClose) onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
return (
|
||||
<div className="ct-dialog-scrim" onClick={(e) => { if (e.target === e.currentTarget && onClose) onClose(); }}>
|
||||
<div className="ct-dialog" role="dialog" aria-modal="true" style={style}>
|
||||
<header className="ct-dialog__header">
|
||||
<h2 className="ct-dialog__title">{title}</h2>
|
||||
{onClose ? <IconButton icon="x" label="Close" size="sm" onClick={onClose} /> : null}
|
||||
</header>
|
||||
<div className="ct-dialog__body">{children}</div>
|
||||
{footer ? <footer className="ct-dialog__footer">{footer}</footer> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user