@bkstg/design-systems 1.0.0 → 1.0.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.
Files changed (125) hide show
  1. package/package.json +1 -1
  2. package/tether/latest/README.md +44 -0
  3. package/tether/latest/charts-core.mjs +349 -0
  4. package/tether/latest/charts.js +58 -0
  5. package/tether/latest/motion.js +168 -0
  6. package/tether/latest/react/ActivityFeed.jsx +18 -0
  7. package/tether/latest/react/Alert.jsx +28 -0
  8. package/tether/latest/react/Annotations.jsx +24 -0
  9. package/tether/latest/react/AppShell.jsx +87 -0
  10. package/tether/latest/react/Avatar.jsx +19 -0
  11. package/tether/latest/react/Badge.jsx +14 -0
  12. package/tether/latest/react/Banner.jsx +17 -0
  13. package/tether/latest/react/Breadcrumbs.jsx +23 -0
  14. package/tether/latest/react/Button.jsx +31 -0
  15. package/tether/latest/react/ButtonGroup.jsx +29 -0
  16. package/tether/latest/react/Calendar.jsx +57 -0
  17. package/tether/latest/react/CardHeader.jsx +14 -0
  18. package/tether/latest/react/Chart.jsx +42 -0
  19. package/tether/latest/react/Chat.jsx +34 -0
  20. package/tether/latest/react/Checkbox.jsx +32 -0
  21. package/tether/latest/react/CodeSnippet.jsx +17 -0
  22. package/tether/latest/react/ColorPicker.jsx +35 -0
  23. package/tether/latest/react/CommandMenu.jsx +54 -0
  24. package/tether/latest/react/DatePicker.jsx +34 -0
  25. package/tether/latest/react/DeviceFrame.jsx +12 -0
  26. package/tether/latest/react/Dialog.jsx +43 -0
  27. package/tether/latest/react/Divider.jsx +10 -0
  28. package/tether/latest/react/Editor.jsx +85 -0
  29. package/tether/latest/react/EmptyState.jsx +13 -0
  30. package/tether/latest/react/Field.jsx +32 -0
  31. package/tether/latest/react/FileUpload.jsx +38 -0
  32. package/tether/latest/react/Filters.jsx +19 -0
  33. package/tether/latest/react/Infographics.jsx +54 -0
  34. package/tether/latest/react/InlineCTA.jsx +14 -0
  35. package/tether/latest/react/LineChart.jsx +23 -0
  36. package/tether/latest/react/Marketing.jsx +161 -0
  37. package/tether/latest/react/Menu.jsx +76 -0
  38. package/tether/latest/react/Metric.jsx +28 -0
  39. package/tether/latest/react/PageHeader.jsx +22 -0
  40. package/tether/latest/react/Pagination.jsx +32 -0
  41. package/tether/latest/react/Progress.jsx +26 -0
  42. package/tether/latest/react/RadioGroup.jsx +27 -0
  43. package/tether/latest/react/Reveal.jsx +17 -0
  44. package/tether/latest/react/SectionHeader.jsx +24 -0
  45. package/tether/latest/react/Select.jsx +19 -0
  46. package/tether/latest/react/Sheet.jsx +25 -0
  47. package/tether/latest/react/SideNav.jsx +37 -0
  48. package/tether/latest/react/Skeleton.jsx +16 -0
  49. package/tether/latest/react/Slider.jsx +27 -0
  50. package/tether/latest/react/Spinner.jsx +14 -0
  51. package/tether/latest/react/Steps.jsx +22 -0
  52. package/tether/latest/react/Switch.jsx +11 -0
  53. package/tether/latest/react/Table.jsx +24 -0
  54. package/tether/latest/react/Tabs.jsx +80 -0
  55. package/tether/latest/react/Tag.jsx +34 -0
  56. package/tether/latest/react/Tooltip.jsx +25 -0
  57. package/tether/latest/react/Tree.jsx +41 -0
  58. package/tether/latest/react/VideoPlayer.jsx +57 -0
  59. package/tether/latest/react/cx.js +4 -0
  60. package/tether/latest/react/index.js +59 -0
  61. package/tether/latest/system.json +44 -0
  62. package/tether/latest/tether.css +1661 -0
  63. package/tether/latest/tether.js +703 -0
  64. package/tether/v1/README.md +44 -0
  65. package/tether/v1/charts-core.mjs +349 -0
  66. package/tether/v1/charts.js +58 -0
  67. package/tether/v1/motion.js +168 -0
  68. package/tether/v1/react/ActivityFeed.jsx +18 -0
  69. package/tether/v1/react/Alert.jsx +28 -0
  70. package/tether/v1/react/Annotations.jsx +24 -0
  71. package/tether/v1/react/AppShell.jsx +87 -0
  72. package/tether/v1/react/Avatar.jsx +19 -0
  73. package/tether/v1/react/Badge.jsx +14 -0
  74. package/tether/v1/react/Banner.jsx +17 -0
  75. package/tether/v1/react/Breadcrumbs.jsx +23 -0
  76. package/tether/v1/react/Button.jsx +31 -0
  77. package/tether/v1/react/ButtonGroup.jsx +29 -0
  78. package/tether/v1/react/Calendar.jsx +57 -0
  79. package/tether/v1/react/CardHeader.jsx +14 -0
  80. package/tether/v1/react/Chart.jsx +42 -0
  81. package/tether/v1/react/Chat.jsx +34 -0
  82. package/tether/v1/react/Checkbox.jsx +32 -0
  83. package/tether/v1/react/CodeSnippet.jsx +17 -0
  84. package/tether/v1/react/ColorPicker.jsx +35 -0
  85. package/tether/v1/react/CommandMenu.jsx +54 -0
  86. package/tether/v1/react/DatePicker.jsx +34 -0
  87. package/tether/v1/react/DeviceFrame.jsx +12 -0
  88. package/tether/v1/react/Dialog.jsx +43 -0
  89. package/tether/v1/react/Divider.jsx +10 -0
  90. package/tether/v1/react/Editor.jsx +85 -0
  91. package/tether/v1/react/EmptyState.jsx +13 -0
  92. package/tether/v1/react/Field.jsx +32 -0
  93. package/tether/v1/react/FileUpload.jsx +38 -0
  94. package/tether/v1/react/Filters.jsx +19 -0
  95. package/tether/v1/react/Infographics.jsx +54 -0
  96. package/tether/v1/react/InlineCTA.jsx +14 -0
  97. package/tether/v1/react/LineChart.jsx +23 -0
  98. package/tether/v1/react/Marketing.jsx +161 -0
  99. package/tether/v1/react/Menu.jsx +76 -0
  100. package/tether/v1/react/Metric.jsx +28 -0
  101. package/tether/v1/react/PageHeader.jsx +22 -0
  102. package/tether/v1/react/Pagination.jsx +32 -0
  103. package/tether/v1/react/Progress.jsx +26 -0
  104. package/tether/v1/react/RadioGroup.jsx +27 -0
  105. package/tether/v1/react/Reveal.jsx +17 -0
  106. package/tether/v1/react/SectionHeader.jsx +24 -0
  107. package/tether/v1/react/Select.jsx +19 -0
  108. package/tether/v1/react/Sheet.jsx +25 -0
  109. package/tether/v1/react/SideNav.jsx +37 -0
  110. package/tether/v1/react/Skeleton.jsx +16 -0
  111. package/tether/v1/react/Slider.jsx +27 -0
  112. package/tether/v1/react/Spinner.jsx +14 -0
  113. package/tether/v1/react/Steps.jsx +22 -0
  114. package/tether/v1/react/Switch.jsx +11 -0
  115. package/tether/v1/react/Table.jsx +24 -0
  116. package/tether/v1/react/Tabs.jsx +80 -0
  117. package/tether/v1/react/Tag.jsx +34 -0
  118. package/tether/v1/react/Tooltip.jsx +25 -0
  119. package/tether/v1/react/Tree.jsx +41 -0
  120. package/tether/v1/react/VideoPlayer.jsx +57 -0
  121. package/tether/v1/react/cx.js +4 -0
  122. package/tether/v1/react/index.js +59 -0
  123. package/tether/v1/system.json +44 -0
  124. package/tether/v1/tether.css +1661 -0
  125. package/tether/v1/tether.js +703 -0
@@ -0,0 +1,80 @@
1
+ import { useId, useLayoutEffect, useRef, useState } from "react";
2
+ import { cx } from "./cx.js";
3
+
4
+ /**
5
+ * <Tabs label="Settings" tabs={[{ id: "a", label: "Account", content: <p>…</p> }]} />
6
+ * variant: "underline" (default) | "pill". Controlled with value + onChange, or uncontrolled.
7
+ * Arrows, Home and End move between tabs; the indicator slides to the selected one.
8
+ */
9
+ export function Tabs({ tabs, label, variant = "underline", value, defaultValue, onChange, className }) {
10
+ const base = useId();
11
+ const [inner, setInner] = useState(defaultValue ?? tabs.find((t) => !t.disabled)?.id);
12
+ const current = value ?? inner;
13
+ const refs = useRef({});
14
+ const indicator = useRef(null);
15
+ const listRef = useRef(null);
16
+
17
+ function choose(id, focus) {
18
+ if (value === undefined) setInner(id);
19
+ onChange?.(id);
20
+ if (focus) refs.current[id]?.focus();
21
+ }
22
+ function onKey(e, id) {
23
+ const on = tabs.filter((t) => !t.disabled);
24
+ const i = on.findIndex((t) => t.id === id);
25
+ const next = { ArrowRight: on[(i + 1) % on.length], ArrowLeft: on[(i - 1 + on.length) % on.length], Home: on[0], End: on[on.length - 1] }[e.key];
26
+ if (next) { e.preventDefault(); choose(next.id, true); }
27
+ }
28
+ useLayoutEffect(() => {
29
+ function move() {
30
+ const t = refs.current[current];
31
+ if (!t || !indicator.current) return;
32
+ indicator.current.style.setProperty("--x", `${t.offsetLeft}px`);
33
+ indicator.current.style.setProperty("--w", `${t.offsetWidth}px`);
34
+ }
35
+ move();
36
+ if (typeof ResizeObserver === "undefined" || !listRef.current) return undefined;
37
+ const ro = new ResizeObserver(move);
38
+ ro.observe(listRef.current);
39
+ return () => ro.disconnect();
40
+ }, [current, tabs]);
41
+
42
+ return (
43
+ <div className={cx("ui-tabs", variant === "pill" && "ui-tabs--pill", className)}>
44
+ <div className="ui-tablist" role="tablist" aria-label={label} ref={listRef}>
45
+ {tabs.map((t) => (
46
+ <button
47
+ key={t.id}
48
+ ref={(el) => { refs.current[t.id] = el; }}
49
+ type="button"
50
+ role="tab"
51
+ id={`${base}-tab-${t.id}`}
52
+ className="ui-tab"
53
+ aria-selected={t.id === current}
54
+ aria-controls={`${base}-panel-${t.id}`}
55
+ tabIndex={t.id === current ? 0 : -1}
56
+ disabled={t.disabled}
57
+ onClick={() => choose(t.id, false)}
58
+ onKeyDown={(e) => onKey(e, t.id)}
59
+ >
60
+ {t.label}
61
+ </button>
62
+ ))}
63
+ <span className="ui-tab-indicator" aria-hidden="true" ref={indicator} />
64
+ </div>
65
+ {tabs.map((t) => (
66
+ <div
67
+ key={t.id}
68
+ className="ui-tabpanel"
69
+ role="tabpanel"
70
+ id={`${base}-panel-${t.id}`}
71
+ aria-labelledby={`${base}-tab-${t.id}`}
72
+ tabIndex={0}
73
+ hidden={t.id !== current}
74
+ >
75
+ {t.content}
76
+ </div>
77
+ ))}
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,34 @@
1
+ import { useRef } from "react";
2
+ import { cx } from "./cx.js";
3
+
4
+ /**
5
+ * <TagList label="Genres">{genres.map(g => <Tag key={g} onRemove={() => drop(g)}>{g}</Tag>)}</TagList>
6
+ * With onRemove, a tag shows a remove button labelled "Remove <text>". After a removal, focus moves to the
7
+ * next remove button, or the previous one, or the list, so a keyboard user is never dropped.
8
+ */
9
+ export function TagList({ label, className, children }) {
10
+ return <div className={cx("ui-tags", className)} role="group" aria-label={label} tabIndex={-1}>{children}</div>;
11
+ }
12
+
13
+ export function Tag({ children, onRemove, removeLabel, className, ...rest }) {
14
+ const ref = useRef(null);
15
+ function remove() {
16
+ const list = ref.current?.parentElement;
17
+ const all = list ? [...list.querySelectorAll(".ui-tag-remove")] : [];
18
+ const i = all.indexOf(ref.current?.querySelector(".ui-tag-remove"));
19
+ const next = all[i + 1] || all[i - 1] || list;
20
+ onRemove();
21
+ // after React removes this tag
22
+ requestAnimationFrame(() => next?.isConnected ? next.focus() : list?.focus());
23
+ }
24
+ return (
25
+ <span ref={ref} className={cx("ui-tag", className)} {...rest}>
26
+ {children}
27
+ {onRemove ? (
28
+ <button type="button" className="ui-tag-remove" aria-label={removeLabel || `Remove ${typeof children === "string" ? children : "tag"}`} onClick={remove}>
29
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
30
+ </button>
31
+ ) : null}
32
+ </span>
33
+ );
34
+ }
@@ -0,0 +1,25 @@
1
+ import { cloneElement, useId, useState } from "react";
2
+ import { cx } from "./cx.js";
3
+
4
+ /**
5
+ * <Tooltip text="Copy link"><button aria-label="Copy">…</button></Tooltip>
6
+ * Shows on hover and keyboard focus, after --tooltip-delay. Escape hides it until the pointer or focus leaves.
7
+ * side: "top" (default) | "bottom". The child must be focusable.
8
+ */
9
+ export function Tooltip({ text, side = "top", children, className }) {
10
+ const id = useId();
11
+ const [dismissed, setDismissed] = useState(false);
12
+ const child = cloneElement(children, { "aria-describedby": id });
13
+ return (
14
+ <span
15
+ className={cx("ui-tooltip-wrap", dismissed && "is-dismissed", className)}
16
+ data-side={side === "bottom" ? "bottom" : undefined}
17
+ onKeyDown={(e) => { if (e.key === "Escape") setDismissed(true); }}
18
+ onMouseLeave={() => setDismissed(false)}
19
+ onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setDismissed(false); }}
20
+ >
21
+ {child}
22
+ <span role="tooltip" id={id} className="ui-tooltip">{text}</span>
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,41 @@
1
+ import { useRef, useState } from "react";
2
+ import { cx } from "./cx.js";
3
+
4
+ /**
5
+ * <Tree label="Files" items={[{ id: "a", label: "Artwork", icon, children: [{ id: "a1", label: "Poster.pdf" }] }]} selected={id} onSelect={setId} />
6
+ * Up and Down move, Right opens or steps in, Left closes or steps out, Home and End, Enter selects. One tab stop.
7
+ */
8
+ export function Tree({ label, items, selected, onSelect, defaultOpen = [], className }) {
9
+ const [open, setOpen] = useState(() => new Set(defaultOpen));
10
+ const [focus, setFocus] = useState(selected || items[0]?.id);
11
+ const root = useRef(null);
12
+ const visible = [];
13
+ const walk = (list, parent) => list.forEach((it) => { visible.push({ it, parent }); if (it.children && open.has(it.id)) walk(it.children, it.id); });
14
+ walk(items, null);
15
+ const go = (id) => { setFocus(id); requestAnimationFrame(() => root.current?.querySelector(`[data-id="${id}"]`)?.focus()); };
16
+ const toggle = (id, on) => setOpen((s) => { const n = new Set(s); (on ?? !n.has(id)) ? n.add(id) : n.delete(id); return n; });
17
+ function onKey(e) {
18
+ const i = visible.findIndex((v) => v.it.id === focus), cur = visible[i]; if (!cur) return;
19
+ const has = !!cur.it.children, isOpen = open.has(cur.it.id);
20
+ const k = e.key;
21
+ if (k === "ArrowDown" && visible[i + 1]) go(visible[i + 1].it.id);
22
+ else if (k === "ArrowUp" && visible[i - 1]) go(visible[i - 1].it.id);
23
+ else if (k === "Home") go(visible[0].it.id);
24
+ else if (k === "End") go(visible[visible.length - 1].it.id);
25
+ else if (k === "ArrowRight" && has) { if (!isOpen) toggle(cur.it.id, true); else go(cur.it.children[0].id); }
26
+ else if (k === "ArrowLeft") { if (has && isOpen) toggle(cur.it.id, false); else if (cur.parent) go(cur.parent); }
27
+ else if (k === "Enter" || k === " ") { onSelect?.(cur.it.id); if (has) toggle(cur.it.id); }
28
+ else return;
29
+ e.preventDefault();
30
+ }
31
+ const render = (list, level) => list.map((it) => (
32
+ <li key={it.id} data-id={it.id} role="treeitem" aria-level={level} aria-selected={it.id === selected} aria-expanded={it.children ? open.has(it.id) : undefined}
33
+ tabIndex={it.id === focus ? 0 : -1}>
34
+ <div className="ui-tree-row" onClick={() => { setFocus(it.id); onSelect?.(it.id); if (it.children) toggle(it.id); }}>
35
+ {it.children ? <span className="ui-tree-caret" aria-hidden="true" /> : <span className="ui-tree-leaf" aria-hidden="true" />}{it.icon}{it.label}
36
+ </div>
37
+ {it.children ? <ul role="group">{render(it.children, level + 1)}</ul> : null}
38
+ </li>
39
+ ));
40
+ return <ul ref={root} className={cx("ui-tree", className)} role="tree" aria-label={label} onKeyDown={onKey}>{render(items, 1)}</ul>;
41
+ }
@@ -0,0 +1,57 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { cx } from "./cx.js";
3
+
4
+ const PLAY = <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15a1 1 0 0 0 1.5.86l12.5-7.5a1 1 0 0 0 0-1.72L8.5 3.64A1 1 0 0 0 7 4.5Z" /></svg>;
5
+ const PAUSE = <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></svg>;
6
+ const o = { fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
7
+ const SOUND = <svg viewBox="0 0 24 24" {...o} aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5Z" /><path d="M15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14" /></svg>;
8
+ const MUTED = <svg viewBox="0 0 24 24" {...o} aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5Z" /><path d="m22 9-6 6M16 9l6 6" /></svg>;
9
+ const FULL = <svg viewBox="0 0 24 24" {...o} aria-hidden="true"><path d="M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3" /></svg>;
10
+ const clock = (s) => { s = Math.max(0, Math.floor(s || 0)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; };
11
+
12
+ /**
13
+ * <VideoPlayer label="Day 1 recap" sources={[{ src: "/recap.mp4", type: "video/mp4" }]} poster="/recap.jpg"
14
+ * captions={{ src: "/recap.vtt", lang: "en" }} />
15
+ * A native <video> with the kit's controls: play and pause, seek, mute, full screen; k or Space plays and pauses.
16
+ * A video with speech needs captions (WCAG 1.2.2): pass a WebVTT file.
17
+ */
18
+ export function VideoPlayer({ label, sources, poster, captions, muted = false, className }) {
19
+ const root = useRef(null), v = useRef(null), idle = useRef(null);
20
+ const [s, setS] = useState({ playing: false, muted, t: 0, d: 0, idle: false });
21
+ const paint = () => { const el = v.current; if (el) setS((p) => ({ ...p, playing: !el.paused, muted: el.muted, t: el.currentTime, d: el.duration || 0 })); };
22
+ useEffect(() => {
23
+ const el = v.current;
24
+ const evs = ["play", "pause", "timeupdate", "loadedmetadata", "volumechange", "ended"];
25
+ evs.forEach((n) => el.addEventListener(n, paint));
26
+ return () => { evs.forEach((n) => el.removeEventListener(n, paint)); clearTimeout(idle.current); };
27
+ }, []);
28
+ const toggle = () => { const el = v.current; if (el.paused) el.play(); else el.pause(); };
29
+ const wake = () => { setS((p) => ({ ...p, idle: false })); clearTimeout(idle.current); idle.current = setTimeout(() => setS((p) => ({ ...p, idle: true })), 2000); };
30
+ const pct = s.d ? (s.t / s.d) * 100 : 0;
31
+
32
+ return (
33
+ <div
34
+ ref={root} className={cx("ui-video", className)} role="group" aria-label={label}
35
+ data-playing={s.playing ? "" : undefined} data-idle={s.idle ? "" : undefined}
36
+ onPointerMove={wake} onFocus={wake}
37
+ onKeyDown={(e) => { if (e.target.closest("button, input")) return; if (e.key === " " || e.key === "k") { e.preventDefault(); toggle(); } }}
38
+ >
39
+ <video ref={v} poster={poster} muted={muted} playsInline preload="metadata" onClick={toggle} tabIndex={-1}>
40
+ {sources.map((x) => <source key={x.src} src={x.src} type={x.type} />)}
41
+ {captions ? <track kind="captions" src={captions.src} srcLang={captions.lang || "en"} label={captions.label || "English"} default /> : null}
42
+ </video>
43
+ <button type="button" className="ui-video-big" aria-label="Play" tabIndex={s.playing ? -1 : 0} onClick={toggle}>{PLAY}</button>
44
+ <div className="ui-video-bar">
45
+ <button type="button" className="ui-video-btn" aria-label={s.playing ? "Pause" : "Play"} onClick={toggle}>{s.playing ? PAUSE : PLAY}</button>
46
+ <input
47
+ type="range" className="ui-slider" aria-label="Seek" min={0} max={Math.round(s.d * 10) || 0} value={Math.round(s.t * 10)}
48
+ aria-valuetext={`${clock(s.t)} of ${clock(s.d)}`} style={{ "--value": `${pct}%` }}
49
+ onChange={(e) => { v.current.currentTime = e.target.value / 10; }}
50
+ />
51
+ <span className="ui-video-time">{clock(s.t)} / {clock(s.d)}</span>
52
+ <button type="button" className="ui-video-btn" aria-label={s.muted ? "Unmute" : "Mute"} onClick={() => { v.current.muted = !v.current.muted; }}>{s.muted ? MUTED : SOUND}</button>
53
+ <button type="button" className="ui-video-btn" aria-label="Full screen" onClick={() => (document.fullscreenElement ? document.exitFullscreen() : root.current.requestFullscreen?.())}>{FULL}</button>
54
+ </div>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,4 @@
1
+ // Join class names, skipping empty ones.
2
+ export function cx(...names) {
3
+ return names.filter(Boolean).join(" ");
4
+ }
@@ -0,0 +1,59 @@
1
+ // React versions of the kit's components. They render the same ui- classes as the HTML versions,
2
+ // so both read one stylesheet: import tokens.css and components.css once, at the app's root.
3
+ export { Button } from "./Button.jsx";
4
+ export { Field, Input, Textarea } from "./Field.jsx";
5
+ export { Select } from "./Select.jsx";
6
+ export { Switch } from "./Switch.jsx";
7
+ export { Tabs } from "./Tabs.jsx";
8
+ export { Dialog } from "./Dialog.jsx";
9
+ export { Badge } from "./Badge.jsx";
10
+ export { Tag, TagList } from "./Tag.jsx";
11
+ export { Avatar, AvatarGroup } from "./Avatar.jsx";
12
+ export { Tooltip } from "./Tooltip.jsx";
13
+ export { Progress } from "./Progress.jsx";
14
+ export { Spinner } from "./Spinner.jsx";
15
+ export { Skeleton } from "./Skeleton.jsx";
16
+ export { Alert } from "./Alert.jsx";
17
+ export { SideNav } from "./SideNav.jsx";
18
+ export { AppShell } from "./AppShell.jsx";
19
+ export { Chart } from "./Chart.jsx";
20
+ export { Compare, Timeline, StatGrid } from "./Infographics.jsx";
21
+ export { Metric } from "./Metric.jsx";
22
+ export { LineChart } from "./LineChart.jsx";
23
+ export { Table } from "./Table.jsx";
24
+ export { ActivityFeed } from "./ActivityFeed.jsx";
25
+ // base controls
26
+ export { Checkbox, Choices } from "./Checkbox.jsx";
27
+ export { RadioGroup } from "./RadioGroup.jsx";
28
+ export { ButtonGroup } from "./ButtonGroup.jsx";
29
+ export { Menu } from "./Menu.jsx";
30
+ export { Slider } from "./Slider.jsx";
31
+ export { Editor, EDITOR_TOOLS } from "./Editor.jsx";
32
+ export { VideoPlayer } from "./VideoPlayer.jsx";
33
+ // app components
34
+ export { Breadcrumbs } from "./Breadcrumbs.jsx";
35
+ export { CardHeader } from "./CardHeader.jsx";
36
+ export { Divider } from "./Divider.jsx";
37
+ export { EmptyState } from "./EmptyState.jsx";
38
+ export { InlineCTA } from "./InlineCTA.jsx";
39
+ export { PageHeader } from "./PageHeader.jsx";
40
+ export { Pagination, pageList } from "./Pagination.jsx";
41
+ export { Steps } from "./Steps.jsx";
42
+ export { SectionHeader, SectionFooter } from "./SectionHeader.jsx";
43
+ export { Calendar, toIso, fromIso } from "./Calendar.jsx";
44
+ export { DatePicker } from "./DatePicker.jsx";
45
+ export { CodeSnippet } from "./CodeSnippet.jsx";
46
+ export { ColorPicker } from "./ColorPicker.jsx";
47
+ export { CommandMenu } from "./CommandMenu.jsx";
48
+ export { FileUpload } from "./FileUpload.jsx";
49
+ export { Chip, FilterBar } from "./Filters.jsx";
50
+ export { Chat } from "./Chat.jsx";
51
+ export { Sheet } from "./Sheet.jsx";
52
+ export { Tree } from "./Tree.jsx";
53
+ // marketing sections
54
+ export { Section, Media, SiteNav, Hero, FeatureCard, Stats, LogoCloud, Testimonial, TeamMember, PostCard, JobList, FAQ, Plan, CTABand, Newsletter, Prose, Footer } from "./Marketing.jsx";
55
+ // shared assets and utility
56
+ export { Banner } from "./Banner.jsx";
57
+ export { DeviceFrame } from "./DeviceFrame.jsx";
58
+ export { Annotations } from "./Annotations.jsx";
59
+ export { Reveal } from "./Reveal.jsx";
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "Tether",
3
+ "base": "shadcn",
4
+ "themes": {
5
+ "dark": "backstage",
6
+ "light": "backstage-light"
7
+ },
8
+ "mode": "light",
9
+ "texture": "off",
10
+ "shape": "standard",
11
+ "type": "dm",
12
+ "motion": "full",
13
+ "tokens": {},
14
+ "colors": {
15
+ "dark": {
16
+ "--p": "#171c1c",
17
+ "--s": "#1d2525",
18
+ "--acc": "#99b8b8",
19
+ "--pos": "#99b8b8",
20
+ "--fill": "#99b8b8",
21
+ "--bar-a": "#179915",
22
+ "--bar-b": "#77c176",
23
+ "--data-2": "#d4d7cb"
24
+ },
25
+ "light": {
26
+ "--i": "#232220",
27
+ "--l": "#E5DDD5",
28
+ "--p": "#fafaf9",
29
+ "--s": "#f5f2f0",
30
+ "--i2": "#4b4a49",
31
+ "--i3": "#525151",
32
+ "--mid": "#c0cece",
33
+ "--fill": "#657272",
34
+ "--bar-a": "#8fcc75",
35
+ "--bar-b": "#2f8d07",
36
+ "--field": "#fafaf9",
37
+ "--top-a": "#fafaf9",
38
+ "--card-a": "#fafaf9",
39
+ "--card-b": "#fafaf9",
40
+ "--data-2": "#2d2d2d",
41
+ "--on-fill": "#fafaf9"
42
+ }
43
+ }
44
+ }