feat(workspace): introduce a responsive signature workspace demo and remove legacy contract editor components
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { DeesElement, property, state, html, customElement, type TemplateResult, css } from '@design.estate/dees-element';
|
||||
import { icon, type TDensity, type TWorkspaceTheme, type TWorkspaceView } from './sdig-workspace.shared.js';
|
||||
import './sdig-workspace-inbox.js';
|
||||
import './sdig-workspace-compose.js';
|
||||
import './sdig-workspace-sign.js';
|
||||
import './sdig-workspace-audit.js';
|
||||
import './sdig-workspace-developers.js';
|
||||
import './sdig-workspace-placeholder.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'sdig-workspace': SdigWorkspace;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('sdig-workspace')
|
||||
export class SdigWorkspace extends DeesElement {
|
||||
public static demo = () => html`<sdig-workspace></sdig-workspace>`;
|
||||
public static demoGroups = ['Signature Digital Workspace'];
|
||||
|
||||
@property({ type: String }) public accessor accent: string = '#3b82f6';
|
||||
@property({ type: String }) public accessor density: TDensity = 'comfortable';
|
||||
@property({ type: String, reflect: true }) public accessor theme: TWorkspaceTheme = 'dark';
|
||||
@property({ type: String }) public accessor initialView: TWorkspaceView = 'inbox';
|
||||
@state() private accessor view: TWorkspaceView = 'inbox';
|
||||
|
||||
public connectedCallback = async () => {
|
||||
await super.connectedCallback();
|
||||
this.view = this.initialView || 'inbox';
|
||||
this.addEventListener('workspace-view-request', this.handleViewRequest as EventListener);
|
||||
};
|
||||
|
||||
public disconnectedCallback = async () => {
|
||||
this.removeEventListener('workspace-view-request', this.handleViewRequest as EventListener);
|
||||
await super.disconnectedCallback();
|
||||
};
|
||||
|
||||
public static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 720px;
|
||||
--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;
|
||||
font-family: Geist, Inter, Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
:host([theme='light']) {
|
||||
--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;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
button { font: inherit; border: 0; cursor: pointer; }
|
||||
.workspace { display: flex; height: 100%; min-height: 720px; background: var(--bg); color: var(--text); overflow: hidden; }
|
||||
.sidebar { width: 220px; background: var(--bg); border-right: 1px solid var(--border-subtle); display: flex; flex-direction: column; flex-shrink: 0; height: 100%; }
|
||||
.brand { padding: 14px 16px 12px; display: flex; align-items: center; gap: 8px; }
|
||||
.logomark { width: 26px; height: 26px; border-radius: 6px; background: var(--bg-el); border: 1px solid var(--border-strong); display: inline-flex; align-items: center; justify-content: center; font-family: 'Plus Jakarta Sans', Inter, sans-serif; font-size: 13px; font-weight: 700; position: relative; }
|
||||
.logomark::after, .wordmark::after { content: ''; display: inline-block; border-radius: 50%; background: var(--accent); }
|
||||
.logomark::after { width: 4px; height: 4px; position: absolute; right: 5px; bottom: 5px; }
|
||||
.wordmark { font-size: 13px; font-weight: 500; letter-spacing: -0.02em; white-space: nowrap; }
|
||||
.wordmark .dot { color: var(--text-muted); }
|
||||
.wordmark::after { width: 4px; height: 4px; margin-left: 3px; transform: translateY(-1px); }
|
||||
.workspace-card { margin: 0 12px 8px; padding: 7px 10px; background: var(--bg-el); border: 1px solid var(--border-subtle); border-radius: 6px; display: flex; align-items: center; gap: 8px; }
|
||||
.workspace-badge { width: 18px; height: 18px; border-radius: 4px; background: linear-gradient(135deg, var(--accent), hsl(280 70% 60%)); color: white; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
|
||||
.workspace-name { font-size: 12px; font-weight: 500; line-height: 1.2; }
|
||||
.workspace-plan { font-size: 10px; color: var(--text-muted); }
|
||||
.nav-block { padding: 4px 0; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; padding: 7px 10px; margin: 1px 8px; border-radius: 6px; color: var(--text-muted); background: transparent; transition: all 0.1s ease; font-size: 13px; position: relative; width: calc(100% - 16px); text-align: left; }
|
||||
.compact .nav-item { padding: 5px 10px; }
|
||||
.nav-item:hover { background: var(--hover-subtle); color: var(--text-sec); }
|
||||
.nav-item.active { background: var(--hover); color: var(--text); font-weight: 500; }
|
||||
.nav-item.active::before { content: ''; position: absolute; left: -8px; width: 2px; height: 14px; border-radius: 2px; background: var(--accent); }
|
||||
.nav-count { margin-left: auto; min-width: 18px; padding: 1px 6px; border-radius: 999px; background: var(--bg-el); color: var(--text-muted); font-size: 10px; text-align: center; }
|
||||
.github-card { margin: 8px 12px; padding: 10px; border: 1px solid var(--border-subtle); border-radius: 6px; background: var(--bg-el); }
|
||||
.sparkline { margin-top: 8px; display: flex; gap: 2px; align-items: flex-end; height: 18px; }
|
||||
.sparkline span { flex: 1; background: var(--border-strong); border-radius: 1px; }
|
||||
.sparkline span:nth-last-child(-n+4) { background: var(--accent); }
|
||||
.user-card { padding: 8px 12px; border-top: 1px solid var(--border-subtle); display: flex; align-items: center; gap: 10px; }
|
||||
.avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--accent); color: white; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
|
||||
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.view-host { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
.statusbar { height: 24px; flex-shrink: 0; border-top: 1px solid var(--border-subtle); background: var(--bg); display: flex; align-items: center; padding: 0 16px; gap: 16px; font-size: 10px; color: var(--text-dim); font-family: 'Intel One Mono', ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
@media (max-width: 920px) { .workspace { flex-direction: column; min-height: 100vh; } .sidebar { width: 100%; height: auto; border-right: 0; border-bottom: 1px solid var(--border-subtle); } .brand, .workspace-card, .github-card, .user-card { display: none; } .nav-block { display: flex; overflow-x: auto; padding: 8px; } .nav-item { width: auto; margin: 0 2px; } .statusbar { display: none; } }
|
||||
`;
|
||||
|
||||
private handleViewRequest = (event: CustomEvent<{ view: TWorkspaceView }>) => {
|
||||
this.setView(event.detail.view);
|
||||
};
|
||||
|
||||
private setView(viewArg: TWorkspaceView) {
|
||||
this.view = viewArg;
|
||||
this.dispatchEvent(new CustomEvent('view-change', { detail: { view: viewArg }, bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
private navButton(item: { id: TWorkspaceView; label: string; icon: string; count?: number }): TemplateResult {
|
||||
return html`<button class="nav-item ${this.view === item.id ? 'active' : ''}" @click=${() => this.setView(item.id)}>${icon(item.icon, 15)}<span>${item.label}</span>${item.count !== undefined ? html`<span class="nav-count">${item.count}</span>` : ''}</button>`;
|
||||
}
|
||||
|
||||
private renderSidebar(): TemplateResult {
|
||||
const navItems = [
|
||||
{ id: 'inbox', label: 'Inbox', icon: 'inbox', count: 4 },
|
||||
{ id: 'compose', label: 'Compose', icon: 'plus' },
|
||||
{ id: 'templates', label: 'Templates', icon: 'folder', count: 12 },
|
||||
{ id: 'audit', label: 'Audit Trail', icon: 'shield' },
|
||||
{ id: 'developers', label: 'Developers', icon: 'code' },
|
||||
] as Array<{ id: TWorkspaceView; label: string; icon: string; count?: number }>;
|
||||
const lowerItems = [
|
||||
{ id: 'team', label: 'Team', icon: 'user' },
|
||||
{ id: 'settings', label: 'Settings', icon: 'settings' },
|
||||
] as Array<{ id: TWorkspaceView; label: string; icon: string }>;
|
||||
|
||||
return html`
|
||||
<aside class="sidebar">
|
||||
<div class="brand"><span class="logomark">s</span><span class="wordmark">signature<span class="dot">.</span>digital</span></div>
|
||||
<div class="workspace-card"><span class="workspace-badge">L</span><div style="flex: 1; min-width: 0;"><div class="workspace-name">Lossless GmbH</div><div class="workspace-plan">Cloud · Pro</div></div>${icon('chevronDown', 12)}</div>
|
||||
<div class="nav-block">${navItems.map((item) => this.navButton(item))}</div>
|
||||
<div style="flex: 1;"></div>
|
||||
<div class="github-card"><div style="display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 11px; color: var(--text-sec); font-family: 'Intel One Mono', ui-monospace;">${icon('github', 13)} signature-digital/core</div><div style="display: flex; gap: 12px; font-size: 11px; color: var(--text-muted);"><span>${icon('star', 11)} 8.2k</span><span>${icon('git', 11)} 248</span></div><div class="sparkline">${[3, 5, 2, 7, 4, 6, 8, 5, 9, 6, 4, 8, 7, 10].map((height) => html`<span style="height: ${height * 10}%"></span>`)}</div></div>
|
||||
<div class="nav-block" style="border-top: 1px solid var(--border-subtle); padding-top: 8px;">${lowerItems.map((item) => this.navButton(item))}</div>
|
||||
<div class="user-card"><span class="avatar">PK</span><div style="flex: 1; min-width: 0;"><div style="font-size: 12px; font-weight: 500;">Philipp K.</div><div style="font-family: 'Intel One Mono', ui-monospace; font-size: 10px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis;">philipp@lossless.com</div></div>${icon('more', 14)}</div>
|
||||
</aside>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderView(): TemplateResult {
|
||||
switch (this.view) {
|
||||
case 'inbox': return html`<sdig-workspace-inbox class="view-host" .density=${this.density}></sdig-workspace-inbox>`;
|
||||
case 'compose': return html`<sdig-workspace-compose class="view-host"></sdig-workspace-compose>`;
|
||||
case 'sign': return html`<sdig-workspace-sign class="view-host"></sdig-workspace-sign>`;
|
||||
case 'audit': return html`<sdig-workspace-audit class="view-host"></sdig-workspace-audit>`;
|
||||
case 'developers': return html`<sdig-workspace-developers class="view-host"></sdig-workspace-developers>`;
|
||||
case 'templates': return html`<sdig-workspace-placeholder class="view-host" label="Templates" subtitle="Reusable agreement templates"></sdig-workspace-placeholder>`;
|
||||
case 'team': return html`<sdig-workspace-placeholder class="view-host" label="Team" subtitle="Workspace members & roles"></sdig-workspace-placeholder>`;
|
||||
case 'settings': return html`<sdig-workspace-placeholder class="view-host" label="Settings" subtitle="Workspace, billing, security"></sdig-workspace-placeholder>`;
|
||||
default: return html`<sdig-workspace-inbox class="view-host" .density=${this.density}></sdig-workspace-inbox>`;
|
||||
}
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`<div class="workspace ${this.density === 'compact' ? 'compact' : ''}" style="--accent: ${this.accent};" data-screen-label=${this.view}>${this.renderSidebar()}<main class="main">${this.renderView()}<div class="statusbar"><span style="display: inline-flex; align-items: center; gap: 5px;"><span style="width: 6px; height: 6px; border-radius: 50%; background: var(--success);"></span>api.signature.digital</span><span>eu-central-1</span><span>4 sigs queued</span><div style="flex: 1;"></div><span style="color: var(--accent);">Open Source · MIT</span><span>v0.42.1</span><span>${icon('git', 11)} main</span></div></main></div>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user