import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element'; import { idpElementStyles } from './tokens.js'; import './idp-badge.js'; export type TIdpDataTableCell = TemplateResult | string | number | null | undefined; export interface IIdpDataTableColumn { label: string; width?: string; align?: 'left' | 'center' | 'right'; mono?: boolean; hideBelow?: 'mobile' | 'tablet'; } export interface IIdpDataTableRow { id?: string; cells: TIdpDataTableCell[]; } export interface IIdpDataTableTab { id: string; label: string; count?: string | number; } export interface IIdpDataTableTabSelectEventDetail { tabId: string; } declare global { interface HTMLElementTagNameMap { 'idp-data-table': IdpDataTable; } } @customElement('idp-data-table') export class IdpDataTable extends DeesElement { public static demo = () => html` EU
Example User
user@example.com
`, 'OAuth grant', 'Desktop', html`approved`, '2m ago', ], }, ]} >
`; public static demoGroups = ['idp.global v3 primitives']; @property({ type: String }) public accessor title = ''; @property({ type: String }) public accessor subtitle = ''; @property({ type: String }) public accessor badge = ''; @property({ type: String, attribute: 'selected-tab' }) public accessor selectedTab = ''; @property({ type: String, attribute: 'empty-title' }) public accessor emptyTitle = 'No rows'; @property({ type: String, attribute: 'empty-description' }) public accessor emptyDescription = 'There is no data to display yet.'; @property({ type: Array }) public accessor columns: IIdpDataTableColumn[] = []; @property({ type: Array }) public accessor rows: IIdpDataTableRow[] = []; @property({ type: Array }) public accessor tabs: IIdpDataTableTab[] = []; public static styles = [ ...idpElementStyles, css` :host { display: block; } .table-card { overflow: hidden; border: 1px solid var(--idp-border); border-radius: 8px; background: var(--idp-card); } .table-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--idp-border-soft); } .title-wrap { min-width: 0; } .title-row { display: flex; align-items: center; gap: 10px; } .title { color: var(--idp-fg); font-size: 13px; font-weight: 600; } .subtitle { margin-top: 2px; color: var(--idp-muted-fg); font-size: 11.5px; } .count-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid var(--idp-border); border-radius: 999px; background: var(--idp-muted); color: var(--idp-muted-fg); font-family: var(--idp-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.04em; line-height: 16px; } .tabs { display: flex; align-items: center; gap: 2px; margin-left: auto; } .tab { padding: 4px 10px; border: 0; border-radius: 4px; background: transparent; color: var(--idp-muted-fg); cursor: pointer; font-family: var(--idp-font); font-size: 11px; } .tab:hover, .tab.active { background: var(--idp-bg-2); color: var(--idp-fg); } .table-scroll { overflow-x: auto; } table { width: 100%; border-collapse: collapse; } th, td { border-bottom: 1px solid var(--idp-border-soft); text-align: left; vertical-align: middle; } th { padding: 9px 16px; background: var(--idp-muted); color: var(--idp-fg-3); font-family: var(--idp-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; } td { padding: 10px 16px; color: var(--idp-fg-2); font-size: 12.5px; } tbody tr:last-child td { border-bottom: 0; } tbody tr:hover td { background: var(--idp-bg-2); } .align-center { text-align: center; } .align-right { text-align: right; } .mono-cell { color: var(--idp-muted-fg); font-family: var(--idp-mono); font-size: 11.5px; } .identity-cell { display: flex; align-items: center; gap: 8px; min-width: 190px; } .identity-avatar { width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid var(--idp-border); border-radius: 999px; background: var(--idp-bg-2); color: var(--avatar-color, var(--idp-accent)); font-family: var(--idp-mono); font-size: 9.5px; font-weight: 600; } .identity-primary { color: var(--idp-fg); font-size: 12.5px; font-weight: 500; } .identity-secondary, .cell-secondary { margin-top: 1px; color: var(--idp-muted-fg); font-size: 11px; line-height: 1.35; } .chip-row, .cell-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .cell-actions { justify-content: flex-end; } .table-action { min-height: 28px; padding: 5px 10px; border: 1px solid var(--idp-border); border-radius: 7px; background: transparent; color: var(--idp-fg); cursor: pointer; font-family: var(--idp-font); font-size: 12px; } .table-action:hover { background: var(--idp-bg-2); } .table-action.destructive { border-color: var(--idp-error-border); color: var(--idp-error); } .table-action.primary { border-color: var(--idp-accent); background: var(--idp-accent); color: var(--idp-accent-fg); } .empty-state { padding: 32px 20px; color: var(--idp-muted-fg); text-align: center; } .empty-title { color: var(--idp-fg); font-size: 13px; font-weight: 600; } .empty-description { max-width: 420px; margin: 6px auto 0; font-size: 12px; line-height: 1.5; } @media (max-width: 900px) { .hide-tablet { display: none; } } @media (max-width: 720px) { .table-head { align-items: flex-start; flex-direction: column; gap: 12px; } .tabs { width: 100%; margin-left: 0; overflow-x: auto; } th, td { padding-left: 16px; padding-right: 16px; } .hide-mobile { display: none; } } `, ]; private selectTab(tabIdArg: string) { this.selectedTab = tabIdArg; this.dispatchEvent(new CustomEvent('idp-data-table-tab-select', { detail: { tabId: tabIdArg }, bubbles: true, composed: true, })); } private columnClass(columnArg: IIdpDataTableColumn): string { const classes = [ columnArg.align === 'center' ? 'align-center' : '', columnArg.align === 'right' ? 'align-right' : '', columnArg.hideBelow === 'mobile' ? 'hide-mobile' : '', columnArg.hideBelow === 'tablet' ? 'hide-tablet' : '', ]; return classes.filter(Boolean).join(' '); } private renderCell(contentArg: TIdpDataTableCell, columnArg: IIdpDataTableColumn): TemplateResult { const content = contentArg === null || contentArg === undefined || contentArg === '' ? '-' : contentArg; return columnArg.mono ? html`${content}` : html`${content}`; } public render(): TemplateResult { const selectedTab = this.selectedTab || this.tabs[0]?.id || ''; return html`
${this.title || this.badge || this.tabs.length ? html`
${this.title ? html`
${this.title}
` : html``} ${this.badge ? html`${this.badge}` : html``}
${this.subtitle ? html`
${this.subtitle}
` : html``}
${this.tabs.length ? html`
${this.tabs.map((tabArg) => html` `)}
` : html``}
` : html``} ${this.rows.length ? html`
${this.columns.map((columnArg) => html` `)} ${this.rows.map((rowArg) => html` ${this.columns.map((columnArg, indexArg) => html` `)} `)}
${columnArg.label}
${this.renderCell(rowArg.cells[indexArg], columnArg)}
` : html`
${this.emptyTitle}
${this.emptyDescription}
`}
`; } }