@tensor_1/react 2.19.1 → 2.20.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.
- package/README.md +14 -0
- package/dist/components/CopyButton.d.ts +25 -0
- package/dist/components/CopyButton.js +52 -0
- package/dist/components/Stat.d.ts +19 -3
- package/dist/components/Stat.js +50 -9
- package/dist/components/Tabs.d.ts +7 -1
- package/dist/components/Tabs.js +85 -15
- package/dist/copy-model.d.ts +12 -0
- package/dist/copy-model.js +12 -0
- package/dist/index.cjs +1030 -856
- package/dist/index.d.cts +49 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,4 +191,18 @@ Radial mode still shortens long labels to fit its ring, but every node carries i
|
|
|
191
191
|
<TabPanel value="history" keepMounted>{history}</TabPanel>
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
+
### Scrolling tabs, toned stat tiles and copying
|
|
195
|
+
|
|
196
|
+
- **`Tabs overflow="scroll"`** keeps a long tab row on one line that scrolls sideways. The selected tab is always scrolled into view, and edge buttons appear only on the side that has hidden tabs. The buttons are pointer-only: keyboard users move with Arrow, Home and End. Combine it with `TabPanel keepMounted` on a record page with many sections.
|
|
197
|
+
- **`Stat tone`** (`warning`, `danger`) draws a toned outline and value; the label stays muted, so the tone never replaces words. `size="compact"` fits dense fact bands.
|
|
198
|
+
- **`Stat onActivate`** makes the whole tile a button that opens an editor (`aria-haspopup="dialog"`). Pass `activateLabel` (e.g. "change") so the name reads "Priority: P1, change", and `expanded` while the editor is open. The ref points at the button, for anchoring a popover.
|
|
199
|
+
- **`CopyButton`** copies `value`. Name it for what it copies (`label="Copy email"`), pass `copiedLabel`, and pass `failedLabel` so a refused copy is explained rather than silent.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<Tabs value={tab} onChange={setTab} overflow="scroll">{tabs}</Tabs>
|
|
203
|
+
<Stat label={t('sla')} value={remaining} tone={breached ? 'danger' : 'neutral'} size="compact" />
|
|
204
|
+
<Stat ref={anchor} label={t('priority')} value="P1" onActivate={openEditor} activateLabel={t('change')} expanded={open} size="compact" />
|
|
205
|
+
<CopyButton value={email} label={t('copyEmail')} copiedLabel={t('copied')} failedLabel={t('copyFailed')} />
|
|
206
|
+
```
|
|
207
|
+
|
|
194
208
|
Ships **ESM + CJS + types**, with per-file `"use client"` so it's safe under React Server Components. Part of the **[UIx v2 styleguide](https://github.com/COx-ORG-H/UIx)**.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface CopyButtonProps {
|
|
4
|
+
/** The text written to the clipboard. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Accessible name of the button, e.g. "Copy email". */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Announced through a polite status region after a successful copy, e.g. "Copied". */
|
|
9
|
+
copiedLabel: string;
|
|
10
|
+
/**
|
|
11
|
+
* Announced when the copy fails (no clipboard, or the write was refused), e.g.
|
|
12
|
+
* "Could not copy — select the text instead". Without it a failure is silent: nothing
|
|
13
|
+
* false is shown, but the person is left guessing why nothing happened.
|
|
14
|
+
*/
|
|
15
|
+
failedLabel?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Icon-only ghost button that copies `value`. On success it shows a check for 1.5 s and
|
|
20
|
+
* announces `copiedLabel`. On failure (no clipboard, or the write was refused) it shows no
|
|
21
|
+
* check and announces `failedLabel` when one is given.
|
|
22
|
+
*/
|
|
23
|
+
declare function CopyButton({ value, label, copiedLabel, failedLabel, className }: CopyButtonProps): react.JSX.Element;
|
|
24
|
+
|
|
25
|
+
export { CopyButton, type CopyButtonProps };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { copyText } from "../copy-model.js";
|
|
6
|
+
const CONFIRM_MS = 1500;
|
|
7
|
+
const CopyGlyph = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
8
|
+
/* @__PURE__ */ jsx("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2" }),
|
|
9
|
+
/* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
|
|
10
|
+
] });
|
|
11
|
+
const CheckGlyph = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
|
|
12
|
+
function CopyButton({ value, label, copiedLabel, failedLabel, className }) {
|
|
13
|
+
const [outcome, setOutcome] = useState("idle");
|
|
14
|
+
const copied = outcome === "copied";
|
|
15
|
+
const timer = useRef(void 0);
|
|
16
|
+
const mounted = useRef(true);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
mounted.current = true;
|
|
19
|
+
return () => {
|
|
20
|
+
mounted.current = false;
|
|
21
|
+
clearTimeout(timer.current);
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
const onClick = async () => {
|
|
25
|
+
const ok = await copyText(value);
|
|
26
|
+
if (!mounted.current) return;
|
|
27
|
+
clearTimeout(timer.current);
|
|
28
|
+
if (!ok && !failedLabel) {
|
|
29
|
+
setOutcome("idle");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
setOutcome(ok ? "copied" : "failed");
|
|
33
|
+
timer.current = setTimeout(() => setOutcome("idle"), CONFIRM_MS);
|
|
34
|
+
};
|
|
35
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"button",
|
|
38
|
+
{
|
|
39
|
+
type: "button",
|
|
40
|
+
className: cx("uix-btn", "uix-btn--ghost", "uix-btn--icon", "uix-btn--sm", "uix-copy-button", className),
|
|
41
|
+
"aria-label": label,
|
|
42
|
+
"data-copied": copied || void 0,
|
|
43
|
+
onClick,
|
|
44
|
+
children: copied ? /* @__PURE__ */ jsx(CheckGlyph, {}) : /* @__PURE__ */ jsx(CopyGlyph, {})
|
|
45
|
+
}
|
|
46
|
+
),
|
|
47
|
+
/* @__PURE__ */ jsx("span", { role: "status", className: "uix-visually-hidden", children: outcome === "copied" ? copiedLabel : outcome === "failed" ? failedLabel : "" })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
CopyButton
|
|
52
|
+
};
|
|
@@ -5,7 +5,7 @@ interface StatTrend {
|
|
|
5
5
|
direction: 'up' | 'down';
|
|
6
6
|
label: ReactNode;
|
|
7
7
|
}
|
|
8
|
-
interface StatProps extends HTMLAttributes<
|
|
8
|
+
interface StatProps extends HTMLAttributes<HTMLElement> {
|
|
9
9
|
label: ReactNode;
|
|
10
10
|
value: ReactNode;
|
|
11
11
|
/** Optional icon shown at the trailing edge of the label row. */
|
|
@@ -14,8 +14,24 @@ interface StatProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
14
14
|
meta?: ReactNode;
|
|
15
15
|
/** Trend chip rendered before the meta text. */
|
|
16
16
|
trend?: StatTrend;
|
|
17
|
+
/** Outline and value colour. `neutral` (default) is today's tile. */
|
|
18
|
+
tone?: 'neutral' | 'warning' | 'danger';
|
|
19
|
+
/** `hero` (default) is today's KPI size; `compact` is for dense fact bands. */
|
|
20
|
+
size?: 'hero' | 'compact';
|
|
21
|
+
/**
|
|
22
|
+
* Makes the whole tile a `<button>` that opens an editor (a popover or dialog). It gets
|
|
23
|
+
* `aria-haspopup="dialog"` and a trailing chevron.
|
|
24
|
+
*/
|
|
25
|
+
onActivate?: () => void;
|
|
26
|
+
/** Action word appended to the accessible name, e.g. "change" → "Priority: P1, change". */
|
|
27
|
+
activateLabel?: string;
|
|
28
|
+
/** Whether the editor this tile opens is currently open (`aria-expanded`). */
|
|
29
|
+
expanded?: boolean;
|
|
17
30
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
31
|
+
/**
|
|
32
|
+
* KPI / stat tile over `.uix-stat`. With `onActivate` the tile is a button; the forwarded
|
|
33
|
+
* ref then points at that button, so a popover can anchor to it.
|
|
34
|
+
*/
|
|
35
|
+
declare const Stat: react.ForwardRefExoticComponent<StatProps & react.RefAttributes<HTMLElement>>;
|
|
20
36
|
|
|
21
37
|
export { Stat, type StatProps, type StatTrend };
|
package/dist/components/Stat.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useId } from "react";
|
|
2
3
|
import { cx } from "../cx.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const Chevron = () => /* @__PURE__ */ jsx("span", { className: "uix-stat__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }) });
|
|
5
|
+
const Stat = forwardRef(function Stat2({ label, value, icon, meta, trend, tone = "neutral", size = "hero", onActivate, activateLabel, expanded, className, onClick, ...props }, ref) {
|
|
6
|
+
const id = useId();
|
|
7
|
+
const interactive = onActivate != null;
|
|
8
|
+
const classes = cx(
|
|
9
|
+
"uix-stat",
|
|
10
|
+
tone !== "neutral" && `uix-stat--${tone}`,
|
|
11
|
+
size === "compact" && "uix-stat--compact",
|
|
12
|
+
interactive && "uix-stat--interactive",
|
|
13
|
+
className
|
|
14
|
+
);
|
|
15
|
+
const Row = interactive ? "span" : "div";
|
|
16
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
/* @__PURE__ */ jsxs(Row, { className: "uix-stat__label", children: [
|
|
18
|
+
/* @__PURE__ */ jsx("span", { id: interactive ? `${id}-label` : void 0, children: label }),
|
|
19
|
+
icon,
|
|
20
|
+
interactive && /* @__PURE__ */ jsx(Chevron, {})
|
|
8
21
|
] }),
|
|
9
|
-
/* @__PURE__ */ jsx(
|
|
10
|
-
(meta != null || trend != null) && /* @__PURE__ */ jsxs(
|
|
22
|
+
/* @__PURE__ */ jsx(Row, { className: "uix-stat__value", id: interactive ? `${id}-value` : void 0, children: value }),
|
|
23
|
+
(meta != null || trend != null) && /* @__PURE__ */ jsxs(Row, { className: "uix-stat__meta", id: interactive ? `${id}-meta` : void 0, children: [
|
|
11
24
|
trend != null && /* @__PURE__ */ jsxs("span", { className: cx("uix-stat__trend", `uix-stat__trend--${trend.direction}`), children: [
|
|
12
25
|
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: trend.direction === "up" ? /* @__PURE__ */ jsx("path", { d: "M12 19V5m-6 6 6-6 6 6" }) : /* @__PURE__ */ jsx("path", { d: "M12 5v14m6-6-6 6-6-6" }) }),
|
|
13
26
|
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: trend.direction === "up" ? "up" : "down" }),
|
|
@@ -16,7 +29,35 @@ function Stat({ label, value, icon, meta, trend, className, ...props }) {
|
|
|
16
29
|
meta
|
|
17
30
|
] })
|
|
18
31
|
] });
|
|
19
|
-
|
|
32
|
+
if (!interactive) {
|
|
33
|
+
return /* @__PURE__ */ jsx("div", { ref, className: classes, onClick, ...props, children: body });
|
|
34
|
+
}
|
|
35
|
+
return /* @__PURE__ */ jsxs(
|
|
36
|
+
"button",
|
|
37
|
+
{
|
|
38
|
+
type: "button",
|
|
39
|
+
...props,
|
|
40
|
+
ref,
|
|
41
|
+
className: classes,
|
|
42
|
+
"aria-haspopup": "dialog",
|
|
43
|
+
"aria-expanded": expanded,
|
|
44
|
+
"aria-labelledby": `${id}-label ${id}-sep ${id}-value${activateLabel ? ` ${id}-action` : ""}`,
|
|
45
|
+
"aria-describedby": meta != null || trend != null ? `${id}-meta` : void 0,
|
|
46
|
+
onClick: (e) => {
|
|
47
|
+
onClick?.(e);
|
|
48
|
+
if (!e.defaultPrevented) onActivate();
|
|
49
|
+
},
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx("span", { id: `${id}-sep`, className: "uix-visually-hidden", children: ":" }),
|
|
52
|
+
activateLabel && /* @__PURE__ */ jsxs("span", { id: `${id}-action`, className: "uix-visually-hidden", children: [
|
|
53
|
+
", ",
|
|
54
|
+
activateLabel
|
|
55
|
+
] }),
|
|
56
|
+
body
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
});
|
|
20
61
|
export {
|
|
21
62
|
Stat
|
|
22
63
|
};
|
|
@@ -7,8 +7,14 @@ interface TabsProps {
|
|
|
7
7
|
onChange?: (value: string) => void;
|
|
8
8
|
children?: ReactNode;
|
|
9
9
|
className?: string;
|
|
10
|
+
/**
|
|
11
|
+
* What happens when the tabs don't fit. `'wrap'` (default) keeps today's layout.
|
|
12
|
+
* `'scroll'` keeps one row that scrolls sideways, with pointer-only edge buttons
|
|
13
|
+
* shown on the side that has hidden tabs. The selected tab is always scrolled into view.
|
|
14
|
+
*/
|
|
15
|
+
overflow?: 'wrap' | 'scroll';
|
|
10
16
|
}
|
|
11
|
-
declare function Tabs({ variant, value, onChange, children, className }: TabsProps): react.JSX.Element;
|
|
17
|
+
declare function Tabs({ variant, value, onChange, children, className, overflow }: TabsProps): react.JSX.Element;
|
|
12
18
|
interface TabProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'onChange'> {
|
|
13
19
|
value: string;
|
|
14
20
|
children?: ReactNode;
|
package/dist/components/Tabs.js
CHANGED
|
@@ -1,23 +1,64 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Children, createContext, isValidElement, useContext, useEffect, useId, useMemo, useRef } from "react";
|
|
3
|
+
import { Children, createContext, isValidElement, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
const TabsCtx = createContext({});
|
|
6
|
-
|
|
6
|
+
const Chevron = ({ d }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d }) });
|
|
7
|
+
const inlineOffset = (el, rtl) => rtl ? -el.scrollLeft : el.scrollLeft;
|
|
8
|
+
function Tabs({ variant = "line", value, onChange, children, className, overflow = "wrap" }) {
|
|
7
9
|
const baseId = useId();
|
|
8
10
|
const listRef = useRef(null);
|
|
9
11
|
const contextValue = useMemo(() => ({ value, onChange, baseId }), [value, onChange, baseId]);
|
|
12
|
+
const scroll = overflow === "scroll";
|
|
13
|
+
const [hidden, setHidden] = useState({ start: false, end: false });
|
|
10
14
|
const kids = Children.toArray(children);
|
|
11
15
|
const panels = kids.filter((k) => isValidElement(k) && k.type === TabPanel);
|
|
12
16
|
const tabs = kids.filter((k) => !(isValidElement(k) && k.type === TabPanel));
|
|
13
17
|
useEffect(() => {
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
16
|
-
const all = Array.from(
|
|
18
|
+
const list2 = listRef.current;
|
|
19
|
+
if (!list2) return;
|
|
20
|
+
const all = Array.from(list2.querySelectorAll('[role="tab"]'));
|
|
17
21
|
const enabled = all.filter((t) => !t.matches(":disabled"));
|
|
18
22
|
const active = enabled.find((t) => t.getAttribute("aria-selected") === "true") ?? enabled[0];
|
|
19
23
|
for (const t of all) t.tabIndex = t === active ? 0 : -1;
|
|
20
24
|
});
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const list2 = listRef.current;
|
|
27
|
+
if (!scroll || !list2) return;
|
|
28
|
+
const measure = () => {
|
|
29
|
+
const rtl = getComputedStyle(list2).direction === "rtl";
|
|
30
|
+
const pos = inlineOffset(list2, rtl);
|
|
31
|
+
const max = list2.scrollWidth - list2.clientWidth;
|
|
32
|
+
const next = { start: pos > 1, end: pos < max - 1 };
|
|
33
|
+
setHidden((prev) => prev.start === next.start && prev.end === next.end ? prev : next);
|
|
34
|
+
};
|
|
35
|
+
measure();
|
|
36
|
+
list2.addEventListener("scroll", measure, { passive: true });
|
|
37
|
+
if (typeof ResizeObserver === "undefined") return () => list2.removeEventListener("scroll", measure);
|
|
38
|
+
const observer = new ResizeObserver(measure);
|
|
39
|
+
observer.observe(list2);
|
|
40
|
+
for (const tab of Array.from(list2.children)) observer.observe(tab);
|
|
41
|
+
return () => {
|
|
42
|
+
list2.removeEventListener("scroll", measure);
|
|
43
|
+
observer.disconnect();
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const list2 = listRef.current;
|
|
48
|
+
const tab = list2?.querySelector('[role="tab"][aria-selected="true"]');
|
|
49
|
+
if (!scroll || !list2 || !tab) return;
|
|
50
|
+
const outer = list2.getBoundingClientRect();
|
|
51
|
+
const inner = tab.getBoundingClientRect();
|
|
52
|
+
const reserve = list2.scrollWidth > list2.clientWidth ? Number.parseFloat(getComputedStyle(list2).getPropertyValue("--uix-control-h")) || 0 : 0;
|
|
53
|
+
if (inner.left < outer.left + reserve) list2.scrollBy({ left: inner.left - outer.left - reserve });
|
|
54
|
+
else if (inner.right > outer.right - reserve) list2.scrollBy({ left: inner.right - outer.right + reserve });
|
|
55
|
+
}, [scroll, value]);
|
|
56
|
+
const page = (direction) => {
|
|
57
|
+
const list2 = listRef.current;
|
|
58
|
+
if (!list2) return;
|
|
59
|
+
const rtl = getComputedStyle(list2).direction === "rtl";
|
|
60
|
+
list2.scrollBy({ left: direction * (rtl ? -1 : 1) * list2.clientWidth * 0.8 });
|
|
61
|
+
};
|
|
21
62
|
const onKeyDown = (e) => {
|
|
22
63
|
const items = Array.from(
|
|
23
64
|
listRef.current?.querySelectorAll('[role="tab"]:not(:disabled)') ?? []
|
|
@@ -49,17 +90,46 @@ function Tabs({ variant = "line", value, onChange, children, className }) {
|
|
|
49
90
|
target.click();
|
|
50
91
|
}
|
|
51
92
|
};
|
|
93
|
+
const list = /* @__PURE__ */ jsx(
|
|
94
|
+
"div",
|
|
95
|
+
{
|
|
96
|
+
ref: listRef,
|
|
97
|
+
role: "tablist",
|
|
98
|
+
className: cx("uix-tabs", `uix-tabs--${variant}`, scroll && "uix-tabs--scroll", className),
|
|
99
|
+
onKeyDown,
|
|
100
|
+
children: tabs
|
|
101
|
+
}
|
|
102
|
+
);
|
|
52
103
|
return /* @__PURE__ */ jsxs(TabsCtx.Provider, { value: contextValue, children: [
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
scroll ? /* @__PURE__ */ jsxs("div", { className: "uix-tabs-scroller", children: [
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
"button",
|
|
107
|
+
{
|
|
108
|
+
type: "button",
|
|
109
|
+
className: "uix-tabs-scroller__prev",
|
|
110
|
+
"aria-hidden": "true",
|
|
111
|
+
tabIndex: -1,
|
|
112
|
+
hidden: !hidden.start,
|
|
113
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
114
|
+
onClick: () => page(-1),
|
|
115
|
+
children: /* @__PURE__ */ jsx(Chevron, { d: "m15 18-6-6 6-6" })
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
list,
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"button",
|
|
121
|
+
{
|
|
122
|
+
type: "button",
|
|
123
|
+
className: "uix-tabs-scroller__next",
|
|
124
|
+
"aria-hidden": "true",
|
|
125
|
+
tabIndex: -1,
|
|
126
|
+
hidden: !hidden.end,
|
|
127
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
128
|
+
onClick: () => page(1),
|
|
129
|
+
children: /* @__PURE__ */ jsx(Chevron, { d: "m9 18 6-6-6-6" })
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] }) : list,
|
|
63
133
|
panels
|
|
64
134
|
] });
|
|
65
135
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The part of the async Clipboard API that CopyButton needs. */
|
|
2
|
+
interface ClipboardWriter {
|
|
3
|
+
writeText(text: string): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Copies `value` and reports whether the write succeeded. It never throws: there may be no
|
|
7
|
+
* clipboard (an insecure context or an old browser), or the write may be refused (no
|
|
8
|
+
* permission, or the document isn't focused).
|
|
9
|
+
*/
|
|
10
|
+
declare function copyText(value: string, clipboard?: ClipboardWriter | undefined): Promise<boolean>;
|
|
11
|
+
|
|
12
|
+
export { type ClipboardWriter, copyText };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async function copyText(value, clipboard = globalThis.navigator?.clipboard) {
|
|
2
|
+
if (!clipboard || typeof clipboard.writeText !== "function") return false;
|
|
3
|
+
try {
|
|
4
|
+
await clipboard.writeText(value);
|
|
5
|
+
return true;
|
|
6
|
+
} catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
copyText
|
|
12
|
+
};
|