mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 08:22:24 +00:00
258 lines
16 KiB
Markdown
258 lines
16 KiB
Markdown
# bitvid Design System Primitives
|
||
|
||
bitvid's visual language is now powered by Tailwind utilities backed by our design tokens. This document lists the primitives available via `css/tailwind.source.css`, how they behave across states, and when to mix them with Tailwind utilities.
|
||
|
||
## Theme System
|
||
|
||
The default experience renders the production dark palette. Pages and embeds can opt into alternates by setting the `data-theme` attribute on the `<html>` (or any ancestor that wraps the component). `data-theme="light"` promotes the light tokens defined in `css/tokens.css`, swapping surface, panel, border, muted, and overlay colors while keeping semantic accents consistent. Omit the attribute or set it back to `default` to inherit the dark baseline. Reserve additional theme keys (for example, `data-theme="contrast"`) for upcoming accessibility palettes—the same hook will activate them once their tokens land.
|
||
|
||
### Per-page overrides
|
||
|
||
Add `<html data-theme="light">` to force a page into light mode. The attribute can also target specific islands—wrap your component tree in a container with `data-theme="light"` when you need to render light cards inside a dark shell.
|
||
|
||
### Embedding in third-party surfaces
|
||
|
||
Components consume design tokens via CSS custom properties, so they pick up the active theme from the closest ancestor. When embedding widgets into a host site, include the `css/tokens.css` bundle and set `data-theme` on the wrapper to control contrast. If the host already defines its own theme, namespace our tokens (e.g., nest them inside a dedicated `.bitvid-embed[data-theme="light"]`) to avoid collisions while still letting overlay, border, and panel tokens cascade.
|
||
|
||
## Focus Handling
|
||
|
||
All interactive primitives mix in the shared `.focus-ring` helper. It removes default outlines and applies a branded ring using `focus-visible` only. When building bespoke components, add `.focus-ring` to your root interactive element or replicate its rules:
|
||
|
||
```html
|
||
<button class="focus-ring inline-flex items-center gap-2 ...">Label</button>
|
||
```
|
||
|
||
The helper includes:
|
||
|
||
- `focus-visible:ring-2` with `ring-info` and `ring-opacity-70`
|
||
- `focus-visible:ring-offset-2` with an offset on `bg-surface`
|
||
- `focus-visible:transition-shadow` using the tokenized `duration-fast` easing curve
|
||
|
||
Never hide focus styles without providing an accessible alternative.
|
||
|
||
## Component Primitives
|
||
|
||
### Buttons
|
||
|
||
| Class | Description |
|
||
| --- | --- |
|
||
| `.btn` | Primary action button (`bg-primary`, white text). Hover lifts to `bg-info-strong`, focus pushes to `bg-info-pressed`, disabled reduces opacity. |
|
||
| `.btn-ghost` | Framed secondary action. Transparent by default with subtle border, fills with `bg-panel-hover` on hover/focus. |
|
||
|
||
Both buttons:
|
||
|
||
- Use `.focus-ring` internally
|
||
- Include pointer-blocking and opacity changes on `:disabled`
|
||
- Are uppercase with wide tracking to match our existing CTA styling
|
||
|
||
Mix in Tailwind utilities for icon spacing (`gap-3`), sizing (`px-lg`, `py-sm`), or layout (`w-full`). Avoid overriding colors unless introducing a new semantic variant.
|
||
|
||
#### Variants & Toggle States
|
||
|
||
- Apply `data-variant="critical"` to `.btn-ghost` when rendering destructive affordances (for example, "Remove variant" pills inside repeater rows). The primitive swaps the border and text colors to the critical palette and deepens the fill on hover/focus.
|
||
- Toggle groups can rely on `[aria-pressed="true"]` or `data-state="active"` without extra utilities. When a `.btn-ghost` reports an active state it automatically promotes to the filled primary style.
|
||
|
||
```html
|
||
<div class="inline-flex items-center rounded-full bg-panel/80 p-1" role="group">
|
||
<button class="btn-ghost flex-1" type="button" aria-pressed="true">Custom</button>
|
||
<button class="btn-ghost flex-1" type="button" aria-pressed="false">Cloudflare</button>
|
||
</div>
|
||
```
|
||
|
||
### Badges
|
||
|
||
`.badge` renders an inline pill (`bg-panel`, uppercase text). Opt into semantic variants with `data-variant="info"`, `data-variant="critical"`, or `data-variant="neutral"`. Layer on utilities for icon alignment or truncation.
|
||
|
||
### Cards
|
||
|
||
`.card` is the base surface for grid items and panels:
|
||
|
||
- Rounded corners (`rounded-lg`) and `bg-card`
|
||
- Token shadows on hover
|
||
- Focus states reuse `.focus-ring`
|
||
- Accepts `data-state="private" | "critical" | "disabled"` for special treatments
|
||
|
||
Combine with padding utilities per layout. Interactive cards should maintain `cursor-pointer` manually (see legacy shim example below) until templates adopt dedicated controller classes.
|
||
|
||
> **2025-10-11 update:** The legacy `.video-card*` shim selectors were removed. Video grids should now render plain `.card` elements and opt into semantic variants via `data-state`, `data-alert`, and `data-motion` attributes. Runtime code no longer maps the deprecated classes, so older selectors will not receive token updates.
|
||
|
||
### Form Inputs
|
||
|
||
`.form-control` is the base primitive for text entry surfaces. It handles layout (`block`, `w-full`), rounded corners, subtle borders, muted placeholders, and the shared focus ring. Hovering deepens the border, focus highlights with `border-info-strong`, `:invalid` raises a critical border, and `:disabled` mutes the field while blocking pointer events.
|
||
|
||
Specialisations reuse `.form-control`:
|
||
|
||
- `.input` – Standard single-line text input.
|
||
- `.textarea` – Multi-line input with a minimum height (`min-h-[8rem]`) and `resize-y` enabled.
|
||
- `.select` – Native `<select>` element with `appearance-none`, an integrated arrow glyph, and consistent spacing (`pr-10`).
|
||
|
||
All three share hover, focus-visible, disabled, and invalid treatments. Layer on utilities (`h-12`, `text-lg`, icon padding) as needed without re-declaring tokens.
|
||
|
||
```html
|
||
<form class="bv-stack bv-stack--tight" aria-labelledby="zapAmountLabel">
|
||
<label
|
||
id="zapAmountLabel"
|
||
class="text-xs font-semibold uppercase tracking-wide text-muted-strong"
|
||
>
|
||
Zap amount (sats)
|
||
</label>
|
||
<input
|
||
id="zapAmountInput"
|
||
type="number"
|
||
min="1"
|
||
step="1"
|
||
class="input"
|
||
placeholder="Enter sats"
|
||
/>
|
||
<button class="btn" type="submit">Send</button>
|
||
</form>
|
||
```
|
||
|
||
For toggles:
|
||
|
||
- `.checkbox` resets native appearance, applies the token border/background, and swaps to the info palette when checked. The checkmark is rendered via `::after`, and disabled states lower contrast while keeping the focus ring behaviour.
|
||
- `.switch` renders a pill track with a translating thumb (`::before`). Apply `[aria-checked="true"]` or `.is-on` to move the thumb and recolor the track. Motion respects `prefers-reduced-motion` while maintaining the focus ring.
|
||
|
||
### Modal Surface
|
||
|
||
- `.bv-modal` anchors fixed overlays (full-viewport flexbox centered layout).
|
||
- `.bv-modal-backdrop` applies the tokenized overlay color with blur.
|
||
- `.bv-modal__panel` hosts modal content (`bg-surface`, `shadow-modal`, focus ring).
|
||
|
||
Compose them together when mounting new modal portals. If you need a drawer or sheet, start from these primitives and adjust spacing/positioning via utilities.
|
||
|
||
#### Modal Actions
|
||
|
||
Action rows in login, disclaimer, and upload flows now pair `.btn-ghost` and `.btn` directly. Layer layout utilities such as `w-full` or `gap-3` without re-adding custom focus rings.
|
||
|
||
```html
|
||
<footer class="modal-footer flex gap-3">
|
||
<button class="btn-ghost flex-1" type="button">Cancel</button>
|
||
<button class="btn flex-1" type="submit">Publish</button>
|
||
</footer>
|
||
```
|
||
|
||
Static modal partials (login, application, feedback, etc.) should call the helpers exported from `js/ui/components/staticModalAccessibility.js`—`prepareStaticModal`, `openStaticModal`, and `closeStaticModal`—to attach focus trapping, Escape/backdrop dismissal, and the shared `data-open`/`html.modal-open` toggles without duplicating controller logic.
|
||
|
||
### Popovers
|
||
|
||
Use `.popover` as the relative anchor for floating content. Nest `.popover__panel` for the floating surface—it’s positioned absolutely below the trigger, inherits the shared focus ring, and animates with opacity/scale tokens. Toggle `[data-state="open"]` on the panel to switch between the collapsed (`opacity-0 scale-95`) and expanded (`opacity-100 scale-100`) states.
|
||
|
||
```html
|
||
<div class="popover">
|
||
<button class="btn" type="button">Open filters</button>
|
||
<div class="popover__panel" role="dialog" data-state="open">
|
||
<p class="text-sm text-muted">Fine-tune the current feed using tags and duration filters.</p>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
Mix utilities such as `right-0` or `translate-x-1/2` on `.popover__panel` when you need alternative alignments. The motion helpers defer to the global motion tokens so Task 5 can adjust timing globally without revisiting individual components.
|
||
|
||
### Menus
|
||
|
||
`.menu` normalises list-based command menus with consistent padding. Pair it with `.menu__heading` for optional section labels, `.menu__separator` for dividers, and `.menu__item` for actionable rows. Each item supports hover, active (`data-state="active"`), and critical (`data-variant="critical"`) palettes plus disabled fallbacks through either `disabled` or `aria-disabled="true"`.
|
||
|
||
```html
|
||
<div class="popover">
|
||
<button class="btn-ghost" type="button">Command palette</button>
|
||
<div class="popover__panel" role="menu" data-state="open">
|
||
<div class="menu" role="none">
|
||
<p class="menu__heading" role="presentation">Navigation</p>
|
||
<button class="menu__item" role="menuitem">Search videos</button>
|
||
<button class="menu__item" role="menuitem" data-state="active">Go live dashboard</button>
|
||
<div class="menu__separator" role="separator"></div>
|
||
<button class="menu__item" role="menuitem" data-variant="critical">Delete current draft</button>
|
||
<button class="menu__item" role="menuitem" disabled>Switch profile (pro only)</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
The helpers rely on layout utilities (`flex`, `gap-*`) so you can safely inject icons, shortcuts, or nested badges without restyling the base tokens.
|
||
|
||
### Zap Popover
|
||
|
||
Compose `.popover` with a zap-specific panel variant when you need to surface the tipping form inline. Toggle `data-state="open"` on the `.popover__panel` and remove its `hidden` attribute when the dialog is visible. The `data-variant="zap"` hook reuses the frosted-glass styling defined in `css/style.css`.
|
||
|
||
```html
|
||
<div class="popover">
|
||
<button class="btn-ghost h-10 w-10 rounded-full p-0" type="button" aria-haspopup="dialog" aria-expanded="false">
|
||
<img src="assets/svg/lightning-bolt.svg" alt="Zap" class="h-5 w-5" />
|
||
</button>
|
||
<div
|
||
class="popover__panel card w-72 max-w-[calc(100vw-2rem)] space-y-4"
|
||
role="dialog"
|
||
aria-hidden="true"
|
||
data-variant="zap"
|
||
data-state="closed"
|
||
hidden
|
||
>
|
||
<div class="flex items-center justify-between gap-4">
|
||
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted">Send a zap</h3>
|
||
<button class="btn-ghost h-8 w-8 rounded-full p-0" type="button" aria-label="Close zap dialog">✕</button>
|
||
</div>
|
||
<form class="space-y-4">
|
||
<label class="block text-xs font-semibold uppercase tracking-wide text-muted" for="zapAmountExample">
|
||
Zap amount (sats)
|
||
</label>
|
||
<input id="zapAmountExample" class="input" type="number" min="1" step="1" inputmode="numeric" />
|
||
<button class="btn w-full" type="submit">Send</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### Grid Stack
|
||
|
||
`.grid-stack` offers gap tokens for dense layouts. Add `data-variant="cards"` for responsive card grids (`grid-cols-1` → `sm:grid-cols-2` → `xl:grid-cols-3`). Switch to `data-orientation="vertical"` for stacked flex columns that reuse the same spacing tokens.
|
||
|
||
### Layout Helpers
|
||
|
||
- `.bv-stack` creates a vertical flex stack with the default large gap (`var(--space-lg)`). Use it for page sections such as the watch-history summary blocks.
|
||
- `.bv-stack--tight` reduces the gap to `var(--space-md)` while inheriting the `.bv-stack` flex behaviour. Combine the modifier when stacking smaller card fragments or form controls inside profile templates.
|
||
- `.bv-grid-video` centralises the responsive video grid (`repeat(auto-fill, minmax(20rem, 1fr))`) with the standard gutter (`gap-xl`) and vertical padding (`py-lg`). Apply it to feed, subscriptions, and channel views instead of repeating inline grid rules.
|
||
|
||
## Legacy Compatibility Shims
|
||
|
||
During migration we keep existing selectors alive by layering `@apply` calls in `css/style.css`. Notable shims:
|
||
|
||
- `#profileModal .profile-switcher*` → `.card`, `.badge`, `.btn-ghost`
|
||
|
||
> **2025-10-11:** The `.video-card` shim family was removed. Templates must emit `.card` elements with the appropriate `data-state` and `data-alert` attributes instead of relying on the legacy class names.
|
||
|
||
We continue to expose aliases such as `.profile-switcher` and nested variations (for example, `.profile-switcher__item`) so existing profile and admin controllers stay functional while downstream templates finish their migration. These shims live under the "Legacy component compatibility" section and will be removed once templates switch to the new primitives.
|
||
|
||
> **Update (Q1 2025):** The modal shim that mapped `.modal-container`/`.modal-content` to the new primitives has been removed. All modal templates must mount `.bv-modal`, `.bv-modal-backdrop`, and `.bv-modal__panel` directly.
|
||
|
||
**Target removal:** Audit usage after the Q1 2025 UI refresh and delete remaining shims no later than April 2025. At that point the compatibility layer will be deleted once dependent feature flags confirm that no legacy selectors remain in production HTML.
|
||
|
||
### Migration Notes (Q4 2024)
|
||
|
||
- Modal templates across the app now mount `.bv-modal`, `.bv-modal-backdrop`, and `.bv-modal__panel` primitives directly. Controllers such as `ProfileModalController` and the upload modal orchestrator were updated to expect these primitives, so any new modal should follow the same structure for consistent accessibility and animation hooks.
|
||
- The compatibility shim keeps the `.profile-switcher` selector family alive temporarily so third-party embeds and cached HTML fragments continue to render while operators redeploy updated templates. Keep the aliases until the April 2025 removal window above, then delete the shim and update any last callers to the new `.card`/`.btn-ghost` structure.
|
||
|
||
## Beacon client migration
|
||
|
||
The torrent beacon now shares the Tailwind bundle used across bitvid. The standalone `torrent/beacon.html` drops Skeleton/FoA CDNs in favour of `css/tailwind.generated.css`, adopts the `.card`, `.btn`, `.btn-ghost`, and `.input` primitives, and relies on `.bv-stack` for spacing inside Angular partials. Inline SVG icons replace FontAwesome so controls stay lightweight while inheriting tokenised focus and hover states.
|
||
|
||
Angular templates under `torrent/views/` should follow the refreshed structure:
|
||
|
||
- Wrap primary sections in `.card` and layer Tailwind utilities for layout (`grid`, `gap-xl`, `overflow-auto`).
|
||
- Use `.input`, `.select`, `.btn`, and `.btn-ghost` (with `data-variant="critical"` for destructive actions) instead of legacy `.u-full-width`, `.button`, or `.button-danger` classes.
|
||
- Prefer token-backed utilities for spacing and typography (`px-6`, `py-6`, `text-muted`) over ad-hoc CSS.
|
||
- Reuse the `.spinner-overlay` markup for async states so overlays stay consistent with the new palette.
|
||
|
||
When adding new torrent affordances, mirror the existing markup: inline 24×24 SVG strokes at 2px, `.focus-ring` on interactive anchors, and `.bv-stack` for vertical rhythm. This keeps the beacon aligned with the design system without reintroducing the deprecated Skeleton grid.
|
||
|
||
## When to Mix Utilities
|
||
|
||
Use the primitives for foundation and lean on Tailwind utilities for:
|
||
|
||
- Layout (`flex`, `grid`, `gap-*`, `justify-*`)
|
||
- Sizing (`w-*`, `h-*`, `min-w-*`)
|
||
- Typography tweaks (`text-left`, `font-light`)
|
||
- State variations not yet encoded as tokens (e.g., success/neutral buttons)
|
||
|
||
Avoid re-declaring token values manually. If a new pattern repeats, promote it into a primitive or request new tokens to keep styling consistent.
|