@sierragridteam/design-system 0.0.0-stage → 0.3.0

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.
Files changed (38) hide show
  1. package/README.md +17 -2
  2. package/assets/sierra-logo-full.png +0 -0
  3. package/assets/sierra-mark.png +0 -0
  4. package/package.json +35 -4
  5. package/src/components/Badge.astro +13 -0
  6. package/src/components/Button.astro +36 -0
  7. package/src/components/Callout.astro +17 -0
  8. package/src/components/Card.astro +33 -0
  9. package/src/components/CopyButton.astro +81 -0
  10. package/src/components/DataDiff.astro +50 -0
  11. package/src/components/DataRow.astro +23 -0
  12. package/src/components/DataStatus.astro +26 -0
  13. package/src/components/DataTable.astro +25 -0
  14. package/src/components/EmptyState.astro +17 -0
  15. package/src/components/Field.astro +25 -0
  16. package/src/components/FilterBar.astro +10 -0
  17. package/src/components/GeoJSONMap.astro +280 -0
  18. package/src/components/GeometryPreview.astro +11 -0
  19. package/src/components/Icon.astro +19 -0
  20. package/src/components/IconSprite.astro +68 -0
  21. package/src/components/IdentifierList.astro +54 -0
  22. package/src/components/Logo.astro +75 -0
  23. package/src/components/MapFrame.astro +25 -0
  24. package/src/components/MetricGrid.astro +35 -0
  25. package/src/components/PageHeader.astro +24 -0
  26. package/src/components/Select.astro +202 -0
  27. package/src/components/ValueTable.astro +32 -0
  28. package/src/lib/basemap-style.ts +269 -0
  29. package/src/lib/controls.ts +126 -0
  30. package/src/lib/data-values.ts +45 -0
  31. package/src/lib/dialog.ts +85 -0
  32. package/src/lib/dom.ts +68 -0
  33. package/src/lib/toast.ts +22 -0
  34. package/src/styles/base.css +165 -0
  35. package/src/styles/components.css +2235 -0
  36. package/src/styles/global.css +4 -0
  37. package/src/styles/shell.css +501 -0
  38. package/src/styles/tokens.css +172 -0
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # SIERRA design system
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,37 @@
1
1
  {
2
2
  "name": "@sierragridteam/design-system",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
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
+ }
@@ -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>
@@ -0,0 +1,10 @@
1
+ ---
2
+ interface Props {
3
+ label?: string;
4
+ }
5
+ const { label = "Filters" } = Astro.props;
6
+ ---
7
+
8
+ <div class="ds-filter-bar" role="group" aria-label={label}>
9
+ <slot />
10
+ </div>