@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.
- package/package.json +1 -1
- package/tether/latest/README.md +44 -0
- package/tether/latest/charts-core.mjs +349 -0
- package/tether/latest/charts.js +58 -0
- package/tether/latest/motion.js +168 -0
- package/tether/latest/react/ActivityFeed.jsx +18 -0
- package/tether/latest/react/Alert.jsx +28 -0
- package/tether/latest/react/Annotations.jsx +24 -0
- package/tether/latest/react/AppShell.jsx +87 -0
- package/tether/latest/react/Avatar.jsx +19 -0
- package/tether/latest/react/Badge.jsx +14 -0
- package/tether/latest/react/Banner.jsx +17 -0
- package/tether/latest/react/Breadcrumbs.jsx +23 -0
- package/tether/latest/react/Button.jsx +31 -0
- package/tether/latest/react/ButtonGroup.jsx +29 -0
- package/tether/latest/react/Calendar.jsx +57 -0
- package/tether/latest/react/CardHeader.jsx +14 -0
- package/tether/latest/react/Chart.jsx +42 -0
- package/tether/latest/react/Chat.jsx +34 -0
- package/tether/latest/react/Checkbox.jsx +32 -0
- package/tether/latest/react/CodeSnippet.jsx +17 -0
- package/tether/latest/react/ColorPicker.jsx +35 -0
- package/tether/latest/react/CommandMenu.jsx +54 -0
- package/tether/latest/react/DatePicker.jsx +34 -0
- package/tether/latest/react/DeviceFrame.jsx +12 -0
- package/tether/latest/react/Dialog.jsx +43 -0
- package/tether/latest/react/Divider.jsx +10 -0
- package/tether/latest/react/Editor.jsx +85 -0
- package/tether/latest/react/EmptyState.jsx +13 -0
- package/tether/latest/react/Field.jsx +32 -0
- package/tether/latest/react/FileUpload.jsx +38 -0
- package/tether/latest/react/Filters.jsx +19 -0
- package/tether/latest/react/Infographics.jsx +54 -0
- package/tether/latest/react/InlineCTA.jsx +14 -0
- package/tether/latest/react/LineChart.jsx +23 -0
- package/tether/latest/react/Marketing.jsx +161 -0
- package/tether/latest/react/Menu.jsx +76 -0
- package/tether/latest/react/Metric.jsx +28 -0
- package/tether/latest/react/PageHeader.jsx +22 -0
- package/tether/latest/react/Pagination.jsx +32 -0
- package/tether/latest/react/Progress.jsx +26 -0
- package/tether/latest/react/RadioGroup.jsx +27 -0
- package/tether/latest/react/Reveal.jsx +17 -0
- package/tether/latest/react/SectionHeader.jsx +24 -0
- package/tether/latest/react/Select.jsx +19 -0
- package/tether/latest/react/Sheet.jsx +25 -0
- package/tether/latest/react/SideNav.jsx +37 -0
- package/tether/latest/react/Skeleton.jsx +16 -0
- package/tether/latest/react/Slider.jsx +27 -0
- package/tether/latest/react/Spinner.jsx +14 -0
- package/tether/latest/react/Steps.jsx +22 -0
- package/tether/latest/react/Switch.jsx +11 -0
- package/tether/latest/react/Table.jsx +24 -0
- package/tether/latest/react/Tabs.jsx +80 -0
- package/tether/latest/react/Tag.jsx +34 -0
- package/tether/latest/react/Tooltip.jsx +25 -0
- package/tether/latest/react/Tree.jsx +41 -0
- package/tether/latest/react/VideoPlayer.jsx +57 -0
- package/tether/latest/react/cx.js +4 -0
- package/tether/latest/react/index.js +59 -0
- package/tether/latest/system.json +44 -0
- package/tether/latest/tether.css +1661 -0
- package/tether/latest/tether.js +703 -0
- package/tether/v1/README.md +44 -0
- package/tether/v1/charts-core.mjs +349 -0
- package/tether/v1/charts.js +58 -0
- package/tether/v1/motion.js +168 -0
- package/tether/v1/react/ActivityFeed.jsx +18 -0
- package/tether/v1/react/Alert.jsx +28 -0
- package/tether/v1/react/Annotations.jsx +24 -0
- package/tether/v1/react/AppShell.jsx +87 -0
- package/tether/v1/react/Avatar.jsx +19 -0
- package/tether/v1/react/Badge.jsx +14 -0
- package/tether/v1/react/Banner.jsx +17 -0
- package/tether/v1/react/Breadcrumbs.jsx +23 -0
- package/tether/v1/react/Button.jsx +31 -0
- package/tether/v1/react/ButtonGroup.jsx +29 -0
- package/tether/v1/react/Calendar.jsx +57 -0
- package/tether/v1/react/CardHeader.jsx +14 -0
- package/tether/v1/react/Chart.jsx +42 -0
- package/tether/v1/react/Chat.jsx +34 -0
- package/tether/v1/react/Checkbox.jsx +32 -0
- package/tether/v1/react/CodeSnippet.jsx +17 -0
- package/tether/v1/react/ColorPicker.jsx +35 -0
- package/tether/v1/react/CommandMenu.jsx +54 -0
- package/tether/v1/react/DatePicker.jsx +34 -0
- package/tether/v1/react/DeviceFrame.jsx +12 -0
- package/tether/v1/react/Dialog.jsx +43 -0
- package/tether/v1/react/Divider.jsx +10 -0
- package/tether/v1/react/Editor.jsx +85 -0
- package/tether/v1/react/EmptyState.jsx +13 -0
- package/tether/v1/react/Field.jsx +32 -0
- package/tether/v1/react/FileUpload.jsx +38 -0
- package/tether/v1/react/Filters.jsx +19 -0
- package/tether/v1/react/Infographics.jsx +54 -0
- package/tether/v1/react/InlineCTA.jsx +14 -0
- package/tether/v1/react/LineChart.jsx +23 -0
- package/tether/v1/react/Marketing.jsx +161 -0
- package/tether/v1/react/Menu.jsx +76 -0
- package/tether/v1/react/Metric.jsx +28 -0
- package/tether/v1/react/PageHeader.jsx +22 -0
- package/tether/v1/react/Pagination.jsx +32 -0
- package/tether/v1/react/Progress.jsx +26 -0
- package/tether/v1/react/RadioGroup.jsx +27 -0
- package/tether/v1/react/Reveal.jsx +17 -0
- package/tether/v1/react/SectionHeader.jsx +24 -0
- package/tether/v1/react/Select.jsx +19 -0
- package/tether/v1/react/Sheet.jsx +25 -0
- package/tether/v1/react/SideNav.jsx +37 -0
- package/tether/v1/react/Skeleton.jsx +16 -0
- package/tether/v1/react/Slider.jsx +27 -0
- package/tether/v1/react/Spinner.jsx +14 -0
- package/tether/v1/react/Steps.jsx +22 -0
- package/tether/v1/react/Switch.jsx +11 -0
- package/tether/v1/react/Table.jsx +24 -0
- package/tether/v1/react/Tabs.jsx +80 -0
- package/tether/v1/react/Tag.jsx +34 -0
- package/tether/v1/react/Tooltip.jsx +25 -0
- package/tether/v1/react/Tree.jsx +41 -0
- package/tether/v1/react/VideoPlayer.jsx +57 -0
- package/tether/v1/react/cx.js +4 -0
- package/tether/v1/react/index.js +59 -0
- package/tether/v1/system.json +44 -0
- package/tether/v1/tether.css +1661 -0
- 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,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
|
+
}
|