feat: establish shared Ekklesia design system
This commit is contained in:
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user