modularize UI
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user