@carissa-io/components-stagger-list 0.0.0-stage → 0.1.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 +74 -2
- package/dist/index.js +74 -0
- package/package.json +57 -4
- package/scss/_stagger-list.scss +26 -0
- package/scss/_variables.scss +14 -0
- package/scss/index.scss +2 -0
package/README.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @carissa-io/components-stagger-list
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A list whose rows enter one after another. The rows stay real list items (`li` inside `ul` / `ol`), so the list keeps its
|
|
4
|
+
semantics. A React component, SCSS-themed, safe on the server. The motion is CSS: it reuses the presets of
|
|
5
|
+
`@carissa-io/components-animate` and the inherited `--c-animate-delay` custom property (the pattern `Sequence` uses), and
|
|
6
|
+
it depends on that package only.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm i @carissa-io/components-stagger-list
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Load the styles of both packages: the pace between rows is set here, the motion itself (duration, distance, easing) in
|
|
13
|
+
`@carissa-io/components-animate`.
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import { StaggerList } from '@carissa-io/components-stagger-list'
|
|
19
|
+
|
|
20
|
+
<StaggerList effect="rise" trigger="view" stagger={100} aria-label="Steps">
|
|
21
|
+
<span>Create an account</span>
|
|
22
|
+
<span>Pick a plan</span>
|
|
23
|
+
<span>Invite your team</span>
|
|
24
|
+
</StaggerList>
|
|
25
|
+
|
|
26
|
+
// or from data
|
|
27
|
+
<StaggerList as="ol" items={steps.map((step) => <Step key={step.id} {...step} />)} />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Pass the content of each row; a child that is already an `li` is used as the row itself (its props are kept).
|
|
31
|
+
|
|
32
|
+
| Prop | |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `children`, `items` | the rows: the children first, then the entries of `items` |
|
|
35
|
+
| `effect` | `fade`, `rise` (default) or `scale`; anything else falls back to `rise` |
|
|
36
|
+
| `trigger` | `load` (default, plays on first paint, no script needed) or `view` (when the list enters the viewport); anything else falls back to `load` |
|
|
37
|
+
| `stagger` | gap between one row and the next, milliseconds or a CSS time; default `$c-stagger-list-stagger` |
|
|
38
|
+
| `delay` | wait before the first row; default `$c-stagger-list-delay` |
|
|
39
|
+
| `duration` | entrance time of one row (sets `--c-animate-duration`) |
|
|
40
|
+
| `once` | `true` by default: plays only the first time; `false` plays again each time the list re-enters the viewport (`trigger="view"`) |
|
|
41
|
+
| `as` | `ul` (default), `ol` or `div` (rows are then `div`s); anything else falls back to `ul` |
|
|
42
|
+
| `className`, `id`, ... | `className` is appended; unknown props go to the root |
|
|
43
|
+
|
|
44
|
+
The component has no strings of its own: the rows come from the app.
|
|
45
|
+
|
|
46
|
+
Invalid times are ignored. The root carries `data-state` (`idle`, `active` or `static`) and each row
|
|
47
|
+
`--c-stagger-list-index`; row N waits `delay + N * stagger`.
|
|
48
|
+
|
|
49
|
+
## Accessibility and reduced motion
|
|
50
|
+
|
|
51
|
+
- The list is a real `ul` / `ol` with `li` rows, so assistive tech announces it as a list with its item count.
|
|
52
|
+
- Rows are in the DOM from the start; with `trigger="view"` they are only transparent until the list is seen, so do not use
|
|
53
|
+
it for content the page cannot do without when JavaScript fails. `trigger="load"` plays without a script.
|
|
54
|
+
- Under `prefers-reduced-motion: reduce` every row is shown at once with no animation (the component and the CSS agree; the
|
|
55
|
+
app does nothing).
|
|
56
|
+
|
|
57
|
+
## Style
|
|
58
|
+
|
|
59
|
+
```scss
|
|
60
|
+
@use '@carissa-io/components-animate/scss';
|
|
61
|
+
@use '@carissa-io/components-stagger-list/scss' with (
|
|
62
|
+
$c-stagger-list-stagger: 0.15s,
|
|
63
|
+
$c-stagger-list-gap: 0.5rem
|
|
64
|
+
);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Classes: `c-stagger-list`, `c-stagger-list__item` (plus animate's `c-animate c-animate--rise|fade|scale c-animate--entrance`
|
|
68
|
+
on each row). Every variable is `!default` (see [scss/_variables.scss](scss/_variables.scss)).
|
|
69
|
+
|
|
70
|
+
| Variable | Default |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `$c-stagger-list-stagger` | `0.08s` |
|
|
73
|
+
| `$c-stagger-list-delay` | `0s` |
|
|
74
|
+
| `$c-stagger-list-gap` | `0` |
|
|
75
|
+
| `$c-stagger-list-direction` | `column` |
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/StaggerList.jsx
|
|
2
|
+
import { Children, cloneElement, isValidElement } from "react";
|
|
3
|
+
import { use_in_view, use_reduced_motion } from "@carissa-io/components-animate";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var EFFECTS = ["fade", "rise", "scale"];
|
|
6
|
+
var TRIGGERS = ["load", "view"];
|
|
7
|
+
var TAGS = ["ul", "ol", "div"];
|
|
8
|
+
var TIME = /^\d+(\.\d+)?(ms|s)$/;
|
|
9
|
+
var join = (...parts) => parts.filter(Boolean).join(" ");
|
|
10
|
+
function to_time(value) {
|
|
11
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return `${value}ms`;
|
|
12
|
+
if (typeof value === "string" && TIME.test(value.trim())) return value.trim();
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
function StaggerList({
|
|
16
|
+
items,
|
|
17
|
+
effect = "rise",
|
|
18
|
+
trigger = "load",
|
|
19
|
+
stagger,
|
|
20
|
+
delay,
|
|
21
|
+
duration,
|
|
22
|
+
once = true,
|
|
23
|
+
as = "ul",
|
|
24
|
+
className = "",
|
|
25
|
+
style,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}) {
|
|
29
|
+
const rows = [...Children.toArray(children), ...Array.isArray(items) ? items : []];
|
|
30
|
+
const safe_effect = EFFECTS.includes(effect) ? effect : "rise";
|
|
31
|
+
const safe_trigger = TRIGGERS.includes(trigger) ? trigger : "load";
|
|
32
|
+
const Tag = TAGS.includes(as) ? as : "ul";
|
|
33
|
+
const Row = Tag === "div" ? "div" : "li";
|
|
34
|
+
const reduced = use_reduced_motion();
|
|
35
|
+
const [ref, in_view] = use_in_view({ once: once !== false });
|
|
36
|
+
const active = safe_trigger === "load" || in_view;
|
|
37
|
+
const state = reduced ? void 0 : active ? "active" : "idle";
|
|
38
|
+
const vars = {};
|
|
39
|
+
const safe_stagger = to_time(stagger);
|
|
40
|
+
const safe_delay = to_time(delay);
|
|
41
|
+
const safe_duration = to_time(duration);
|
|
42
|
+
if (safe_stagger) vars["--c-stagger-list-stagger"] = safe_stagger;
|
|
43
|
+
if (safe_delay) vars["--c-stagger-list-delay"] = safe_delay;
|
|
44
|
+
if (safe_duration) vars["--c-animate-duration"] = safe_duration;
|
|
45
|
+
const row_classes = join("c-stagger-list__item", "c-animate", `c-animate--${safe_effect}`, "c-animate--entrance");
|
|
46
|
+
return /* @__PURE__ */ jsx(
|
|
47
|
+
Tag,
|
|
48
|
+
{
|
|
49
|
+
...rest,
|
|
50
|
+
ref,
|
|
51
|
+
"data-state": reduced ? "static" : active ? "active" : "idle",
|
|
52
|
+
style: { ...vars, ...style },
|
|
53
|
+
className: join("c-stagger-list", className),
|
|
54
|
+
children: rows.map((row, index) => {
|
|
55
|
+
const props = {
|
|
56
|
+
"data-state": state,
|
|
57
|
+
style: { "--c-stagger-list-index": index }
|
|
58
|
+
};
|
|
59
|
+
if (Row === "li" && isValidElement(row) && row.type === "li") {
|
|
60
|
+
return cloneElement(row, {
|
|
61
|
+
...props,
|
|
62
|
+
key: row.key ?? index,
|
|
63
|
+
className: join(row_classes, row.props.className),
|
|
64
|
+
style: { ...props.style, ...row.props.style }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return /* @__PURE__ */ jsx(Row, { className: row_classes, ...props, children: row }, isValidElement(row) && row.key != null ? row.key : index);
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
StaggerList
|
|
74
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carissa-io/components-stagger-list",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Carissa UI: stagger-list as a React component with SCSS variables",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"scss",
|
|
10
|
+
"THIRD-PARTY-NOTICES"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./scss": "./scss/index.scss",
|
|
15
|
+
"./scss/*": "./scss/*"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "esbuild src/index.js --bundle --packages=external --format=esm --jsx=automatic --outfile=dist/index.js",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"lint": "eslint .",
|
|
22
|
+
"prepublishOnly": "node -e \"if (!process.env.GITHUB_ACTIONS) { console.error('Refusing to publish outside GitHub Actions CI: commit and push to main instead.'); process.exit(1); }\" && npm run build"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/carissa-io/components-stagger-list.git"
|
|
27
|
+
},
|
|
28
|
+
"license": "UNLICENSED",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=24"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@carissa-io/components-animate": "^0.1.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^10.0.1",
|
|
40
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
41
|
+
"@testing-library/react": "^16.3.3",
|
|
42
|
+
"@testing-library/user-event": "^14.6.7",
|
|
43
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
44
|
+
"esbuild": "^0.28.0",
|
|
45
|
+
"eslint": "^10.12.0",
|
|
46
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
47
|
+
"globals": "^17.13.0",
|
|
48
|
+
"jsdom": "^29.1.1",
|
|
49
|
+
"react": "^19.3.0",
|
|
50
|
+
"react-dom": "^19.3.0",
|
|
51
|
+
"sass-embedded": "^1.105.1",
|
|
52
|
+
"vite": "^8.3.2",
|
|
53
|
+
"vitest": "^4.1.11"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"registry": "https://registry.npmjs.org",
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@use 'variables' as *;
|
|
2
|
+
|
|
3
|
+
.c-stagger-list {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: $c-stagger-list-direction;
|
|
6
|
+
gap: $c-stagger-list-gap;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
list-style: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Each row inherits the animation of `.c-animate--*` (the preset) and takes its place in the order as a delay
|
|
13
|
+
.c-stagger-list__item {
|
|
14
|
+
--c-animate-delay: calc(
|
|
15
|
+
var(--c-stagger-list-delay, #{$c-stagger-list-delay}) + var(--c-stagger-list-index, 0) *
|
|
16
|
+
var(--c-stagger-list-stagger, #{$c-stagger-list-stagger})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-reduced-motion: reduce) {
|
|
21
|
+
.c-stagger-list__item {
|
|
22
|
+
opacity: 1;
|
|
23
|
+
transform: none;
|
|
24
|
+
animation: none;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Every value is `!default`: set it before the first @use / through `with (...)` and it wins.
|
|
2
|
+
//
|
|
3
|
+
// @use '@carissa-io/components-stagger-list/scss' with ($c-stagger-list-stagger: 0.15s, $c-stagger-list-gap: 0.5rem);
|
|
4
|
+
//
|
|
5
|
+
// The rows enter with the presets of `@carissa-io/components-animate`: load that package's styles too, since the motion
|
|
6
|
+
// itself (duration, distance, easing) is configured there. Here you set the pace between rows.
|
|
7
|
+
// Under `prefers-reduced-motion: reduce` every row shows at once.
|
|
8
|
+
|
|
9
|
+
$c-stagger-list-stagger: 0.08s !default; // gap between one row and the next; per instance: `stagger` prop or `--c-stagger-list-stagger`
|
|
10
|
+
$c-stagger-list-delay: 0s !default; // wait before the first row; per instance: `delay` prop or `--c-stagger-list-delay`
|
|
11
|
+
|
|
12
|
+
// Layout of the rows (neutral: stacked, no marker, no gap)
|
|
13
|
+
$c-stagger-list-gap: 0 !default;
|
|
14
|
+
$c-stagger-list-direction: column !default;
|
package/scss/index.scss
ADDED