@carissa-io/components-ticker 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 +86 -2
- package/dist/index.js +112 -0
- package/package.json +57 -4
- package/scss/_ticker.scss +180 -0
- package/scss/_variables.scss +24 -0
- package/scss/index.scss +2 -0
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @carissa-io/components-ticker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Content that scrolls continuously (a ticker, a marquee): logos, headlines, announcements. A React component, SCSS-themed,
|
|
4
|
+
safe on the server. The loop is CSS (a transform, no measuring), it has a real pause control, and under
|
|
5
|
+
`prefers-reduced-motion: reduce` it becomes a static list.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @carissa-io/components-ticker
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It builds on `@carissa-io/components-animate` (the `use_reduced_motion` and `use_in_view` hooks).
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
import { Ticker } from '@carissa-io/components-ticker'
|
|
17
|
+
|
|
18
|
+
<Ticker aria-label="Partners" pause_label="Pause scrolling" play_label="Resume scrolling" duration="40s">
|
|
19
|
+
<img src="/acme.svg" alt="Acme" />
|
|
20
|
+
<img src="/globex.svg" alt="Globex" />
|
|
21
|
+
<a href="/press/1">Carissa wins an award</a>
|
|
22
|
+
</Ticker>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Every child becomes an item (`li`) of a list. The list is rendered twice and the track moves by exactly one list, so the
|
|
26
|
+
loop has no seam. Add enough items for one list to fill the width (a short list is stretched to the viewport width).
|
|
27
|
+
|
|
28
|
+
| Prop | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `children` | the items |
|
|
31
|
+
| `duration` | time of one full loop: a CSS time (`30s`) or milliseconds; default `$c-ticker-duration`. The speed follows the width of the content (more items, faster for the same duration) |
|
|
32
|
+
| `direction` | `left` (default), `right`, `up` or `down`; anything else falls back to `left`. The vertical ones need a height (`$c-ticker-height`) |
|
|
33
|
+
| `gap` | space between items: pixels (`24`) or a CSS length (`1.5rem`); default `$c-ticker-gap` |
|
|
34
|
+
| `pause_on_hover` | `true` by default: pause while the pointer is over the ticker |
|
|
35
|
+
| `pause_label`, `play_label` | the accessible name of the control while it is playing / paused. Required in practice: the text comes from the app, nothing is built in |
|
|
36
|
+
| `playing`, `default_playing`, `on_playing_change` | controlled / uncontrolled (default `true`) play state; `on_playing_change(playing)` is called when the control is used, also when controlled |
|
|
37
|
+
| `as` | the tag of the root, `div` by default |
|
|
38
|
+
| `className`, `id`, ... | `className` is appended; unknown props go to the root (give it an `aria-label` to name the region) |
|
|
39
|
+
|
|
40
|
+
No text is built in: every label is a prop, and a button without its label prop has no accessible name. The app passes `pause_label` and `play_label` from its own translations, the same rule as `components-carousel`.
|
|
41
|
+
|
|
42
|
+
The root carries `data-state`: `running`, `paused` (by the control), `idle` (off-screen or hidden tab) or `static`
|
|
43
|
+
(reduced motion), and `data-direction`.
|
|
44
|
+
|
|
45
|
+
## Accessibility
|
|
46
|
+
|
|
47
|
+
- **Pause control:** a real `<button>` (`aria-label` is `pause_label` while playing and `play_label` while paused). Moving
|
|
48
|
+
content must be stoppable (WCAG 2.2.2).
|
|
49
|
+
- **Read once:** the second copy that closes the loop is `aria-hidden` and `inert`, so screen readers and the keyboard see
|
|
50
|
+
the items only once. Links inside the ticker are reachable and the ticker pauses while focus is inside the list.
|
|
51
|
+
- **Hover:** it pauses while the pointer is over it (`pause_on_hover`). Hover is not available on touch screens, so the
|
|
52
|
+
pause control is the way to stop it there.
|
|
53
|
+
- **Pauses by itself** while it is off-screen or the tab is hidden.
|
|
54
|
+
|
|
55
|
+
## Reduced motion
|
|
56
|
+
|
|
57
|
+
Under `prefers-reduced-motion: reduce` it renders a static list: the items wrap, nothing scrolls, there is no second copy
|
|
58
|
+
and no pause control (the hook and the CSS agree; the app does nothing).
|
|
59
|
+
|
|
60
|
+
## Style
|
|
61
|
+
|
|
62
|
+
```scss
|
|
63
|
+
@use '@carissa-io/components-ticker/scss' with (
|
|
64
|
+
$c-ticker-duration: 45s,
|
|
65
|
+
$c-ticker-gap: 3rem
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Classes: `c-ticker`, `c-ticker--left|right|up|down`, `c-ticker--horizontal|vertical`, `c-ticker--pause-on-hover`,
|
|
70
|
+
`c-ticker__viewport`, `__track`, `__list` (`--copy`), `__item`, `__toggle` (`--playing|paused`). Every variable is `!default`
|
|
71
|
+
(see [scss/_variables.scss](scss/_variables.scss)); a theme sets the character of the motion with them.
|
|
72
|
+
|
|
73
|
+
| Variable | Default |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `$c-ticker-duration` | `30s` |
|
|
76
|
+
| `$c-ticker-easing` | `linear` |
|
|
77
|
+
| `$c-ticker-gap` | `2rem` |
|
|
78
|
+
| `$c-ticker-height` | `12rem` (vertical) |
|
|
79
|
+
| `$c-ticker-fade-size` | `0` (soft edges off) |
|
|
80
|
+
| `$c-ticker-toggle-size` | `2rem` |
|
|
81
|
+
| `$c-ticker-toggle-color` | `currentcolor` |
|
|
82
|
+
| `$c-ticker-toggle-background` | `transparent` |
|
|
83
|
+
| `$c-ticker-toggle-border-width` | `1px` |
|
|
84
|
+
| `$c-ticker-toggle-border-color` | `currentcolor` |
|
|
85
|
+
| `$c-ticker-toggle-radius` | `50%` |
|
|
86
|
+
| `$c-ticker-toggle-icon-size` | `0.375rem` |
|
|
87
|
+
| `$c-ticker-toggle-focus-outline` | `2px solid currentcolor` |
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/Ticker.jsx
|
|
2
|
+
import { Children, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { use_in_view, use_reduced_motion } from "@carissa-io/components-animate";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var DIRECTIONS = ["left", "right", "up", "down"];
|
|
6
|
+
var TAG = /^[a-z][a-z0-9-]*$/i;
|
|
7
|
+
var TIME = /^\d+(\.\d+)?(ms|s)$/;
|
|
8
|
+
var LENGTH = /^\d+(\.\d+)?(px|rem|em|%)$/;
|
|
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()) && parseFloat(value) > 0) return value.trim();
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
function to_length(value) {
|
|
16
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return `${value}px`;
|
|
17
|
+
if (typeof value === "string" && LENGTH.test(value.trim())) return value.trim();
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
function subscribe_visibility(on_change) {
|
|
21
|
+
if (typeof document === "undefined") return () => {
|
|
22
|
+
};
|
|
23
|
+
document.addEventListener("visibilitychange", on_change);
|
|
24
|
+
return () => document.removeEventListener("visibilitychange", on_change);
|
|
25
|
+
}
|
|
26
|
+
var tab_is_visible = () => typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
27
|
+
var make_inert = (node) => node?.setAttribute("inert", "");
|
|
28
|
+
function Ticker({
|
|
29
|
+
direction = "left",
|
|
30
|
+
duration,
|
|
31
|
+
gap,
|
|
32
|
+
pause_on_hover = true,
|
|
33
|
+
pause_label,
|
|
34
|
+
play_label,
|
|
35
|
+
playing,
|
|
36
|
+
default_playing = true,
|
|
37
|
+
on_playing_change,
|
|
38
|
+
as = "div",
|
|
39
|
+
className = "",
|
|
40
|
+
style,
|
|
41
|
+
children,
|
|
42
|
+
...rest
|
|
43
|
+
}) {
|
|
44
|
+
const items = Children.toArray(children);
|
|
45
|
+
const safe_direction = DIRECTIONS.includes(direction) ? direction : "left";
|
|
46
|
+
const axis = safe_direction === "up" || safe_direction === "down" ? "vertical" : "horizontal";
|
|
47
|
+
const Tag = typeof as === "string" && TAG.test(as) ? as : "div";
|
|
48
|
+
const reduced = use_reduced_motion();
|
|
49
|
+
const [ref, in_view] = use_in_view();
|
|
50
|
+
const visible_tab = useSyncExternalStore(subscribe_visibility, tab_is_visible, () => true);
|
|
51
|
+
const [own_playing, set_own_playing] = useState(default_playing !== false);
|
|
52
|
+
const wants_play = playing === void 0 ? own_playing : playing !== false;
|
|
53
|
+
const running = wants_play && in_view && visible_tab;
|
|
54
|
+
let state = "running";
|
|
55
|
+
if (reduced) state = "static";
|
|
56
|
+
else if (!wants_play) state = "paused";
|
|
57
|
+
else if (!running) state = "idle";
|
|
58
|
+
const toggle = () => {
|
|
59
|
+
const next = !wants_play;
|
|
60
|
+
if (playing === void 0) set_own_playing(next);
|
|
61
|
+
on_playing_change?.(next);
|
|
62
|
+
};
|
|
63
|
+
const vars = {};
|
|
64
|
+
const safe_duration = to_time(duration);
|
|
65
|
+
const safe_gap = to_length(gap);
|
|
66
|
+
if (safe_duration) vars["--c-ticker-duration"] = safe_duration;
|
|
67
|
+
if (safe_gap) vars["--c-ticker-gap"] = safe_gap;
|
|
68
|
+
const list = (copy) => /* @__PURE__ */ jsx(
|
|
69
|
+
"ul",
|
|
70
|
+
{
|
|
71
|
+
className: join("c-ticker__list", copy && "c-ticker__list--copy"),
|
|
72
|
+
"aria-hidden": copy ? "true" : void 0,
|
|
73
|
+
ref: copy ? make_inert : void 0,
|
|
74
|
+
children: items.map((child, index) => /* @__PURE__ */ jsx("li", { className: "c-ticker__item", children: child }, index))
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
78
|
+
Tag,
|
|
79
|
+
{
|
|
80
|
+
...rest,
|
|
81
|
+
ref,
|
|
82
|
+
"data-state": state,
|
|
83
|
+
"data-direction": safe_direction,
|
|
84
|
+
style: { ...vars, ...style },
|
|
85
|
+
className: join(
|
|
86
|
+
"c-ticker",
|
|
87
|
+
`c-ticker--${axis}`,
|
|
88
|
+
`c-ticker--${safe_direction}`,
|
|
89
|
+
pause_on_hover !== false && "c-ticker--pause-on-hover",
|
|
90
|
+
className
|
|
91
|
+
),
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ jsx("div", { className: "c-ticker__viewport", children: /* @__PURE__ */ jsxs("div", { className: "c-ticker__track", children: [
|
|
94
|
+
list(false),
|
|
95
|
+
reduced ? null : list(true)
|
|
96
|
+
] }) }),
|
|
97
|
+
reduced ? null : /* @__PURE__ */ jsx(
|
|
98
|
+
"button",
|
|
99
|
+
{
|
|
100
|
+
type: "button",
|
|
101
|
+
className: join("c-ticker__toggle", wants_play ? "c-ticker__toggle--playing" : "c-ticker__toggle--paused"),
|
|
102
|
+
"aria-label": wants_play ? pause_label : play_label,
|
|
103
|
+
onClick: toggle
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
Ticker
|
|
112
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carissa-io/components-ticker",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Carissa UI: ticker 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-ticker.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,180 @@
|
|
|
1
|
+
@use 'variables' as *;
|
|
2
|
+
|
|
3
|
+
@keyframes c-ticker-x {
|
|
4
|
+
from {
|
|
5
|
+
transform: translateX(0);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
to {
|
|
9
|
+
transform: translateX(-50%);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@keyframes c-ticker-y {
|
|
14
|
+
from {
|
|
15
|
+
transform: translateY(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
to {
|
|
19
|
+
transform: translateY(-50%);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.c-ticker {
|
|
24
|
+
position: relative;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: $c-ticker-gap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.c-ticker__viewport {
|
|
31
|
+
flex: 1;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
|
|
35
|
+
@if $c-ticker-fade-size != 0 {
|
|
36
|
+
mask-image: linear-gradient(to right, transparent, #000 $c-ticker-fade-size, #000 calc(100% - #{$c-ticker-fade-size}), transparent);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.c-ticker__track {
|
|
41
|
+
display: flex;
|
|
42
|
+
width: max-content;
|
|
43
|
+
animation-duration: var(--c-ticker-duration, #{$c-ticker-duration});
|
|
44
|
+
animation-timing-function: $c-ticker-easing;
|
|
45
|
+
animation-iteration-count: infinite;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Each list is at least as wide as the viewport so a short list never leaves a hole; the padding closes the gap
|
|
49
|
+
// between the end of one list and the start of the next, so the loop moves by exactly one list.
|
|
50
|
+
.c-ticker__list {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex: none;
|
|
53
|
+
gap: var(--c-ticker-gap, #{$c-ticker-gap});
|
|
54
|
+
min-width: 100vw;
|
|
55
|
+
padding: 0 var(--c-ticker-gap, #{$c-ticker-gap}) 0 0;
|
|
56
|
+
margin: 0;
|
|
57
|
+
list-style: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.c-ticker__item {
|
|
61
|
+
flex: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.c-ticker--horizontal .c-ticker__track {
|
|
65
|
+
animation-name: c-ticker-x;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.c-ticker--right .c-ticker__track,
|
|
69
|
+
.c-ticker--down .c-ticker__track {
|
|
70
|
+
animation-direction: reverse;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.c-ticker--vertical {
|
|
74
|
+
.c-ticker__viewport {
|
|
75
|
+
height: $c-ticker-height;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.c-ticker__track {
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
width: auto;
|
|
81
|
+
animation-name: c-ticker-y;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.c-ticker__list {
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
min-height: $c-ticker-height;
|
|
88
|
+
padding: 0 0 var(--c-ticker-gap, #{$c-ticker-gap}) 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Not playing: paused by the control, off-screen or in a hidden tab (and always while focus is inside the list)
|
|
93
|
+
.c-ticker[data-state='paused'] .c-ticker__track,
|
|
94
|
+
.c-ticker[data-state='idle'] .c-ticker__track,
|
|
95
|
+
.c-ticker__viewport:focus-within .c-ticker__track,
|
|
96
|
+
.c-ticker--pause-on-hover .c-ticker__viewport:hover .c-ticker__track {
|
|
97
|
+
animation-play-state: paused;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Reduced motion (the component also renders the static form by itself): items wrap, nothing scrolls
|
|
101
|
+
.c-ticker[data-state='static'] {
|
|
102
|
+
.c-ticker__track {
|
|
103
|
+
width: auto;
|
|
104
|
+
animation: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.c-ticker__list {
|
|
108
|
+
flex: 0 1 auto;
|
|
109
|
+
flex-wrap: wrap;
|
|
110
|
+
min-width: 0;
|
|
111
|
+
min-height: 0;
|
|
112
|
+
padding: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.c-ticker__viewport {
|
|
116
|
+
height: auto;
|
|
117
|
+
mask-image: none;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.c-ticker__toggle {
|
|
122
|
+
position: relative;
|
|
123
|
+
box-sizing: border-box;
|
|
124
|
+
flex: none;
|
|
125
|
+
width: $c-ticker-toggle-size;
|
|
126
|
+
height: $c-ticker-toggle-size;
|
|
127
|
+
padding: 0;
|
|
128
|
+
color: $c-ticker-toggle-color;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
background: $c-ticker-toggle-background;
|
|
131
|
+
border: $c-ticker-toggle-border-width solid $c-ticker-toggle-border-color;
|
|
132
|
+
border-radius: $c-ticker-toggle-radius;
|
|
133
|
+
|
|
134
|
+
&::before {
|
|
135
|
+
position: absolute;
|
|
136
|
+
top: 50%;
|
|
137
|
+
left: 50%;
|
|
138
|
+
content: '';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
&:focus-visible {
|
|
142
|
+
outline: $c-ticker-toggle-focus-outline;
|
|
143
|
+
outline-offset: 2px;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.c-ticker__toggle--playing::before {
|
|
148
|
+
width: $c-ticker-toggle-icon-size;
|
|
149
|
+
height: $c-ticker-toggle-icon-size * 1.5;
|
|
150
|
+
border-right: $c-ticker-toggle-icon-size * 0.375 solid currentcolor;
|
|
151
|
+
border-left: $c-ticker-toggle-icon-size * 0.375 solid currentcolor;
|
|
152
|
+
transform: translate(-50%, -50%);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.c-ticker__toggle--paused::before {
|
|
156
|
+
border-top: $c-ticker-toggle-icon-size * 0.75 solid transparent;
|
|
157
|
+
border-bottom: $c-ticker-toggle-icon-size * 0.75 solid transparent;
|
|
158
|
+
border-left: $c-ticker-toggle-icon-size * 1.25 solid currentcolor;
|
|
159
|
+
transform: translate(-35%, -50%);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@media (prefers-reduced-motion: reduce) {
|
|
163
|
+
.c-ticker__track {
|
|
164
|
+
width: auto;
|
|
165
|
+
animation: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.c-ticker__list--copy,
|
|
169
|
+
.c-ticker__toggle {
|
|
170
|
+
display: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.c-ticker__list {
|
|
174
|
+
flex: 0 1 auto;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
min-width: 0;
|
|
177
|
+
min-height: 0;
|
|
178
|
+
padding: 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Every value is `!default`: set it before the first @use / through `with (...)` and it wins.
|
|
2
|
+
//
|
|
3
|
+
// @use '@carissa-io/components-ticker/scss' with ($c-ticker-duration: 45s, $c-ticker-gap: 3rem);
|
|
4
|
+
//
|
|
5
|
+
// The ticker scrolls by one list per loop, so `$c-ticker-duration` is the time of a full loop: a longer list moves
|
|
6
|
+
// faster for the same duration. Under `prefers-reduced-motion: reduce` it is a static list that wraps.
|
|
7
|
+
|
|
8
|
+
$c-ticker-duration: 30s !default; // one full loop; per instance: `duration` prop or `--c-ticker-duration`
|
|
9
|
+
$c-ticker-easing: linear !default; // a marquee moves at a constant speed
|
|
10
|
+
|
|
11
|
+
// Layout
|
|
12
|
+
$c-ticker-gap: 2rem !default; // between items; per instance: `gap` prop or `--c-ticker-gap`
|
|
13
|
+
$c-ticker-height: 12rem !default; // viewport height of the vertical directions (`up`, `down`)
|
|
14
|
+
$c-ticker-fade-size: 0 !default; // soft edge at both ends of the viewport (a length, `0` is off)
|
|
15
|
+
|
|
16
|
+
// Pause control
|
|
17
|
+
$c-ticker-toggle-size: 2rem !default;
|
|
18
|
+
$c-ticker-toggle-color: currentcolor !default;
|
|
19
|
+
$c-ticker-toggle-background: transparent !default;
|
|
20
|
+
$c-ticker-toggle-border-width: 1px !default;
|
|
21
|
+
$c-ticker-toggle-border-color: currentcolor !default;
|
|
22
|
+
$c-ticker-toggle-radius: 50% !default;
|
|
23
|
+
$c-ticker-toggle-icon-size: 0.375rem !default;
|
|
24
|
+
$c-ticker-toggle-focus-outline: 2px solid currentcolor !default;
|
package/scss/index.scss
ADDED