feat(catalog): add admin dashboard components

This commit is contained in:
2026-05-07 15:35:37 +00:00
parent 5dbbe90b43
commit 3992adbafa
17 changed files with 2832 additions and 802 deletions
File diff suppressed because it is too large Load Diff
+25 -4
View File
@@ -40,9 +40,15 @@ export class IdpButton extends DeesElement {
@property({ type: String })
public accessor icon = '';
@property({ type: String })
public accessor type: 'button' | 'submit' | 'reset' = 'button';
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
@property({ type: Boolean, reflect: true })
public accessor loading = false;
public static styles = [
...idpElementStyles,
css`
@@ -101,7 +107,7 @@ export class IdpButton extends DeesElement {
}
.accent {
background: var(--idp-accent);
color: #fff;
color: var(--idp-accent-fg);
box-shadow: 0 4px 14px color-mix(in srgb, var(--idp-accent), transparent 64%);
}
.accent:hover:not(:disabled) {
@@ -126,18 +132,33 @@ export class IdpButton extends DeesElement {
}
.destructive {
background: var(--idp-destructive);
color: #fff;
color: var(--idp-accent-fg);
}
idp-icon {
flex: 0 0 auto;
}
.spinner {
width: 13px;
height: 13px;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 999px;
animation: spin 700ms linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
`,
];
public render(): TemplateResult {
return html`
<button class="${this.variant} ${this.size}" ?disabled=${this.disabled} part="button">
${this.icon ? html`<idp-icon name=${this.icon as any} size="14"></idp-icon>` : html``}
<button class="${this.variant} ${this.size}" type=${this.type} ?disabled=${this.disabled || this.loading} part="button">
${this.loading
? html`<span class="spinner" aria-hidden="true"></span>`
: this.icon ? html`<idp-icon name=${this.icon as any} size="14"></idp-icon>` : html``}
<slot></slot>
</button>
`;
+143
View File
@@ -0,0 +1,143 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
declare global {
interface HTMLElementTagNameMap {
'idp-checkbox': IdpCheckbox;
}
}
@customElement('idp-checkbox')
export class IdpCheckbox extends DeesElement {
public static demo = () => html`<idp-checkbox label="I agree to the terms" required></idp-checkbox>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor label = '';
@property({ type: String })
public accessor name = '';
@property({ type: String })
public accessor key = '';
@property({ type: String })
public accessor value = 'on';
@property({ type: String })
public accessor hint = '';
@property({ type: String })
public accessor error = '';
@property({ type: Boolean, reflect: true })
public accessor checked = false;
@property({ type: Boolean, reflect: true })
public accessor required = false;
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
label {
display: grid;
grid-template-columns: 18px 1fr;
gap: 10px;
align-items: start;
color: var(--idp-fg);
font-size: 13px;
line-height: 1.4;
cursor: pointer;
}
input {
appearance: none;
width: 18px;
height: 18px;
margin: 0;
border: 1px solid var(--idp-border);
border-radius: 5px;
background: var(--idp-card);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
}
input:checked {
border-color: var(--idp-accent);
background: var(--idp-accent);
box-shadow: inset 0 0 0 3px var(--idp-card);
}
input:focus-visible {
outline: 2px solid color-mix(in srgb, var(--idp-accent), transparent 68%);
outline-offset: 2px;
}
input[aria-invalid='true'] {
border-color: var(--idp-destructive);
}
input:disabled,
:host([disabled]) label {
cursor: not-allowed;
opacity: 0.55;
}
.copy {
display: grid;
gap: 4px;
}
.hint,
.error {
color: var(--idp-muted-fg);
font-size: 12px;
}
.error {
color: var(--idp-destructive);
}
`,
];
public validate() {
if (this.required && !this.checked) {
this.error = `${this.label || this.name || 'This field'} is required.`;
return false;
}
this.error = '';
return true;
}
private handleChange(eventArg: Event) {
this.checked = (eventArg.target as HTMLInputElement).checked;
if (this.error) {
this.validate();
}
this.dispatchEvent(new CustomEvent('idp-checkbox-change', {
detail: { name: this.name || this.key, key: this.key || this.name, checked: this.checked, value: this.value },
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<label>
<input
type="checkbox"
name=${this.name || this.key}
value=${this.value}
?checked=${this.checked}
?required=${this.required}
?disabled=${this.disabled}
aria-invalid=${this.error ? 'true' : 'false'}
@change=${this.handleChange}
/>
<span class="copy">
<span>${this.label}</span>
${this.error ? html`<span class="error">${this.error}</span>` : this.hint ? html`<span class="hint">${this.hint}</span>` : html``}
</span>
</label>
`;
}
}
+38 -67
View File
@@ -3,6 +3,7 @@ import { idpElementStyles } from './tokens.js';
import './idp-badge.js';
import './idp-button.js';
import './idp-icon.js';
import './idp-data-table.js';
type TDashboardStat = {
label: string;
@@ -24,7 +25,7 @@ declare global {
@customElement('idp-dashboard-window')
export class IdpDashboardWindow extends DeesElement {
public static demo = () => html`<idp-dashboard-window dark></idp-dashboard-window>`;
public static demo = () => html`<idp-dashboard-window></idp-dashboard-window>`;
public static demoGroups = ['idp.global v3 composed surfaces'];
public static styles = [
@@ -139,7 +140,7 @@ export class IdpDashboardWindow extends DeesElement {
place-items: center;
border-radius: 4px;
background: var(--idp-accent);
color: #fff;
color: var(--idp-accent-fg);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 700;
@@ -340,51 +341,6 @@ export class IdpDashboardWindow extends DeesElement {
font-size: 13px;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px 16px;
border-bottom: 1px solid var(--idp-border-soft);
text-align: left;
font-size: 12.5px;
}
th {
color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.user {
display: flex;
align-items: center;
gap: 8px;
}
.row-avatar {
width: 22px;
height: 22px;
display: inline-grid;
place-items: center;
border: 1px solid var(--idp-border);
border-radius: 50%;
background: var(--idp-card-2);
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 9.5px;
font-weight: 700;
}
.row-name {
color: var(--idp-fg);
font-weight: 500;
}
.row-email, .dim {
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
}
.feed-item {
display: grid;
grid-template-columns: 14px 1fr auto;
@@ -450,9 +406,6 @@ export class IdpDashboardWindow extends DeesElement {
align-items: flex-start;
flex-direction: column;
}
th:nth-child(3), td:nth-child(3), th:nth-child(4), td:nth-child(4) {
display: none;
}
}
`,
];
@@ -522,8 +475,25 @@ export class IdpDashboardWindow extends DeesElement {
}
public render(): TemplateResult {
const approvalRows = this.approvals.map((rowArg) => ({
cells: [
html`
<div class="identity-cell">
<span class="identity-avatar">${rowArg[0].slice(0, 2).toUpperCase()}</span>
<div>
<div class="identity-primary">${rowArg[0]}</div>
<div class="identity-secondary">${rowArg[1]}</div>
</div>
</div>
`,
rowArg[2],
rowArg[3],
html`<idp-badge variant=${rowArg[5] as any}>${rowArg[4]}</idp-badge>`,
],
}));
return html`
<div class="dash" theme="dark">
<div class="dash">
<div class="chrome">
<span class="tdot red"></span><span class="tdot yellow"></span><span class="tdot green"></span>
<span class="url"><idp-icon name="lock" size="11" style="color: var(--idp-ok)"></idp-icon> console.idp.global / dashboard</span>
@@ -561,22 +531,23 @@ export class IdpDashboardWindow extends DeesElement {
${this.stats.map((statArg) => this.renderStat(statArg))}
</div>
<div class="grid">
<section class="card">
<div class="card-head"><span class="card-title">Recent approvals</span><idp-badge>142 total</idp-badge></div>
<table>
<thead><tr><th>User</th><th>Action</th><th>Device</th><th>Status</th></tr></thead>
<tbody>
${this.approvals.map((rowArg) => html`
<tr>
<td><div class="user"><span class="row-avatar">${rowArg[0].slice(0, 2).toUpperCase()}</span><div><div class="row-name">${rowArg[0]}</div><div class="row-email">${rowArg[1]}</div></div></div></td>
<td>${rowArg[2]}</td>
<td><span class="dim">${rowArg[3]}</span></td>
<td><idp-badge variant=${rowArg[5] as any}>${rowArg[4]}</idp-badge></td>
</tr>
`)}
</tbody>
</table>
</section>
<idp-data-table
title="Recent approvals"
badge="142 total"
selected-tab="all"
.tabs=${[
{ id: 'all', label: 'All' },
{ id: 'pending', label: 'Pending' },
{ id: 'denied', label: 'Denied' },
]}
.columns=${[
{ label: 'User' },
{ label: 'Action' },
{ label: 'Device', mono: true, hideBelow: 'mobile' },
{ label: 'Status' },
]}
.rows=${approvalRows}
></idp-data-table>
<section class="card">
<div class="card-head"><span class="card-title">Cardano feed</span><idp-badge variant="accent"><span class="live-dot"></span>live</idp-badge></div>
${this.feed.map((itemArg) => html`
+407
View File
@@ -0,0 +1,407 @@
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`
<idp-data-table
title="Recent activity"
badge="3 total"
.columns=${[
{ label: 'User' },
{ label: 'Action' },
{ label: 'Device', mono: true, hideBelow: 'mobile' },
{ label: 'Status' },
{ label: 'When', align: 'right', mono: true },
]}
.tabs=${[
{ id: 'all', label: 'All' },
{ id: 'pending', label: 'Pending' },
{ id: 'denied', label: 'Denied' },
]}
selected-tab="all"
.rows=${[
{
cells: [
html`<div class="identity-cell"><span class="identity-avatar">EU</span><div><div class="identity-primary">Example User</div><div class="identity-secondary">user@example.com</div></div></div>`,
'OAuth grant',
'Desktop',
html`<idp-badge variant="ok">approved</idp-badge>`,
'2m ago',
],
},
]}
></idp-data-table>
`;
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<IIdpDataTableTabSelectEventDetail>('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`<span class="mono-cell">${content}</span>` : html`${content}`;
}
public render(): TemplateResult {
const selectedTab = this.selectedTab || this.tabs[0]?.id || '';
return html`
<section class="table-card">
${this.title || this.badge || this.tabs.length ? html`
<div class="table-head">
<div class="title-wrap">
<div class="title-row">
${this.title ? html`<div class="title">${this.title}</div>` : html``}
${this.badge ? html`<span class="count-pill">${this.badge}</span>` : html``}
</div>
${this.subtitle ? html`<div class="subtitle">${this.subtitle}</div>` : html``}
</div>
${this.tabs.length ? html`
<div class="tabs" role="tablist">
${this.tabs.map((tabArg) => html`
<button
class="tab ${tabArg.id === selectedTab ? 'active' : ''}"
role="tab"
aria-selected=${tabArg.id === selectedTab ? 'true' : 'false'}
@click=${() => this.selectTab(tabArg.id)}
>${tabArg.label}${tabArg.count !== undefined ? html` (${tabArg.count})` : html``}</button>
`)}
</div>
` : html``}
</div>
` : html``}
${this.rows.length ? html`
<div class="table-scroll">
<table>
<thead>
<tr>
${this.columns.map((columnArg) => html`
<th class=${this.columnClass(columnArg)} style=${columnArg.width ? `width:${columnArg.width}` : ''}>${columnArg.label}</th>
`)}
</tr>
</thead>
<tbody>
${this.rows.map((rowArg) => html`
<tr>
${this.columns.map((columnArg, indexArg) => html`
<td class=${this.columnClass(columnArg)}>${this.renderCell(rowArg.cells[indexArg], columnArg)}</td>
`)}
</tr>
`)}
</tbody>
</table>
</div>
` : html`
<div class="empty-state">
<div class="empty-title">${this.emptyTitle}</div>
<div class="empty-description">${this.emptyDescription}</div>
</div>
`}
</section>
`;
}
}
+65
View File
@@ -0,0 +1,65 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-button.js';
import type { TIdpButtonSize, TIdpButtonVariant } from './idp-button.js';
declare global {
interface HTMLElementTagNameMap {
'idp-form-submit': IdpFormSubmit;
}
}
@customElement('idp-form-submit')
export class IdpFormSubmit extends DeesElement {
public static demo = () => html`<idp-form-submit>Continue</idp-form-submit>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor text = '';
@property({ type: String })
public accessor variant: TIdpButtonVariant = 'accent';
@property({ type: String })
public accessor size: TIdpButtonSize = 'md';
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
@property({ type: Boolean, reflect: true })
public accessor loading = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
idp-button {
width: 100%;
}
`,
];
private handleClick() {
if (this.disabled || this.loading) {
return;
}
this.dispatchEvent(new CustomEvent('idp-form-submit-request', {
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<idp-button
variant=${this.variant}
size=${this.size}
.disabled=${this.disabled || this.loading}
.loading=${this.loading}
@click=${this.handleClick}
>${this.text || html`<slot></slot>`}</idp-button>
`;
}
}
+159
View File
@@ -0,0 +1,159 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-input.js';
import './idp-checkbox.js';
export type TIdpFormStatus = 'idle' | 'pending' | 'success' | 'error';
export type TIdpFormData = Record<string, string | boolean>;
export interface IIdpFormSubmitEventDetail {
data: TIdpFormData;
form: IdpForm;
}
type TFormControl = HTMLElement & {
name?: string;
key?: string;
value?: string;
checked?: boolean;
validate?: () => boolean;
};
declare global {
interface HTMLElementTagNameMap {
'idp-form': IdpForm;
}
}
@customElement('idp-form')
export class IdpForm extends DeesElement {
public static demo = () => html`
<idp-form>
<idp-input name="email" label="Email" type="email" required></idp-input>
<idp-form-submit>Continue</idp-form-submit>
</idp-form>
`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String, reflect: true })
public accessor status: TIdpFormStatus = 'idle';
@property({ type: String })
public accessor statusMessage = '';
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
form {
display: flex;
flex-direction: column;
gap: 16px;
}
::slotted(idp-form-submit) {
margin-top: 2px;
}
.status {
padding: 11px 13px;
border-radius: 10px;
border: 1px solid var(--idp-border);
background: var(--idp-muted);
color: var(--idp-muted-fg);
font-size: 13px;
line-height: 1.45;
}
.status.pending {
border-color: color-mix(in srgb, var(--idp-accent), transparent 60%);
color: var(--idp-accent);
}
.status.success {
border-color: color-mix(in srgb, var(--idp-ok), transparent 60%);
color: var(--idp-ok);
}
.status.error {
border-color: color-mix(in srgb, var(--idp-destructive), transparent 60%);
color: var(--idp-destructive);
}
`,
];
public setStatus(statusArg: TIdpFormStatus, messageArg = '') {
this.status = statusArg;
this.statusMessage = messageArg;
}
public resetStatus() {
this.setStatus('idle', '');
}
public submit() {
this.handleSubmit();
}
private getControls() {
return Array.from(this.querySelectorAll('idp-input, idp-checkbox')) as TFormControl[];
}
private validateControls() {
return this.getControls().every((controlArg) => controlArg.validate ? controlArg.validate() : true);
}
private getFormData(): TIdpFormData {
const data: TIdpFormData = {};
for (const control of this.getControls()) {
const name = control.name || control.key;
if (!name) {
continue;
}
if (typeof control.checked === 'boolean') {
data[name] = control.checked;
} else {
data[name] = control.value || '';
}
}
return data;
}
private handleSubmitRequest(eventArg: Event) {
eventArg.preventDefault();
eventArg.stopPropagation();
this.handleSubmit();
}
private handleSubmit(eventArg?: Event) {
eventArg?.preventDefault();
if (this.disabled || this.status === 'pending') {
return;
}
if (!this.validateControls()) {
this.setStatus('error', 'Please check the highlighted fields.');
return;
}
this.dispatchEvent(new CustomEvent<IIdpFormSubmitEventDetail>('idp-submit', {
detail: {
data: this.getFormData(),
form: this,
},
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<form novalidate @submit=${this.handleSubmit} @idp-form-submit-request=${this.handleSubmitRequest}>
<slot></slot>
${this.statusMessage
? html`<div class="status ${this.status}" role="status">${this.statusMessage}</div>`
: html``}
</form>
`;
}
}
+53 -3
View File
@@ -15,6 +15,12 @@ export class IdpInput extends DeesElement {
@property({ type: String })
public accessor label = '';
@property({ type: String })
public accessor name = '';
@property({ type: String })
public accessor key = '';
@property({ type: String })
public accessor hint = '';
@@ -27,6 +33,15 @@ export class IdpInput extends DeesElement {
@property({ type: String })
public accessor type = 'text';
@property({ type: String })
public accessor autocomplete = '';
@property({ type: String })
public accessor error = '';
@property({ type: Boolean, reflect: true })
public accessor required = false;
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
@@ -66,18 +81,49 @@ export class IdpInput extends DeesElement {
input:disabled {
opacity: 0.5;
}
.hint {
input[aria-invalid='true'] {
border-color: var(--idp-destructive);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-destructive), transparent 86%);
}
.hint,
.error {
color: var(--idp-muted-fg);
font-size: 12px;
line-height: 1.4;
}
.error {
color: var(--idp-destructive);
}
`,
];
public focus() {
this.shadowRoot?.querySelector('input')?.focus();
}
public validate() {
const input = this.shadowRoot?.querySelector('input');
if (this.required && !this.value.trim()) {
this.error = `${this.label || this.name || 'This field'} is required.`;
return false;
}
if (input && !input.checkValidity()) {
this.error = input.validationMessage;
return false;
}
this.error = '';
return true;
}
private handleInput(eventArg: Event) {
this.value = (eventArg.target as HTMLInputElement).value;
if (this.error) {
this.validate();
}
this.dispatchEvent(new CustomEvent('idp-input-change', {
detail: { value: this.value },
detail: { name: this.name || this.key, key: this.key || this.name, value: this.value },
bubbles: true,
composed: true,
}));
@@ -89,12 +135,16 @@ export class IdpInput extends DeesElement {
${this.label ? html`<span class="label">${this.label}</span>` : html``}
<input
.value=${this.value}
name=${this.name || this.key}
type=${this.type}
placeholder=${this.placeholder}
autocomplete=${this.autocomplete}
?required=${this.required}
?disabled=${this.disabled}
aria-invalid=${this.error ? 'true' : 'false'}
@input=${this.handleInput}
/>
${this.hint ? html`<span class="hint">${this.hint}</span>` : html``}
${this.error ? html`<span class="error">${this.error}</span>` : this.hint ? html`<span class="hint">${this.hint}</span>` : html``}
</label>
`;
}
+12 -12
View File
@@ -24,14 +24,14 @@ export class IdpLandingHero extends DeesElement {
.hero {
position: relative;
overflow: hidden;
background: #0a0a0a;
color: #fafafa;
border-bottom: 1px solid #1c1c1c;
background: var(--idp-bg);
color: var(--idp-fg);
border-bottom: 1px solid var(--idp-border-soft);
}
.grid {
position: absolute;
inset: 0;
background-image: linear-gradient(to right, rgba(255,255,255,0.025) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.025) 1px, transparent 1px);
background-image: linear-gradient(to right, color-mix(in srgb, var(--idp-fg), transparent 97%) 1px, transparent 1px), linear-gradient(to bottom, color-mix(in srgb, var(--idp-fg), transparent 97%) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 70%);
pointer-events: none;
@@ -60,17 +60,17 @@ export class IdpLandingHero extends DeesElement {
gap: 7px;
margin-bottom: 28px;
padding: 5px 12px 5px 8px;
border: 1px solid #262626;
border: 1px solid var(--idp-border);
border-radius: 999px;
background: rgba(255,255,255,0.04);
color: hsl(0 0% 70%);
background: var(--idp-muted);
color: var(--idp-fg-3);
font-size: 12px;
}
.pill {
padding: 2px 7px;
border-radius: 999px;
background: rgba(59,130,246,0.18);
color: #60a5fa;
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 600;
@@ -85,7 +85,7 @@ export class IdpLandingHero extends DeesElement {
line-height: 0.96;
}
h1 em {
color: #60a5fa;
color: var(--idp-accent-hover);
font-family: var(--idp-serif);
font-style: italic;
font-weight: 400;
@@ -93,7 +93,7 @@ export class IdpLandingHero extends DeesElement {
.sub {
max-width: 660px;
margin: 0 auto 36px;
color: hsl(0 0% 70%);
color: var(--idp-fg-3);
font-size: clamp(16px, 1.6vw, 19px);
line-height: 1.55;
}
@@ -106,7 +106,7 @@ export class IdpLandingHero extends DeesElement {
margin-bottom: 20px;
}
.fineprint {
color: hsl(0 0% 28%);
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.04em;
@@ -156,7 +156,7 @@ export class IdpLandingHero extends DeesElement {
<div class="fineprint"><span>MIT licensed</span><span>Self-hostable</span><span>No credit card</span><span>Cardano mainnet</span></div>
<div class="product">
<div class="product-glow"></div>
<idp-dashboard-window dark></idp-dashboard-window>
<idp-dashboard-window></idp-dashboard-window>
</div>
</div>
</section>
+6 -19
View File
@@ -21,19 +21,6 @@ export class IdpLandingPage extends DeesElement {
css`
:host {
display: block;
--idp-bg: #0a0a0a;
--idp-bg-2: #111111;
--idp-card: #121212;
--idp-card-2: #161616;
--idp-fg: #fafafa;
--idp-fg-2: #d4d4d8;
--idp-fg-3: hsl(0 0% 70%);
--idp-muted-fg: hsl(0 0% 55%);
--idp-border: #262626;
--idp-border-soft: #1c1c1c;
--idp-border-strong: #333333;
--idp-accent: #3b82f6;
--idp-accent-hover: #60a5fa;
background: var(--idp-bg);
color: var(--idp-fg);
}
@@ -54,7 +41,7 @@ export class IdpLandingPage extends DeesElement {
margin: 0 auto;
padding: 0 32px;
border-bottom: 1px solid var(--idp-border-soft);
background: rgba(10,10,10,0.86);
background: color-mix(in srgb, var(--idp-bg), transparent 14%);
backdrop-filter: blur(14px) saturate(140%);
}
.nav-shell {
@@ -62,7 +49,7 @@ export class IdpLandingPage extends DeesElement {
top: 0;
z-index: 20;
border-bottom: 1px solid var(--idp-border-soft);
background: rgba(10,10,10,0.86);
background: color-mix(in srgb, var(--idp-bg), transparent 14%);
}
.logo {
display: inline-flex;
@@ -93,7 +80,7 @@ export class IdpLandingPage extends DeesElement {
text-decoration: none;
}
.links a:hover {
background: rgba(255,255,255,0.04);
background: var(--idp-muted);
color: var(--idp-fg);
}
.status {
@@ -288,7 +275,7 @@ export class IdpLandingPage extends DeesElement {
border: 1px solid var(--idp-border);
border-radius: 10px;
padding: 20px;
background: linear-gradient(140deg, #1a1a1a 0%, #0a0a0a 100%);
background: linear-gradient(140deg, var(--idp-card) 0%, var(--idp-bg) 100%);
}
.identity-card::after {
content: '';
@@ -416,7 +403,7 @@ export class IdpLandingPage extends DeesElement {
}
.chain-block.idp {
border-left: 2px solid var(--idp-accent);
background: rgba(0,80,185,0.08);
background: var(--idp-accent-soft);
}
.tiers {
display: grid;
@@ -431,7 +418,7 @@ export class IdpLandingPage extends DeesElement {
}
.tier.featured {
border-color: var(--idp-accent);
background: linear-gradient(180deg, rgba(59,130,246,0.06) 0%, var(--idp-bg-2) 40%);
background: linear-gradient(180deg, var(--idp-accent-soft) 0%, var(--idp-bg-2) 40%);
}
.tier-name {
margin-bottom: 10px;
+31 -23
View File
@@ -25,8 +25,8 @@ export class IdpMobileShowcase extends DeesElement {
.showcase {
min-height: 100vh;
padding: 56px;
background: radial-gradient(circle at 1px 1px, rgba(0,0,0,0.08) 1px, transparent 0) 0 0 / 24px 24px, #fafafa;
color: #09090b;
background: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--idp-fg), transparent 92%) 1px, transparent 0) 0 0 / 24px 24px, var(--idp-bg);
color: var(--idp-fg);
}
.head {
max-width: 1180px;
@@ -38,10 +38,10 @@ export class IdpMobileShowcase extends DeesElement {
gap: 6px;
margin-bottom: 16px;
padding: 4px 10px;
border: 1px solid #e4e4e7;
border: 1px solid var(--idp-border);
border-radius: 999px;
background: #fff;
color: #52525b;
background: var(--idp-card);
color: var(--idp-fg-3);
font-size: 11px;
font-weight: 500;
}
@@ -49,7 +49,7 @@ export class IdpMobileShowcase extends DeesElement {
width: 6px;
height: 6px;
border-radius: 50%;
background: #16a34a;
background: var(--idp-ok);
}
h1 {
max-width: 900px;
@@ -63,7 +63,7 @@ export class IdpMobileShowcase extends DeesElement {
p {
max-width: 680px;
margin: 0;
color: #52525b;
color: var(--idp-fg-3);
font-size: 16px;
line-height: 1.55;
}
@@ -73,13 +73,13 @@ export class IdpMobileShowcase extends DeesElement {
gap: 24px;
margin-top: 24px;
padding: 16px;
border: 1px solid #e4e4e7;
border: 1px solid var(--idp-border);
border-radius: 12px;
background: #fff;
background: var(--idp-card);
}
.token-label {
margin-bottom: 4px;
color: #71717a;
color: var(--idp-muted-fg);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
@@ -89,7 +89,7 @@ export class IdpMobileShowcase extends DeesElement {
display: flex;
align-items: center;
gap: 8px;
color: #18181b;
color: var(--idp-fg);
font-size: 13px;
font-weight: 550;
}
@@ -98,7 +98,7 @@ export class IdpMobileShowcase extends DeesElement {
height: 18px;
border-radius: 5px;
background: var(--swatch);
border: 1px solid #e4e4e7;
border: 1px solid var(--idp-border);
}
.section {
max-width: 1180px;
@@ -106,7 +106,7 @@ export class IdpMobileShowcase extends DeesElement {
}
.section-title {
margin-bottom: 18px;
color: #71717a;
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 12px;
font-weight: 700;
@@ -126,9 +126,9 @@ export class IdpMobileShowcase extends DeesElement {
align-items: start;
}
.watch, .ipad, .mac {
border: 1px solid #e4e4e7;
background: #fff;
box-shadow: 0 20px 50px rgba(0,0,0,0.08);
border: 1px solid var(--idp-border);
background: var(--idp-card);
box-shadow: 0 20px 50px color-mix(in srgb, var(--idp-fg), transparent 92%);
}
.watch {
width: 236px;
@@ -188,8 +188,8 @@ export class IdpMobileShowcase extends DeesElement {
}
.ipad-sidebar {
padding: 18px;
border-right: 1px solid #e4e4e7;
background: #f8f8f7;
border-right: 1px solid var(--idp-border);
background: var(--idp-card-2);
}
.ipad-main {
padding: 22px;
@@ -200,8 +200,9 @@ export class IdpMobileShowcase extends DeesElement {
gap: 10px;
margin-top: 16px;
padding: 16px;
border: 1px solid #e4e4e7;
border: 1px solid var(--idp-border);
border-radius: 12px;
background: var(--idp-card);
}
.mac {
overflow: hidden;
@@ -211,7 +212,7 @@ export class IdpMobileShowcase extends DeesElement {
display: flex;
gap: 6px;
padding: 11px 14px;
border-bottom: 1px solid #e4e4e7;
border-bottom: 1px solid var(--idp-border);
}
.tdot {
width: 10px;
@@ -229,8 +230,15 @@ export class IdpMobileShowcase extends DeesElement {
justify-content: space-between;
gap: 12px;
padding: 12px;
border: 1px solid #e4e4e7;
border: 1px solid var(--idp-border);
border-radius: 10px;
background: var(--idp-card);
}
.inline-muted {
color: var(--idp-muted-fg);
}
.watch-muted {
color: #a1a1aa;
}
.row-label {
display: inline-flex;
@@ -276,8 +284,8 @@ export class IdpMobileShowcase extends DeesElement {
<section class="section">
<div class="section-title">Watch, iPad, Mac</div>
<div class="multi">
<div class="watch"><div class="watch-screen"><div class="watch-app">idp.global</div><idp-icon name="shield" size="28" style="margin:0 auto;color:#60a5fa"></idp-icon><div class="watch-title">GitHub wants access</div><div style="color:#a1a1aa;font-size:12px;">repo:read - Berlin</div><div class="watch-actions"><button><idp-icon name="x" size="13"></idp-icon>Deny</button><button class="approve"><idp-icon name="check" size="13"></idp-icon>Approve</button></div></div></div>
<div class="ipad"><div class="ipad-shell"><aside class="ipad-sidebar"><strong>Inbox</strong><p>3 pending approvals</p><div class="ipad-card"><idp-icon name="globe" size="16"></idp-icon><div><strong>GitHub OAuth</strong><br/><span style="color:#71717a">repo:read - now</span></div></div><div class="ipad-card"><idp-icon name="cloud" size="16"></idp-icon><div><strong>Hetzner Cloud</strong><br/><span style="color:#71717a">new network - 8m</span></div></div></aside><main class="ipad-main"><h2>Approval detail</h2><p>Full context before a sensitive action is approved.</p><div class="ipad-card"><idp-icon name="laptop" size="16"></idp-icon><div><strong>Device</strong><br/>MacBook Pro - Safari - Berlin, DE</div></div><div class="ipad-card"><idp-icon name="key" size="16"></idp-icon><div><strong>Requested scopes</strong><br/>openid, profile, email, repo:read</div></div></main></div></div>
<div class="watch"><div class="watch-screen"><div class="watch-app">idp.global</div><idp-icon name="shield" size="28" style="margin:0 auto;color:var(--idp-accent-hover)"></idp-icon><div class="watch-title">GitHub wants access</div><div class="watch-muted" style="font-size:12px;">repo:read - Berlin</div><div class="watch-actions"><button><idp-icon name="x" size="13"></idp-icon>Deny</button><button class="approve"><idp-icon name="check" size="13"></idp-icon>Approve</button></div></div></div>
<div class="ipad"><div class="ipad-shell"><aside class="ipad-sidebar"><strong>Inbox</strong><p>3 pending approvals</p><div class="ipad-card"><idp-icon name="globe" size="16"></idp-icon><div><strong>GitHub OAuth</strong><br/><span class="inline-muted">repo:read - now</span></div></div><div class="ipad-card"><idp-icon name="cloud" size="16"></idp-icon><div><strong>Hetzner Cloud</strong><br/><span class="inline-muted">new network - 8m</span></div></div></aside><main class="ipad-main"><h2>Approval detail</h2><p>Full context before a sensitive action is approved.</p><div class="ipad-card"><idp-icon name="laptop" size="16"></idp-icon><div><strong>Device</strong><br/>MacBook Pro - Safari - Berlin, DE</div></div><div class="ipad-card"><idp-icon name="key" size="16"></idp-icon><div><strong>Requested scopes</strong><br/>openid, profile, email, repo:read</div></div></main></div></div>
<div class="mac"><div class="mac-bar"><span class="tdot" style="background:#ff5f57"></span><span class="tdot" style="background:#ffbd2e"></span><span class="tdot" style="background:#28c840"></span></div><div class="mac-body"><strong>Menu bar approvals</strong><div class="mac-row"><span class="row-label"><idp-icon name="globe" size="15"></idp-icon>GitHub OAuth</span><idp-button variant="accent" size="sm" icon="check">Approve</idp-button></div><div class="mac-row"><span class="row-label"><idp-icon name="nfc" size="15"></idp-icon>NFC tap - door 4F</span><idp-button variant="ghost" size="sm" icon="chevron-right">Review</idp-button></div><div class="mac-row"><span class="row-label"><idp-icon name="key" size="15"></idp-icon>Key rotation</span><idp-button variant="ghost" size="sm" icon="shield">Confirm</idp-button></div></div></div>
</div>
</section>
+119
View File
@@ -0,0 +1,119 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
export interface IIdpSelectOption {
option: string;
key: string;
payload?: string;
}
export interface IIdpSelectEventDetail {
key: string;
payload?: string;
selectedOption: IIdpSelectOption | null;
}
declare global {
interface HTMLElementTagNameMap {
'idp-select': IdpSelect;
}
}
@customElement('idp-select')
export class IdpSelect extends DeesElement {
public static demo = () => html`
<idp-select
label="Organization"
.options=${[
{ option: 'Lossless GmbH', key: 'lossless', payload: 'lossless' },
{ option: '+ Create new...', key: '__create_new__', payload: '__create_new__' },
]}
></idp-select>
`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor label = '';
@property({ type: String })
public accessor placeholder = 'Select...';
@property({ type: Array })
public accessor options: IIdpSelectOption[] = [];
@property({ type: Object })
public accessor selectedOption: IIdpSelectOption | null = null;
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
label {
display: grid;
gap: 6px;
}
.label {
color: var(--idp-fg);
font-size: 13px;
font-weight: 500;
}
select {
width: 100%;
height: 36px;
box-sizing: border-box;
padding: 0 34px 0 10px;
border: 1px solid var(--idp-border);
border-radius: 8px;
outline: none;
background: var(--idp-card);
color: var(--idp-fg);
font-family: var(--idp-font);
font-size: 13px;
cursor: pointer;
transition: border-color 120ms ease, box-shadow 120ms ease;
}
select:focus {
border-color: var(--idp-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-accent), transparent 86%);
}
select:disabled {
cursor: not-allowed;
opacity: 0.5;
}
`,
];
private handleChange(eventArg: Event) {
const key = (eventArg.target as HTMLSelectElement).value;
const selectedOption = this.options.find((optionArg) => optionArg.key === key) || null;
this.selectedOption = selectedOption;
this.dispatchEvent(new CustomEvent<IIdpSelectEventDetail>('idp-select', {
detail: {
key,
payload: selectedOption?.payload,
selectedOption,
},
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<label>
${this.label ? html`<span class="label">${this.label}</span>` : html``}
<select .value=${this.selectedOption?.key || ''} ?disabled=${this.disabled} @change=${this.handleChange}>
<option value="" disabled>${this.placeholder}</option>
${this.options.map((optionArg) => html`
<option value=${optionArg.key}>${optionArg.option}</option>
`)}
</select>
</label>
`;
}
}
+5
View File
@@ -4,6 +4,11 @@ export * from './idp-button.js';
export * from './idp-badge.js';
export * from './idp-card.js';
export * from './idp-input.js';
export * from './idp-checkbox.js';
export * from './idp-form.js';
export * from './idp-form-submit.js';
export * from './idp-select.js';
export * from './idp-data-table.js';
export * from './idp-toggle.js';
export * from './idp-approval-card.js';
export * from './idp-mobile-frame.js';
+157 -84
View File
@@ -11,11 +11,11 @@ export const idpTheme = {
card: 'var(--idp-card)',
primary: 'var(--idp-primary)',
primaryFg: 'var(--idp-primary-fg)',
accent: 'var(--idp-accent)',
accentHover: 'var(--idp-accent-hover)',
accentSoft: 'var(--idp-accent-soft)',
info: 'var(--idp-info)',
destructive: 'var(--idp-destructive)',
accent: 'var(--idp-accent)',
accentHover: 'var(--idp-accent-hover)',
accentSoft: 'var(--idp-accent-soft)',
info: 'var(--idp-info)',
destructive: 'var(--idp-destructive)',
ok: 'var(--idp-ok)',
warn: 'var(--idp-warn)',
radius: 'var(--idp-radius)',
@@ -23,47 +23,104 @@ export const idpTheme = {
export const idpBaseStyles = css`
:host {
--idp-bg: #fafaf9;
--idp-bg-2: #f4f4f2;
--idp-fg: #0a0a0a;
--idp-fg-2: #3f3f46;
--idp-fg-3: #52525b;
--idp-muted: #f1f1ef;
--idp-muted-fg: #71717a;
--idp-border: #e4e4e7;
--idp-border-soft: #ededec;
--idp-border-strong: #d4d4d8;
--idp-card: #ffffff;
--idp-card-2: #f8f8f7;
--idp-primary: #18181b;
--idp-primary-fg: #fafafa;
--idp-accent: #0050b9;
--idp-accent-hover: #0069f2;
--idp-accent-fg: #ffffff;
--idp-accent-soft: #e6effb;
--idp-destructive: #ef4444;
--idp-ok: #16a34a;
--idp-ok-bg: #f0fdf4;
--idp-ok-border: #bbf7d0;
--idp-warn: #b45309;
--idp-warn-bg: #fef9c3;
--idp-warn-border: #fde68a;
--idp-error: #dc2626;
--idp-error-bg: #fef2f2;
--idp-error-border: #fecaca;
--idp-info: #1e40af;
--idp-info-bg: #eff6ff;
--idp-info-border: #bfdbfe;
--idp-chart-1: #0050b9;
--idp-chart-2: #16a34a;
--idp-chart-3: #dc2626;
--idp-chart-4: #b45309;
--idp-chart-5: #6e5be6;
--idp-spark-up: #16a34a;
--idp-spark-down: #dc2626;
--idp-spark-info: #0050b9;
--idp-radius: 8px;
--idp-radius-lg: 12px;
--sh-bg: ${cssManager.bdTheme('#FAFAF9', '#0A0A0A')};
--sh-bg-2: ${cssManager.bdTheme('#F4F4F2', '#111111')};
--sh-card: ${cssManager.bdTheme('#FFFFFF', '#121212')};
--sh-card-2: ${cssManager.bdTheme('#F8F8F7', '#161616')};
--sh-muted: ${cssManager.bdTheme('#F1F1EF', '#161616')};
--sh-muted-fg: ${cssManager.bdTheme('#71717A', 'hsl(0 0% 55%)')};
--sh-fg: ${cssManager.bdTheme('#0A0A0A', '#FAFAFA')};
--sh-fg-2: ${cssManager.bdTheme('#3F3F46', '#D4D4D8')};
--sh-fg-3: ${cssManager.bdTheme('#52525B', 'hsl(0 0% 70%)')};
--sh-fg-4: ${cssManager.bdTheme('#A1A1AA', 'hsl(0 0% 35%)')};
--sh-border: ${cssManager.bdTheme('#E4E4E7', '#262626')};
--sh-border-soft: ${cssManager.bdTheme('#EDEDEC', '#1C1C1C')};
--sh-border-strong: ${cssManager.bdTheme('#D4D4D8', '#333333')};
--sh-input: ${cssManager.bdTheme('#FFFFFF', '#161616')};
--sh-input-bg: ${cssManager.bdTheme('#FFFFFF', '#0A0A0A')};
--sh-hover: ${cssManager.bdTheme('rgba(0,0,0,0.04)', 'rgba(255,255,255,0.04)')};
--sh-primary: ${cssManager.bdTheme('#18181B', '#FAFAFA')};
--sh-primary-fg: ${cssManager.bdTheme('#FAFAFA', '#18181B')};
--sh-accent: ${cssManager.bdTheme('#0050B9', '#3B82F6')};
--sh-accent-h: ${cssManager.bdTheme('#0069F2', '#60A5FA')};
--sh-accent-fg: #FFFFFF;
--sh-accent-soft: ${cssManager.bdTheme('#E6EFFB', 'rgba(59,130,246,0.15)')};
--sh-accent-soft-fg: ${cssManager.bdTheme('#0050B9', '#93BBFD')};
--sh-accent-ring: ${cssManager.bdTheme('rgba(0,80,185,0.2)', 'rgba(59,130,246,0.35)')};
--sh-ok: ${cssManager.bdTheme('#16A34A', '#4ADE80')};
--sh-ok-bg: ${cssManager.bdTheme('#F0FDF4', 'rgba(20,83,45,0.4)')};
--sh-ok-border: ${cssManager.bdTheme('#BBF7D0', 'rgba(74,222,128,0.25)')};
--sh-warn: ${cssManager.bdTheme('#B45309', '#FBBF24')};
--sh-warn-bg: ${cssManager.bdTheme('#FEF9C3', 'rgba(69,26,3,0.6)')};
--sh-warn-border: ${cssManager.bdTheme('#FDE68A', 'rgba(251,191,36,0.25)')};
--sh-err: ${cssManager.bdTheme('#DC2626', '#F87171')};
--sh-err-bg: ${cssManager.bdTheme('#FEF2F2', 'rgba(69,10,10,0.6)')};
--sh-err-border: ${cssManager.bdTheme('#FECACA', 'rgba(248,113,113,0.25)')};
--sh-info: ${cssManager.bdTheme('#1E40AF', '#93BBFD')};
--sh-info-bg: ${cssManager.bdTheme('#EFF6FF', 'rgba(59,130,246,0.15)')};
--sh-info-border: ${cssManager.bdTheme('#BFDBFE', 'rgba(59,130,246,0.3)')};
--sh-destructive: #EF4444;
--sh-ring: ${cssManager.bdTheme('#A1A1AA', '#3B82F6')};
--sh-radius: 8px;
--sh-radius-lg: 12px;
--sh-chart-1: ${cssManager.bdTheme('#0050B9', '#3B82F6')};
--sh-chart-2: ${cssManager.bdTheme('#16A34A', '#4ADE80')};
--sh-chart-3: ${cssManager.bdTheme('#DC2626', '#F87171')};
--sh-chart-4: ${cssManager.bdTheme('#B45309', '#FBBF24')};
--sh-chart-5: ${cssManager.bdTheme('#6E5BE6', '#A78BFA')};
--sh-grid: ${cssManager.bdTheme('#E4E4E7', '#262626')};
--sh-grid-soft: ${cssManager.bdTheme('rgba(0,0,0,0.04)', 'rgba(255,255,255,0.04)')};
--sh-spark-up: ${cssManager.bdTheme('#16A34A', '#4ADE80')};
--sh-spark-down: ${cssManager.bdTheme('#DC2626', '#F87171')};
--sh-spark-info: ${cssManager.bdTheme('#0050B9', '#93BBFD')};
--sh-d1: ${cssManager.bdTheme('#0050B9', '#93BBFD')};
--sh-d2: ${cssManager.bdTheme('#16A34A', '#4ADE80')};
--sh-d3: ${cssManager.bdTheme('#B45309', '#FBBF24')};
--sh-d4: ${cssManager.bdTheme('#DC2626', '#F87171')};
--sh-d5: ${cssManager.bdTheme('#6E5BE6', '#A78BFA')};
--sh-d6: ${cssManager.bdTheme('#0891B2', '#22D3EE')};
--idp-bg: var(--sh-bg);
--idp-bg-2: var(--sh-bg-2);
--idp-fg: var(--sh-fg);
--idp-fg-2: var(--sh-fg-2);
--idp-fg-3: var(--sh-fg-3);
--idp-muted: var(--sh-muted);
--idp-muted-fg: var(--sh-muted-fg);
--idp-border: var(--sh-border);
--idp-border-soft: var(--sh-border-soft);
--idp-border-strong: var(--sh-border-strong);
--idp-card: var(--sh-card);
--idp-card-2: var(--sh-card-2);
--idp-primary: var(--sh-primary);
--idp-primary-fg: var(--sh-primary-fg);
--idp-accent: var(--sh-accent);
--idp-accent-hover: var(--sh-accent-h);
--idp-accent-fg: var(--sh-accent-fg);
--idp-accent-soft: var(--sh-accent-soft);
--idp-destructive: var(--sh-destructive);
--idp-ok: var(--sh-ok);
--idp-ok-bg: var(--sh-ok-bg);
--idp-ok-border: var(--sh-ok-border);
--idp-warn: var(--sh-warn);
--idp-warn-bg: var(--sh-warn-bg);
--idp-warn-border: var(--sh-warn-border);
--idp-error: var(--sh-err);
--idp-error-bg: var(--sh-err-bg);
--idp-error-border: var(--sh-err-border);
--idp-info: var(--sh-info);
--idp-info-bg: var(--sh-info-bg);
--idp-info-border: var(--sh-info-border);
--idp-chart-1: var(--sh-chart-1);
--idp-chart-2: var(--sh-chart-2);
--idp-chart-3: var(--sh-chart-3);
--idp-chart-4: var(--sh-chart-4);
--idp-chart-5: var(--sh-chart-5);
--idp-spark-up: var(--sh-spark-up);
--idp-spark-down: var(--sh-spark-down);
--idp-spark-info: var(--sh-spark-info);
--idp-radius: var(--sh-radius);
--idp-radius-lg: var(--sh-radius-lg);
--idp-font: 'Geist', ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--idp-display: 'Plus Jakarta Sans', 'Geist', ui-sans-serif, system-ui, sans-serif;
--idp-mono: 'Intel One Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
@@ -76,44 +133,60 @@ export const idpBaseStyles = css`
:host([theme="dark"]),
:host([dark]) {
--idp-bg: #0a0a0a;
--idp-bg-2: #111111;
--idp-fg: #fafafa;
--idp-fg-2: #d4d4d8;
--idp-fg-3: hsl(0 0% 70%);
--idp-muted: #161616;
--idp-muted-fg: hsl(0 0% 55%);
--idp-border: #262626;
--idp-border-soft: #1c1c1c;
--idp-border-strong: #333333;
--idp-card: #121212;
--idp-card-2: #161616;
--idp-primary: #fafafa;
--idp-primary-fg: #18181b;
--idp-accent: #3b82f6;
--idp-accent-hover: #60a5fa;
--idp-accent-soft: rgba(59, 130, 246, 0.15);
--idp-destructive: #ef4444;
--idp-ok: #4ade80;
--idp-ok-bg: rgba(20, 83, 45, 0.4);
--idp-ok-border: rgba(74, 222, 128, 0.25);
--idp-warn: #fbbf24;
--idp-warn-bg: rgba(69, 26, 3, 0.6);
--idp-warn-border: rgba(251, 191, 36, 0.25);
--idp-error: #f87171;
--idp-error-bg: rgba(69, 10, 10, 0.6);
--idp-error-border: rgba(248, 113, 113, 0.25);
--idp-info: #93bbfd;
--idp-info-bg: rgba(59, 130, 246, 0.15);
--idp-info-border: rgba(59, 130, 246, 0.3);
--idp-chart-1: #3b82f6;
--idp-chart-2: #4ade80;
--idp-chart-3: #f87171;
--idp-chart-4: #fbbf24;
--idp-chart-5: #a78bfa;
--idp-spark-up: #4ade80;
--idp-spark-down: #f87171;
--idp-spark-info: #93bbfd;
--sh-bg: #0A0A0A;
--sh-bg-2: #111111;
--sh-card: #121212;
--sh-card-2: #161616;
--sh-muted: #161616;
--sh-muted-fg: hsl(0 0% 55%);
--sh-fg: #FAFAFA;
--sh-fg-2: #D4D4D8;
--sh-fg-3: hsl(0 0% 70%);
--sh-fg-4: hsl(0 0% 35%);
--sh-border: #262626;
--sh-border-soft: #1C1C1C;
--sh-border-strong: #333333;
--sh-input: #161616;
--sh-input-bg: #0A0A0A;
--sh-hover: rgba(255,255,255,0.04);
--sh-primary: #FAFAFA;
--sh-primary-fg: #18181B;
--sh-accent: #3B82F6;
--sh-accent-h: #60A5FA;
--sh-accent-fg: #FFFFFF;
--sh-accent-soft: rgba(59,130,246,0.15);
--sh-accent-soft-fg: #93BBFD;
--sh-accent-ring: rgba(59,130,246,0.35);
--sh-ok: #4ADE80;
--sh-ok-bg: rgba(20,83,45,0.4);
--sh-ok-border: rgba(74,222,128,0.25);
--sh-warn: #FBBF24;
--sh-warn-bg: rgba(69,26,3,0.6);
--sh-warn-border: rgba(251,191,36,0.25);
--sh-err: #F87171;
--sh-err-bg: rgba(69,10,10,0.6);
--sh-err-border: rgba(248,113,113,0.25);
--sh-info: #93BBFD;
--sh-info-bg: rgba(59,130,246,0.15);
--sh-info-border: rgba(59,130,246,0.3);
--sh-destructive: #EF4444;
--sh-ring: #3B82F6;
--sh-chart-1: #3B82F6;
--sh-chart-2: #4ADE80;
--sh-chart-3: #F87171;
--sh-chart-4: #FBBF24;
--sh-chart-5: #A78BFA;
--sh-grid: #262626;
--sh-grid-soft: rgba(255,255,255,0.04);
--sh-spark-up: #4ADE80;
--sh-spark-down: #F87171;
--sh-spark-info: #93BBFD;
--sh-d1: #93BBFD;
--sh-d2: #4ADE80;
--sh-d3: #FBBF24;
--sh-d4: #F87171;
--sh-d5: #A78BFA;
--sh-d6: #22D3EE;
}
`;