@blakesteve/roster 5.12.0 → 5.14.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 +188 -13
- package/dist/components/molecules/Carousel/Carousel.cjs +1 -1
- package/dist/components/molecules/Carousel/Carousel.d.ts +65 -3
- package/dist/components/molecules/Carousel/Carousel.js +376 -225
- package/dist/components/molecules/Carousel/carousel-math.cjs +1 -1
- package/dist/components/molecules/Carousel/carousel-math.d.ts +16 -0
- package/dist/components/molecules/Carousel/carousel-math.js +63 -44
- package/dist/components/molecules/Carousel/carousel-selection.cjs +1 -0
- package/dist/components/molecules/Carousel/carousel-selection.d.ts +28 -0
- package/dist/components/molecules/Carousel/carousel-selection.js +23 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.d.ts +29 -0
- package/dist/components/molecules/LiquidTabs/LiquidCount.js +21 -0
- package/dist/components/molecules/LiquidTabs/LiquidNav.cjs +1 -1
- package/dist/components/molecules/LiquidTabs/LiquidNav.d.ts +12 -1
- package/dist/components/molecules/LiquidTabs/LiquidNav.js +50 -44
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.d.ts +52 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabPanel.js +32 -0
- package/dist/components/molecules/LiquidTabs/LiquidTabs.cjs +1 -1
- package/dist/components/molecules/LiquidTabs/LiquidTabs.d.ts +20 -1
- package/dist/components/molecules/LiquidTabs/LiquidTabs.js +60 -55
- package/dist/components/molecules/LiquidTabs/liquid-count.cjs +1 -0
- package/dist/components/molecules/LiquidTabs/liquid-count.d.ts +37 -0
- package/dist/components/molecules/LiquidTabs/liquid-count.js +9 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -1
- package/dist/components/organisms/ModerationQueue/ModerationQueue.js +214 -193
- package/dist/index.d.ts +2 -0
- package/dist/internal/dev.cjs +1 -0
- package/dist/internal/dev.d.ts +10 -0
- package/dist/internal/dev.js +10 -0
- package/dist/internal/key-focus.d.ts +27 -2
- package/dist/meta.json +3 -3
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +150 -145
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=6,a=1;function u(t){return Math.max(0,t.scrollWidth-t.clientWidth)}function m(t){return u(t)>a}function L(t){return t.scrollLeft>a}function x(t){return t.scrollLeft<u(t)-a}function o(t,n){const e=t.items[n];if(!e)return 0;const r=t.align==="start"?e.start-t.paddingStart:(e.start+e.end)/2-(t.paddingStart+(t.clientWidth-t.paddingStart-t.paddingEnd)/2);return Math.min(Math.max(0,r),u(t))}function f(t){const n=t.scrollLeft-a,e=t.scrollLeft+t.clientWidth+a,r=[];return t.items.forEach((i,l)=>{i.start>=n&&i.end<=e&&r.push(l)}),r}function h(t){let n=0,e=1/0;return t.items.forEach((r,i)=>{const l=Math.abs(o(t,i)-t.scrollLeft);l<e&&(n=i,e=l)}),n}function E(t){const n=f(t);return n.length?n[0]:h(t)}function b(t,n){const e=t.items.length;if(!e)return 0;const r=f(t),i=r.length?r[0]:h(t),l=Math.max(1,r.length);let c=Math.min(e-1,Math.max(0,i+n*l));for(;c>0&&c<e-1&&Math.abs(o(t,c)-t.scrollLeft)<=a;)c+=n;return o(t,c)}function v(t){return Math.max(1,f(t).length)}function M(t,n,e=t.scrollLeft){const r=t.items[n];if(!r)return null;const i=s=>[s+t.paddingStart-a,s+t.clientWidth-t.paddingEnd+a],[l,c]=i(e);if(r.start>=l&&r.end<=c)return null;if(t.align==="center"||r.start<l)return o(t,n);for(let s=0;s<=n;s++){const d=o(t,s);if(d<e)continue;const[,S]=i(d);if(r.end<=S)return d}return o(t,n)}exports.CAROUSEL_DRAG_THRESHOLD=p;exports.CAROUSEL_TOLERANCE=a;exports.canScrollNext=x;exports.canScrollPrev=L;exports.currentIndex=E;exports.fullyVisible=f;exports.maxScroll=u;exports.nearestIndex=h;exports.overflows=m;exports.pageStep=v;exports.pageTarget=b;exports.revealTarget=M;exports.snapPosition=o;
|
|
@@ -58,3 +58,19 @@ export declare function currentIndex(g: CarouselGeometry): number;
|
|
|
58
58
|
* Returns the target `scrollLeft`.
|
|
59
59
|
*/
|
|
60
60
|
export declare function pageTarget(g: CarouselGeometry, direction: 1 | -1): number;
|
|
61
|
+
/**
|
|
62
|
+
* How far Page Up and Page Down move in selection mode: as many options as
|
|
63
|
+
* are wholly in view, at least one.
|
|
64
|
+
*/
|
|
65
|
+
export declare function pageStep(g: CarouselGeometry): number;
|
|
66
|
+
/**
|
|
67
|
+
* The `scrollLeft` that brings item `i` wholly into view, measured from
|
|
68
|
+
* `from` (where the row is, or where a glide is taking it), or `null` when it
|
|
69
|
+
* already is. Inside the scroll padding, so a revealed item clears the gutter.
|
|
70
|
+
*
|
|
71
|
+
* The row moves as little as it can and always lands on a snap point, which
|
|
72
|
+
* the browser would snap it to anyway: an item off the leading side snaps to
|
|
73
|
+
* its own place; one off the trailing side lands on the first snap point that
|
|
74
|
+
* shows it whole. With `center`, an item out of view is centered.
|
|
75
|
+
*/
|
|
76
|
+
export declare function revealTarget(g: CarouselGeometry, i: number, from?: number): number | null;
|
|
@@ -1,58 +1,77 @@
|
|
|
1
|
-
const
|
|
2
|
-
function
|
|
1
|
+
const L = 6, h = 1;
|
|
2
|
+
function u(t) {
|
|
3
3
|
return Math.max(0, t.scrollWidth - t.clientWidth);
|
|
4
4
|
}
|
|
5
|
-
function
|
|
6
|
-
return
|
|
5
|
+
function R(t) {
|
|
6
|
+
return u(t) > 1;
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function A(t) {
|
|
9
9
|
return t.scrollLeft > 1;
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
return t.scrollLeft <
|
|
11
|
+
function O(t) {
|
|
12
|
+
return t.scrollLeft < u(t) - 1;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
17
|
-
const r = t.align === "start" ?
|
|
18
|
-
return Math.min(Math.max(0, r),
|
|
14
|
+
function l(t, n) {
|
|
15
|
+
const e = t.items[n];
|
|
16
|
+
if (!e) return 0;
|
|
17
|
+
const r = t.align === "start" ? e.start - t.paddingStart : (e.start + e.end) / 2 - (t.paddingStart + (t.clientWidth - t.paddingStart - t.paddingEnd) / 2);
|
|
18
|
+
return Math.min(Math.max(0, r), u(t));
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
return t.items.forEach((i,
|
|
23
|
-
i.start >=
|
|
20
|
+
function f(t) {
|
|
21
|
+
const n = t.scrollLeft - 1, e = t.scrollLeft + t.clientWidth + 1, r = [];
|
|
22
|
+
return t.items.forEach((i, c) => {
|
|
23
|
+
i.start >= n && i.end <= e && r.push(c);
|
|
24
24
|
}), r;
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
let
|
|
26
|
+
function E(t) {
|
|
27
|
+
let n = 0, e = 1 / 0;
|
|
28
28
|
return t.items.forEach((r, i) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
}),
|
|
29
|
+
const c = Math.abs(l(t, i) - t.scrollLeft);
|
|
30
|
+
c < e && (n = i, e = c);
|
|
31
|
+
}), n;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
34
|
-
const
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
if (!
|
|
40
|
-
const r =
|
|
41
|
-
let
|
|
42
|
-
for (;
|
|
43
|
-
|
|
44
|
-
return
|
|
33
|
+
function C(t) {
|
|
34
|
+
const n = f(t);
|
|
35
|
+
return n.length ? n[0] : E(t);
|
|
36
|
+
}
|
|
37
|
+
function S(t, n) {
|
|
38
|
+
const e = t.items.length;
|
|
39
|
+
if (!e) return 0;
|
|
40
|
+
const r = f(t), i = r.length ? r[0] : E(t), c = Math.max(1, r.length);
|
|
41
|
+
let s = Math.min(e - 1, Math.max(0, i + n * c));
|
|
42
|
+
for (; s > 0 && s < e - 1 && Math.abs(l(t, s) - t.scrollLeft) <= 1; )
|
|
43
|
+
s += n;
|
|
44
|
+
return l(t, s);
|
|
45
|
+
}
|
|
46
|
+
function m(t) {
|
|
47
|
+
return Math.max(1, f(t).length);
|
|
48
|
+
}
|
|
49
|
+
function p(t, n, e = t.scrollLeft) {
|
|
50
|
+
const r = t.items[n];
|
|
51
|
+
if (!r) return null;
|
|
52
|
+
const i = (a) => [a + t.paddingStart - 1, a + t.clientWidth - t.paddingEnd + 1], [c, s] = i(e);
|
|
53
|
+
if (r.start >= c && r.end <= s) return null;
|
|
54
|
+
if (t.align === "center" || r.start < c) return l(t, n);
|
|
55
|
+
for (let a = 0; a <= n; a++) {
|
|
56
|
+
const o = l(t, a);
|
|
57
|
+
if (o < e) continue;
|
|
58
|
+
const [, d] = i(o);
|
|
59
|
+
if (r.end <= d) return o;
|
|
60
|
+
}
|
|
61
|
+
return l(t, n);
|
|
45
62
|
}
|
|
46
63
|
export {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
L as CAROUSEL_DRAG_THRESHOLD,
|
|
65
|
+
h as CAROUSEL_TOLERANCE,
|
|
66
|
+
O as canScrollNext,
|
|
67
|
+
A as canScrollPrev,
|
|
68
|
+
C as currentIndex,
|
|
69
|
+
f as fullyVisible,
|
|
70
|
+
u as maxScroll,
|
|
71
|
+
E as nearestIndex,
|
|
72
|
+
R as overflows,
|
|
73
|
+
m as pageStep,
|
|
74
|
+
S as pageTarget,
|
|
75
|
+
p as revealTarget,
|
|
76
|
+
l as snapPosition
|
|
58
77
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=700;function d(r,t,n,i){if(!r||!n)return-1;const e=[...n].every(l=>l===n[0]),f=e?n[0]:n,u=e?t+1:t;for(let l=0;l<r;l++){const a=((u+l)%r+r)%r;if(i(a).trim().toLowerCase().startsWith(f))return a}return-1}function h(r,t,n){if(n===null)return null;const i=r.indexOf(n);if(i===-1)return null;for(let e=i+1;e<r.length;e++)if(t.has(r[e]))return r[e];for(let e=i-1;e>=0;e--)if(t.has(r[e]))return r[e];return null}exports.TYPEAHEAD_RESET_MS=s;exports.survivingNeighbor=h;exports.typeaheadIndex=d;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection mode's decisions that need no DOM: which option type-ahead lands
|
|
3
|
+
* on, and where the active option goes when the one it was on leaves.
|
|
4
|
+
*
|
|
5
|
+
* Pure, so they're tested with literals; the stories drive the real keys.
|
|
6
|
+
*/
|
|
7
|
+
/** How long a pause starts type-ahead over, in ms. */
|
|
8
|
+
export declare const TYPEAHEAD_RESET_MS = 700;
|
|
9
|
+
/**
|
|
10
|
+
* The option type-ahead lands on, or -1 for none.
|
|
11
|
+
*
|
|
12
|
+
* `text` is everything typed since the last pause, lowercased. One letter, or
|
|
13
|
+
* the same letter again and again, steps through the options starting with
|
|
14
|
+
* it, beginning after `from` and wrapping, so pressing "g" three times visits
|
|
15
|
+
* the third option starting with "g". Anything longer matches from `from`
|
|
16
|
+
* itself, so "gl" typed after "g" stays put when the active option already
|
|
17
|
+
* starts with "gl".
|
|
18
|
+
*
|
|
19
|
+
* `labelAt(i)` is option `i`'s text, read only as far as the search goes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function typeaheadIndex(count: number, from: number, text: string, labelAt: (index: number) => string): number;
|
|
22
|
+
/**
|
|
23
|
+
* Where the active option goes when its own has left the row: the nearest
|
|
24
|
+
* option after it that's still there, in the order before the change, else
|
|
25
|
+
* the nearest before it. `null` when none of its old neighbors is left, or
|
|
26
|
+
* when it wasn't in the old order at all.
|
|
27
|
+
*/
|
|
28
|
+
export declare function survivingNeighbor(previous: readonly string[], present: ReadonlySet<string>, lost: string | null): string | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const u = 700;
|
|
2
|
+
function h(r, e, t, i) {
|
|
3
|
+
if (!r || !t) return -1;
|
|
4
|
+
const n = [...t].every((f) => f === t[0]), a = n ? t[0] : t, s = n ? e + 1 : e;
|
|
5
|
+
for (let f = 0; f < r; f++) {
|
|
6
|
+
const l = ((s + f) % r + r) % r;
|
|
7
|
+
if (i(l).trim().toLowerCase().startsWith(a)) return l;
|
|
8
|
+
}
|
|
9
|
+
return -1;
|
|
10
|
+
}
|
|
11
|
+
function c(r, e, t) {
|
|
12
|
+
if (t === null) return null;
|
|
13
|
+
const i = r.indexOf(t);
|
|
14
|
+
if (i === -1) return null;
|
|
15
|
+
for (let n = i + 1; n < r.length; n++) if (e.has(r[n])) return r[n];
|
|
16
|
+
for (let n = i - 1; n >= 0; n--) if (e.has(r[n])) return r[n];
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
u as TYPEAHEAD_RESET_MS,
|
|
21
|
+
c as survivingNeighbor,
|
|
22
|
+
h as typeaheadIndex
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),n=require("./liquid-count.cjs");function i({label:s,count:r,labels:e}){return n.shownCount(r)?t.jsxs("span",{className:"rst:inline-flex rst:items-center rst:justify-center rst:gap-1.5",children:[s,t.jsx("span",{"aria-hidden":"true","data-tab-count":"",className:"rst:inline-flex rst:h-5 rst:min-w-5 rst:items-center rst:justify-center rst:rounded-full rst:bg-current/12 rst:px-1.5 rst:text-xs rst:font-semibold rst:leading-none rst:tabular-nums",children:e.countMark(r)}),t.jsx("span",{className:"rst:sr-only",children:`, ${e.count(r)}`})]}):t.jsx(t.Fragment,{children:s})}exports.LiquidCount=i;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { LiquidTabsLabels } from './liquid-count';
|
|
3
|
+
/**
|
|
4
|
+
* A tab's or a link's label, with its count beside it when it has one.
|
|
5
|
+
*
|
|
6
|
+
* The mark is drawn from the item's own text color: its number in that color,
|
|
7
|
+
* its fill a light tint of it. So it follows the item through every state it
|
|
8
|
+
* already styles (at rest, selected on the pill, hovered, focused), in both
|
|
9
|
+
* themes and both variants, and through any palette a consumer sets on the
|
|
10
|
+
* `--roster-lt-*` tokens. Badge and Pill each draw from a fixed palette, which
|
|
11
|
+
* on the selected tab would be a gray or brand chip sitting on the pill.
|
|
12
|
+
*
|
|
13
|
+
* The tint is 12% of the ink: enough to read as a mark, and light enough
|
|
14
|
+
* that the number still clears 4.5:1 against it in every state on Roster's
|
|
15
|
+
* own tokens (4.88:1 at the least, the selected tab and the dark strip at
|
|
16
|
+
* rest), where 15% left one state at 4.61.
|
|
17
|
+
*
|
|
18
|
+
* The mark is hidden from assistive technology. The count is said by the
|
|
19
|
+
* item's `aria-label` when its label is a string (`countName`), and by text
|
|
20
|
+
* hidden beside the label otherwise, so the name is "Open, 12 items" rather
|
|
21
|
+
* than "Open12" either way. The hidden text stays under an `aria-label` too,
|
|
22
|
+
* for a link component that doesn't forward one. Render it only for a count that shows (`shownCount`): with
|
|
23
|
+
* none, the item takes its label as it always has, wrapper and all.
|
|
24
|
+
*/
|
|
25
|
+
export declare function LiquidCount({ label, count, labels }: {
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
count: number | undefined;
|
|
28
|
+
labels: LiquidTabsLabels;
|
|
29
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as r, Fragment as e, jsxs as i } from "react/jsx-runtime";
|
|
3
|
+
import { shownCount as a } from "./liquid-count.js";
|
|
4
|
+
function o({ label: s, count: t, labels: n }) {
|
|
5
|
+
return a(t) ? /* @__PURE__ */ i("span", { className: "rst:inline-flex rst:items-center rst:justify-center rst:gap-1.5", children: [
|
|
6
|
+
s,
|
|
7
|
+
/* @__PURE__ */ r(
|
|
8
|
+
"span",
|
|
9
|
+
{
|
|
10
|
+
"aria-hidden": "true",
|
|
11
|
+
"data-tab-count": "",
|
|
12
|
+
className: "rst:inline-flex rst:h-5 rst:min-w-5 rst:items-center rst:justify-center rst:rounded-full rst:bg-current/12 rst:px-1.5 rst:text-xs rst:font-semibold rst:leading-none rst:tabular-nums",
|
|
13
|
+
children: n.countMark(t)
|
|
14
|
+
}
|
|
15
|
+
),
|
|
16
|
+
/* @__PURE__ */ r("span", { className: "rst:sr-only", children: `, ${n.count(t)}` })
|
|
17
|
+
] }) : /* @__PURE__ */ r(e, { children: s });
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
o as LiquidCount
|
|
21
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),N=require("react"),a=require("./liquid-pill.cjs"),x=require("./LiquidCount.cjs"),f=require("./liquid-count.cjs"),P=require("../../../lib/labels.cjs"),R=require("../../../lib/utils.cjs");function S({href:r,className:s,children:d,onClick:c,"aria-current":i,"aria-label":n,"data-tab-id":o}){return t.jsx("a",{href:r,className:s,onClick:c,"aria-current":i,"aria-label":n,"data-tab-id":o,children:d})}function _({items:r,activeTab:s,onChange:d,linkComponent:c,variant:i="pill",fullWidth:n=!1,size:o="md","aria-label":j,"aria-labelledby":p,className:C,labels:I}){const q=P.withLabels(f.LIQUID_COUNT_LABELS,I),L=N.useRef(null),y=N.useRef(null),h=c??S;return a.useLiquidPill(L,y,s,r.map(e=>e.id).join("\0")),t.jsxs("nav",{ref:L,"aria-label":j,"aria-labelledby":p,"data-testid":"liquid-nav",style:a.LIQUID_STRIP_STYLE,className:a.liquidStripClassName(i,n,C),children:[t.jsx("div",{ref:y,"aria-hidden":!0,"data-testid":"liquid-tabs-pill",className:a.liquidPillClassName(i),style:a.LIQUID_PILL_STYLE}),r.map(e=>{const u=e.id===s,b=typeof e.label=="function"?e.label(u):e.label;return t.jsx(h,{href:e.href,to:e.href,"aria-current":u?"page":void 0,"aria-label":f.countName(b,e.count,q),"data-tab-id":e.id,onClick:l=>{l.defaultPrevented||l.button!==0||l.metaKey||l.ctrlKey||l.shiftKey||l.altKey||u||d?.(e.id)},className:R.cn(a.liquidItemClassName({variant:i,fullWidth:n,size:o,isActive:u}),"rst:inline-flex rst:items-center rst:justify-center rst:no-underline"),children:f.shownCount(e.count)?t.jsx(x.LiquidCount,{label:b,count:e.count,labels:q}):b},e.id)})]})}exports.LiquidNav=_;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ComponentType, MouseEvent, ReactNode } from 'react';
|
|
2
2
|
import { LiquidSize, LiquidVariant } from './liquid-pill';
|
|
3
3
|
import { TabItem } from './LiquidTabs';
|
|
4
|
+
import { LiquidTabsLabels } from './liquid-count';
|
|
4
5
|
export interface LiquidNavItem extends Omit<TabItem, "panelId"> {
|
|
5
6
|
href: string;
|
|
6
7
|
}
|
|
@@ -19,6 +20,12 @@ export interface LiquidNavLinkProps {
|
|
|
19
20
|
className: string;
|
|
20
21
|
children: ReactNode;
|
|
21
22
|
"aria-current"?: "page";
|
|
23
|
+
/**
|
|
24
|
+
* The link's whole name when it has a count and a string label: "Inbox, 3
|
|
25
|
+
* items". Forward it to the `<a>`; a component that doesn't still gets a
|
|
26
|
+
* name with the count in it, from text hidden beside the label.
|
|
27
|
+
*/
|
|
28
|
+
"aria-label"?: string;
|
|
22
29
|
onClick: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
23
30
|
"data-tab-id": string;
|
|
24
31
|
}
|
|
@@ -49,6 +56,8 @@ export interface LiquidNavProps {
|
|
|
49
56
|
"aria-label"?: string;
|
|
50
57
|
"aria-labelledby"?: string;
|
|
51
58
|
className?: string;
|
|
59
|
+
/** Every word a count says, with English defaults: see `TabItem.count`. */
|
|
60
|
+
labels?: Partial<LiquidTabsLabels>;
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
54
63
|
* `LiquidTabs`' look, for navigation between routes: a `nav` of links rather
|
|
@@ -58,5 +67,7 @@ export interface LiquidNavProps {
|
|
|
58
67
|
* things to assistive technology. Tabs switch panels on the same page and take
|
|
59
68
|
* one tab stop with arrow keys between them; links go somewhere, and each is
|
|
60
69
|
* its own tab stop, as in any nav.
|
|
70
|
+
*
|
|
71
|
+
* An item can carry a `count`, as a tab does: see `TabItem.count`.
|
|
61
72
|
*/
|
|
62
|
-
export declare function LiquidNav({ items, activeTab, onChange, linkComponent, variant, fullWidth, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, }: LiquidNavProps): import("react/jsx-runtime").JSX.Element;
|
|
73
|
+
export declare function LiquidNav({ items, activeTab, onChange, linkComponent, variant, fullWidth, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, labels, }: LiquidNavProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,70 +1,76 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as q, jsx as
|
|
3
|
-
import { useRef as
|
|
4
|
-
import { useLiquidPill as
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { jsxs as q, jsx as o } from "react/jsx-runtime";
|
|
3
|
+
import { useRef as m } from "react";
|
|
4
|
+
import { useLiquidPill as C, LIQUID_STRIP_STYLE as _, LIQUID_PILL_STYLE as P, liquidPillClassName as S, liquidItemClassName as j, liquidStripClassName as x } from "./liquid-pill.js";
|
|
5
|
+
import { LiquidCount as D } from "./LiquidCount.js";
|
|
6
|
+
import { shownCount as K, countName as R, LIQUID_COUNT_LABELS as U } from "./liquid-count.js";
|
|
7
|
+
import { withLabels as k } from "../../../lib/labels.js";
|
|
8
|
+
import { cn as E } from "../../../lib/utils.js";
|
|
9
|
+
function Q({
|
|
10
|
+
href: r,
|
|
11
|
+
className: e,
|
|
12
|
+
children: s,
|
|
13
|
+
onClick: u,
|
|
14
|
+
"aria-current": i,
|
|
15
|
+
"aria-label": t,
|
|
16
|
+
"data-tab-id": d
|
|
13
17
|
}) {
|
|
14
|
-
return /* @__PURE__ */
|
|
18
|
+
return /* @__PURE__ */ o("a", { href: r, className: e, onClick: u, "aria-current": i, "aria-label": t, "data-tab-id": d, children: s });
|
|
15
19
|
}
|
|
16
|
-
function
|
|
17
|
-
items:
|
|
18
|
-
activeTab:
|
|
19
|
-
onChange:
|
|
20
|
-
linkComponent:
|
|
21
|
-
variant:
|
|
20
|
+
function z({
|
|
21
|
+
items: r,
|
|
22
|
+
activeTab: e,
|
|
23
|
+
onChange: s,
|
|
24
|
+
linkComponent: u,
|
|
25
|
+
variant: i = "pill",
|
|
22
26
|
fullWidth: t = !1,
|
|
23
|
-
size:
|
|
24
|
-
"aria-label":
|
|
25
|
-
"aria-labelledby":
|
|
26
|
-
className:
|
|
27
|
+
size: d = "md",
|
|
28
|
+
"aria-label": L,
|
|
29
|
+
"aria-labelledby": y,
|
|
30
|
+
className: I,
|
|
31
|
+
labels: N
|
|
27
32
|
}) {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
33
|
+
const f = k(U, N), b = m(null), p = m(null), h = u ?? Q;
|
|
34
|
+
return C(b, p, e, r.map((a) => a.id).join("\0")), /* @__PURE__ */ q(
|
|
30
35
|
"nav",
|
|
31
36
|
{
|
|
32
|
-
ref:
|
|
33
|
-
"aria-label":
|
|
34
|
-
"aria-labelledby":
|
|
37
|
+
ref: b,
|
|
38
|
+
"aria-label": L,
|
|
39
|
+
"aria-labelledby": y,
|
|
35
40
|
"data-testid": "liquid-nav",
|
|
36
|
-
style:
|
|
37
|
-
className: x(
|
|
41
|
+
style: _,
|
|
42
|
+
className: x(i, t, I),
|
|
38
43
|
children: [
|
|
39
|
-
/* @__PURE__ */
|
|
44
|
+
/* @__PURE__ */ o(
|
|
40
45
|
"div",
|
|
41
46
|
{
|
|
42
|
-
ref:
|
|
47
|
+
ref: p,
|
|
43
48
|
"aria-hidden": !0,
|
|
44
49
|
"data-testid": "liquid-tabs-pill",
|
|
45
|
-
className:
|
|
46
|
-
style:
|
|
50
|
+
className: S(i),
|
|
51
|
+
style: P
|
|
47
52
|
}
|
|
48
53
|
),
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
return /* @__PURE__ */
|
|
52
|
-
|
|
54
|
+
r.map((a) => {
|
|
55
|
+
const n = a.id === e, c = typeof a.label == "function" ? a.label(n) : a.label;
|
|
56
|
+
return /* @__PURE__ */ o(
|
|
57
|
+
h,
|
|
53
58
|
{
|
|
54
59
|
href: a.href,
|
|
55
60
|
to: a.href,
|
|
56
|
-
"aria-current":
|
|
61
|
+
"aria-current": n ? "page" : void 0,
|
|
62
|
+
"aria-label": R(c, a.count, f),
|
|
57
63
|
"data-tab-id": a.id,
|
|
58
|
-
onClick: (
|
|
59
|
-
|
|
64
|
+
onClick: (l) => {
|
|
65
|
+
l.defaultPrevented || l.button !== 0 || l.metaKey || l.ctrlKey || l.shiftKey || l.altKey || n || s?.(a.id);
|
|
60
66
|
},
|
|
61
|
-
className:
|
|
62
|
-
j({ variant:
|
|
67
|
+
className: E(
|
|
68
|
+
j({ variant: i, fullWidth: t, size: d, isActive: n }),
|
|
63
69
|
/* A button centers its label and draws no underline on its own;
|
|
64
70
|
a link does neither, and a host stylesheet may underline it. */
|
|
65
71
|
"rst:inline-flex rst:items-center rst:justify-center rst:no-underline"
|
|
66
72
|
),
|
|
67
|
-
children:
|
|
73
|
+
children: K(a.count) ? /* @__PURE__ */ o(D, { label: c, count: a.count, labels: f }) : c
|
|
68
74
|
},
|
|
69
75
|
a.id
|
|
70
76
|
);
|
|
@@ -74,5 +80,5 @@ function D({
|
|
|
74
80
|
);
|
|
75
81
|
}
|
|
76
82
|
export {
|
|
77
|
-
|
|
83
|
+
z as LiquidNav
|
|
78
84
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("react/jsx-runtime"),t=require("react"),y=require("./liquid-tab-id.cjs"),p='a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), iframe, summary, audio[controls], video[controls], [contenteditable]:not([contenteditable="false"]), [tabindex]:not([tabindex="-1"])';function x({tabsId:d,id:l,tab:n,activeTab:a,keepMounted:u=!1,tabIndex:b,children:c,...f}){const i=t.useRef(null),r=a===void 0||a===n,[h,q]=t.useState(!1);return t.useLayoutEffect(()=>{const e=i.current;if(!e)return;const o=()=>q(!!e.querySelector(p));if(o(),typeof MutationObserver>"u")return;const s=new MutationObserver(o);return s.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["disabled","tabindex","href","contenteditable"]}),()=>s.disconnect()},[]),v.jsx("div",{...f,ref:i,role:"tabpanel",id:l,"aria-labelledby":y.liquidTabId(d,n),hidden:!r,tabIndex:b??(h?void 0:0),"data-tab-panel":"",children:r||u?c:null})}exports.LiquidTabPanel=x;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface LiquidTabPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, "id" | "role" | "hidden" | "children"> {
|
|
3
|
+
/** The `id` of the `LiquidTabs` this panel belongs to (its `id` prop). */
|
|
4
|
+
tabsId: string;
|
|
5
|
+
/** This panel's `id`: the `panelId` its tab carries, or every tab, for one panel that swaps its content. */
|
|
6
|
+
id: string;
|
|
7
|
+
/**
|
|
8
|
+
* The tab that names this panel. For one panel that swaps its content, the
|
|
9
|
+
* selected tab: the panel's name follows the selection. For a panel per tab,
|
|
10
|
+
* its own tab.
|
|
11
|
+
*/
|
|
12
|
+
tab: string;
|
|
13
|
+
/**
|
|
14
|
+
* For a panel per tab: the selected tab. The panel shows when it is `tab`,
|
|
15
|
+
* and is hidden otherwise. Leave it out for one panel that swaps its
|
|
16
|
+
* content, which always shows.
|
|
17
|
+
*/
|
|
18
|
+
activeTab?: string;
|
|
19
|
+
/**
|
|
20
|
+
* For a panel per tab: keep the content mounted while the panel is hidden,
|
|
21
|
+
* so what's in it (a half-filled form, a scroll position) survives a switch
|
|
22
|
+
* away and back. By default a hidden panel renders no content. The panel
|
|
23
|
+
* element itself always stays, hidden, so its tab's `aria-controls` points
|
|
24
|
+
* at something.
|
|
25
|
+
*/
|
|
26
|
+
keepMounted?: boolean;
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The panel a `LiquidTabs` tab controls, paired with it by construction:
|
|
31
|
+
* `role="tabpanel"`, its `id` the tab's `panelId`, and `aria-labelledby`
|
|
32
|
+
* pointing at the tab through `liquidTabId`, so it's named for its tab.
|
|
33
|
+
*
|
|
34
|
+
* ```tsx
|
|
35
|
+
* <LiquidTabs id="views" tabs={[{ id: "open", label: "Open", panelId: "views-panel" }, …]} … />
|
|
36
|
+
* <LiquidTabPanel tabsId="views" id="views-panel" tab={view}>…</LiquidTabPanel>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Two ways to lay it out. **One panel that swaps its content:** every tab
|
|
40
|
+
* carries the same `panelId`, and `tab` is the selected tab. **A panel per
|
|
41
|
+
* tab:** each tab has its own `panelId`, each panel its own `tab`, and every
|
|
42
|
+
* panel gets `activeTab`, so only the selected one shows.
|
|
43
|
+
*
|
|
44
|
+
* The WAI tabs pattern puts the panel in the tab order when nothing inside it
|
|
45
|
+
* can take focus, so a keyboard user can reach and scroll what was just
|
|
46
|
+
* selected. Whether the content holds anything focusable isn't known until it
|
|
47
|
+
* renders, and it can change as content loads, so the panel starts reachable
|
|
48
|
+
* (`tabIndex={0}`) and leaves the tab order once its content holds something
|
|
49
|
+
* that can be Tabbed to, rechecking as the content changes. Pass `tabIndex`
|
|
50
|
+
* to decide it yourself.
|
|
51
|
+
*/
|
|
52
|
+
export declare function LiquidTabPanel({ tabsId, id, tab, activeTab, keepMounted, tabIndex, children, ...rest }: LiquidTabPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as p } from "react/jsx-runtime";
|
|
3
|
+
import { useRef as m, useState as v, useLayoutEffect as x } from "react";
|
|
4
|
+
import { liquidTabId as y } from "./liquid-tab-id.js";
|
|
5
|
+
const L = 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), iframe, summary, audio[controls], video[controls], [contenteditable]:not([contenteditable="false"]), [tabindex]:not([tabindex="-1"])';
|
|
6
|
+
function w({ tabsId: d, id: s, tab: t, activeTab: n, keepMounted: l = !1, tabIndex: b, children: u, ...c }) {
|
|
7
|
+
const r = m(null), a = n === void 0 || n === t, [f, h] = v(!1);
|
|
8
|
+
return x(() => {
|
|
9
|
+
const e = r.current;
|
|
10
|
+
if (!e) return;
|
|
11
|
+
const o = () => h(!!e.querySelector(L));
|
|
12
|
+
if (o(), typeof MutationObserver > "u") return;
|
|
13
|
+
const i = new MutationObserver(o);
|
|
14
|
+
return i.observe(e, { childList: !0, subtree: !0, attributes: !0, attributeFilter: ["disabled", "tabindex", "href", "contenteditable"] }), () => i.disconnect();
|
|
15
|
+
}, []), /* @__PURE__ */ p(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
...c,
|
|
19
|
+
ref: r,
|
|
20
|
+
role: "tabpanel",
|
|
21
|
+
id: s,
|
|
22
|
+
"aria-labelledby": y(d, t),
|
|
23
|
+
hidden: !a,
|
|
24
|
+
tabIndex: b ?? (f ? void 0 : 0),
|
|
25
|
+
"data-tab-panel": "",
|
|
26
|
+
children: a || l ? u : null
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
w as LiquidTabPanel
|
|
32
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react/jsx-runtime"),d=require("react"),o=require("./liquid-pill.cjs"),U=require("./liquid-tab-id.cjs"),O=require("./LiquidCount.cjs"),m=require("./liquid-count.cjs"),Q=require("../../../lib/labels.cjs"),B=require("../../../internal/key-focus.cjs");function K({tabs:t,activeTab:r,onChange:C,variant:c="pill",fullWidth:y=!1,size:A="md",activation:R="automatic",id:h,"aria-label":w,"aria-labelledby":N,className:j,labels:k}){B.useKeyFocus();const L=Q.withLabels(m.LIQUID_COUNT_LABELS,k),a=d.useRef(null),I=d.useRef(null),x=d.useId(),p=h??x;o.useLiquidPill(a,I,r,t.map(e=>e.id).join("\0"));const b=e=>{e!==r&&C(e)},[f,S]=d.useState(null),T=t.some(e=>e.id===r),P=f!==null&&t.some(e=>e.id===f)?f:T?r:t[0]?.id,_=e=>{e.currentTarget.contains(e.relatedTarget)||S(null)},D=e=>{const i=a.current;if(!i)return;const l=Array.from(i.querySelectorAll('[role="tab"]')),n=l.indexOf(e.target);if(n===-1)return;const q=l.length-1,g=getComputedStyle(i).direction==="rtl",E=g?"ArrowLeft":"ArrowRight",F=g?"ArrowRight":"ArrowLeft";let s;if(e.key===E)s=n===q?0:n+1;else if(e.key===F)s=n===0?q:n-1;else if(e.key==="Home")s=0;else if(e.key==="End")s=q;else return;e.preventDefault(),l[s].focus(),R==="automatic"&&b(t[s].id)};return u.jsxs("div",{ref:a,id:p,role:"tablist","aria-label":w,"aria-labelledby":N,"data-testid":"liquid-tabs",style:o.LIQUID_STRIP_STYLE,className:o.liquidStripClassName(c,y,j),onKeyDown:D,onBlur:_,children:[u.jsx("div",{ref:I,"aria-hidden":!0,"data-testid":"liquid-tabs-pill",className:o.liquidPillClassName(c),style:o.LIQUID_PILL_STYLE}),t.map(e=>{const i=e.id===r,l=typeof e.label=="function"?e.label(i):e.label;return u.jsx("button",{type:"button",role:"tab",id:U.liquidTabId(p,e.id),"aria-selected":i,"aria-controls":e.panelId,"aria-label":m.countName(l,e.count,L),tabIndex:e.id===P?0:-1,"data-tab-id":e.id,"data-testid":`liquid-tab-${e.id}`,onClick:()=>b(e.id),onFocus:()=>S(e.id),className:o.liquidItemClassName({variant:c,fullWidth:y,size:A,isActive:i}),children:m.shownCount(e.count)?u.jsx(O.LiquidCount,{label:l,count:e.count,labels:L}):l},e.id)})]})}exports.LiquidTabs=K;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { LiquidSize, LiquidVariant } from './liquid-pill';
|
|
3
|
+
import { LiquidTabsLabels } from './liquid-count';
|
|
4
|
+
export type { LiquidTabsLabels };
|
|
3
5
|
export interface TabItem {
|
|
4
6
|
id: string;
|
|
5
7
|
/** Static label, or a render function that receives whether this tab is active. */
|
|
@@ -9,6 +11,18 @@ export interface TabItem {
|
|
|
9
11
|
* every tab the same id when one panel swaps its content.
|
|
10
12
|
*/
|
|
11
13
|
panelId?: string;
|
|
14
|
+
/**
|
|
15
|
+
* A number in a small mark beside the label, read as part of the tab's
|
|
16
|
+
* name: "Open, 12 items" (`labels.count` says it).
|
|
17
|
+
*
|
|
18
|
+
* **Zero shows**, as "0" and "0 items": a count that vanished at zero would
|
|
19
|
+
* change the tab's width and move every tab after it, and an empty list is
|
|
20
|
+
* worth saying. To hide it, pass `undefined` for zero. `undefined` means no
|
|
21
|
+
* count and no mark. A negative or fractional count isn't shown at all,
|
|
22
|
+
* never rounded or clamped. Large counts aren't abbreviated: 12,345 shows
|
|
23
|
+
* as "12,345" unless `labels.countMark` writes it shorter.
|
|
24
|
+
*/
|
|
25
|
+
count?: number;
|
|
12
26
|
}
|
|
13
27
|
export interface LiquidTabsProps {
|
|
14
28
|
tabs: TabItem[];
|
|
@@ -49,6 +63,8 @@ export interface LiquidTabsProps {
|
|
|
49
63
|
"aria-labelledby"?: string;
|
|
50
64
|
/** Extra classes applied to the outer container. */
|
|
51
65
|
className?: string;
|
|
66
|
+
/** Every word a count says, with English defaults: see `TabItem.count`. */
|
|
67
|
+
labels?: Partial<LiquidTabsLabels>;
|
|
52
68
|
}
|
|
53
69
|
/**
|
|
54
70
|
* A controlled tab strip with a liquid sliding indicator.
|
|
@@ -64,6 +80,9 @@ export interface LiquidTabsProps {
|
|
|
64
80
|
* The pill follows `activeTab`, never the click, so a change the consumer
|
|
65
81
|
* vetoes, or one that waits on navigation, never leaves it on the wrong tab.
|
|
66
82
|
*
|
|
83
|
+
* A tab can carry a `count`, and a panel pairs with its tab by construction
|
|
84
|
+
* through `LiquidTabPanel`.
|
|
85
|
+
*
|
|
67
86
|
* For navigation between routes, use `LiquidNav`, which renders links.
|
|
68
87
|
*/
|
|
69
|
-
export declare function LiquidTabs({ tabs, activeTab, onChange, variant, fullWidth, size, activation, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, }: LiquidTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
88
|
+
export declare function LiquidTabs({ tabs, activeTab, onChange, variant, fullWidth, size, activation, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, labels, }: LiquidTabsProps): import("react/jsx-runtime").JSX.Element;
|