feat(workspace): introduce a responsive signature workspace demo and remove legacy contract editor components
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
import { html, css, type TemplateResult } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-catalog/ts_web/elements/00group-utility/dees-icon/dees-icon.js';
|
||||
|
||||
export type TWorkspaceView =
|
||||
| 'inbox'
|
||||
| 'compose'
|
||||
| 'sign'
|
||||
| 'audit'
|
||||
| 'developers'
|
||||
| 'templates'
|
||||
| 'team'
|
||||
| 'settings';
|
||||
|
||||
export type TWorkspaceTheme = 'dark' | 'light';
|
||||
export type TDensity = 'compact' | 'comfortable';
|
||||
|
||||
export interface IDocumentRow {
|
||||
id: string;
|
||||
title: string;
|
||||
status: 'awaiting' | 'signed' | 'draft' | 'declined';
|
||||
recipients: Array<{ name: string; initials: string; signed: boolean }>;
|
||||
updated: string;
|
||||
sender: string;
|
||||
pages: number;
|
||||
deadline?: string;
|
||||
}
|
||||
|
||||
export interface IRecipient {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
color: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface IFieldPlacement {
|
||||
id: string;
|
||||
type: 'signature' | 'date' | 'text' | 'initials' | 'check';
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
page: number;
|
||||
recipient: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const demoDocuments: IDocumentRow[] = [
|
||||
{ id: 'doc_8mK3pL', title: 'Master Services Agreement - Acme Corp', status: 'awaiting', recipients: [{ name: 'Sarah Chen', initials: 'SC', signed: true }, { name: 'David Park', initials: 'DP', signed: false }, { name: 'You', initials: 'PK', signed: true }], updated: '2 min ago', sender: 'You', pages: 14, deadline: 'May 5' },
|
||||
{ id: 'doc_2nQ7vR', title: 'NDA - Helio Robotics', status: 'signed', recipients: [{ name: 'Marcus Tan', initials: 'MT', signed: true }, { name: 'You', initials: 'PK', signed: true }], updated: '1h ago', sender: 'You', pages: 3 },
|
||||
{ id: 'doc_5tH1zM', title: 'Series B Term Sheet (Lead) v3', status: 'awaiting', recipients: [{ name: 'Anna Lindqvist', initials: 'AL', signed: false }, { name: 'Roy Banerjee', initials: 'RB', signed: true }, { name: 'You', initials: 'PK', signed: false }], updated: '3h ago', sender: 'Sequoia Counsel', pages: 22, deadline: 'May 3' },
|
||||
{ id: 'doc_9wB4cX', title: 'Employment Offer - Mira Abebe', status: 'declined', recipients: [{ name: 'Mira Abebe', initials: 'MA', signed: false }, { name: 'You', initials: 'PK', signed: true }], updated: 'yesterday', sender: 'You', pages: 6 },
|
||||
{ id: 'doc_1jF6kY', title: 'Lease - Berlin office Q3', status: 'draft', recipients: [{ name: 'You', initials: 'PK', signed: false }], updated: 'yesterday', sender: 'You', pages: 11 },
|
||||
{ id: 'doc_4dN8sP', title: 'API Reseller Agreement - Northwind', status: 'signed', recipients: [{ name: 'Lila Brooks', initials: 'LB', signed: true }, { name: 'You', initials: 'PK', signed: true }], updated: '2 days ago', sender: 'You', pages: 8 },
|
||||
];
|
||||
|
||||
export const demoRecipients: IRecipient[] = [
|
||||
{ id: 0, name: 'Sarah Chen', email: 'sarah@acme.com', color: '#60a5fa', order: 1 },
|
||||
{ id: 1, name: 'David Park', email: 'd.park@acme.com', color: '#fbbf24', order: 2 },
|
||||
{ id: 2, name: 'Philipp K.', email: 'philipp@lossless.com', color: '#3b82f6', order: 3 },
|
||||
];
|
||||
|
||||
export const demoFields: IFieldPlacement[] = [
|
||||
{ id: 'f1', type: 'signature', x: 60, y: 580, w: 200, h: 50, page: 1, recipient: 0, label: 'Signature' },
|
||||
{ id: 'f2', type: 'date', x: 320, y: 580, w: 120, h: 30, page: 1, recipient: 0, label: 'Date' },
|
||||
{ id: 'f3', type: 'text', x: 60, y: 460, w: 280, h: 30, page: 1, recipient: 1, label: 'Full legal name' },
|
||||
{ id: 'f4', type: 'signature', x: 60, y: 700, w: 200, h: 50, page: 1, recipient: 1, label: 'Counter-signature' },
|
||||
];
|
||||
|
||||
export const workspaceBaseStyles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-family: Geist, Inter, Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-feature-settings: 'cv11', 'tnum', 'cv05' 1;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button, input, textarea { font: inherit; }
|
||||
button { border: 0; cursor: pointer; }
|
||||
dees-icon { flex-shrink: 0; }
|
||||
|
||||
.mono {
|
||||
font-family: 'Intel One Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: 56px;
|
||||
flex-shrink: 0;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.top-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.top-title > span:first-child {
|
||||
font-family: 'Plus Jakarta Sans', Inter, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
|
||||
.btn.small {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.btn.outline {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn.ghost {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.btn:hover { background-color: var(--hover); }
|
||||
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-el);
|
||||
color: var(--text-sec);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill::before {
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
display: none;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.pill.dot::before { display: block; }
|
||||
.pill.success { background: rgba(34,197,94,0.12); color: #4ade80; }
|
||||
.pill.warning { background: rgba(245,158,11,0.12); color: #fbbf24; }
|
||||
.pill.error { background: rgba(239,68,68,0.12); color: #f87171; }
|
||||
.pill.info { background: rgba(59,130,246,0.12); color: #60a5fa; }
|
||||
|
||||
.content-scroll {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.label-upper {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.document-page {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
min-height: 800px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.35), 0 0 0 1px rgba(255,255,255,0.05);
|
||||
color: hsl(0 0% 20%);
|
||||
}
|
||||
|
||||
.fake-document {
|
||||
padding: 48px 56px;
|
||||
font-size: 11px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.fake-title {
|
||||
font-family: 'Plus Jakarta Sans', Inter, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
color: hsl(0 0% 10%);
|
||||
}
|
||||
|
||||
.fake-line {
|
||||
height: 6px;
|
||||
background: hsl(0 0% 82%);
|
||||
margin-bottom: 7px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.fake-line.heavy { background: hsl(0 0% 65%); }
|
||||
.fake-line.short { width: 70%; }
|
||||
|
||||
.field-box {
|
||||
position: absolute;
|
||||
left: var(--x);
|
||||
top: var(--y);
|
||||
width: var(--w);
|
||||
height: var(--h);
|
||||
background: color-mix(in srgb, var(--field-color) 13%, transparent);
|
||||
border: 1.5px dashed var(--field-color);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: var(--field-color);
|
||||
}
|
||||
|
||||
.field-box.selected {
|
||||
border-style: solid;
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--field-color) 18%, transparent);
|
||||
}
|
||||
|
||||
.recipient-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-sec);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.recipient-line.active {
|
||||
background: var(--hover);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
height: 4px;
|
||||
background: var(--bg-el);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--accent);
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.topbar { padding: 0 16px; }
|
||||
.actions { display: none; }
|
||||
.content-scroll { padding: 16px; }
|
||||
}
|
||||
`;
|
||||
|
||||
export function icon(name: string, size = 14): TemplateResult {
|
||||
const iconMap: Record<string, string> = {
|
||||
inbox: 'lucide:Inbox', plus: 'lucide:Plus', folder: 'lucide:Folder', shield: 'lucide:Shield', code: 'lucide:Code2',
|
||||
user: 'lucide:User', settings: 'lucide:Settings', upload: 'lucide:Upload', file: 'lucide:FileText', sign: 'lucide:PenTool',
|
||||
clock: 'lucide:Clock', search: 'lucide:Search', more: 'lucide:MoreHorizontal', send: 'lucide:Send', check: 'lucide:Check',
|
||||
eye: 'lucide:Eye', calendar: 'lucide:Calendar', type: 'lucide:Type', download: 'lucide:Download', hash: 'lucide:Hash',
|
||||
github: 'lucide:GitBranch', git: 'lucide:GitBranch', server: 'lucide:Server', star: 'lucide:Star', sparkle: 'lucide:Sparkles',
|
||||
chevronRight: 'lucide:ChevronRight', chevronDown: 'lucide:ChevronDown', x: 'lucide:X', activity: 'lucide:Activity',
|
||||
};
|
||||
return html`<dees-icon .icon=${iconMap[name] || iconMap.file} style="font-size: ${size}px;"></dees-icon>`;
|
||||
}
|
||||
|
||||
export function pill(label: string, tone: 'default' | 'success' | 'warning' | 'error' | 'info' = 'default', dot = false): TemplateResult {
|
||||
return html`<span class="pill ${tone} ${dot ? 'dot' : ''}">${label}</span>`;
|
||||
}
|
||||
|
||||
export function actionButton(label: string, variant: 'primary' | 'outline' | 'ghost' = 'outline', iconName?: string, onClick?: () => void): TemplateResult {
|
||||
return html`<button class="btn ${variant}" @click=${onClick || (() => undefined)}>${iconName ? icon(iconName, 13) : ''}${label}</button>`;
|
||||
}
|
||||
|
||||
export function topBar(config: { breadcrumb: string[]; title: string; subtitle?: TemplateResult; actions?: TemplateResult }): TemplateResult {
|
||||
return html`
|
||||
<div class="topbar">
|
||||
<div style="min-width: 0; flex: 1;">
|
||||
<div class="breadcrumb">
|
||||
${config.breadcrumb.map((part, index) => html`${index > 0 ? icon('chevronRight', 10) : ''}<span>${part}</span>`)}
|
||||
</div>
|
||||
<div class="top-title"><span>${config.title}</span>${config.subtitle || ''}</div>
|
||||
</div>
|
||||
<div class="actions">${config.actions || ''}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function workspaceDemoFrame(content: TemplateResult, theme: TWorkspaceTheme = 'dark'): TemplateResult {
|
||||
const darkVars = `
|
||||
--accent: #3b82f6;
|
||||
--bg: hsl(0 0% 3.9%);
|
||||
--bg-el: hsl(0 0% 6%);
|
||||
--bg-card: hsl(0 0% 7%);
|
||||
--bg-input: hsl(0 0% 9%);
|
||||
--border: hsl(0 0% 14.9%);
|
||||
--border-subtle: hsl(0 0% 11%);
|
||||
--border-strong: hsl(0 0% 20%);
|
||||
--text: hsl(0 0% 98%);
|
||||
--text-sec: hsl(0 0% 63.9%);
|
||||
--text-muted: hsl(0 0% 48%);
|
||||
--text-dim: hsl(0 0% 32%);
|
||||
--hover: rgba(255,255,255,0.06);
|
||||
--hover-subtle: rgba(255,255,255,0.03);
|
||||
--row-hover: rgba(255,255,255,0.025);
|
||||
--success: #22c55e;
|
||||
--warning: #f59e0b;
|
||||
--error: #ef4444;
|
||||
`;
|
||||
const lightVars = `
|
||||
--accent: #3b82f6;
|
||||
--bg: hsl(0 0% 99%);
|
||||
--bg-el: hsl(0 0% 97%);
|
||||
--bg-card: hsl(0 0% 100%);
|
||||
--bg-input: hsl(0 0% 98%);
|
||||
--border: hsl(0 0% 90%);
|
||||
--border-subtle: hsl(0 0% 93%);
|
||||
--border-strong: hsl(0 0% 80%);
|
||||
--text: hsl(0 0% 9%);
|
||||
--text-sec: hsl(0 0% 32%);
|
||||
--text-muted: hsl(0 0% 45%);
|
||||
--text-dim: hsl(0 0% 62%);
|
||||
--hover: rgba(0,0,0,0.04);
|
||||
--hover-subtle: rgba(0,0,0,0.02);
|
||||
--row-hover: rgba(0,0,0,0.02);
|
||||
--success: #16a34a;
|
||||
--warning: #d97706;
|
||||
--error: #dc2626;
|
||||
`;
|
||||
|
||||
return html`<div style="${theme === 'dark' ? darkVars : lightVars} height: 720px; min-height: 720px; background: var(--bg); color: var(--text); overflow: hidden; font-family: Geist, Inter, Roboto, -apple-system, BlinkMacSystemFont, sans-serif;">${content}</div>`;
|
||||
}
|
||||
|
||||
export function fakeDocument(): TemplateResult {
|
||||
return html`
|
||||
<div class="fake-document">
|
||||
<div class="fake-title">Master Services Agreement</div>
|
||||
<div class="mono" style="font-size: 10px; color: hsl(0 0% 45%); margin-bottom: 24px;">Effective: May 2, 2026 · Acme Corp ↔ Lossless GmbH</div>
|
||||
${Array.from({ length: 18 }).map((_, index) => html`<div class="fake-line ${index % 5 === 0 ? 'heavy' : ''} ${index % 4 === 3 ? 'short' : ''}"></div>`)}
|
||||
<div style="height: 16px;"></div>
|
||||
${Array.from({ length: 8 }).map((_, index) => html`<div class="fake-line ${index % 3 === 2 ? 'short' : ''}"></div>`)}
|
||||
<div style="margin-top: 60px; font-size: 10px; font-weight: 600; color: hsl(0 0% 35%);">SIGNED ON BEHALF OF ACME CORP</div>
|
||||
<div style="margin-top: 70px; font-size: 10px; font-weight: 600; color: hsl(0 0% 35%);">SIGNED ON BEHALF OF LOSSLESS GMBH</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function requestWorkspaceView(element: HTMLElement, view: TWorkspaceView) {
|
||||
element.dispatchEvent(new CustomEvent('workspace-view-request', {
|
||||
detail: { view },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user