441 lines
14 KiB
TypeScript
441 lines
14 KiB
TypeScript
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,
|
|
}));
|
|
}
|