import { DeesElement, customElement, html, css, cssManager, property, type TemplateResult, } from '@design.estate/dees-element'; import type { ISgPackage, TSgProtocol } from '../interfaces.js'; import './sg-protocol-badge.js'; declare global { interface HTMLElementTagNameMap { 'sg-packages-list-view': SgPackagesListView; } } const ALL_PROTOCOLS: TSgProtocol[] = ['npm', 'oci', 'maven', 'cargo', 'composer', 'pypi', 'rubygems']; @customElement('sg-packages-list-view') export class SgPackagesListView extends DeesElement { public static demo = () => html`
`; public static demoGroups = ['Packages']; @property({ type: Array }) public accessor packages: ISgPackage[] = []; @property({ type: Number }) public accessor total: number = 0; @property({ type: Array }) public accessor protocols: string[] = []; @property({ type: String }) public accessor query: string = ''; private activeFilter: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: block; color: ${cssManager.bdTheme('#111', '#fff')}; } .container { display: flex; flex-direction: column; gap: 20px; } .page-title { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; } /* Search bar */ .search-bar { display: flex; gap: 0; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; } .search-input { flex: 1; padding: 10px 14px; background: ${cssManager.bdTheme('#fff', '#0a0a0a')}; border: none; font-size: 14px; color: ${cssManager.bdTheme('#111', '#fff')}; outline: none; font-family: inherit; } .search-input::placeholder { color: ${cssManager.bdTheme('#aaa', '#555')}; } .search-btn { padding: 10px 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; } .search-btn:hover { opacity: 0.85; } /* Protocol filters */ .filters { display: flex; gap: 4px; flex-wrap: wrap; } .filter-btn { padding: 6px 12px; background: transparent; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; font-size: 12px; font-weight: 500; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; text-transform: uppercase; letter-spacing: 0.04em; } .filter-btn:hover { border-color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#111', '#fff')}; } .filter-btn.active { background: ${cssManager.bdTheme('#111', '#fff')}; color: ${cssManager.bdTheme('#fff', '#111')}; border-color: ${cssManager.bdTheme('#111', '#fff')}; } /* Package list */ .package-list { display: flex; flex-direction: column; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; } .package-row { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; background: ${cssManager.bdTheme('#fff', '#111')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; cursor: pointer; transition: background 100ms ease; } .package-row:last-child { border-bottom: none; } .package-row:hover { background: ${cssManager.bdTheme('#fafafa', '#1a1a1a')}; } .package-info { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; } .package-name-row { display: flex; align-items: center; gap: 8px; } .package-name { font-size: 14px; font-weight: 600; font-family: 'JetBrains Mono', monospace; color: ${cssManager.bdTheme('#111', '#fff')}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .private-badge { font-size: 10px; font-weight: 600; text-transform: uppercase; padding: 1px 5px; background: rgba(239, 68, 68, 0.15); color: #ef4444; flex-shrink: 0; } .package-description { font-size: 13px; color: ${cssManager.bdTheme('#666', '#aaa')}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .package-right { display: flex; align-items: center; gap: 16px; flex-shrink: 0; margin-left: 16px; } .version-tag { font-size: 12px; font-family: 'JetBrains Mono', monospace; color: ${cssManager.bdTheme('#666', '#999')}; background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')}; padding: 2px 8px; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; } .download-count { font-size: 12px; color: ${cssManager.bdTheme('#888', '#777')}; font-family: 'JetBrains Mono', monospace; white-space: nowrap; } .updated-at { font-size: 12px; color: ${cssManager.bdTheme('#aaa', '#666')}; white-space: nowrap; } /* Pagination */ .pagination { display: flex; justify-content: space-between; align-items: center; } .pagination-info { font-size: 13px; color: ${cssManager.bdTheme('#888', '#777')}; } .pagination-buttons { display: flex; gap: 4px; } .page-btn { padding: 6px 12px; background: transparent; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; font-size: 13px; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; } .page-btn:hover { background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')}; color: ${cssManager.bdTheme('#111', '#fff')}; } .page-btn:disabled { opacity: 0.4; cursor: not-allowed; } .empty-state { text-align: center; padding: 48px 32px; font-size: 14px; color: ${cssManager.bdTheme('#888', '#777')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; background: ${cssManager.bdTheme('#fff', '#111')}; } `, ]; public render(): TemplateResult { const availableProtocols = this.protocols.length > 0 ? this.protocols : ALL_PROTOCOLS; return html`
Packages
${availableProtocols.map( (proto) => html` ` )}
${this.packages.length > 0 ? html`
${this.packages.map( (pkg) => html`
this.handleSelect(pkg.id)}>
${pkg.name} ${pkg.isPrivate ? html`Private` : ''}
${pkg.description ? html`
${pkg.description}
` : ''}
${pkg.latestVersion ? html`${pkg.latestVersion}` : ''} ${this.formatNumber(pkg.downloadCount)} ${this.formatDate(pkg.updatedAt)}
` )}
` : html`
No packages found${this.query ? ` for "${this.query}"` : ''}
`}
`; } private formatNumber(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return n.toString(); } private formatDate(dateStr: string): string { if (!dateStr) return ''; try { return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } catch { return dateStr; } } private handleSearch() { this.dispatchEvent( new CustomEvent('search', { detail: { query: this.query }, bubbles: true, composed: true, }) ); } private handleFilter(protocol: string) { this.activeFilter = protocol; this.requestUpdate(); this.dispatchEvent( new CustomEvent('filter', { detail: { protocol }, bubbles: true, composed: true, }) ); } private handleSelect(packageId: string) { this.dispatchEvent( new CustomEvent('select', { detail: { packageId }, bubbles: true, composed: true, }) ); } private handlePage(direction: number) { const currentOffset = this.packages.length; const offset = direction > 0 ? currentOffset : Math.max(0, currentOffset - this.packages.length * 2); this.dispatchEvent( new CustomEvent('page', { detail: { offset }, bubbles: true, composed: true, }) ); } }