@mitchellslager/carousel 0.0.0 → 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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/package.json +10 -10
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mitchell Slager
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @mitchellslager/carousel
2
+
3
+ A headless, accessible image carousel for React 19, driven by native CSS scroll snapping.
4
+
5
+ - Thumbnail navigation, synchronised with the carousel
6
+ - A "3 / 10" counter
7
+ - The WAI-ARIA carousel pattern, with translatable labels
8
+ - Respects `prefers-reduced-motion`
9
+
10
+ [Demo](https://mitchellslager.github.io/carousel/) · [Design decisions](https://github.com/mitchellslager/carousel/blob/main/DESIGN.md)
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ pnpm add @mitchellslager/carousel
16
+ ```
17
+
18
+ ```tsx
19
+ import { Carousel } from "@mitchellslager/carousel"
20
+ import "@mitchellslager/carousel/styles.css"
21
+
22
+ export function ProductGallery({
23
+ images,
24
+ }: {
25
+ images: { src: string; alt: string }[]
26
+ }) {
27
+ return (
28
+ <Carousel.Root aria-label="Product images">
29
+ <Carousel.Viewport>
30
+ {images.map((image) => (
31
+ <Carousel.Slide key={image.src}>
32
+ <img src={image.src} alt={image.alt} />
33
+ </Carousel.Slide>
34
+ ))}
35
+ </Carousel.Viewport>
36
+ <Carousel.Prev>←</Carousel.Prev>
37
+ <Carousel.Counter />
38
+ <Carousel.Next>→</Carousel.Next>
39
+ <Carousel.Thumbnails>
40
+ {images.map((image, index) => (
41
+ <Carousel.Thumbnail key={image.src} index={index}>
42
+ <img src={image.src} alt="" />
43
+ </Carousel.Thumbnail>
44
+ ))}
45
+ </Carousel.Thumbnails>
46
+ </Carousel.Root>
47
+ )
48
+ }
49
+ ```
50
+
51
+ Give `Root` an accessible name with `aria-label` or `aria-labelledby`. Every part is optional and can be placed anywhere inside `Root`.
52
+
53
+ ### Parts
54
+
55
+ | Part | Renders | Notes |
56
+ | -------------- | -------- | ---------------------------------------------------------------- |
57
+ | `Root` | `div` | `index` / `defaultIndex` / `onIndexChange`, `labels`, `motion`. |
58
+ | `Viewport` | `div` | The scroll-snap container. Its `Slide` children are the slides. |
59
+ | `Slide` | `div` | `inert` unless active. `data-active` when active. |
60
+ | `Prev`, `Next` | `button` | `aria-disabled` and `data-disabled` at the edges. `render` prop. |
61
+ | `Counter` | `span` | "3 / 10" by default; pass a function child to change it. |
62
+ | `Thumbnails` | `div` | Tab list; `orientation` is `"horizontal"` or `"vertical"`. |
63
+ | `Thumbnail` | `button` | Tab for slide `index`. `data-active` when active. `render` prop. |
64
+
65
+ ### State
66
+
67
+ `onIndexChange` fires once scrolling settles on a new slide. Pass `index` to request a slide from outside: changing it scrolls there, but a swipe always wins and reports itself through `onIndexChange`. To reset the carousel, for example when a product variant changes its images, give `Root` a new `key`.
68
+
69
+ ### Labels
70
+
71
+ All accessible text is English by default. Translate it with `labels`:
72
+
73
+ ```tsx
74
+ <Carousel.Root
75
+ aria-label="Productafbeeldingen"
76
+ labels={{
77
+ carousel: "carrousel",
78
+ slideLabel: (index, total) => `${index + 1} van ${total}`,
79
+ previous: "Vorige afbeelding",
80
+ next: "Volgende afbeelding",
81
+ thumbnails: "Afbeeldingen",
82
+ thumbnailLabel: (index) => `Afbeelding ${index + 1}`,
83
+ announcement: (index, total) => `Afbeelding ${index + 1} van ${total}`,
84
+ }}
85
+ >
86
+ ```
87
+
88
+ ### Motion
89
+
90
+ The carousel scrolls smoothly, and jumps instead when the user prefers reduced motion. Pass `motion="reduce"` to always jump.
91
+
92
+ ### Custom elements
93
+
94
+ `Prev`, `Next` and `Thumbnail` take a `render` prop to render as your own component. Props are merged: class names are joined, and your event handlers run first (call `event.preventDefault()` to skip the carousel's own).
95
+
96
+ ```tsx
97
+ <Carousel.Next render={<Button variant="ghost" />}>Next</Carousel.Next>
98
+ ```
99
+
100
+ ## Styling
101
+
102
+ Import the base styles once:
103
+
104
+ ```ts
105
+ import "@mitchellslager/carousel/styles.css"
106
+ ```
107
+
108
+ They only handle layout, snapping and reduced motion, inside `@layer carousel`. Unlayered CSS of your own always wins. If you use Tailwind or other cascade layers, declare the carousel layer first so your utilities override it:
109
+
110
+ ```css
111
+ @layer carousel;
112
+ @import "tailwindcss";
113
+ ```
114
+
115
+ ## Contributing
116
+
117
+ Development happens in the [GitHub repository](https://github.com/mitchellslager/carousel).
118
+
119
+ ## License
120
+
121
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitchellslager/carousel",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "A headless, accessible image carousel for React, driven by native CSS scroll snapping.",
5
5
  "license": "MIT",
6
6
  "author": "Mitchell Slager",
@@ -42,14 +42,6 @@
42
42
  "access": "public",
43
43
  "provenance": true
44
44
  },
45
- "scripts": {
46
- "build": "tsdown",
47
- "test": "vitest run",
48
- "test:watch": "vitest",
49
- "typecheck": "tsc",
50
- "check:package": "publint --strict --pack npm && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
51
- "size": "size-limit"
52
- },
53
45
  "peerDependencies": {
54
46
  "react": ">=19",
55
47
  "react-dom": ">=19"
@@ -76,5 +68,13 @@
76
68
  "tsdown": "^0.23.5",
77
69
  "vitest": "^5.1.0",
78
70
  "vitest-browser-react": "^2.3.0"
71
+ },
72
+ "scripts": {
73
+ "build": "tsdown",
74
+ "test": "vitest run",
75
+ "test:watch": "vitest",
76
+ "typecheck": "tsc",
77
+ "check:package": "publint --strict --pack npm && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
78
+ "size": "size-limit"
79
79
  }
80
- }
80
+ }