444 lines
12 KiB
TypeScript
444 lines
12 KiB
TypeScript
import {
|
|
DeesElement,
|
|
customElement,
|
|
html,
|
|
css,
|
|
cssManager,
|
|
property,
|
|
state,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
import type { ISgOrganization } from '../interfaces.js';
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'sg-organizations-list-view': SgOrganizationsListView;
|
|
}
|
|
}
|
|
|
|
@customElement('sg-organizations-list-view')
|
|
export class SgOrganizationsListView extends DeesElement {
|
|
public static demo = () => html`
|
|
<div style="padding: 24px; max-width: 1200px; background: #09090b;">
|
|
<sg-organizations-list-view
|
|
.organizations=${[
|
|
{ id: 'org1', name: 'myorg', displayName: 'My Organization', description: 'Primary development organization', isPublic: true, memberCount: 12, createdAt: '2025-06-01' },
|
|
{ id: 'org2', name: 'acme-corp', displayName: 'ACME Corp', description: 'Enterprise packages and images', isPublic: false, memberCount: 48, createdAt: '2025-01-15' },
|
|
{ id: 'org3', name: 'oss-collective', displayName: 'Open Source Collective', isPublic: true, memberCount: 156, createdAt: '2024-11-20' },
|
|
]}
|
|
></sg-organizations-list-view>
|
|
</div>
|
|
`;
|
|
|
|
public static demoGroups = ['Organizations'];
|
|
|
|
@property({ type: Array })
|
|
public accessor organizations: ISgOrganization[] = [];
|
|
|
|
@state()
|
|
accessor showCreateForm: boolean = false;
|
|
|
|
@state()
|
|
accessor createName: string = '';
|
|
|
|
@state()
|
|
accessor createDisplayName: string = '';
|
|
|
|
@state()
|
|
accessor createDescription: string = '';
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
color: ${cssManager.bdTheme('#111', '#fff')};
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.create-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 16px;
|
|
background: ${cssManager.bdTheme('#111', '#fff')};
|
|
border: none;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#fff', '#111')};
|
|
cursor: pointer;
|
|
transition: opacity 150ms ease;
|
|
}
|
|
|
|
.create-btn:hover {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.create-btn svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.orgs-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 1px;
|
|
background: ${cssManager.bdTheme('#e5e5e5', '#333')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')};
|
|
}
|
|
|
|
.org-card {
|
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
|
padding: 20px;
|
|
cursor: pointer;
|
|
transition: background 100ms ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.org-card:hover {
|
|
background: ${cssManager.bdTheme('#fafafa', '#1a1a1a')};
|
|
}
|
|
|
|
.org-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.org-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: ${cssManager.bdTheme('#e5e5e5', '#333')};
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
color: ${cssManager.bdTheme('#666', '#999')};
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.org-titles {
|
|
min-width: 0;
|
|
}
|
|
|
|
.org-name {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111', '#fff')};
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.org-handle {
|
|
font-size: 12px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
color: ${cssManager.bdTheme('#888', '#777')};
|
|
}
|
|
|
|
.org-description {
|
|
font-size: 13px;
|
|
color: ${cssManager.bdTheme('#666', '#aaa')};
|
|
line-height: 1.4;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.org-footer {
|
|
display: flex;
|
|
gap: 16px;
|
|
font-size: 12px;
|
|
color: ${cssManager.bdTheme('#888', '#777')};
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.visibility-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 1px 6px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.visibility-badge.public {
|
|
background: rgba(34, 197, 94, 0.15);
|
|
color: #22c55e;
|
|
}
|
|
|
|
.visibility-badge.private {
|
|
background: rgba(239, 68, 68, 0.15);
|
|
color: #ef4444;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 64px 32px;
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')};
|
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
|
}
|
|
|
|
.empty-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111', '#fff')};
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 14px;
|
|
color: ${cssManager.bdTheme('#888', '#777')};
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Create form */
|
|
.create-form {
|
|
background: ${cssManager.bdTheme('#fff', '#111')};
|
|
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')};
|
|
padding: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.create-form-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.form-label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#111', '#ddd')};
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
.form-input,
|
|
.form-textarea {
|
|
padding: 10px 12px;
|
|
background: ${cssManager.bdTheme('#fff', '#0a0a0a')};
|
|
border: 1px solid ${cssManager.bdTheme('#ddd', '#333')};
|
|
font-size: 14px;
|
|
color: ${cssManager.bdTheme('#111', '#fff')};
|
|
outline: none;
|
|
font-family: inherit;
|
|
max-width: 400px;
|
|
}
|
|
|
|
.form-input:focus,
|
|
.form-textarea:focus {
|
|
border-color: ${cssManager.bdTheme('#111', '#fff')};
|
|
}
|
|
|
|
.form-textarea {
|
|
resize: vertical;
|
|
min-height: 60px;
|
|
max-width: 400px;
|
|
}
|
|
|
|
.form-hint {
|
|
font-size: 12px;
|
|
color: ${cssManager.bdTheme('#aaa', '#666')};
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.submit-btn {
|
|
padding: 8px 20px;
|
|
background: ${cssManager.bdTheme('#111', '#fff')};
|
|
border: none;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: ${cssManager.bdTheme('#fff', '#111')};
|
|
cursor: pointer;
|
|
transition: opacity 150ms ease;
|
|
}
|
|
|
|
.submit-btn:hover {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.cancel-btn {
|
|
padding: 8px 20px;
|
|
background: transparent;
|
|
border: 1px solid ${cssManager.bdTheme('#ddd', '#333')};
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: ${cssManager.bdTheme('#333', '#ddd')};
|
|
cursor: pointer;
|
|
transition: all 150ms ease;
|
|
}
|
|
|
|
.cancel-btn:hover {
|
|
border-color: ${cssManager.bdTheme('#111', '#fff')};
|
|
color: ${cssManager.bdTheme('#111', '#fff')};
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
return html`
|
|
<div class="container">
|
|
<div class="header">
|
|
<div class="page-title">Organizations</div>
|
|
<button class="create-btn" @click=${this.handleCreate}>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
New Organization
|
|
</button>
|
|
</div>
|
|
|
|
${this.showCreateForm
|
|
? html`
|
|
<div class="create-form">
|
|
<div class="create-form-title">Create Organization</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Name *</label>
|
|
<input
|
|
type="text"
|
|
class="form-input"
|
|
.value=${this.createName}
|
|
@input=${(e: InputEvent) => { this.createName = (e.target as HTMLInputElement).value; }}
|
|
placeholder="my-org"
|
|
>
|
|
<span class="form-hint">Lowercase letters, numbers, and dashes only</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Display Name</label>
|
|
<input
|
|
type="text"
|
|
class="form-input"
|
|
.value=${this.createDisplayName}
|
|
@input=${(e: InputEvent) => { this.createDisplayName = (e.target as HTMLInputElement).value; }}
|
|
placeholder="My Organization"
|
|
>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Description</label>
|
|
<textarea
|
|
class="form-textarea"
|
|
.value=${this.createDescription}
|
|
@input=${(e: InputEvent) => { this.createDescription = (e.target as HTMLTextAreaElement).value; }}
|
|
placeholder="A short description of the organization"
|
|
></textarea>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button class="submit-btn" @click=${this.handleCreateSubmit}>Create</button>
|
|
<button class="cancel-btn" @click=${this.handleCreateCancel}>Cancel</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ''}
|
|
|
|
${this.organizations.length > 0
|
|
? html`
|
|
<div class="orgs-grid">
|
|
${this.organizations.map(
|
|
(org) => html`
|
|
<div class="org-card" @click=${() => this.handleSelect(org.id)}>
|
|
<div class="org-header">
|
|
<div class="org-avatar">${(org.displayName || org.name).charAt(0).toUpperCase()}</div>
|
|
<div class="org-titles">
|
|
<div class="org-name">${org.displayName || org.name}</div>
|
|
<div class="org-handle">@${org.name}</div>
|
|
</div>
|
|
</div>
|
|
${org.description ? html`<div class="org-description">${org.description}</div>` : ''}
|
|
<div class="org-footer">
|
|
<span>${org.memberCount} member${org.memberCount !== 1 ? 's' : ''}</span>
|
|
<span class="visibility-badge ${org.isPublic ? 'public' : 'private'}">
|
|
${org.isPublic ? 'Public' : 'Private'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
`
|
|
)}
|
|
</div>
|
|
`
|
|
: html`
|
|
<div class="empty-state">
|
|
<div class="empty-title">No organizations yet</div>
|
|
<div class="empty-text">Create an organization to start publishing packages.</div>
|
|
<button class="create-btn" @click=${this.handleCreate}>Create Organization</button>
|
|
</div>
|
|
`}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private handleCreate() {
|
|
this.showCreateForm = true;
|
|
}
|
|
|
|
private handleCreateSubmit() {
|
|
if (!this.createName.trim()) return;
|
|
this.dispatchEvent(
|
|
new CustomEvent('create', {
|
|
detail: {
|
|
name: this.createName.trim(),
|
|
displayName: this.createDisplayName.trim(),
|
|
description: this.createDescription.trim(),
|
|
},
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
this.createName = '';
|
|
this.createDisplayName = '';
|
|
this.createDescription = '';
|
|
this.showCreateForm = false;
|
|
}
|
|
|
|
private handleCreateCancel() {
|
|
this.createName = '';
|
|
this.createDisplayName = '';
|
|
this.createDescription = '';
|
|
this.showCreateForm = false;
|
|
}
|
|
|
|
private handleSelect(organizationId: string) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('select', {
|
|
detail: { organizationId },
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
}
|
|
}
|