@sierragridteam/design-system 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/assets/sierra-logo-full.png +0 -0
- package/assets/sierra-mark.png +0 -0
- package/package.json +36 -4
- package/src/components/Badge.astro +13 -0
- package/src/components/Button.astro +36 -0
- package/src/components/Callout.astro +17 -0
- package/src/components/Card.astro +33 -0
- package/src/components/CopyButton.astro +81 -0
- package/src/components/DataDiff.astro +50 -0
- package/src/components/DataRow.astro +23 -0
- package/src/components/DataStatus.astro +26 -0
- package/src/components/DataTable.astro +25 -0
- package/src/components/EmptyState.astro +17 -0
- package/src/components/Field.astro +25 -0
- package/src/components/FilterBar.astro +10 -0
- package/src/components/GeoJSONMap.astro +280 -0
- package/src/components/GeometryPreview.astro +11 -0
- package/src/components/Icon.astro +19 -0
- package/src/components/IconSprite.astro +68 -0
- package/src/components/IdentifierList.astro +54 -0
- package/src/components/Logo.astro +75 -0
- package/src/components/MapFrame.astro +25 -0
- package/src/components/MetricGrid.astro +35 -0
- package/src/components/PageHeader.astro +24 -0
- package/src/components/Select.astro +202 -0
- package/src/components/ValueTable.astro +32 -0
- package/src/lib/basemap-style.ts +269 -0
- package/src/lib/controls.ts +126 -0
- package/src/lib/data-values.ts +45 -0
- package/src/lib/dialog.ts +85 -0
- package/src/lib/dom.ts +68 -0
- package/src/lib/toast.ts +22 -0
- package/src/styles/base.css +165 -0
- package/src/styles/components.css +2235 -0
- package/src/styles/global.css +4 -0
- package/src/styles/shell.css +501 -0
- package/src/styles/tokens.css +172 -0
package/README.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SIERRA design system
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared Astro components, CSS tokens, and browser UI helpers.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @sierragridteam/design-system
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Import components from `@sierragridteam/design-system/components/Button.astro`,
|
|
10
|
+
styles from `@sierragridteam/design-system/styles/global.css`, and browser helpers
|
|
11
|
+
from `@sierragridteam/design-system/lib/controls`.
|
|
12
|
+
|
|
13
|
+
See https://github.com/sierragridteam/sierra-ds for the demo and usage documentation.
|
|
14
|
+
|
|
15
|
+
Copyright © 2026 Signal Integrity & Emergency Radio Response Alliance.
|
|
16
|
+
All rights reserved. Public availability does not grant permission to reuse
|
|
17
|
+
code, design, name, or brand assets. Contact info@sierragridteam.org for permission.
|
|
18
|
+
Third-party dependencies retain their own licenses.
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sierragridteam/design-system",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"src",
|
|
7
|
+
"assets"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
"./components/*": "./src/components/*",
|
|
11
|
+
"./styles/*": "./src/styles/*",
|
|
12
|
+
"./lib/*": "./src/lib/*.ts",
|
|
13
|
+
"./assets/*": "./assets/*"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"astro": "^7.3.4",
|
|
17
|
+
"maplibre-gl": "^5.24.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependenciesMeta": {
|
|
20
|
+
"maplibre-gl": {
|
|
21
|
+
"optional": true
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@types/geojson": "^7946.0.16"
|
|
26
|
+
},
|
|
27
|
+
"license": "UNLICENSED",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/sierragridteam/sierra-ds.git",
|
|
31
|
+
"directory": "packages/design-system"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "f90196f9027ad76d88633445fe0a9817ffbd82f9"
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Badge — status label with a dot. Tones map to the shared state colours. */
|
|
3
|
+
interface Props {
|
|
4
|
+
tone?: 'ok' | 'elevated' | 'alarm' | 'deep' | 'muted';
|
|
5
|
+
dot?: boolean;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
const { tone = 'muted', dot = true, class: cls } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<span class:list={['badge', tone !== 'muted' && `badge--${tone}`, !dot && 'badge--plain', cls]}>
|
|
12
|
+
<slot />
|
|
13
|
+
</span>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Button — renders <a> when href is given, else <button>. Emits the global .btn classes. */
|
|
3
|
+
interface Props {
|
|
4
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'outline' | 'danger';
|
|
5
|
+
size?: 'md' | 'sm';
|
|
6
|
+
href?: string;
|
|
7
|
+
type?: 'button' | 'submit';
|
|
8
|
+
block?: boolean;
|
|
9
|
+
icon?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
const {
|
|
15
|
+
variant = 'primary',
|
|
16
|
+
size = 'md',
|
|
17
|
+
href,
|
|
18
|
+
type = 'button',
|
|
19
|
+
block = false,
|
|
20
|
+
icon,
|
|
21
|
+
disabled,
|
|
22
|
+
class: cls,
|
|
23
|
+
...rest
|
|
24
|
+
} = Astro.props;
|
|
25
|
+
const Tag = href ? 'a' : 'button';
|
|
26
|
+
const classes = ['btn', `btn--${variant}`, size === 'sm' && 'btn--sm', block && 'btn--block', cls];
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<Tag class:list={classes} href={href} type={href ? undefined : type} disabled={disabled} {...rest}>
|
|
30
|
+
{icon && (
|
|
31
|
+
<svg class="icon" aria-hidden="true">
|
|
32
|
+
<use href={`#i-${icon}`} />
|
|
33
|
+
</svg>
|
|
34
|
+
)}
|
|
35
|
+
<slot />
|
|
36
|
+
</Tag>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Callout — banner surface with a coloured left rule. Orange is for risk only. */
|
|
3
|
+
interface Props {
|
|
4
|
+
tone?: 'green' | 'brass' | 'orange';
|
|
5
|
+
title?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
const { tone = 'green', title, class: cls } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
class:list={['callout', tone !== 'green' && `callout--${tone}`, cls]}
|
|
13
|
+
role={tone === 'orange' ? 'alert' : undefined}
|
|
14
|
+
>
|
|
15
|
+
{title && <span class="callout__title">{title}</span>}
|
|
16
|
+
<slot />
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Card — flat panel: card surface, tan border, 4px radius, never a shadow. */
|
|
3
|
+
interface Props {
|
|
4
|
+
title?: string;
|
|
5
|
+
padded?: boolean;
|
|
6
|
+
surface?: 'card' | 'raised' | 'banner';
|
|
7
|
+
as?: 'div' | 'section' | 'article';
|
|
8
|
+
class?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
}
|
|
11
|
+
const { title, padded = true, surface = 'card', as: Tag = 'div', class: cls, id } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<Tag class:list={['card', surface !== 'card' && `card--${surface}`, cls]} id={id}>
|
|
15
|
+
{(title || Astro.slots.has('actions')) && (
|
|
16
|
+
<div class="card__header">
|
|
17
|
+
{title && <h3 class="card__title">{title}</h3>}
|
|
18
|
+
<slot name="actions" />
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
{padded ? (
|
|
22
|
+
<div class="card__body">
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
) : (
|
|
26
|
+
<slot />
|
|
27
|
+
)}
|
|
28
|
+
{Astro.slots.has('footer') && (
|
|
29
|
+
<div class="card__footer">
|
|
30
|
+
<slot name="footer" />
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
</Tag>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Icon from "./Icon.astro";
|
|
3
|
+
interface Props {
|
|
4
|
+
value: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
const { value, label = "Copy value" } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<sierra-copy data-value={value}>
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="btn btn--ghost btn--sm btn--icon"
|
|
14
|
+
aria-label={label}
|
|
15
|
+
title={label}
|
|
16
|
+
>
|
|
17
|
+
<span data-copy-icon>
|
|
18
|
+
<Icon name="copy" />
|
|
19
|
+
</span>
|
|
20
|
+
<span data-copy-check hidden>
|
|
21
|
+
<Icon name="check" />
|
|
22
|
+
</span>
|
|
23
|
+
</button>
|
|
24
|
+
<span class="sr-only" role="status" aria-live="polite"></span>
|
|
25
|
+
</sierra-copy>
|
|
26
|
+
<script>
|
|
27
|
+
class SierraCopy extends HTMLElement {
|
|
28
|
+
controller?: AbortController;
|
|
29
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
this.controller?.abort();
|
|
32
|
+
this.controller = new AbortController();
|
|
33
|
+
const { signal } = this.controller;
|
|
34
|
+
const button = this.querySelector<HTMLButtonElement>("button")!;
|
|
35
|
+
const icon = this.querySelector<HTMLElement>("[data-copy-icon]")!;
|
|
36
|
+
const check = this.querySelector<HTMLElement>("[data-copy-check]")!;
|
|
37
|
+
const status = this.querySelector<HTMLElement>("[role=status]")!;
|
|
38
|
+
const originalTitle = button.title;
|
|
39
|
+
button.addEventListener(
|
|
40
|
+
"click",
|
|
41
|
+
async () => {
|
|
42
|
+
clearTimeout(this.timer);
|
|
43
|
+
icon.hidden = false;
|
|
44
|
+
check.hidden = true;
|
|
45
|
+
status.className = "sr-only";
|
|
46
|
+
status.textContent = "";
|
|
47
|
+
button.disabled = true;
|
|
48
|
+
try {
|
|
49
|
+
await navigator.clipboard.writeText(this.dataset.value ?? "");
|
|
50
|
+
if (signal.aborted) return;
|
|
51
|
+
icon.hidden = true;
|
|
52
|
+
check.hidden = false;
|
|
53
|
+
status.textContent = "Copied.";
|
|
54
|
+
button.title = "Copied";
|
|
55
|
+
this.timer = setTimeout(() => {
|
|
56
|
+
icon.hidden = false;
|
|
57
|
+
check.hidden = true;
|
|
58
|
+
button.title = originalTitle;
|
|
59
|
+
status.textContent = "";
|
|
60
|
+
}, 1800);
|
|
61
|
+
} catch {
|
|
62
|
+
if (signal.aborted) return;
|
|
63
|
+
status.className = "ds-copy-error";
|
|
64
|
+
status.textContent =
|
|
65
|
+
"Could not copy. Select the text to copy manually.";
|
|
66
|
+
button.title = originalTitle;
|
|
67
|
+
} finally {
|
|
68
|
+
if (!signal.aborted) button.disabled = false;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{ signal },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
disconnectedCallback() {
|
|
75
|
+
this.controller?.abort();
|
|
76
|
+
clearTimeout(this.timer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!customElements.get("sierra-copy"))
|
|
80
|
+
customElements.define("sierra-copy", SierraCopy);
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { formatDataValue, type DataChange } from "../lib/data-values";
|
|
3
|
+
interface Props {
|
|
4
|
+
label: string;
|
|
5
|
+
changes: DataChange[];
|
|
6
|
+
emptyMessage?: string;
|
|
7
|
+
}
|
|
8
|
+
const { label, changes, emptyMessage = "No changes." } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<section class="ds-diff" aria-label={label}>
|
|
12
|
+
<div class="ds-diff-heading">
|
|
13
|
+
<strong>{label}</strong>
|
|
14
|
+
<span class="meta">
|
|
15
|
+
{changes.length} {changes.length === 1 ? "change" : "changes"}
|
|
16
|
+
</span>
|
|
17
|
+
</div>
|
|
18
|
+
{changes.length ? (
|
|
19
|
+
<ol class="ds-diff-list">
|
|
20
|
+
{changes.map((change) => (
|
|
21
|
+
<li data-kind={change.kind}>
|
|
22
|
+
<code class="ds-diff-field">{change.path || "(root)"}</code>
|
|
23
|
+
<div class="ds-diff-values">
|
|
24
|
+
<span class="sr-only">
|
|
25
|
+
{change.kind === "added"
|
|
26
|
+
? "Added: "
|
|
27
|
+
: change.kind === "removed"
|
|
28
|
+
? "Removed: "
|
|
29
|
+
: "Before: "}
|
|
30
|
+
</span>
|
|
31
|
+
{change.kind !== "added" && (
|
|
32
|
+
<del class="ds-diff-before">{formatDataValue(change.before)}</del>
|
|
33
|
+
)}{" "}
|
|
34
|
+
<span class="ds-diff-arrow" aria-hidden="true">
|
|
35
|
+
{change.kind === "added" ? "+" : "→"}
|
|
36
|
+
</span>{" "}
|
|
37
|
+
{change.kind === "changed" && <span class="sr-only">After: </span>}
|
|
38
|
+
{change.kind === "removed" ? (
|
|
39
|
+
<span class="ds-diff-removed">removed</span>
|
|
40
|
+
) : (
|
|
41
|
+
<ins class="ds-diff-after">{formatDataValue(change.after)}</ins>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
</ol>
|
|
47
|
+
) : (
|
|
48
|
+
<p class="ds-data-note">{emptyMessage}</p>
|
|
49
|
+
)}
|
|
50
|
+
</section>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** A record and its annotation are always one semantic row. Slot the remaining td cells. */
|
|
3
|
+
interface Props {
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
note?: string;
|
|
7
|
+
noteTone?: "neutral" | "caution";
|
|
8
|
+
}
|
|
9
|
+
const { label, description, note, noteTone = "neutral" } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<tr class="ds-data-row">
|
|
13
|
+
<th scope="row">
|
|
14
|
+
<span>{label}</span>
|
|
15
|
+
{description && <p class="ds-data-note">{description}</p>}
|
|
16
|
+
{note && (
|
|
17
|
+
<p class="ds-row-note ds-data-note" data-tone={noteTone}>
|
|
18
|
+
{note}
|
|
19
|
+
</p>
|
|
20
|
+
)}
|
|
21
|
+
</th>
|
|
22
|
+
<slot />
|
|
23
|
+
</tr>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
state: "loading" | "fresh" | "stale" | "unavailable" | "empty";
|
|
4
|
+
message?: string;
|
|
5
|
+
updatedAt?: string;
|
|
6
|
+
}
|
|
7
|
+
const { state, message, updatedAt } = Astro.props;
|
|
8
|
+
const labels = {
|
|
9
|
+
loading: "Loading data",
|
|
10
|
+
fresh: "Data available",
|
|
11
|
+
stale: "Showing last known data",
|
|
12
|
+
unavailable: "Data unavailable — state unknown",
|
|
13
|
+
empty: "No matching records",
|
|
14
|
+
};
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div class="ds-data-status" data-state={state} role="status">
|
|
18
|
+
<strong>{labels[state]}</strong>
|
|
19
|
+
{message && <span>{message}</span>}
|
|
20
|
+
{updatedAt && (
|
|
21
|
+
<span>
|
|
22
|
+
Last success: <time datetime={updatedAt}>{updatedAt}</time>
|
|
23
|
+
</span>
|
|
24
|
+
)}
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
caption: string;
|
|
4
|
+
columns: { label: string; numeric?: boolean }[];
|
|
5
|
+
}
|
|
6
|
+
const { caption, columns } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="table-wrap" tabindex="0" role="region" aria-label={caption}>
|
|
10
|
+
<table class="table ds-data-table">
|
|
11
|
+
<caption>{caption}</caption>
|
|
12
|
+
<thead>
|
|
13
|
+
<tr>
|
|
14
|
+
{columns.map((column) => (
|
|
15
|
+
<th scope="col" class:list={{ "ds-numeric": column.numeric }}>
|
|
16
|
+
{column.label}
|
|
17
|
+
</th>
|
|
18
|
+
))}
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
<slot />
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** EmptyState — for lists with nothing in them. Calm, on the hero tint. */
|
|
3
|
+
interface Props {
|
|
4
|
+
title: string;
|
|
5
|
+
icon?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
const { title, icon = 'inbox', class: cls } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div class:list={['empty', cls]}>
|
|
12
|
+
<svg class="icon" aria-hidden="true">
|
|
13
|
+
<use href={`#i-${icon}`}></use>
|
|
14
|
+
</svg>
|
|
15
|
+
<p class="empty__title">{title}</p>
|
|
16
|
+
<slot />
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Field — label + control + help/error. The control is slotted so any input type works. */
|
|
3
|
+
interface Props {
|
|
4
|
+
label: string;
|
|
5
|
+
for: string;
|
|
6
|
+
help?: string;
|
|
7
|
+
optional?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
const { label, for: htmlFor, help, optional = false, class: cls } = Astro.props;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class:list={['field', cls]}>
|
|
14
|
+
<label class="field__label" for={htmlFor}>
|
|
15
|
+
{label}
|
|
16
|
+
{optional && <span class="field__optional">(optional)</span>}
|
|
17
|
+
</label>
|
|
18
|
+
<slot />
|
|
19
|
+
{help && (
|
|
20
|
+
<p class="field__help" id={`${htmlFor}-help`}>
|
|
21
|
+
{help}
|
|
22
|
+
</p>
|
|
23
|
+
)}
|
|
24
|
+
<p class="field__error" id={`${htmlFor}-error`} hidden></p>
|
|
25
|
+
</div>
|