Build the ui/ component library and persistent app shell
Button, Input, Select, Badge, SeverityBadge, Table, Card, Modal, Tooltip, Tabs, Skeleton, EmptyState -- a shared library so pages stop hand-rolling markup per page (web/src/lib/components/ui, barrel export in index.ts). Modal and CommandPalette are built on native <dialog> for a real focus trap, Escape-to-close, and top-layer stacking instead of hand-rolling those. Tabs uses roving tabindex with arrow-key nav. NavSidebar replaces the old top-nav with a persistent sidebar (Search/Dashboards/Alerts/Data Sources/Settings), a live tenant indicator (api.ts's new getCurrentSession(), a client for the already-existing POST /internal/authorize -- zero new backend surface), theme/density quick-toggles, and a command-palette hint. Collapses to an off-canvas drawer under 860px. CommandPalette (Cmd/Ctrl+K) indexes the five static destinations plus live-fetched dashboards/alert rules. Data Sources is a new, honestly-scoped placeholder page (one data source per tenant today, no UI needed yet). Settings and Select-tenant are re-tokened onto the new component library. +layout.svelte's content wrapper is a plain <div>, not a second <main> -- every page already renders its own top-level <main>.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
value = $bindable(''),
|
||||
disabled = false,
|
||||
id,
|
||||
children,
|
||||
...rest
|
||||
}: {
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
children: Snippet;
|
||||
[key: string]: unknown;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
<select {id} {disabled} bind:value class="select" {...rest}>
|
||||
{@render children()}
|
||||
</select>
|
||||
<span class="chev" aria-hidden="true">⌄</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.select {
|
||||
appearance: none;
|
||||
height: var(--control-height);
|
||||
padding: 0 var(--space-6) 0 var(--space-3);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-base);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.select:hover:not(:disabled) {
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
.select:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.chev {
|
||||
position: absolute;
|
||||
right: var(--space-3);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--color-text-muted);
|
||||
pointer-events: none;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user