@blakesteve/roster 5.1.0 → 5.2.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 +57 -3
- package/dist/components/atoms/Input/Input.cjs +1 -1
- package/dist/components/atoms/Input/Input.js +46 -41
- package/dist/components/atoms/Spinner/Spinner.cjs +1 -1
- package/dist/components/atoms/Spinner/Spinner.d.ts +6 -1
- package/dist/components/atoms/Spinner/Spinner.js +9 -8
- package/dist/components/molecules/Carousel/Carousel.cjs +1 -0
- package/dist/components/molecules/Carousel/Carousel.d.ts +146 -0
- package/dist/components/molecules/Carousel/Carousel.js +363 -0
- package/dist/components/molecules/Carousel/carousel-layout.cjs +1 -0
- package/dist/components/molecules/Carousel/carousel-layout.d.ts +23 -0
- package/dist/components/molecules/Carousel/carousel-layout.js +22 -0
- package/dist/components/molecules/Carousel/carousel-math.cjs +1 -0
- package/dist/components/molecules/Carousel/carousel-math.d.ts +60 -0
- package/dist/components/molecules/Carousel/carousel-math.js +58 -0
- package/dist/components/molecules/EmptyState/EmptyState.cjs +1 -1
- package/dist/components/molecules/EmptyState/EmptyState.d.ts +7 -1
- package/dist/components/molecules/EmptyState/EmptyState.js +18 -14
- package/dist/components/organisms/Sheet/Sheet.cjs +1 -1
- package/dist/components/organisms/Sheet/Sheet.d.ts +8 -5
- package/dist/components/organisms/Sheet/Sheet.js +122 -121
- package/dist/components/organisms/Sheet/sheet-title.cjs +1 -0
- package/dist/components/organisms/Sheet/sheet-title.d.ts +10 -0
- package/dist/components/organisms/Sheet/sheet-title.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/meta.json +3 -3
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +55 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -491,6 +491,9 @@ the sweep stops and the highlight parks off-frame. The highlight is deliberately
|
|
|
491
491
|
exempt from that bar: it is a specular pass on screen for a fraction of a second
|
|
492
492
|
per glyph, and holding it to 3:1 would flatten the effect.
|
|
493
493
|
|
|
494
|
+
`Spinner` holds still under reduced motion: the arc still reads as loading, and
|
|
495
|
+
its `label` (default "loading") says so.
|
|
496
|
+
|
|
494
497
|
### Scrollbars
|
|
495
498
|
|
|
496
499
|
`custom-scrollbar` gives any scrollable surface a slim themed scrollbar.
|
|
@@ -1104,6 +1107,47 @@ escape the box. Lay the options out across with `orientation` rather than
|
|
|
1104
1107
|
through `optionsClassName`, for the same reason: the horizontal spacing that
|
|
1105
1108
|
keeps two radios 44px apart rides on that prop.
|
|
1106
1109
|
|
|
1110
|
+
### Carousel
|
|
1111
|
+
|
|
1112
|
+
A sideways row: a row of cards, a selection strip, or a gallery of one per
|
|
1113
|
+
view. It is a real scroll container with CSS scroll snap, so touch, trackpad,
|
|
1114
|
+
Shift with the wheel and keyboard focus all scroll it without script, and every
|
|
1115
|
+
item is in the server render. No clones, no virtualization, no loop, and
|
|
1116
|
+
nothing moves on its own.
|
|
1117
|
+
|
|
1118
|
+
```tsx
|
|
1119
|
+
<Carousel aria-label="Songs" itemWidth={240} gap={12} gutter={16}>
|
|
1120
|
+
{songs.map((song) => <SongCard key={song.id} song={song} />)}
|
|
1121
|
+
</Carousel>
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
Each child is one item, wrapped in the `li` the row snaps to. Size the items
|
|
1125
|
+
with `itemWidth` (fixed) or `perView` (fluid: `{ base: 1.3, md: 2.5 }`, where
|
|
1126
|
+
the fraction is the peek of the next item). `gutter` is both the inline padding
|
|
1127
|
+
and the scroll padding, so a snapped item lines up with the text above it, and
|
|
1128
|
+
`bleed` pulls the row out by one gutter to run edge to edge.
|
|
1129
|
+
|
|
1130
|
+
Script adds what a mouse is missing:
|
|
1131
|
+
|
|
1132
|
+
- **Drag.** A mouse drags the row one to one and it settles on the nearest
|
|
1133
|
+
item. A drag past 6px drops the one click that ends it, and nothing else.
|
|
1134
|
+
Touch and pen keep native scrolling.
|
|
1135
|
+
- **Arrows.** Previous and next page by as many items as are wholly in view,
|
|
1136
|
+
measured each time, and land on an item's edge. At an end the arrow is
|
|
1137
|
+
`disabled`, out of the tab order. They show where a pointer can hover, on a
|
|
1138
|
+
line above the row by default (`arrowPlacement="overlay"` floats them over
|
|
1139
|
+
the ends; `renderArrows` puts them anywhere, such as a heading line). They
|
|
1140
|
+
are 44px, drawn with inline SVG, and named "Next" plus the row's label.
|
|
1141
|
+
- **Keyboard focus** snaps its item into place at once. If no item can take
|
|
1142
|
+
focus, the list itself is the tab stop.
|
|
1143
|
+
|
|
1144
|
+
A vertical wheel over the row scrolls the page, and sideways overscroll is
|
|
1145
|
+
contained. Under reduced motion every scroll the component makes is instant.
|
|
1146
|
+
`ref` gives `scrollToIndex(i)`, `scrollPrev()` and `scrollNext()`;
|
|
1147
|
+
`onIndexChange` reports the first item wholly in view. For a gallery,
|
|
1148
|
+
`showPosition` shows "2 of 5", which is also each item's name and is announced
|
|
1149
|
+
politely when it changes.
|
|
1150
|
+
|
|
1107
1151
|
### Components that render links
|
|
1108
1152
|
|
|
1109
1153
|
`Breadcrumbs` renders a plain `<a>` by default, which is right for a static
|
|
@@ -1171,7 +1215,7 @@ function App() {
|
|
|
1171
1215
|
| `Disclosure` | Show/hide toggle using HeadlessUI |
|
|
1172
1216
|
| `Eyebrow` | Small tracked-out uppercase label above a heading or beside a rule; polymorphic via `as` |
|
|
1173
1217
|
| `InlineCode` | Inline `<code>` for identifiers in running prose |
|
|
1174
|
-
| `Input` | Text input with label, error state, icon slots, and a size scale matching `Button`
|
|
1218
|
+
| `Input` | Text input with label, error state, icon slots, and a size scale matching `Button`; keeps a passed `aria-invalid` |
|
|
1175
1219
|
| `LabeledDivider` | Horizontal rule carrying a label, with an optional trailing count |
|
|
1176
1220
|
| `Link` | Styled anchor with variant support |
|
|
1177
1221
|
| `MultiSelect` | A `Select` that holds more than one value: dismissible chips, same panel |
|
|
@@ -1197,8 +1241,9 @@ function App() {
|
|
|
1197
1241
|
| `Breadcrumbs` | Navigation trail; pass `linkComponent` to keep navigation client-side |
|
|
1198
1242
|
| `CheckboxGroup` | Checkbox set reporting one `string[]`; groups, columns, scroll, error |
|
|
1199
1243
|
| `CallToAction` | Prominent hero-style CTA block |
|
|
1244
|
+
| `Carousel` | Sideways row of items on native scroll snap; mouse drag, paging arrows, no autoplay |
|
|
1200
1245
|
| `DescriptionList` | Label and value pairs as a real `<dl>`: inline, stacked, or split |
|
|
1201
|
-
| `EmptyState` | Zero-data placeholder with icon and action slot
|
|
1246
|
+
| `EmptyState` | Zero-data placeholder with icon and action slot; `headingLevel` sets its title's `h1` to `h6` |
|
|
1202
1247
|
| `ErrorState` | Error display with retry action |
|
|
1203
1248
|
| `LiquidNav` | `LiquidTabs`' strip as a `nav` of links; takes a router's link component |
|
|
1204
1249
|
| `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size |
|
|
@@ -1217,7 +1262,7 @@ function App() {
|
|
|
1217
1262
|
| `Dialog` | Modal dialog |
|
|
1218
1263
|
| `Footer` | Site footer |
|
|
1219
1264
|
| `Navbar` | Responsive navigation bar with mobile slide-out |
|
|
1220
|
-
| `Sheet` | Bottom sheet: drag, Escape or backdrop to dismiss; only its content scrolls; returns focus, even from a deep link
|
|
1265
|
+
| `Sheet` | Bottom sheet: drag, Escape or backdrop to dismiss; only its content scrolls; returns focus, even from a deep link; the title may be a node |
|
|
1221
1266
|
| `Table` | Static data table |
|
|
1222
1267
|
|
|
1223
1268
|
### DataTable columns
|
|
@@ -1370,6 +1415,15 @@ reason and the build goes green on the state it inherited while still failing on
|
|
|
1370
1415
|
anything new. It also fails when a listed class starts emitting, so a fix cannot
|
|
1371
1416
|
leave a stale entry behind. Run it with `--verbose` to print the list.
|
|
1372
1417
|
|
|
1418
|
+
Story tests that need the user's own input, a wheel or a mouse drag, get it
|
|
1419
|
+
from `.storybook/real-input.ts`: Vitest browser commands that drive
|
|
1420
|
+
Playwright's mouse, whose events are trusted. A synthetic wheel or pointer
|
|
1421
|
+
event never scrolls anything, so "a vertical wheel scrolls the page, not the
|
|
1422
|
+
row" can't be tested without them. The commands fail loudly when the pointer
|
|
1423
|
+
isn't over their target, rather than letting a wheel land on the page and a
|
|
1424
|
+
test pass for the wrong reason. Storybook's own UI has no such input, so those
|
|
1425
|
+
steps are skipped there.
|
|
1426
|
+
|
|
1373
1427
|
`src/lib/utils.test.ts` pins `cn`. `tailwind-merge` has to be configured with the
|
|
1374
1428
|
prefix or it stops recognising Roster's classes as utilities and silently
|
|
1375
1429
|
degrades to concatenation: conflicting classes both survive and the winner falls
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),g=require("react"),V=require("../../../node_modules/@headlessui/react/dist/components/description/description.cjs"),k=require("../../../node_modules/@headlessui/react/dist/components/field/field.cjs"),w=require("../../../node_modules/@headlessui/react/dist/components/input/input.cjs"),P=require("../../../node_modules/@headlessui/react/dist/components/label/label.cjs"),t=require("../../../lib/utils.cjs"),o=require("./input-variants.cjs"),m=g.forwardRef(({className:x,type:f,label:d,helperText:u,errorMessage:e,variant:i,size:n,error:v,startIcon:a,endIcon:l,disabled:p,inputClassName:j,...q},N)=>{const s=!!e||v,{"aria-invalid":c,invalid:b,...h}=q,y=!!b||c!==void 0&&c!==!1&&c!=="false";return r.jsxs(k.Field,{disabled:p,className:t.cn("rst:w-full",x),children:[d&&r.jsx(P.Label,{className:t.cn("rst:block rst:text-sm rst:font-medium rst:leading-none rst:text-left rst:mb-1.5 rst:transition-colors","rst:text-[var(--roster-control-text)]","rst:peer-disabled:cursor-not-allowed rst:peer-disabled:opacity-70"),children:d}),r.jsxs("div",{className:"rst:relative",children:[a&&r.jsx("div",{className:t.cn(o.iconVariants({variant:i,error:s}),"rst:left-3"),children:a}),r.jsx(w.Input,{ref:N,type:f,disabled:p,className:t.cn(o.inputVariants({variant:i,size:n,error:s}),a&&(n==="sm"?"rst:pl-9":"rst:pl-10"),l&&(n==="sm"?"rst:pr-9":"rst:pr-10"),j),...h,invalid:y}),l&&r.jsx("div",{className:t.cn(o.iconVariants({variant:i,error:s}),"rst:right-3"),children:l})]}),(u||e)&&r.jsx(V.Description,{className:t.cn("rst:text-xs rst:text-left rst:mt-1.5",s?"rst:text-error-600 rst:dark:text-error-400 rst:font-medium":"rst:text-gray-500 rst:dark:text-gray-400"),children:e||u})]})});m.displayName="Input";exports.Input=m;
|
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as
|
|
3
|
-
import
|
|
4
|
-
import { Description as
|
|
5
|
-
import { Field as
|
|
6
|
-
import { Input as
|
|
7
|
-
import { Label as
|
|
2
|
+
import { jsxs as c, jsx as t } from "react/jsx-runtime";
|
|
3
|
+
import g from "react";
|
|
4
|
+
import { Description as j } from "../../../node_modules/@headlessui/react/dist/components/description/description.js";
|
|
5
|
+
import { Field as P } from "../../../node_modules/@headlessui/react/dist/components/field/field.js";
|
|
6
|
+
import { Input as R } from "../../../node_modules/@headlessui/react/dist/components/input/input.js";
|
|
7
|
+
import { Label as V } from "../../../node_modules/@headlessui/react/dist/components/label/label.js";
|
|
8
8
|
import { cn as r } from "../../../lib/utils.js";
|
|
9
|
-
import { iconVariants as
|
|
10
|
-
const
|
|
9
|
+
import { iconVariants as f, inputVariants as _ } from "./input-variants.js";
|
|
10
|
+
const D = g.forwardRef(
|
|
11
11
|
({
|
|
12
|
-
className:
|
|
13
|
-
type:
|
|
14
|
-
label:
|
|
15
|
-
helperText:
|
|
16
|
-
errorMessage:
|
|
17
|
-
variant:
|
|
18
|
-
size:
|
|
19
|
-
error:
|
|
20
|
-
startIcon:
|
|
21
|
-
endIcon:
|
|
12
|
+
className: x,
|
|
13
|
+
type: u,
|
|
14
|
+
label: n,
|
|
15
|
+
helperText: d,
|
|
16
|
+
errorMessage: i,
|
|
17
|
+
variant: e,
|
|
18
|
+
size: a,
|
|
19
|
+
error: v,
|
|
20
|
+
startIcon: o,
|
|
21
|
+
endIcon: l,
|
|
22
22
|
disabled: p,
|
|
23
23
|
inputClassName: N,
|
|
24
24
|
...h
|
|
25
25
|
}, y) => {
|
|
26
|
-
const s = !!
|
|
26
|
+
const s = !!i || v, {
|
|
27
|
+
"aria-invalid": m,
|
|
28
|
+
invalid: b,
|
|
29
|
+
...k
|
|
30
|
+
} = h, w = !!b || m !== void 0 && m !== !1 && m !== "false";
|
|
27
31
|
return (
|
|
28
32
|
/* No `space-y-*` here, deliberately. Headless UI's Field appends a
|
|
29
33
|
hidden zero-height <span> after the control, and Tailwind v4 applies
|
|
@@ -38,72 +42,73 @@ const R = b.forwardRef(
|
|
|
38
42
|
DisabledProvider stays false and the Label and Description never dim.
|
|
39
43
|
Fixed in both rather than one, because a form with a disabled Input
|
|
40
44
|
and a disabled Select that grey differently is worse than neither. */
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
/* @__PURE__ */ c(P, { disabled: p, className: r("rst:w-full", x), children: [
|
|
46
|
+
n && /* @__PURE__ */ t(
|
|
47
|
+
V,
|
|
44
48
|
{
|
|
45
49
|
className: r(
|
|
46
50
|
"rst:block rst:text-sm rst:font-medium rst:leading-none rst:text-left rst:mb-1.5 rst:transition-colors",
|
|
47
51
|
"rst:text-[var(--roster-control-text)]",
|
|
48
52
|
"rst:peer-disabled:cursor-not-allowed rst:peer-disabled:opacity-70"
|
|
49
53
|
),
|
|
50
|
-
children:
|
|
54
|
+
children: n
|
|
51
55
|
}
|
|
52
56
|
),
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
|
|
57
|
+
/* @__PURE__ */ c("div", { className: "rst:relative", children: [
|
|
58
|
+
o && /* @__PURE__ */ t(
|
|
55
59
|
"div",
|
|
56
60
|
{
|
|
57
61
|
className: r(
|
|
58
|
-
|
|
62
|
+
f({ variant: e, error: s }),
|
|
59
63
|
"rst:left-3"
|
|
60
64
|
),
|
|
61
|
-
children:
|
|
65
|
+
children: o
|
|
62
66
|
}
|
|
63
67
|
),
|
|
64
68
|
/* @__PURE__ */ t(
|
|
65
|
-
|
|
69
|
+
R,
|
|
66
70
|
{
|
|
67
71
|
ref: y,
|
|
68
|
-
type:
|
|
72
|
+
type: u,
|
|
69
73
|
disabled: p,
|
|
70
74
|
className: r(
|
|
71
|
-
|
|
75
|
+
_({ variant: e, size: a, error: s }),
|
|
72
76
|
/* Icon inset tracks the size's own padding: sm is px-3, so it
|
|
73
77
|
needs one step less clearance than the 16px sizes. */
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
o && (a === "sm" ? "rst:pl-9" : "rst:pl-10"),
|
|
79
|
+
l && (a === "sm" ? "rst:pr-9" : "rst:pr-10"),
|
|
76
80
|
N
|
|
77
81
|
),
|
|
78
|
-
...
|
|
82
|
+
...k,
|
|
83
|
+
invalid: w
|
|
79
84
|
}
|
|
80
85
|
),
|
|
81
|
-
|
|
86
|
+
l && /* @__PURE__ */ t(
|
|
82
87
|
"div",
|
|
83
88
|
{
|
|
84
89
|
className: r(
|
|
85
|
-
|
|
90
|
+
f({ variant: e, error: s }),
|
|
86
91
|
"rst:right-3"
|
|
87
92
|
),
|
|
88
|
-
children:
|
|
93
|
+
children: l
|
|
89
94
|
}
|
|
90
95
|
)
|
|
91
96
|
] }),
|
|
92
|
-
(
|
|
93
|
-
|
|
97
|
+
(d || i) && /* @__PURE__ */ t(
|
|
98
|
+
j,
|
|
94
99
|
{
|
|
95
100
|
className: r(
|
|
96
101
|
"rst:text-xs rst:text-left rst:mt-1.5",
|
|
97
102
|
s ? "rst:text-error-600 rst:dark:text-error-400 rst:font-medium" : "rst:text-gray-500 rst:dark:text-gray-400"
|
|
98
103
|
),
|
|
99
|
-
children:
|
|
104
|
+
children: i || d
|
|
100
105
|
}
|
|
101
106
|
)
|
|
102
107
|
] })
|
|
103
108
|
);
|
|
104
109
|
}
|
|
105
110
|
);
|
|
106
|
-
|
|
111
|
+
D.displayName = "Input";
|
|
107
112
|
export {
|
|
108
|
-
|
|
113
|
+
D as Input
|
|
109
114
|
};
|
|
@@ -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 n=require("react/jsx-runtime"),d=require("../../../node_modules/class-variance-authority/dist/index.cjs"),o=require("../../../lib/utils.cjs"),i=d.cva("rst:animate-spin rst:motion-reduce:animate-none rst:rounded-full rst:transition-colors",{variants:{variant:{primary:"rst:border-primary-600 rst:dark:border-primary-500",neutral:"rst:border-gray-600 rst:dark:border-gray-400",danger:"rst:border-error-600 rst:dark:border-error-500",white:"rst:border-white",current:"rst:border-current"},size:{sm:"rst:h-4 rst:w-4 rst:border-2",md:"rst:h-6 rst:w-6 rst:border-[3px]",lg:"rst:h-8 rst:w-8 rst:border-4"},animation:{classic:"rst:border-solid rst:border-r-transparent rst:dark:border-r-transparent",half:"rst:border-solid rst:border-r-transparent rst:border-b-transparent rst:dark:border-r-transparent rst:dark:border-b-transparent",dashed:"rst:border-dashed",dotted:"rst:border-dotted rst:border-r-transparent rst:dark:border-r-transparent"}},defaultVariants:{variant:"primary",size:"md",animation:"classic"}}),b=({variant:r,size:t,animation:e,className:s,label:a="loading"})=>n.jsx("div",{className:o.cn(i({variant:r,size:t,animation:e}),s),role:"status","aria-label":a});exports.Spinner=b;
|
|
@@ -6,6 +6,11 @@ declare const spinnerVariants: (props?: ({
|
|
|
6
6
|
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
7
7
|
export interface SpinnerProps extends VariantProps<typeof spinnerVariants> {
|
|
8
8
|
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* What a screen reader hears: "loading" by default. Say what is loading
|
|
11
|
+
* ("Loading results") when there is more than one on screen.
|
|
12
|
+
*/
|
|
13
|
+
label?: string;
|
|
9
14
|
}
|
|
10
|
-
export declare const Spinner: ({ variant, size, animation, className, }: SpinnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare const Spinner: ({ variant, size, animation, className, label, }: SpinnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as
|
|
3
|
-
import { cva as
|
|
4
|
-
import { cn as
|
|
5
|
-
const
|
|
2
|
+
import { jsx as d } from "react/jsx-runtime";
|
|
3
|
+
import { cva as o } from "../../../node_modules/class-variance-authority/dist/index.js";
|
|
4
|
+
import { cn as n } from "../../../lib/utils.js";
|
|
5
|
+
const i = /* @__PURE__ */ o("rst:animate-spin rst:motion-reduce:animate-none rst:rounded-full rst:transition-colors", {
|
|
6
6
|
variants: {
|
|
7
7
|
variant: {
|
|
8
8
|
primary: "rst:border-primary-600 rst:dark:border-primary-500",
|
|
@@ -36,13 +36,14 @@ const n = /* @__PURE__ */ d("rst:animate-spin rst:rounded-full rst:transition-co
|
|
|
36
36
|
variant: r,
|
|
37
37
|
size: t,
|
|
38
38
|
animation: s,
|
|
39
|
-
className: a
|
|
40
|
-
|
|
39
|
+
className: a,
|
|
40
|
+
label: e = "loading"
|
|
41
|
+
}) => /* @__PURE__ */ d(
|
|
41
42
|
"div",
|
|
42
43
|
{
|
|
43
|
-
className:
|
|
44
|
+
className: n(i({ variant: r, size: t, animation: s }), a),
|
|
44
45
|
role: "status",
|
|
45
|
-
"aria-label":
|
|
46
|
+
"aria-label": e
|
|
46
47
|
}
|
|
47
48
|
);
|
|
48
49
|
export {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),Fe=require("react"),qe=require("../../atoms/Button/Button.cjs"),T=require("../../../lib/utils.cjs"),$e=require("../../../internal/motion.cjs"),u=require("./carousel-math.cjs"),U=require("./carousel-layout.cjs");function Be(a){const i=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(a){for(const d in a)if(d!=="default"){const b=Object.getOwnPropertyDescriptor(a,d);Object.defineProperty(i,d,b.get?b:{enumerable:!0,get:()=>a[d]})}}return i.default=a,Object.freeze(i)}const n=Be(Fe),ze=a=>Math.min(3e3,500+a*.35),He='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable]:not([contenteditable="false"])',V=a=>typeof a=="number"?`${a}px`:a;function ne(a){const i=parseFloat(a);return Number.isFinite(i)?i:0}const Ue="rst:hidden rst:[@media(any-hover:hover)]:inline-flex",Ve="rst:hidden rst:[@media(any-hover:hover)]:flex";function se({direction:a}){return l.jsx("svg",{"aria-hidden":"true",focusable:"false",viewBox:"0 0 20 20",className:"rst:h-5 rst:w-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:l.jsx("path",{d:a==="prev"?"M12.5 4.5 7 10l5.5 5.5":"M7.5 4.5 13 10l-5.5 5.5"})})}const oe={overflowing:null,canScrollPrev:!1,canScrollNext:!1,index:0},We=n.forwardRef(function({"aria-label":i,"aria-labelledby":d,children:b,itemWidth:I,perView:k,gap:W=12,gutter:X=16,bleed:le=!1,snap:L="start",snapStrictness:ce="mandatory",oneAtATime:ae=!1,arrows:j="hover",arrowPlacement:M="controls",renderArrows:P,prevLabel:ie,nextLabel:ue,prevIcon:de,nextIcon:fe,arrowButtonProps:ge,fade:ve=!1,scrollbar:pe=!1,showPosition:G=!1,onIndexChange:J,onScrollStateChange:K,itemClassName:me,listClassName:xe,className:be,style:he,...Se},ye){const v=n.useRef(null),Q=`${n.useId()}-list`,Y=n.Children.toArray(b),E=Y.length,[o,we]=n.useState(oe),Z=n.useRef(oe),[Ne,Te]=n.useState(""),[Ie,Pe]=n.useState(!1),O=n.useRef({onIndexChange:J,onScrollStateChange:K});n.useEffect(()=>{O.current={onIndexChange:J,onScrollStateChange:K}});const f=n.useCallback(()=>{const e=v.current;if(!e)return null;const t=e.getBoundingClientRect(),r=getComputedStyle(e),s=t.left+e.clientLeft-e.scrollLeft;return{items:Array.from(e.children).map(g=>{const x=g.getBoundingClientRect();return{start:x.left-s,end:x.right-s}}),scrollLeft:e.scrollLeft,clientWidth:e.clientWidth,scrollWidth:e.scrollWidth,paddingStart:ne(r.scrollPaddingInlineStart||r.scrollPaddingLeft),paddingEnd:ne(r.scrollPaddingInlineEnd||r.scrollPaddingRight),align:L}},[L]),h=n.useCallback(()=>{const e=f();if(!e)return;const t={overflowing:u.overflows(e),canScrollPrev:u.canScrollPrev(e),canScrollNext:u.canScrollNext(e),index:e.items.length?u.currentIndex(e):0},r=Z.current;r.overflowing===t.overflowing&&r.canScrollPrev===t.canScrollPrev&&r.canScrollNext===t.canScrollNext&&r.index===t.index||(Z.current=t,we(t),r.index!==t.index&&O.current.onIndexChange?.(t.index),(r.canScrollPrev!==t.canScrollPrev||r.canScrollNext!==t.canScrollNext)&&O.current.onScrollStateChange?.({canScrollPrev:t.canScrollPrev,canScrollNext:t.canScrollNext}))},[f]),Ee=JSON.stringify([W,X,I,k]);n.useLayoutEffect(()=>{const e=v.current;if(!e)return;h();let t=0;const r=()=>{t||(t=requestAnimationFrame(()=>{t=0,h()}))};e.addEventListener("scroll",r,{passive:!0});const s=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>h()),c=()=>{s?.observe(e),Array.from(e.children).forEach(x=>s?.observe(x))};c();const g=typeof MutationObserver>"u"?null:new MutationObserver(()=>{c(),h()});return g?.observe(e,{childList:!0}),()=>{e.removeEventListener("scroll",r),s?.disconnect(),g?.disconnect(),t&&cancelAnimationFrame(t)}},[h,E,Ee]),n.useLayoutEffect(()=>{const e=v.current;if(e&&(Pe(!e.querySelector(He)),d)){const t=d.split(/\s+/).map(r=>document.getElementById(r)?.textContent?.trim()??"").filter(Boolean).join(" ");Te(t)}},[b,d]);const S=n.useRef(null),p=n.useCallback(()=>{const e=v.current,t=S.current;t&&(S.current=null,window.clearTimeout(t.timer),e?.removeEventListener("scrollend",t.done),e&&(e.style.scrollSnapType="",delete e.dataset.settling))},[]),m=n.useCallback((e,t)=>{const r=v.current;if(!r)return;if(p(),t!=="smooth"||$e.prefersReducedMotion()||Math.abs(r.scrollLeft-e)<1){r.style.scrollSnapType="none",r.scrollTo({left:e,behavior:"instant"}),r.style.scrollSnapType="";return}r.style.scrollSnapType="none",r.dataset.settling="";const c=()=>p();S.current={timer:window.setTimeout(c,ze(Math.abs(r.scrollLeft-e))),done:c,target:e},r.addEventListener("scrollend",c,{once:!0}),r.scrollTo({left:e,behavior:"smooth"})},[p]),C=n.useCallback(e=>{const t=f();if(!t)return;const r=S.current?{...t,scrollLeft:S.current.target}:t;m(u.pageTarget(r,e),"smooth")},[f,m]);n.useImperativeHandle(ye,()=>({scrollToIndex:(e,t)=>{const r=f();if(!r||!r.items.length)return;const s=Math.min(Math.max(0,Math.round(e)),r.items.length-1);m(u.snapPosition(r,s),t?.behavior??"smooth")},scrollPrev:()=>C(-1),scrollNext:()=>C(1)}),[f,C,m]),n.useEffect(()=>p,[p]);const D=n.useRef(null),_=n.useRef(null),A=n.useRef(null);n.useLayoutEffect(()=>{const e=A.current;if(!e)return;const t=e==="prev"?D.current:_.current,r=e==="prev"?_.current:D.current,s=e==="prev"?!o.canScrollPrev:!o.canScrollNext,c=document.activeElement;s&&(c!==t&&c!==document.body&&c!==null||r&&!r.disabled&&r.focus())},[o.canScrollPrev,o.canScrollNext]);const y=n.useRef(null),w=n.useRef(!1),Ce=e=>{e.pointerType!=="mouse"||e.button!==0||o.overflowing&&(p(),w.current=!1,y.current={pointerId:e.pointerId,x0:e.clientX,scroll0:e.currentTarget.scrollLeft,dragging:!1})},Re=e=>{const t=y.current;if(!t||e.pointerId!==t.pointerId)return;const r=e.currentTarget,s=e.clientX-t.x0;if(!t.dragging){if(Math.abs(s)<=u.CAROUSEL_DRAG_THRESHOLD)return;t.dragging=!0,r.dataset.dragging="",r.style.scrollSnapType="none",window.getSelection?.()?.removeAllRanges();try{r.setPointerCapture(e.pointerId)}catch{}}r.scrollLeft=t.scroll0-s},F=e=>{const t=y.current;if(!t||e.pointerId!==t.pointerId||(y.current=null,!t.dragging))return;const r=e.currentTarget;delete r.dataset.dragging,w.current=e.type==="pointerup",window.setTimeout(()=>{w.current=!1},0);const s=f();s?m(u.snapPosition(s,u.nearestIndex(s)),"smooth"):r.style.scrollSnapType=""},ke=e=>{!w.current||e.detail===0||(w.current=!1,e.preventDefault(),e.stopPropagation())},Le=e=>{o.overflowing&&e.preventDefault()},je=e=>{const t=e.currentTarget,r=e.target;if(r===t||y.current?.dragging)return;let s=!1;try{s=r.matches(":focus-visible")}catch{s=!1}if(!s)return;const c=r.closest("[data-carousel-item]");if(!c||c.parentElement!==t)return;const g=f();if(!g)return;const x=Array.prototype.indexOf.call(t.children,c);m(u.snapPosition(g,x),"instant")},[Me,Oe]=n.useState(0);n.useEffect(()=>{const e=window.setTimeout(()=>Oe(o.index),150);return()=>window.clearTimeout(e)},[o.index]);const ee=i??Ne,te=e=>ee?`${e} ${ee}`:e,q=j!=="none"&&o.overflowing!==!1,De=j==="hover"?Ue:"rst:inline-flex",re=e=>{const t=e==="prev",{className:r,...s}=ge??{};return l.jsx(qe.Button,{type:"button",variant:"outline",colorScheme:"neutral",size:"icon",...s,"aria-label":t?ie??te("Previous"):ue??te("Next"),"aria-controls":Q,disabled:t?!o.canScrollPrev:!o.canScrollNext,onClick:()=>C(t?-1:1),ref:t?D:_,onFocus:()=>{A.current=e},onBlur:c=>{c.relatedTarget&&(A.current=null)},"data-carousel-arrow":e,className:T.cn("rst:size-11 rst:shrink-0 rst:rounded-full",M==="overlay"&&!P&&"rst:bg-[var(--roster-popover-bg)] rst:elevation-raised rst:hover:bg-[var(--roster-popover-bg)] rst:disabled:invisible",De,r),children:t?de??l.jsx(se,{direction:"prev"}):fe??l.jsx(se,{direction:"next"})})},R=q?re("prev"):null,$=q?re("next"):null,N=G&&E?l.jsxs("p",{"aria-live":"polite",className:"rst:font-ui rst:m-0 rst:text-sm rst:tabular-nums rst:opacity-75","data-carousel-position":"",children:[Me+1," of ",E]}):null;let B=null;P?B=P({prev:R,next:$,position:N}):M==="controls"&&(R||N)&&(B=l.jsxs("div",{className:T.cn("rst:mb-2 rst:items-center rst:justify-end rst:gap-2",!N&&j==="hover"?Ve:"rst:flex"),"data-carousel-controls":"",children:[N&&l.jsx("div",{className:"rst:mr-auto",children:N}),R,$]}));const z=ve&&o.overflowing&&(o.canScrollPrev||o.canScrollNext)?`linear-gradient(to right, ${o.canScrollPrev?"transparent, black 48px":"black"}, ${o.canScrollNext?"black calc(100% - 48px), transparent":"black"})`:void 0,H=I===void 0&&k!==void 0,_e={"--rst-cv-gap":V(W),"--rst-cv-gutter":V(X),...H?U.perViewVars(k):{},...z?{maskImage:z,WebkitMaskImage:z}:{}},Ae=I!==void 0?{width:V(I)}:H?{width:U.FLUID_ITEM_WIDTH}:void 0;return l.jsxs("div",{className:T.cn("rst:min-w-0",be),style:he,...Se,children:[B,l.jsxs("div",{className:"rst:relative",children:[l.jsx("ul",{ref:v,id:Q,role:"list","aria-label":i,"aria-labelledby":i?void 0:d,tabIndex:Ie&&o.overflowing?0:void 0,"data-overflowing":o.overflowing?"":void 0,onPointerDown:Ce,onPointerMove:Re,onPointerUp:F,onPointerCancel:F,onLostPointerCapture:F,onClickCapture:ke,onDragStart:Le,onFocus:je,style:_e,className:T.cn("rst:m-0 rst:flex rst:list-none rst:overflow-x-auto rst:overflow-y-hidden","rst:gap-(--rst-cv-gap) rst:px-(--rst-cv-gutter) rst:py-1 rst:scroll-px-(--rst-cv-gutter)","rst:overscroll-x-contain",ce==="mandatory"?"rst:snap-x rst:snap-mandatory":"rst:snap-x rst:snap-proximity",le&&"rst:-mx-(--rst-cv-gutter)",!pe&&"rst:[scrollbar-width:none] rst:[&::-webkit-scrollbar]:hidden",H&&U.FLUID_CLASSES,"rst:data-overflowing:cursor-grab rst:data-dragging:cursor-grabbing rst:data-dragging:select-none","rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-inset",xe),children:Y.map((e,t)=>l.jsx("li",{"data-carousel-item":"","aria-label":G?`${t+1} of ${E}`:void 0,style:Ae,className:T.cn("rst:shrink-0",L==="start"?"rst:snap-start":"rst:snap-center",ae&&"rst:snap-always",me),children:e},n.isValidElement(e)&&e.key!=null?e.key:t))}),!P&&M==="overlay"&&q&&l.jsxs(l.Fragment,{children:[l.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:start-1 rst:flex rst:items-center",children:l.jsx("div",{className:"rst:pointer-events-auto",children:R})}),l.jsx("div",{className:"rst:pointer-events-none rst:absolute rst:inset-y-0 rst:end-1 rst:flex rst:items-center",children:l.jsx("div",{className:"rst:pointer-events-auto",children:$})})]})]})]})});exports.Carousel=We;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ButtonProps } from '../../atoms/Button/Button';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/** Items per view at each width, from Roster's breakpoints up. */
|
|
4
|
+
export interface CarouselPerView {
|
|
5
|
+
base: number;
|
|
6
|
+
sm?: number;
|
|
7
|
+
md?: number;
|
|
8
|
+
lg?: number;
|
|
9
|
+
xl?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CarouselHandle {
|
|
12
|
+
/** Snaps item `i` into place. Instant under reduced motion, whatever is asked. */
|
|
13
|
+
scrollToIndex: (index: number, options?: {
|
|
14
|
+
behavior?: ScrollBehavior;
|
|
15
|
+
}) => void;
|
|
16
|
+
/** What the previous arrow does: back one page. */
|
|
17
|
+
scrollPrev: () => void;
|
|
18
|
+
/** What the next arrow does: on one page. */
|
|
19
|
+
scrollNext: () => void;
|
|
20
|
+
}
|
|
21
|
+
export interface CarouselArrowsRender {
|
|
22
|
+
/** The previous arrow, or `null` while the row fits and there is nothing to page. */
|
|
23
|
+
prev: React.ReactNode;
|
|
24
|
+
next: React.ReactNode;
|
|
25
|
+
/** The "2 of 5" readout when `showPosition` is on, otherwise `null`. */
|
|
26
|
+
position: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
export interface CarouselProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "aria-label" | "aria-labelledby"> {
|
|
29
|
+
/** The row's name. One of this or `aria-labelledby` is required. */
|
|
30
|
+
"aria-label"?: string;
|
|
31
|
+
/** The id of the heading that names the row. */
|
|
32
|
+
"aria-labelledby"?: string;
|
|
33
|
+
/** One child per item. Each is wrapped in the list item the row snaps to. */
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Fixed mode: every item is this wide (a number is px). The row overflows
|
|
37
|
+
* once the items and gaps outgrow the box, and the next item peeks from then
|
|
38
|
+
* on.
|
|
39
|
+
*/
|
|
40
|
+
itemWidth?: number | string;
|
|
41
|
+
/**
|
|
42
|
+
* Fluid mode: how many items are in view at each width, and the component
|
|
43
|
+
* sizes them. A fraction is the peek: `1.3` is one item and a third of the
|
|
44
|
+
* next. Ignored when `itemWidth` is set.
|
|
45
|
+
*/
|
|
46
|
+
perView?: number | CarouselPerView;
|
|
47
|
+
/** Space between items. A number is px. */
|
|
48
|
+
gap?: number | string;
|
|
49
|
+
/**
|
|
50
|
+
* The inline padding at each end, and the scroll padding a snapped item
|
|
51
|
+
* lines up with. Match the page's own gutter so a snapped item sits under
|
|
52
|
+
* the text above it. A number is px.
|
|
53
|
+
*/
|
|
54
|
+
gutter?: number | string;
|
|
55
|
+
/**
|
|
56
|
+
* Pulls the row out by one gutter on each side, so it runs to the edges of
|
|
57
|
+
* its container while the first item still lines up with the text.
|
|
58
|
+
*/
|
|
59
|
+
bleed?: boolean;
|
|
60
|
+
/** Where an item settles. */
|
|
61
|
+
snap?: "start" | "center";
|
|
62
|
+
/** `mandatory` always settles on an item; `proximity` only near one. */
|
|
63
|
+
snapStrictness?: "mandatory" | "proximity";
|
|
64
|
+
/** One item per swipe, for a gallery of one per view (`scroll-snap-stop: always`). */
|
|
65
|
+
oneAtATime?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* When the arrows show. `hover`, the default, is wherever a pointer can
|
|
68
|
+
* hover: a touch-only screen swipes instead, and gets no arrows to aim at.
|
|
69
|
+
*/
|
|
70
|
+
arrows?: "hover" | "always" | "none";
|
|
71
|
+
/**
|
|
72
|
+
* `controls`, the default, puts the arrows on a line of their own above the
|
|
73
|
+
* row, where they never cover an item or the peek and never fight a drag.
|
|
74
|
+
* `overlay` floats them over the row's two ends and takes no space.
|
|
75
|
+
* `renderArrows` overrides both.
|
|
76
|
+
*/
|
|
77
|
+
arrowPlacement?: "controls" | "overlay";
|
|
78
|
+
/**
|
|
79
|
+
* Put the arrows (and the position readout) anywhere: a heading line beside
|
|
80
|
+
* a "See all" link, say. What it returns replaces the default controls line,
|
|
81
|
+
* so a heading rendered here sits above the row as before.
|
|
82
|
+
*/
|
|
83
|
+
renderArrows?: (parts: CarouselArrowsRender) => React.ReactNode;
|
|
84
|
+
/** The previous arrow's accessible name. Defaults to "Previous" and the row's label. */
|
|
85
|
+
prevLabel?: string;
|
|
86
|
+
/** The next arrow's accessible name. Defaults to "Next" and the row's label. */
|
|
87
|
+
nextLabel?: string;
|
|
88
|
+
/** Replaces the previous arrow's chevron. */
|
|
89
|
+
prevIcon?: React.ReactNode;
|
|
90
|
+
/** Replaces the next arrow's chevron. */
|
|
91
|
+
nextIcon?: React.ReactNode;
|
|
92
|
+
/** Passed to both arrow buttons, over the defaults. */
|
|
93
|
+
arrowButtonProps?: Omit<ButtonProps, "onClick" | "disabled" | "children" | "aria-label">;
|
|
94
|
+
/**
|
|
95
|
+
* Fades the edge on a side with more to see. Never at an end, so the last
|
|
96
|
+
* item is never faded.
|
|
97
|
+
*/
|
|
98
|
+
fade?: boolean;
|
|
99
|
+
/** Shows the native scrollbar. Hidden by default; the arrows and the mouse drag stand in for it. */
|
|
100
|
+
scrollbar?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Shows "2 of 5", for a gallery of one item per view. It is also each item's
|
|
103
|
+
* name, and a change is announced politely.
|
|
104
|
+
*/
|
|
105
|
+
showPosition?: boolean;
|
|
106
|
+
/** The first item wholly in view, whenever it changes. */
|
|
107
|
+
onIndexChange?: (index: number) => void;
|
|
108
|
+
/** Whether the row can move each way, whenever that changes. */
|
|
109
|
+
onScrollStateChange?: (state: {
|
|
110
|
+
canScrollPrev: boolean;
|
|
111
|
+
canScrollNext: boolean;
|
|
112
|
+
}) => void;
|
|
113
|
+
/** Classes for each list item. */
|
|
114
|
+
itemClassName?: string;
|
|
115
|
+
/** Classes for the scrolling list. `className` is the outer wrapper. */
|
|
116
|
+
listClassName?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A sideways row of items: a row of cards, a selection strip, a gallery of one
|
|
120
|
+
* per view.
|
|
121
|
+
*
|
|
122
|
+
* Native first. The row is a real scroll container with CSS scroll snap, so
|
|
123
|
+
* touch, trackpad, Shift with the wheel and focus all scroll it without
|
|
124
|
+
* script, and every item is in the server render: no clones, no
|
|
125
|
+
* virtualization, no loop, and nothing moves on its own.
|
|
126
|
+
*
|
|
127
|
+
* Script adds what a mouse is missing. Pressing and dragging moves the row one
|
|
128
|
+
* to one and settles it on the nearest item; a drag past 6px drops the one
|
|
129
|
+
* click that ends it and nothing else. Previous and next arrows page by as
|
|
130
|
+
* many items as are wholly in view, measured each time, and are `disabled`,
|
|
131
|
+
* out of the tab order, at an end. A vertical wheel over the row scrolls the
|
|
132
|
+
* page, and sideways overscroll is contained so a swipe at an end can't go
|
|
133
|
+
* back a page.
|
|
134
|
+
*
|
|
135
|
+
* Keyboard focus moving onto an item snaps that item into place at once. If
|
|
136
|
+
* no item can take focus, the list itself is the tab stop, so a keyboard can
|
|
137
|
+
* still scroll it.
|
|
138
|
+
*
|
|
139
|
+
* Under reduced motion every programmatic scroll is instant: the arrows, the
|
|
140
|
+
* settle after a drag, and `scrollToIndex`.
|
|
141
|
+
*
|
|
142
|
+
* Renders a `ul` named by `aria-label` or `aria-labelledby`, one `li` per
|
|
143
|
+
* child. There is no carousel role and no slide role: nothing rotates.
|
|
144
|
+
*/
|
|
145
|
+
declare const Carousel: React.ForwardRefExoticComponent<CarouselProps & React.RefAttributes<CarouselHandle>>;
|
|
146
|
+
export { Carousel };
|