modularize UI

This commit is contained in:
KingKDot
2026-08-19 10:38:50 -05:00
parent 7a8fcf39ec
commit 82d6864afe
13 changed files with 362 additions and 34 deletions
+8
View File
@@ -0,0 +1,8 @@
@import "./web/styles/tokens.css";
@import "./web/styles/base.css";
@import "./web/styles/primitives/cards.css";
@import "./web/styles/primitives/buttons.css";
@import "./web/styles/primitives/badges.css";
@import "./web/styles/primitives/popovers.css";
@import "./web/styles/patterns/toolbars.css";
+29
View File
@@ -0,0 +1,29 @@
# Overlord UI styles
The dashboard is the visual reference for this design system. Source styles are
bundled from `src/ui.css` into `public/assets/ui.css`.
## Layers
- `tokens.css` contains semantic theme values. Components should consume these
variables instead of introducing raw palette values when a token exists.
- `base.css` contains application-wide accessibility and motion behavior.
- `primitives/` contains reusable controls with no page-specific layout.
- `patterns/` composes primitives into recurring structures such as toolbars.
- Feature-specific layout belongs in a future `features/` module or the existing
feature stylesheet until it is migrated.
Load order is `tailwind.css`, `main.css`, `ui.css`, then `custom.css`. This lets
the design system replace legacy declarations while preserving administrator
branding overrides.
## Conventions
- Use the `ui-` prefix for reusable components.
- Use `data-tone` for semantic color variants and `data-size` for sizing.
- Use `is-*` classes for transient JavaScript state during legacy migration.
- Keep data fetching in feature modules; UI primitives should only render or
manage their own interaction behavior.
- Retain a legacy class beside its `ui-*` replacement until all consumers and
selectors have migrated, then remove the legacy declaration separately.
+18
View File
@@ -0,0 +1,18 @@
/* A single visible keyboard-focus treatment replaces page-specific resets. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--ui-accent) !important;
outline-offset: 2px !important;
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.16) !important;
}
:where(a, button, input, select, textarea, summary, [tabindex]):focus:not(:focus-visible) {
outline: none;
}
@media (prefers-reduced-motion: reduce) {
:where(.ui-card, .ui-button, .ui-icon-button, .ui-popover) {
scroll-behavior: auto;
transition-duration: 0.01ms !important;
}
}
@@ -0,0 +1,38 @@
.ui-toolbar {
position: relative;
z-index: 20;
display: flex;
align-items: center;
gap: 10px;
}
.ui-toolbar__search {
flex: 1 1 420px;
min-width: 240px;
}
.ui-toolbar__actions {
display: inline-flex;
align-items: center;
gap: 6px;
flex: 0 0 auto;
}
@media (max-width: 820px) {
.ui-toolbar {
align-items: stretch;
flex-direction: column;
}
.ui-toolbar__search {
flex-basis: auto;
width: 100%;
}
.ui-toolbar__actions {
width: 100%;
justify-content: flex-end;
flex-wrap: wrap;
}
}
@@ -0,0 +1,53 @@
.ui-badge {
display: inline-flex;
align-items: center;
flex-shrink: 0;
max-width: 9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid rgba(100, 116, 139, 0.25);
border-radius: var(--ui-radius-full);
background: rgba(30, 41, 59, 0.55);
color: #cbd5e1;
padding: 3px 8px;
font-size: 0.7rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.ui-badge[data-tone="info"] {
border-color: rgba(56, 189, 248, 0.22);
background: rgba(8, 47, 73, 0.32);
color: #bae6fd;
}
.ui-status-dot {
width: 8px;
height: 8px;
flex: 0 0 auto;
border-radius: var(--ui-radius-full);
background: var(--ui-text-faint);
box-shadow: 0 0 0 4px rgba(100, 116, 139, 0.12);
}
.ui-status-dot:is([data-state="healthy"], .is-live) {
background: var(--ui-success);
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.13), 0 0 16px rgba(34, 197, 94, 0.36);
}
.ui-status-dot:is([data-state="warning"], .is-warn) {
background: var(--ui-warning);
box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.12), 0 0 16px rgba(251, 191, 36, 0.3);
}
.ui-status-dot:is([data-state="danger"], .is-bad) {
background: var(--ui-danger);
box-shadow: 0 0 0 4px rgba(251, 113, 133, 0.12), 0 0 16px rgba(251, 113, 133, 0.3);
}
.ui-status-dot:is([data-state="neutral"], .is-muted) {
background: var(--ui-text-faint);
box-shadow: 0 0 0 4px rgba(100, 116, 139, 0.1);
}
@@ -0,0 +1,80 @@
.ui-button,
.ui-icon-button {
border: 1px solid rgba(71, 85, 105, 0.72);
border-radius: var(--ui-radius-control);
background: var(--ui-surface-solid);
color: #cbd5e1;
cursor: pointer;
transition:
transform 150ms ease,
border-color 150ms ease,
background-color 150ms ease,
color 150ms ease;
}
.ui-button:hover,
.ui-icon-button:hover {
transform: translateY(-1px);
border-color: rgba(129, 140, 248, 0.55);
background: var(--ui-surface-solid-hover);
color: var(--ui-text-strong);
}
.ui-button:disabled,
.ui-icon-button:disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
}
.ui-button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: var(--ui-control-height);
gap: var(--ui-space-2);
padding: 0 var(--ui-space-3);
font-size: 0.8125rem;
font-weight: 600;
}
.ui-icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--ui-control-height);
height: var(--ui-control-height);
padding: 0;
}
.ui-icon-button[data-size="compact"] {
width: 34px;
height: 34px;
background: var(--ui-surface-2);
color: var(--ui-text-muted);
border-color: var(--ui-border-subtle);
}
.ui-icon-button[data-size="compact"]:hover {
transform: none;
background: var(--ui-surface-hover);
color: var(--ui-text);
border-color: var(--ui-border-strong);
}
.ui-icon-button:has(input:not(:checked)) {
color: var(--ui-text-faint);
background: rgba(15, 23, 42, 0.75);
}
.ui-icon-button input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.ui-menu[open] > .ui-icon-button {
color: var(--ui-accent-secondary);
border-color: rgba(129, 140, 248, 0.55);
background: #20294a;
}
@@ -0,0 +1,22 @@
.ui-card {
position: relative;
min-width: 0;
border: 1px solid var(--ui-border-subtle);
border-radius: var(--ui-radius-panel);
background: var(--ui-surface-1);
overflow: hidden;
}
.ui-card--interactive {
cursor: grab;
}
.ui-card--interactive:active {
cursor: grabbing;
}
.ui-card--interactive.is-dragging {
opacity: 0.52;
border-color: rgba(56, 189, 248, 0.62);
}
@@ -0,0 +1,25 @@
.ui-popover {
position: absolute;
top: calc(100% + var(--ui-space-2));
right: 0;
z-index: 40;
display: flex;
flex-direction: column;
gap: var(--ui-space-2);
min-width: 220px;
padding: 10px;
border: 1px solid var(--ui-border-strong);
border-radius: var(--ui-radius-control);
background: var(--ui-popover);
box-shadow: 0 18px 44px rgba(2, 6, 23, 0.55);
}
.ui-popover[data-size="compact"] {
display: grid;
gap: 6px;
width: 196px;
min-width: 196px;
border-color: rgba(51, 65, 85, 0.82);
background: var(--ui-popover-translucent);
box-shadow: 0 18px 38px rgba(2, 6, 23, 0.42);
}
+41
View File
@@ -0,0 +1,41 @@
:root {
/* Canvas and surfaces */
--ui-canvas: #04070d;
--ui-canvas-raised: #020617;
--ui-surface-1: rgba(15, 23, 42, 0.72);
--ui-surface-2: rgba(30, 41, 59, 0.7);
--ui-surface-hover: rgba(51, 65, 85, 0.78);
--ui-surface-solid: #172033;
--ui-surface-solid-hover: #1f2a3e;
--ui-popover: #111827;
--ui-popover-translucent: rgba(15, 23, 42, 0.98);
/* Content and borders */
--ui-text: #e2e8f0;
--ui-text-strong: #f8fafc;
--ui-text-muted: #94a3b8;
--ui-text-faint: #64748b;
--ui-border-subtle: rgba(51, 65, 85, 0.68);
--ui-border-strong: rgba(71, 85, 105, 0.8);
/* Intent */
--ui-accent: #38bdf8;
--ui-accent-secondary: #818cf8;
--ui-success: #22c55e;
--ui-warning: #fbbf24;
--ui-danger: #fb7185;
/* Shape, spacing, and motion */
--ui-radius-sm: 6px;
--ui-radius-control: 8px;
--ui-radius-panel: 10px;
--ui-radius-full: 999px;
--ui-control-height: 36px;
--ui-space-1: 4px;
--ui-space-2: 8px;
--ui-space-3: 12px;
--ui-space-4: 16px;
--ui-motion-fast: 140ms;
--ui-motion-normal: 220ms;
--ui-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
}