Files
bitvid/docs/kitchen-sink.html
T

762 lines
32 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid Design System Kitchen Sink</title>
<link rel="stylesheet" href="../css/tailwind.generated.css" />
</head>
<body class="bg-page font-sans text-text-strong">
<div class="min-h-screen bg-page-alt">
<header class="bg-surface text-text shadow-modal">
<div
class="mx-auto flex max-w-6xl items-center justify-between gap-sm px-md py-sm"
>
<h1 class="text-2xl font-semibold">bitvid UI Kitchen Sink</h1>
<div class="flex items-center gap-sm">
<label class="sr-only" for="themeSwitcher">Theme</label>
<select id="themeSwitcher" class="select h-10" aria-label="Select theme">
<option value="default">Default (Dark)</option>
<option value="light">Light</option>
<option value="contrast">High Contrast (Preview)</option>
</select>
<button class="btn">Primary Action</button>
</div>
</div>
</header>
<main class="mx-auto max-w-6xl space-y-xl px-md py-xl">
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">Buttons</h2>
<p class="text-sm text-muted">
Each block shows the default, simulated hover, focus, and disabled
states.
</p>
</div>
<div class="grid gap-lg lg:grid-cols-2">
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Primary (.btn)</h3>
<div class="grid gap-sm sm:grid-cols-2 xl:grid-cols-4">
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Default</p>
<button class="btn">Default</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Hover (simulated)</p>
<button class="btn bg-info-strong shadow-lg">Hover</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Focus (simulated)</p>
<button
class="btn bg-info-pressed ring-2 ring-info ring-opacity-70 ring-offset-2 ring-offset-surface"
>
Focus
</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Disabled</p>
<button class="btn" disabled>Disabled</button>
</div>
</div>
</div>
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Ghost (.btn-ghost)</h3>
<div class="grid gap-sm sm:grid-cols-2 xl:grid-cols-4">
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Default</p>
<button class="btn-ghost">Default</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Hover (simulated)</p>
<button class="btn-ghost bg-panel-hover text-text">
Hover
</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Focus (simulated)</p>
<button
class="btn-ghost bg-panel-hover ring-2 ring-info ring-opacity-70 ring-offset-2 ring-offset-surface"
>
Focus
</button>
</div>
<div class="space-y-xs">
<p class="text-xs uppercase text-muted">Disabled</p>
<button class="btn-ghost" disabled>Disabled</button>
</div>
</div>
</div>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">Badges</h2>
<p class="text-sm text-muted">
Use `data-variant` for semantic colorways.
</p>
</div>
<div class="flex flex-wrap items-center gap-sm">
<span class="badge">Default</span>
<span class="badge" data-variant="info">Info</span>
<span class="badge" data-variant="critical">Critical</span>
<span class="badge" data-variant="neutral">Neutral</span>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">Cards</h2>
<p class="text-sm text-muted">
Cards inherit focus-ring behaviour and support semantic states.
</p>
</div>
<div class="grid-stack" data-variant="cards">
<article class="card p-md">
<h3 class="text-lg font-semibold">Default Card</h3>
<p class="mt-xs text-muted">
Combine with layout utilities like `p-md` or `gap-sm` for
content spacing.
</p>
<button class="btn mt-sm">Watch</button>
</article>
<article
class="card -translate-y-1 bg-card p-md shadow-lg transition-none"
>
<h3 class="text-lg font-semibold">Hover (simulated)</h3>
<p class="mt-xs text-muted">Applies the hover shadow and lift.</p>
</article>
<article
class="card bg-card p-md ring-2 ring-info ring-opacity-70 ring-offset-2 ring-offset-surface"
>
<h3 class="text-lg font-semibold">Focus (simulated)</h3>
<p class="mt-xs text-muted">Rings appear on `focus-visible`.</p>
</article>
<article class="card p-md" data-state="disabled">
<h3 class="text-lg font-semibold">Disabled</h3>
<p class="mt-xs text-muted">
Opacity drops and the shadow is removed.
</p>
</article>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">
Layout Helpers
</h2>
<p class="text-sm text-muted">
`.bv-stack` and `.bv-grid-video` consolidate our common feed and
profile layouts.
</p>
</div>
<div class="grid gap-lg lg:grid-cols-2">
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Stack (.bv-stack)</h3>
<div class="bv-stack">
<article class="card p-md">
<h4 class="text-base font-semibold">Default spacing</h4>
<p class="text-sm text-muted">
Use `.bv-stack` for page-level groupings such as the watch
history header and banners.
</p>
</article>
<article class="card p-md">
<h4 class="text-base font-semibold">Nested card</h4>
<p class="text-sm text-muted">
The helper keeps consistent gaps without repeating
`space-y-*` utilities.
</p>
</article>
</div>
<div class="bv-stack bv-stack--tight">
<article class="card p-md">
<h4 class="text-base font-semibold">Tight stack</h4>
<p class="text-sm text-muted">
Apply `.bv-stack--tight` when stacking compact forms or
metadata blocks in profile views.
</p>
</article>
<article class="card p-md">
<h4 class="text-base font-semibold">Form footer</h4>
<p class="text-sm text-muted">
The modifier inherits flex behaviour from `.bv-stack` while
reducing the gap to `var(--space-md)`.
</p>
</article>
</div>
</div>
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Video Grid (.bv-grid-video)</h3>
<div class="bv-grid-video">
<article class="card p-md">
<h4 class="text-base font-semibold">Feed Tile</h4>
<p class="text-sm text-muted">
Auto-fills with a 20rem minimum column size on desktop.
</p>
</article>
<article class="card p-md">
<h4 class="text-base font-semibold">Subscription Tile</h4>
<p class="text-sm text-muted">
The grid collapses to a single column below 40rem.
</p>
</article>
<article class="card p-md">
<h4 class="text-base font-semibold">Channel Tile</h4>
<p class="text-sm text-muted">
Reuse the same helper instead of duplicating inline grid
declarations.
</p>
</article>
</div>
</div>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">Form Inputs</h2>
<p class="text-sm text-muted">
`.input` works for both `&lt;input&gt;` and `&lt;textarea&gt;`
elements.
</p>
</div>
<div class="grid gap-md md:grid-cols-2">
<div class="space-y-xs">
<label
class="text-sm font-medium text-muted"
for="demo-input-default"
>Default</label
>
<input
id="demo-input-default"
class="input"
type="text"
placeholder="Display name"
/>
</div>
<div class="space-y-xs">
<label
class="text-sm font-medium text-muted"
for="demo-input-hover"
>Hover (simulated)</label
>
<input
id="demo-input-hover"
class="input border-border"
type="text"
value="Hovering"
/>
</div>
<div class="space-y-xs">
<label
class="text-sm font-medium text-muted"
for="demo-input-focus"
>Focus (simulated)</label
>
<input
id="demo-input-focus"
class="input border-info-strong ring-2 ring-info ring-opacity-70 ring-offset-2 ring-offset-surface"
type="text"
value="In focus"
/>
</div>
<div class="space-y-xs">
<label
class="text-sm font-medium text-muted"
for="demo-input-disabled"
>Disabled</label
>
<input
id="demo-input-disabled"
class="input"
type="text"
value="Read only"
disabled
/>
</div>
<div class="space-y-xs md:col-span-2">
<label class="text-sm font-medium text-muted" for="demo-textarea"
>Textarea</label
>
<textarea
id="demo-textarea"
class="input"
rows="3"
placeholder="Describe the video..."
></textarea>
</div>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">
Focus Ring Helper
</h2>
<p class="text-sm text-muted">
Add `.focus-ring` to custom interactives when you opt out of
`.btn` or other primitives.
</p>
</div>
<div class="flex flex-wrap gap-sm">
<a
href="#"
class="focus-ring inline-flex items-center gap-xs rounded-md border border-border bg-panel px-sm py-xs text-text no-underline"
>
Focusable Link
</a>
<button
class="focus-ring inline-flex items-center gap-xs rounded-md border border-border px-sm py-xs"
>
Plain Button
</button>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">
Modal Surface
</h2>
<p class="text-sm text-muted">
`.bv-modal` anchors the overlay, `.bv-modal-backdrop` dims the
page, and `.bv-modal__panel` styles content.
</p>
</div>
<div
class="relative isolate overflow-hidden rounded-xl bg-surface-alt p-lg text-text shadow-inner"
>
<div class="absolute inset-0" style="position: relative">
<div class="bv-modal" style="position: absolute; inset: 0">
<div class="bv-modal-backdrop"></div>
<div class="bv-modal__panel space-y-sm">
<h3 class="text-lg font-semibold">Upload Video</h3>
<p class="text-muted">
Use `.input` and `.btn` primitives inside panels for
consistency.
</p>
<input class="input" type="text" placeholder="Magnet URI" />
<div class="flex justify-end gap-sm">
<button class="btn-ghost">Cancel</button>
<button class="btn">Publish</button>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">
Profile &amp; Edit Modal Shells
</h2>
<p class="text-sm text-muted">
Both shells share the `.bv-modal` overlay and tokenized primitives
(`.card`, `.btn`, `.btn-ghost`, `.badge`, `.grid-stack`,
`.focus-ring`). Compatibility hooks like `modal-content` remain on
the panels so legacy CSS selectors keep working alongside the new
primitives.
</p>
</div>
<div class="grid gap-lg lg:grid-cols-2">
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Profile Modal</h3>
<p class="text-sm text-muted">
Static scaffold that mirrors `components/profile-modal.html`.
Tab buttons keep their `aria-controls`/`data-state` attributes
so designers can map the layout to controller behaviour.
</p>
<div
class="relative isolate overflow-hidden rounded-xl bg-surface-alt p-lg text-text shadow-inner"
>
<div class="absolute inset-0" style="position: relative">
<div
id="profileModal"
class="bv-modal items-start"
style="position: absolute; inset: 0"
>
<div class="bv-modal-backdrop"></div>
<div
class="bv-modal__panel modal-content flex max-h-[80vh] flex-col gap-lg"
role="dialog"
aria-labelledby="profileModalTitle"
>
<header
class="card flex items-center justify-between gap-sm p-md"
>
<div>
<p class="text-xs uppercase tracking-wide text-muted">
Modal header
</p>
<h4
id="profileModalTitle"
class="text-lg font-semibold"
>
Active profile
</h4>
</div>
<button
class="focus-ring inline-flex h-10 w-10 items-center justify-center rounded-full"
type="button"
aria-label="Close profile modal"
>
&times;
</button>
</header>
<div class="flex flex-1 flex-col gap-lg lg:flex-row">
<nav
class="grid gap-xs sm:grid-cols-3 lg:flex lg:w-56 lg:flex-col"
aria-label="Profile sections"
>
<button
class="btn-ghost focus-ring justify-start"
type="button"
data-state="active"
aria-controls="profilePaneAccount"
>
Account
</button>
<button
class="btn-ghost focus-ring justify-start"
type="button"
aria-controls="profilePaneRelays"
>
Relays
</button>
<button
class="btn-ghost focus-ring justify-start"
type="button"
aria-controls="profilePaneBlocked"
>
Blocked users
</button>
</nav>
<div
class="grid-stack flex-1 gap-md"
data-variant="cards"
>
<article
id="profilePaneAccount"
class="card space-y-sm p-md"
data-profile-pane
>
<div
class="flex items-start justify-between gap-sm"
>
<div>
<p
class="text-xs uppercase tracking-wide text-muted"
>
Current identity
</p>
<h5 class="text-base font-semibold">
npub1example...
</h5>
</div>
<span class="badge" data-variant="info"
>Active</span
>
</div>
<p class="text-sm text-muted">
`.grid-stack` arranges saved identities with
responsive gutters. Cards inherit `.focus-ring`
styles when they become actionable.
</p>
<footer class="flex flex-wrap gap-sm">
<button class="btn">Switch</button>
<button class="btn-ghost">Manage keys</button>
</footer>
</article>
<article
class="card space-y-sm p-md"
data-profile-pane
>
<h5 class="text-base font-semibold">
Relay presets
</h5>
<p class="text-sm text-muted">
Use badges to indicate status (e.g.
<span class="badge" data-variant="neutral"
>New</span
>) while keeping content spacing consistent with
the production modal.
</p>
<ul class="space-y-xs text-sm text-muted">
<li data-relay-row>relay.example.com</li>
<li data-relay-row>wss://nostr.tools</li>
<li data-relay-row>wss://bitvid.relays</li>
</ul>
</article>
</div>
</div>
<footer
class="card flex flex-wrap items-center justify-between gap-sm p-md"
>
<p class="text-xs text-muted">
Footer keeps shared spacing tokens while exposing
legacy hooks like `data-profile-action` for scripts if
needed.
</p>
<div class="flex gap-sm">
<button
class="btn-ghost"
data-profile-action="cancel"
>
Cancel
</button>
<button class="btn" data-profile-action="save">
Save changes
</button>
</div>
</footer>
</div>
</div>
</div>
</div>
</div>
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Edit Video Modal</h3>
<p class="text-sm text-muted">
Layout reflects `components/edit-video-modal.html`. The
`.bv-modal__panel.modal-content` pairing keeps backwards
selectors alive while new primitives manage spacing and
typography.
</p>
<div
class="relative isolate overflow-hidden rounded-xl bg-surface-alt p-lg text-text shadow-inner"
>
<div class="absolute inset-0" style="position: relative">
<div
id="editVideoModal"
class="bv-modal items-start"
style="position: absolute; inset: 0"
role="dialog"
aria-labelledby="editVideoModalTitle"
>
<div class="bv-modal-backdrop"></div>
<div
class="bv-modal__panel modal-content flex max-h-[80vh] flex-col gap-lg"
data-modal="edit"
>
<header
class="card flex items-center justify-between gap-sm p-md"
>
<div class="space-y-xs">
<p class="text-xs uppercase tracking-wide text-muted">
Edit metadata
</p>
<h4
id="editVideoModalTitle"
class="text-lg font-semibold"
>
Update video note
</h4>
</div>
<button
class="focus-ring inline-flex h-10 w-10 items-center justify-center rounded-full"
type="button"
aria-label="Close edit modal"
>
&times;
</button>
</header>
<div class="space-y-lg px-md">
<p class="text-sm text-muted">
Form rows reuse `.card` groupings and `.btn-ghost`
action chips. Locked fields can keep
`data-edit-target` attributes to stay compatible with
controller toggles.
</p>
<div class="card space-y-md p-md">
<div class="space-y-xs">
<label
class="text-sm font-medium text-text"
for="editTitleDemo"
>
Title
</label>
<div class="flex flex-wrap gap-sm sm:flex-nowrap">
<input
id="editTitleDemo"
class="input flex-1"
type="text"
placeholder="Bitvid Weekly Update"
/>
<button
class="btn-ghost focus-ring"
type="button"
data-edit-target="title"
>
Edit field
</button>
</div>
</div>
<div
class="grid gap-md md:grid-cols-2"
data-nip71-grid
>
<div class="space-y-xs">
<label
class="text-sm font-medium text-text"
for="editKindDemo"
>
NIP-71 kind
</label>
<div
class="card space-y-xs p-sm"
data-nip71-fieldset
>
<label class="flex items-center gap-xs text-sm">
<input
class="focus-ring"
type="radio"
name="editKindDemo"
checked
/>
Kind 21 — Full video
</label>
<label class="flex items-center gap-xs text-sm">
<input
class="focus-ring"
type="radio"
name="editKindDemo"
/>
Kind 22 — Short
</label>
</div>
</div>
<div class="space-y-xs">
<label
class="text-sm font-medium text-text"
for="editPublishDemo"
>
Published at
</label>
<input
id="editPublishDemo"
class="input"
type="datetime-local"
value="2024-05-01T15:00"
/>
<p class="text-xs text-muted">
Shared input tokens handle spacing; legacy
scripts can read the timestamp via
`data-nip71-input` if needed.
</p>
</div>
</div>
</div>
</div>
<footer
class="card flex flex-wrap items-center justify-between gap-sm p-md"
>
<p class="text-xs text-muted">
Footer actions mix `.btn` variants while referencing
`data-submit-state` for controller hand-offs.
</p>
<div class="flex gap-sm">
<button class="btn-ghost" data-submit-state="idle">
Cancel
</button>
<button class="btn" data-submit-state="pending">
Save draft
</button>
</div>
</footer>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="space-y-md">
<div class="space-y-xs">
<h2 class="text-xl font-semibold text-text-strong">Grid Stack</h2>
<p class="text-sm text-muted">
Use `data-variant="cards"` for responsive columns or
`data-orientation="vertical"` for flex stacks.
</p>
</div>
<div class="grid gap-lg lg:grid-cols-2">
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Card Grid</h3>
<div class="grid-stack" data-variant="cards">
<div class="card p-md">
<p class="text-sm text-muted">Card A</p>
</div>
<div class="card p-md">
<p class="text-sm text-muted">Card B</p>
</div>
<div class="card p-md">
<p class="text-sm text-muted">Card C</p>
</div>
</div>
</div>
<div class="space-y-sm">
<h3 class="text-lg font-semibold">Vertical Stack</h3>
<div class="grid-stack" data-orientation="vertical">
<div class="card p-sm">
<p class="text-sm text-muted">Row 1</p>
</div>
<div class="card p-sm">
<p class="text-sm text-muted">Row 2</p>
</div>
<div class="card p-sm" data-state="disabled">
<p class="text-sm text-muted">Row 3 (disabled)</p>
</div>
</div>
</div>
</div>
</section>
</main>
</div>
<script>
(() => {
const storageKey = "bitvid-docs-theme";
const root = document.documentElement;
const switcher = document.getElementById("themeSwitcher");
const applyTheme = (value) => {
if (!value || value === "default") {
delete root.dataset.theme;
} else {
root.dataset.theme = value;
}
};
let savedTheme;
try {
savedTheme = window.localStorage.getItem(storageKey) || undefined;
} catch (error) {
savedTheme = undefined;
}
applyTheme(savedTheme);
if (switcher) {
switcher.value = savedTheme ?? "default";
switcher.addEventListener("change", (event) => {
const { value } = event.target;
applyTheme(value);
try {
if (!value || value === "default") {
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, value);
}
} catch (error) {
/* Ignore storage failures (private mode, etc.) */
}
});
}
})();
</script>
</body>
</html>