CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 52s
CI / migration-smoke (pull_request) Successful in 2m20s
CI / image-build (pull_request) Successful in 1m5s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / package-publication (pull_request) Successful in 58s
CI / deploy-smoke (split) (pull_request) Successful in 2m38s
The theme foundation DESIGN.md requires (ADR 0060): - A design token may carry a `dark` value and a `reduced` value. The token stylesheet selects them in CSS alone: dark under prefers-color-scheme unless the host chose light, or when it chose dark; motion durations zero under prefers-reduced-motion unless it allowed motion, or when it chose reduce. `color-scheme` travels with the tokens. System is live, an explicit choice wins, and every module root follows through custom-property inheritance, with no module contract change. - @ekklesia/design-tokens adds a small appearance API: system/light/dark and system/reduce/allow, parsers for untrusted stored values, resolvers, and a controller that applies the attributes immediately. The shell applies it before React renders. That is the hook the account preferences work will feed; nothing is persisted. The Dashboard page declares `color-scheme: light dark` so a dark system never flashes white. Displays stay light. - A designed dark theme: teal-tinted charcoal, never pure black or white, the same teal accent. - WCAG AA contrast is tested for 41 element pairings in both schemes. Two light pairings failed and are fixed visibly: text.muted darkens, and form controls get a new border.control (3:1). background.placeholder keeps skeletons visible in dark. - Motion tokens (duration-short, duration-ambient) replace literal durations and every per-element reduced-motion rule. radius.scale multiplies sm/md/lg, unchanged at its default of 1. - The sign-in card and module loading/error text move from literal colours to tokens. Guards keep raw colours and literal radii out of Dashboard product source and @ekklesia/ui. - Fixes ConfirmationDialog's phrase input, which used a class that does not exist (`ek-input`) and so was unstyled. Co-Authored-By: Claude Opus 5.5 <[email protected]>
827 lines
22 KiB
TypeScript
827 lines
22 KiB
TypeScript
import { designToken } from "@ekklesia/design-tokens";
|
|
|
|
/**
|
|
* Every Ekklesia element's presentation, expressed once as a stylesheet built entirely from the
|
|
* global semantic tokens.
|
|
*
|
|
* ## Why a stylesheet string rather than a `.css` file
|
|
*
|
|
* These elements are mounted in two very different places: a document (the Dashboard shell, a
|
|
* standalone host page) and an isolated shadow root (a module's own UI artifact, a contributed
|
|
* renderer). A `.css` file would need a bundler on both sides and a served asset on one of them; a
|
|
* string is installed into whichever root the caller actually has, with the identical bytes. It is
|
|
* also what lets `:focus-visible`, `:hover`, and `:disabled` be real CSS rather than JavaScript
|
|
* approximations of them — which is the difference between a focus treatment that works for a
|
|
* keyboard user and one that only looks like it does.
|
|
*
|
|
* ## Every value here is a token reference
|
|
*
|
|
* There is no literal colour, font stack, radius, or shadow below: each is `var(--ekklesia-…)`
|
|
* resolved from the registry a host installs. That is enforced by a test, not by review, so a
|
|
* hardcoded brand colour cannot quietly enter the shared elements and make a future theme editor a
|
|
* lie. Structural values that carry no theme meaning — a `0`, a `100%`, a `flex` — stay literal on
|
|
* purpose: they are layout, not theme.
|
|
*/
|
|
export const EKKLESIA_UI_STYLESHEET = `
|
|
.ek-root {
|
|
color: ${designToken("text.primary")};
|
|
font-family: ${designToken("typography.family-sans")};
|
|
font-size: ${designToken("typography.size-md")};
|
|
line-height: ${designToken("typography.line-height-normal")};
|
|
}
|
|
|
|
.ek-stack {
|
|
display: grid;
|
|
gap: ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-focusable:focus-visible {
|
|
outline: ${designToken("state.focus-visible-width")} solid ${designToken("accent.focus")};
|
|
outline-offset: ${designToken("state.focus-visible-offset")};
|
|
}
|
|
|
|
.ek-button {
|
|
align-items: center;
|
|
border: ${designToken("border.width")} solid transparent;
|
|
border-radius: ${designToken("radius.md")};
|
|
box-sizing: border-box;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
font-family: inherit;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
gap: ${designToken("spacing.sm")};
|
|
height: ${designToken("control.height-md")};
|
|
justify-content: center;
|
|
line-height: ${designToken("typography.line-height-tight")};
|
|
padding-inline: ${designToken("control.padding-inline")};
|
|
}
|
|
|
|
.ek-button--sm {
|
|
font-size: ${designToken("typography.size-xs")};
|
|
height: ${designToken("control.height-sm")};
|
|
}
|
|
|
|
.ek-button--lg {
|
|
font-size: ${designToken("typography.size-md")};
|
|
height: ${designToken("control.height-lg")};
|
|
}
|
|
|
|
.ek-button--primary {
|
|
background: ${designToken("accent.base")};
|
|
color: ${designToken("accent.foreground")};
|
|
}
|
|
|
|
.ek-button--primary:hover:not(:disabled) {
|
|
background: ${designToken("accent.hover")};
|
|
}
|
|
|
|
.ek-button--primary:active:not(:disabled) {
|
|
background: ${designToken("accent.active")};
|
|
}
|
|
|
|
.ek-button--secondary {
|
|
background: ${designToken("background.surface")};
|
|
border-color: ${designToken("border.base")};
|
|
color: ${designToken("text.primary")};
|
|
}
|
|
|
|
.ek-button--secondary:hover:not(:disabled) {
|
|
background: ${designToken("state.hover-background")};
|
|
}
|
|
|
|
.ek-button--secondary:active:not(:disabled) {
|
|
background: ${designToken("state.pressed-background")};
|
|
}
|
|
|
|
.ek-button--quiet {
|
|
background: transparent;
|
|
color: ${designToken("text.secondary")};
|
|
}
|
|
|
|
.ek-button--quiet:hover:not(:disabled) {
|
|
background: ${designToken("state.hover-background")};
|
|
color: ${designToken("text.primary")};
|
|
}
|
|
|
|
.ek-button--quiet:active:not(:disabled) {
|
|
background: ${designToken("state.pressed-background")};
|
|
}
|
|
|
|
.ek-button--danger {
|
|
background: ${designToken("status.danger-background")};
|
|
border-color: ${designToken("status.danger-border")};
|
|
color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-button--danger:hover:not(:disabled) {
|
|
border-color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-button[aria-pressed="true"] {
|
|
background: ${designToken("state.selected-background")};
|
|
color: ${designToken("state.selected-foreground")};
|
|
}
|
|
|
|
.ek-button:disabled {
|
|
cursor: not-allowed;
|
|
opacity: ${designToken("state.disabled-opacity")};
|
|
}
|
|
|
|
.ek-icon-button {
|
|
aspect-ratio: 1;
|
|
padding-inline: 0;
|
|
width: ${designToken("control.height-md")};
|
|
}
|
|
|
|
.ek-field {
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-field__label {
|
|
color: ${designToken("text.primary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
}
|
|
|
|
.ek-field__hint {
|
|
color: ${designToken("text.muted")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
}
|
|
|
|
.ek-field__error {
|
|
color: ${designToken("text.danger")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
}
|
|
|
|
.ek-field__error::before {
|
|
content: ${designToken("status.danger-symbol")};
|
|
margin-inline-end: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-control {
|
|
background: ${designToken("background.surface")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.control")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-sizing: border-box;
|
|
color: ${designToken("text.primary")};
|
|
font-family: inherit;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
height: ${designToken("control.height-md")};
|
|
padding-inline: ${designToken("control.padding-inline")};
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-control:hover:not(:disabled) {
|
|
border-color: ${designToken("border.strong")};
|
|
}
|
|
|
|
.ek-control::placeholder {
|
|
color: ${designToken("text.muted")};
|
|
}
|
|
|
|
.ek-control:disabled {
|
|
cursor: not-allowed;
|
|
opacity: ${designToken("state.disabled-opacity")};
|
|
}
|
|
|
|
.ek-control[aria-invalid="true"] {
|
|
border-color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-checkbox {
|
|
align-items: center;
|
|
color: ${designToken("text.primary")};
|
|
display: inline-flex;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-checkbox__input {
|
|
accent-color: ${designToken("accent.base")};
|
|
height: ${designToken("spacing.lg")};
|
|
width: ${designToken("spacing.lg")};
|
|
}
|
|
|
|
.ek-checkbox__input:disabled {
|
|
cursor: not-allowed;
|
|
opacity: ${designToken("state.disabled-opacity")};
|
|
}
|
|
|
|
.ek-panel {
|
|
background: ${designToken("background.surface")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.lg")};
|
|
box-shadow: ${designToken("shadow.sm")};
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
gap: ${designToken("spacing.lg")};
|
|
padding: ${designToken("spacing.xl")};
|
|
}
|
|
|
|
.ek-panel--raised {
|
|
background: ${designToken("background.raised")};
|
|
box-shadow: ${designToken("shadow.md")};
|
|
}
|
|
|
|
.ek-panel__header {
|
|
align-items: flex-start;
|
|
display: flex;
|
|
gap: ${designToken("spacing.md")};
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.ek-panel__heading {
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-panel__title {
|
|
font-size: ${designToken("typography.size-heading-3")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
line-height: ${designToken("typography.line-height-tight")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-panel__description {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-dialog {
|
|
background: ${designToken("background.raised")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.lg")};
|
|
box-shadow: ${designToken("shadow.lg")};
|
|
color: ${designToken("text.primary")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.lg")};
|
|
max-width: 32rem;
|
|
padding: ${designToken("spacing.xl")};
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-dialog__scrim {
|
|
align-items: center;
|
|
background: ${designToken("background.overlay")};
|
|
display: flex;
|
|
inset: 0;
|
|
justify-content: center;
|
|
padding: ${designToken("spacing.lg")};
|
|
position: fixed;
|
|
}
|
|
|
|
.ek-dialog__title {
|
|
font-size: ${designToken("typography.size-heading-2")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
line-height: ${designToken("typography.line-height-tight")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-dialog__actions {
|
|
display: flex;
|
|
gap: ${designToken("spacing.sm")};
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.ek-tabs__list {
|
|
border-bottom: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
display: flex;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-tab {
|
|
background: transparent;
|
|
border: 0;
|
|
border-bottom: ${designToken("state.focus-visible-width")} solid transparent;
|
|
color: ${designToken("text.secondary")};
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
border-radius: ${designToken("radius.md")} ${designToken("radius.md")} 0 0;
|
|
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-tab:hover {
|
|
background: ${designToken("state.hover-background")};
|
|
color: ${designToken("text.primary")};
|
|
}
|
|
|
|
.ek-tab[aria-selected="true"] {
|
|
background: ${designToken("accent.subtle")};
|
|
border-bottom-color: ${designToken("accent.base")};
|
|
color: ${designToken("state.selected-foreground")};
|
|
}
|
|
|
|
.ek-tabs__panel {
|
|
padding-block-start: ${designToken("spacing.lg")};
|
|
}
|
|
|
|
.ek-table {
|
|
border-collapse: collapse;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-table th,
|
|
.ek-table td {
|
|
border-bottom: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
text-align: start;
|
|
}
|
|
|
|
.ek-table th {
|
|
color: ${designToken("text.secondary")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
}
|
|
|
|
.ek-table tbody tr:hover {
|
|
background: ${designToken("state.hover-background")};
|
|
}
|
|
|
|
.ek-table tbody tr[aria-selected="true"] {
|
|
background: ${designToken("state.selected-background")};
|
|
color: ${designToken("state.selected-foreground")};
|
|
}
|
|
|
|
.ek-list {
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.ek-list__item {
|
|
background: ${designToken("background.surface")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.divider")};
|
|
border-radius: ${designToken("radius.md")};
|
|
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-badge {
|
|
align-items: center;
|
|
border: ${designToken("border.width")} solid transparent;
|
|
border-radius: ${designToken("radius.pill")};
|
|
display: inline-flex;
|
|
font-size: ${designToken("typography.size-xs")};
|
|
font-weight: ${designToken("typography.weight-medium")};
|
|
gap: ${designToken("spacing.xs")};
|
|
padding: ${designToken("spacing.2xs")} ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-badge--neutral {
|
|
background: ${designToken("background.inset")};
|
|
border-color: ${designToken("border.base")};
|
|
color: ${designToken("text.secondary")};
|
|
}
|
|
|
|
.ek-badge--info {
|
|
background: ${designToken("status.info-background")};
|
|
border-color: ${designToken("status.info-border")};
|
|
color: ${designToken("accent.strong")};
|
|
}
|
|
|
|
.ek-badge--success {
|
|
background: ${designToken("status.success-background")};
|
|
border-color: ${designToken("status.success-border")};
|
|
color: ${designToken("text.success")};
|
|
}
|
|
|
|
.ek-badge--warning {
|
|
background: ${designToken("status.warning-background")};
|
|
border-color: ${designToken("status.warning-border")};
|
|
color: ${designToken("text.warning")};
|
|
}
|
|
|
|
.ek-badge--danger {
|
|
background: ${designToken("status.danger-background")};
|
|
border-color: ${designToken("status.danger-border")};
|
|
color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-badge__symbol {
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
.ek-toast {
|
|
background: ${designToken("background.raised")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-inline-start-width: ${designToken("spacing.xs")};
|
|
border-radius: ${designToken("radius.md")};
|
|
box-shadow: ${designToken("shadow.md")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
padding: ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-toast--success {
|
|
border-inline-start-color: ${designToken("status.success-border")};
|
|
}
|
|
|
|
.ek-toast--warning {
|
|
border-inline-start-color: ${designToken("status.warning-border")};
|
|
}
|
|
|
|
.ek-toast--danger {
|
|
border-inline-start-color: ${designToken("status.danger-border")};
|
|
}
|
|
|
|
.ek-toast--info {
|
|
border-inline-start-color: ${designToken("status.info-border")};
|
|
}
|
|
|
|
.ek-toast__title {
|
|
font-size: ${designToken("typography.size-sm")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
.ek-toast__body {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
}
|
|
|
|
.ek-toast__stack {
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-message {
|
|
background: ${designToken("background.canvas")};
|
|
align-items: center;
|
|
border: ${designToken("border.width")} dashed ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.lg")};
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
justify-items: center;
|
|
padding: ${designToken("spacing.xl")};
|
|
text-align: center;
|
|
}
|
|
|
|
.ek-page-header {
|
|
align-items: flex-end;
|
|
display: flex;
|
|
gap: ${designToken("spacing.xl")};
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.ek-page-header__copy {
|
|
display: grid;
|
|
gap: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-page-header__eyebrow {
|
|
color: ${designToken("accent.strong")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.ek-page-header__title {
|
|
font-size: ${designToken("typography.size-heading-1")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
line-height: ${designToken("typography.line-height-tight")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-page-header__description {
|
|
color: ${designToken("text.secondary")};
|
|
margin: 0;
|
|
max-width: 64ch;
|
|
}
|
|
|
|
.ek-page-header__actions,
|
|
.ek-action-bar {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-notice {
|
|
align-items: flex-start;
|
|
background: ${designToken("background.inset")};
|
|
border: ${designToken("border.width")} solid ${designToken("border.base")};
|
|
border-radius: ${designToken("radius.md")};
|
|
color: ${designToken("text.secondary")};
|
|
display: flex;
|
|
font-size: ${designToken("typography.size-sm")};
|
|
gap: ${designToken("spacing.sm")};
|
|
padding: ${designToken("spacing.md")};
|
|
}
|
|
|
|
.ek-notice--info {
|
|
background: ${designToken("status.info-background")};
|
|
border-color: ${designToken("status.info-border")};
|
|
}
|
|
|
|
.ek-notice--success {
|
|
background: ${designToken("status.success-background")};
|
|
border-color: ${designToken("status.success-border")};
|
|
}
|
|
|
|
.ek-notice--warning {
|
|
background: ${designToken("status.warning-background")};
|
|
border-color: ${designToken("status.warning-border")};
|
|
}
|
|
|
|
.ek-notice--danger {
|
|
background: ${designToken("status.danger-background")};
|
|
border-color: ${designToken("status.danger-border")};
|
|
color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-notice__symbol,
|
|
.ek-notice__title {
|
|
color: ${designToken("text.primary")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
}
|
|
|
|
.ek-notice__body {
|
|
display: grid;
|
|
gap: ${designToken("spacing.2xs")};
|
|
}
|
|
|
|
@media (max-width: 48rem) {
|
|
.ek-page-header,
|
|
.ek-panel__header {
|
|
align-items: stretch;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
.ek-message__title {
|
|
font-size: ${designToken("typography.size-lg")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-message__body {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: 0;
|
|
max-width: 40ch;
|
|
}
|
|
|
|
.ek-message--error {
|
|
border-color: ${designToken("status.danger-border")};
|
|
border-style: solid;
|
|
}
|
|
|
|
.ek-message--error .ek-message__title {
|
|
color: ${designToken("text.danger")};
|
|
}
|
|
|
|
.ek-message--error .ek-message__title::before {
|
|
content: ${designToken("status.danger-symbol")};
|
|
margin-inline-end: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-message--loading {
|
|
border-style: solid;
|
|
border-color: ${designToken("border.divider")};
|
|
}
|
|
|
|
.ek-spinner {
|
|
/* Spins at 0.625 of the ambient cycle (1s by default); not at all when motion is reduced. */
|
|
animation: ek-spin calc(${designToken("motion.duration-ambient")} * 0.625) linear infinite;
|
|
border: ${designToken("state.focus-visible-width")} solid ${designToken("border.base")};
|
|
border-block-start-color: ${designToken("accent.base")};
|
|
border-radius: ${designToken("radius.pill")};
|
|
height: ${designToken("spacing.xl")};
|
|
width: ${designToken("spacing.xl")};
|
|
}
|
|
|
|
.ek-visually-hidden {
|
|
border: 0;
|
|
clip-path: inset(50%);
|
|
height: 1px;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
padding: 0;
|
|
position: absolute;
|
|
white-space: nowrap;
|
|
width: 1px;
|
|
}
|
|
|
|
.ek-dialog__description {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-sm")};
|
|
margin: 0;
|
|
}
|
|
|
|
.ek-confirmation {
|
|
display: grid;
|
|
gap: ${designToken("spacing.lg")};
|
|
margin: 0;
|
|
}
|
|
|
|
/* A destructive confirmation says so with a symbol and a heavier frame, never by colour alone. */
|
|
.ek-confirmation--destructive {
|
|
border-color: ${designToken("status.danger-border")};
|
|
border-width: calc(${designToken("border.width")} * 2);
|
|
}
|
|
|
|
.ek-confirmation__symbol::before {
|
|
content: ${designToken("status.warning-symbol")};
|
|
font-weight: ${designToken("typography.weight-bold")};
|
|
margin-inline-end: ${designToken("spacing.xs")};
|
|
}
|
|
|
|
.ek-confirmation__phrase {
|
|
font-family: ${designToken("typography.family-mono")};
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.ek-toast-region {
|
|
box-sizing: border-box;
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
left: 50%;
|
|
max-width: calc(100vw - (${designToken("spacing.md")} * 2));
|
|
pointer-events: none;
|
|
position: fixed;
|
|
top: ${designToken("spacing.lg")};
|
|
transform: translateX(-50%);
|
|
width: 28rem;
|
|
z-index: 50;
|
|
}
|
|
|
|
.ek-toast-region__list {
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-toast-region .ek-toast {
|
|
animation: ek-toast-enter ${designToken("motion.duration-short")} ease-out;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.ek-toast {
|
|
align-items: start;
|
|
display: flex;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-toast__content {
|
|
display: grid;
|
|
flex: 1 1 auto;
|
|
gap: ${designToken("spacing.2xs")};
|
|
min-width: 0;
|
|
}
|
|
|
|
.ek-toast__dismiss {
|
|
flex: 0 0 auto;
|
|
min-width: ${designToken("control.height-sm")};
|
|
}
|
|
|
|
.ek-message__actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${designToken("spacing.sm")};
|
|
justify-content: center;
|
|
}
|
|
|
|
.ek-message__details {
|
|
color: ${designToken("text.secondary")};
|
|
font-size: ${designToken("typography.size-xs")};
|
|
max-width: 100%;
|
|
text-align: start;
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-message__details summary {
|
|
cursor: pointer;
|
|
min-height: ${designToken("control.height-sm")};
|
|
padding-block: ${designToken("spacing.2xs")};
|
|
}
|
|
|
|
.ek-message__details-text {
|
|
background: ${designToken("background.inset")};
|
|
border-radius: ${designToken("radius.sm")};
|
|
font-family: ${designToken("typography.family-mono")};
|
|
margin: ${designToken("spacing.xs")} 0 0;
|
|
max-height: 16rem;
|
|
overflow: auto;
|
|
padding: ${designToken("spacing.sm")};
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.ek-message__reference {
|
|
margin: ${designToken("spacing.xs")} 0 0;
|
|
}
|
|
|
|
.ek-page-header__back {
|
|
justify-self: start;
|
|
margin-inline-start: calc(-1 * ${designToken("control.padding-inline")});
|
|
text-decoration: none;
|
|
}
|
|
|
|
.ek-skeleton-group {
|
|
display: grid;
|
|
gap: ${designToken("spacing.sm")};
|
|
}
|
|
|
|
.ek-skeleton {
|
|
animation: ek-skeleton-pulse ${designToken("motion.duration-ambient")} ease-in-out infinite;
|
|
background: ${designToken("background.placeholder")};
|
|
border-radius: ${designToken("radius.sm")};
|
|
display: block;
|
|
}
|
|
|
|
.ek-skeleton--text {
|
|
height: 1em;
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-skeleton--block {
|
|
height: 6rem;
|
|
width: 100%;
|
|
}
|
|
|
|
.ek-skeleton--circle {
|
|
border-radius: ${designToken("radius.pill")};
|
|
height: 2.5rem;
|
|
width: 2.5rem;
|
|
}
|
|
|
|
@keyframes ek-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes ek-skeleton-pulse {
|
|
50% {
|
|
opacity: 0.55;
|
|
}
|
|
}
|
|
|
|
@keyframes ek-toast-enter {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-0.25rem);
|
|
}
|
|
}
|
|
|
|
/* Every animation above takes its duration from a motion token, which is zero when motion is
|
|
reduced — by the system, or by a person's own preference (ADR 0060) — so none of them needs a
|
|
reduced-motion rule of its own, and nothing here depends on movement to be understood. */
|
|
`;
|
|
|
|
/** Marks a stylesheet this package installed, so installation is idempotent per root. */
|
|
export const EKKLESIA_UI_STYLE_ATTRIBUTE = "data-ekklesia-ui";
|
|
|
|
/** A document or a shadow root — the two places these elements are ever mounted. Structural rather
|
|
* than nominal for the same reason the token installer's root type is. */
|
|
export interface EkklesiaUiStyleRoot {
|
|
querySelector(selectors: string): Element | null;
|
|
appendChild<Node_ extends Node>(node: Node_): Node_;
|
|
readonly ownerDocument?: Document | null;
|
|
}
|
|
|
|
function documentFor(root: EkklesiaUiStyleRoot): Document | undefined {
|
|
if (typeof (root as Partial<Document>).createElement === "function") {
|
|
return root as Document;
|
|
}
|
|
return root.ownerDocument ?? undefined;
|
|
}
|
|
|
|
/**
|
|
* Installs the element stylesheet into one root, once, and returns a remover for exactly what it
|
|
* inserted.
|
|
*
|
|
* The React equivalent is `<EkklesiaUiStyles />`, which is what an artifact renders so its styles
|
|
* land inside its own isolated root rather than in the host's document. This imperative form exists
|
|
* for a host that owns a document directly.
|
|
*/
|
|
export function installEkklesiaUiStyles(root: EkklesiaUiStyleRoot): () => void {
|
|
if (root.querySelector(`style[${EKKLESIA_UI_STYLE_ATTRIBUTE}]`)) {
|
|
return () => undefined;
|
|
}
|
|
const ownerDocument = documentFor(root);
|
|
if (!ownerDocument) {
|
|
return () => undefined;
|
|
}
|
|
const style = ownerDocument.createElement("style");
|
|
style.setAttribute(EKKLESIA_UI_STYLE_ATTRIBUTE, "");
|
|
style.textContent = EKKLESIA_UI_STYLESHEET;
|
|
const target =
|
|
(ownerDocument === (root as unknown as Document) ? ownerDocument.head : undefined) ?? root;
|
|
target.appendChild(style);
|
|
|
|
let removed = false;
|
|
return () => {
|
|
if (removed) {
|
|
return;
|
|
}
|
|
removed = true;
|
|
style.remove();
|
|
};
|
|
}
|