feat: establish shared Ekklesia design system
CI / verify (pull_request) Successful in 48s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 1m47s

This commit is contained in:
ekklesia
2026-09-30 18:21:09 -05:00
parent 3ca6b2cb32
commit 3a26ef49ad
9 changed files with 336 additions and 110 deletions
+60 -59
View File
@@ -1,3 +1,5 @@
import { designToken } from "@ekklesia/design-tokens";
import { Button, Field, TextInput } from "@ekklesia/ui";
import { type FormEvent, type ReactElement, useCallback, useEffect, useState } from "react";
import {
approveDevicePairing,
@@ -19,33 +21,27 @@ import {
* a viewer without it is told nothing about which devices exist.
*/
const panelStyles = {
action: {
background: "transparent",
border: "1px solid #d6d3d1",
borderRadius: "0.25rem",
cursor: "pointer",
font: "inherit",
padding: "0.25rem 0.5rem",
meta: {
color: designToken("text.muted"),
fontSize: designToken("typography.size-xs"),
margin: 0,
},
input: {
border: "1px solid #d6d3d1",
borderRadius: "0.25rem",
font: "inherit",
minWidth: 0,
padding: "0.25rem 0.5rem",
width: "100%",
},
meta: { color: "#78716c", fontSize: "0.8125rem", margin: 0 },
row: {
alignItems: "center",
borderTop: "1px solid #e7e5e4",
borderTop: `${designToken("border.width")} solid ${designToken("border.divider")}`,
display: "flex",
gap: "0.5rem",
gap: designToken("spacing.sm"),
justifyContent: "space-between",
padding: "0.375rem 0",
padding: `${designToken("spacing.sm")} 0`,
},
section: { display: "grid", gap: designToken("spacing.sm") },
title: {
color: designToken("text.secondary"),
fontSize: designToken("typography.size-xs"),
fontWeight: designToken("typography.weight-bold"),
letterSpacing: "0.05em",
margin: 0,
},
section: { display: "grid", gap: "0.5rem" },
title: { fontSize: "0.8125rem", fontWeight: 600, letterSpacing: "0.04em", margin: 0 },
} as const;
type PairingStep =
@@ -123,52 +119,58 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
<section data-testid="host-devices" style={panelStyles.section}>
<h2 style={panelStyles.title}>DISPLAY DEVICES</h2>
<form onSubmit={(event) => void lookUp(event)} style={{ display: "grid", gap: "0.375rem" }}>
<label htmlFor="device-pairing-code" style={panelStyles.meta}>
Pairing code shown on the screen
</label>
<input
autoComplete="off"
data-testid="device-pairing-code-input"
id="device-pairing-code"
inputMode="numeric"
maxLength={6}
onChange={(event) => setCode(event.target.value)}
style={panelStyles.input}
value={code}
/>
<button data-testid="device-pairing-lookup" style={panelStyles.action} type="submit">
<form
onSubmit={(event) => void lookUp(event)}
style={{ display: "grid", gap: designToken("spacing.sm") }}
>
<Field label="Pairing code shown on the screen">
{(control) => (
<TextInput
{...control}
autoComplete="off"
data-testid="device-pairing-code-input"
inputMode="numeric"
maxLength={6}
onChange={(event) => setCode(event.target.value)}
value={code}
/>
)}
</Field>
<Button data-testid="device-pairing-lookup" size="sm" type="submit">
Find display
</button>
</Button>
</form>
{pairing.step === "found" ? (
<div data-testid="device-pairing-found" style={{ display: "grid", gap: "0.375rem" }}>
<div
data-testid="device-pairing-found"
style={{ display: "grid", gap: designToken("spacing.sm") }}
>
<p style={panelStyles.meta}>
A display is asking to show <strong>{pairing.moduleId}</strong>.
</p>
<label htmlFor="device-pairing-name" style={panelStyles.meta}>
Name this display
</label>
<input
autoComplete="off"
data-testid="device-pairing-name"
id="device-pairing-name"
maxLength={128}
onChange={(event) => setName(event.target.value)}
placeholder="Foyer TV"
style={panelStyles.input}
value={name}
/>
<button
<Field label="Name this display">
{(control) => (
<TextInput
{...control}
autoComplete="off"
data-testid="device-pairing-name"
maxLength={128}
onChange={(event) => setName(event.target.value)}
placeholder="Foyer TV"
value={name}
/>
)}
</Field>
<Button
data-testid="device-pairing-approve"
disabled={name.trim().length === 0}
onClick={() => void approve()}
style={panelStyles.action}
type="button"
size="sm"
variant="primary"
>
Approve
</button>
</Button>
</div>
) : undefined}
@@ -191,14 +193,13 @@ export function DevicePanel(props: DevicePanelProps): ReactElement | undefined {
{device.revoked ? " (revoked)" : ""}
</span>
{device.revoked ? undefined : (
<button
<Button
data-testid={`device-revoke-${device.id}`}
onClick={() => void revoke(device.id)}
style={panelStyles.action}
type="button"
size="sm"
>
Revoke
</button>
</Button>
)}
</div>
))}