@a11y-context/mcp-server 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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +62 -0
  3. package/corpus/web/react/components/accordion.basic.md +157 -0
  4. package/corpus/web/react/components/button.basic.md +91 -0
  5. package/corpus/web/react/components/button.toggle.md +109 -0
  6. package/corpus/web/react/components/carousel.dots.md +376 -0
  7. package/corpus/web/react/components/carousel.thumbnails.md +395 -0
  8. package/corpus/web/react/components/collection-row.basic.md +179 -0
  9. package/corpus/web/react/components/combobox.autocomplete.md +293 -0
  10. package/corpus/web/react/components/dialog.modal.md +202 -0
  11. package/corpus/web/react/components/dialog.nonmodal.md +184 -0
  12. package/corpus/web/react/components/disclosure.basic.md +97 -0
  13. package/corpus/web/react/components/grid.channel-guide.md +453 -0
  14. package/corpus/web/react/components/link.basic.md +105 -0
  15. package/corpus/web/react/components/listbox.basic.md +263 -0
  16. package/corpus/web/react/components/menu.basic.md +294 -0
  17. package/corpus/web/react/components/menu.menubar.md +296 -0
  18. package/corpus/web/react/components/navigation-menu.basic.md +349 -0
  19. package/corpus/web/react/components/navigation-menu.dropdown.md +220 -0
  20. package/corpus/web/react/components/select.basic.md +318 -0
  21. package/corpus/web/react/components/select.native.md +108 -0
  22. package/corpus/web/react/components/switch.basic.md +151 -0
  23. package/corpus/web/react/components/toast.basic.md +112 -0
  24. package/corpus/web/react/components/tooltip.basic.md +139 -0
  25. package/corpus/web/react/global/global_rules.md +292 -0
  26. package/corpus/web/react/patterns.json +946 -0
  27. package/dist/config.js +35 -0
  28. package/dist/contracts/v1/types.js +6 -0
  29. package/dist/http.js +103 -0
  30. package/dist/index.js +8 -0
  31. package/dist/mcp/createServer.js +122 -0
  32. package/dist/mcp/response.js +7 -0
  33. package/dist/mcp/server.js +15 -0
  34. package/dist/repo/cache.js +27 -0
  35. package/dist/repo/globalRules.js +304 -0
  36. package/dist/repo/index.js +178 -0
  37. package/dist/repo/paths.js +25 -0
  38. package/dist/repo/sections.js +166 -0
  39. package/dist/smoke/smoke-remote-mcp.js +43 -0
  40. package/dist/telemetry.js +15 -0
  41. package/dist/telemetryWrap.js +48 -0
  42. package/dist/tools/getGlobalRules.js +33 -0
  43. package/dist/tools/getPattern.js +54 -0
  44. package/dist/tools/listPatterns.js +33 -0
  45. package/dist/utils/fs.js +18 -0
  46. package/dist/utils/hash.js +4 -0
  47. package/package.json +68 -0
@@ -0,0 +1,376 @@
1
+ ---
2
+ id: carousel.dots
3
+ title: Carousel with Dot Navigation
4
+ stack: web/react
5
+ status: beta
6
+ latest_version: 0.2.0
7
+ tags: [marquee, carousel, slider, gallery, dots, autoplay, reduced-motion]
8
+ aliases: [hero carousel, marquee, featured gallery, hero gallery, image gallery, slider]
9
+ summary: Horizontally-advancing carousel (aka hero or marquee carousel) with 'dot' navigation, prev/next buttons, and pause behavior.
10
+ ---
11
+
12
+ # Carousel with Dot Navigation
13
+
14
+ Pattern ID: `carousel.dots`
15
+
16
+ Horizontally-advancing carousel (aka hero or marquee carousel) with dot navigation, prev/next buttons, and pause behavior.
17
+
18
+ ## Use When
19
+ - Use when slides are navigated sequentially using next/previous controls or dot indicators.
20
+ - Use when dot buttons indicate slide position but do not display image previews.
21
+
22
+ ## Do Not Use When
23
+ - Do not use when thumbnail images are shown for direct slide selection (use `carousel.thumbnails`).
24
+ - Do not use when multiple slides are visible at once in a horizontal list (use `content-shelf`).
25
+
26
+ ## Must Haves
27
+ - Render a carousel container with `aria-roledescription="carousel"` and an accessible name (`aria-label`).
28
+ - Ensure the carousel container has a semantic HTML5 element or role, such as `<section>` or `role="region"`.
29
+ - Each slide must have `role="group"`, `aria-roledescription="slide"` and an `aria-label` like "1 of N" (N is total number of slides).
30
+ - Slides that are not currently visible must not be rendered, or must be hidden in the DOM (e.g., via the `hidden` attribute), so their content cannot be reached by keyboard or screen readers.
31
+ - Provide Previous/Next buttons as real `<button>` elements, with `aria-label` like "Previous Slide" and "Next Slide".
32
+ - Provide dot navigation as real `<button>` elements in normal tab order (no roving tabindex), with `aria-label` like "Go to slide 2", and with `aria-current="true"` on the button corresponding to the active slide.
33
+ - Provide a Pause/Play button as the first focusable element inside the carousel container.
34
+ - Default to paused when `prefers-reduced-motion: reduce`.
35
+ - Pause when keyboard focus enters the carousel region.
36
+ - Ensure a visible focus state (e.g., a 2px solid outline offset by 1-2px) on each focusable element, including the previous/next buttons, pause button, and dots.
37
+
38
+ ## Customizable
39
+ - The contents of each slide are customizable. However, if they contain a title, then these should usually be `<h2>`.
40
+
41
+ ## Don'ts
42
+ - Do not auto-advance the slides without a visible Pause/Play control.
43
+ - Do not ignore `prefers-reduced-motion`.
44
+ - Do not keep moving while the user is interacting (focus inside carousel must pause autoplay).
45
+
46
+ ## Golden Pattern
47
+
48
+ Structural reference for AI coding assistants — semantics, focus, and keyboard behavior. Styling, copy, and demo data are illustrative.
49
+
50
+ ```jsx
51
+ "use client";
52
+
53
+ export function CarouselDotsDemo({
54
+ ariaLabel = "Featured content",
55
+ items = DEFAULT_ITEMS,
56
+ autoplay = true,
57
+ intervalMs = 5000,
58
+ }) {
59
+ const [index, setIndex] = useState(0);
60
+ const [isPaused, setIsPaused] = useState(false);
61
+
62
+ const hasUserToggledPauseRef = useRef(false);
63
+ const reducedMotionRef = useRef(false);
64
+ const timerRef = useRef(null);
65
+ const skipFocusPauseRef = useRef(false);
66
+
67
+ const count = items.length;
68
+
69
+ // Reduced motion: paused by default.
70
+ useEffect(() => {
71
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
72
+ const apply = () => {
73
+ reducedMotionRef.current = !!mq.matches;
74
+ if (mq.matches) {
75
+ setIsPaused(true);
76
+ }
77
+ };
78
+
79
+ apply();
80
+
81
+ // Safari still supports addListener in some versions
82
+ if (mq.addEventListener) mq.addEventListener("change", apply);
83
+ else mq.addListener(apply);
84
+
85
+ return () => {
86
+ if (mq.removeEventListener) mq.removeEventListener("change", apply);
87
+ else mq.removeListener(apply);
88
+ };
89
+ }, []);
90
+
91
+ function goTo(nextIndex) {
92
+ const clamped = ((nextIndex % count) + count) % count;
93
+ setIndex(clamped);
94
+ }
95
+
96
+ function goPrev() {
97
+ goTo(index - 1);
98
+ }
99
+
100
+ function goNext() {
101
+ goTo(index + 1);
102
+ }
103
+
104
+ function pause() {
105
+ setIsPaused(true);
106
+ }
107
+
108
+ function togglePause() {
109
+ skipFocusPauseRef.current = false;
110
+ hasUserToggledPauseRef.current = true;
111
+ setIsPaused((p) => !p);
112
+ }
113
+
114
+ function onPauseButtonPointerDown() {
115
+ skipFocusPauseRef.current = true;
116
+ }
117
+
118
+ // Pause autoplay when focus enters the carousel.
119
+ function onFocusCapture() {
120
+ if (skipFocusPauseRef.current) {
121
+ skipFocusPauseRef.current = false;
122
+ return;
123
+ }
124
+ pause();
125
+ }
126
+
127
+ // Autoplay (respects reduced motion, focus-paused state, and user pause).
128
+ useEffect(() => {
129
+ if (!autoplay || isPaused || reducedMotionRef.current) return;
130
+
131
+ // Functional update avoids stale-closure reads of the current index.
132
+ timerRef.current = window.setInterval(() => {
133
+ setIndex((i) => (i + 1) % count);
134
+ }, intervalMs);
135
+
136
+ return () => {
137
+ if (timerRef.current) {
138
+ window.clearInterval(timerRef.current);
139
+ timerRef.current = null;
140
+ }
141
+ };
142
+ }, [autoplay, isPaused, intervalMs, count]);
143
+
144
+ const active = items[index];
145
+
146
+ // aria-live: off while moving, polite when paused so changes can be announced if user moves slides.
147
+ const ariaLive = isPaused ? "polite" : "off";
148
+
149
+ return (
150
+ <section
151
+ aria-roledescription="carousel"
152
+ aria-label={ariaLabel}
153
+ onFocusCapture={onFocusCapture}
154
+ style={{
155
+ position: "relative",
156
+ maxWidth: 960,
157
+ margin: "0 auto",
158
+ padding: 16,
159
+ background: "#fff",
160
+ color: "#111",
161
+ borderRadius: 12,
162
+ }}
163
+ >
164
+ {/* Slides viewport */}
165
+ <div
166
+ aria-live={ariaLive}
167
+ style={{
168
+ position: "relative",
169
+ overflow: "hidden",
170
+ borderRadius: 12,
171
+ minHeight: 400,
172
+ background: "#000",
173
+ }}
174
+ >
175
+ <button
176
+ type="button"
177
+ onPointerDown={onPauseButtonPointerDown}
178
+ onClick={togglePause}
179
+ aria-label={isPaused ? "Play automatic rotation" : "Pause automatic rotation"}
180
+ aria-pressed={isPaused}
181
+ style={pauseButtonStyle}
182
+ >
183
+ <span aria-hidden="true">
184
+ {isPaused ? "[play-icon]" : "[pause-icon]"}
185
+ </span>
186
+ </button>
187
+
188
+ {/* Slide */}
189
+ <div
190
+ aria-roledescription="slide"
191
+ aria-label={`${index + 1} of ${count}`}
192
+ style={{
193
+ display: "grid",
194
+ gridTemplateColumns: "1fr",
195
+ alignItems: "end",
196
+ minHeight: 400,
197
+ padding: "16px 72px 40px",
198
+ backgroundImage: active.image ? `url(${active.image})` : undefined,
199
+ backgroundSize: "cover",
200
+ backgroundPosition: "center",
201
+ }}
202
+ >
203
+ <div
204
+ style={{
205
+ maxWidth: 520,
206
+ background: "#000",
207
+ color: "#fff",
208
+ padding: 12,
209
+ borderRadius: 10,
210
+ }}
211
+ >
212
+ <h2 style={{ margin: 0, fontSize: 24 }}>{active.title}</h2>
213
+ <p style={{ marginTop: 8, marginBottom: 12, lineHeight: 1.4 }}>
214
+ {active.description}
215
+ </p>
216
+ <a
217
+ href={active.href}
218
+ style={{
219
+ display: "inline-block",
220
+ padding: "10px 12px",
221
+ borderRadius: 10,
222
+ background: "#fff",
223
+ color: "#000",
224
+ textDecoration: "none",
225
+ fontWeight: 600,
226
+ }}
227
+ >
228
+ View details
229
+ </a>
230
+ </div>
231
+ </div>
232
+
233
+ {/* Prev / Next (inside visible slide area) */}
234
+ <button
235
+ type="button"
236
+ onClick={() => {
237
+ pause();
238
+ goPrev();
239
+ }}
240
+ aria-label="Previous slide"
241
+ style={navButtonStyle("left")}
242
+ >
243
+
244
+ </button>
245
+
246
+ <button
247
+ type="button"
248
+ onClick={() => {
249
+ pause();
250
+ goNext();
251
+ }}
252
+ aria-label="Next slide"
253
+ style={navButtonStyle("right")}
254
+ >
255
+
256
+ </button>
257
+ </div>
258
+
259
+ {/* Controls row below the viewport */}
260
+ <div
261
+ style={{
262
+ display: "flex",
263
+ alignItems: "center",
264
+ justifyContent: "center",
265
+ marginTop: 12,
266
+ }}
267
+ >
268
+ <div style={{ display: "flex", gap: 8 }} aria-label="Choose a slide">
269
+ {items.map((_, i) => {
270
+ const isActive = i === index;
271
+ return (
272
+ <button
273
+ key={i}
274
+ type="button"
275
+ onClick={() => {
276
+ pause();
277
+ goTo(i);
278
+ }}
279
+ aria-label={`Go to slide ${i + 1}`}
280
+ aria-current={isActive ? "true" : undefined}
281
+ style={dotStyle(isActive)}
282
+ />
283
+ );
284
+ })}
285
+ </div>
286
+ </div>
287
+ </section>
288
+ );
289
+ }
290
+
291
+ function navButtonStyle(side) {
292
+ return {
293
+ position: "absolute",
294
+ top: "50%",
295
+ transform: "translateY(-50%)",
296
+ [side]: 12,
297
+ width: 44,
298
+ height: 44,
299
+ borderRadius: 12,
300
+ border: "1px solid rgba(255,255,255,0.25)",
301
+ background: "rgba(0,0,0,0.45)",
302
+ color: "#fff",
303
+ display: "grid",
304
+ placeItems: "center",
305
+ cursor: "pointer",
306
+ fontSize: 28,
307
+ lineHeight: 1,
308
+ };
309
+ }
310
+
311
+ const pauseButtonStyle = {
312
+ position: "absolute",
313
+ top: 12,
314
+ left: 12,
315
+ zIndex: 2,
316
+ width: 40,
317
+ height: 40,
318
+ borderRadius: 10,
319
+ border: "1px solid rgba(255,255,255,0.25)",
320
+ background: "rgba(0,0,0,0.6)",
321
+ color: "#fff",
322
+ display: "grid",
323
+ placeItems: "center",
324
+ cursor: "pointer",
325
+ };
326
+
327
+ function dotStyle(active) {
328
+ return {
329
+ width: 12,
330
+ height: 12,
331
+ borderRadius: 999,
332
+ border: "1px solid rgba(0,0,0,0.35)",
333
+ background: active ? "#111" : "rgba(0,0,0,0.18)",
334
+ cursor: "pointer",
335
+ };
336
+ }
337
+
338
+ const DEFAULT_ITEMS = [
339
+ {
340
+ title: "Neighbors",
341
+ description: "A chaotic dispute spirals. Watch the latest episode now.",
342
+ href: "#",
343
+ image: "https://picsum.photos/seed/hero-1/1200/600",
344
+ },
345
+ {
346
+ title: "UCLA at Michigan",
347
+ description: "Tip-off at 12:45 PM ET. Catch it live.",
348
+ href: "#",
349
+ image: "https://picsum.photos/seed/hero-2/1200/600",
350
+ },
351
+ {
352
+ title: "Fire Country",
353
+ description: "A risky mission tests loyalties and nerves.",
354
+ href: "#",
355
+ image: "https://picsum.photos/seed/hero-3/1200/600",
356
+ },
357
+ ];
358
+ ```
359
+
360
+ ## Acceptance Checks
361
+ - Semantics:
362
+ - The carousel container has `aria-roledescription="carousel"` and an accessible name.
363
+ - Each slide has `aria-roledescription="slide"` and exposes position (e.g., "2 of 3").
364
+ - Content of non-visible slides is not reachable by keyboard or screen reader.
365
+ - Autoplay:
366
+ - With `prefers-reduced-motion: reduce`, autoplay is paused by default.
367
+ - Tabbing into the carousel pauses autoplay.
368
+ - Autoplay does not run while paused.
369
+ - Controls (keyboard):
370
+ - Previous and Next buttons are reachable via Tab and move one slide per activation.
371
+ - Each dot is reachable via Tab and activates its corresponding slide.
372
+ - The active dot exposes state (e.g., `aria-current="true"`).
373
+ - Content:
374
+ - Each slide includes a visible title (`<h2>`), a short description, and one primary CTA link.
375
+ - Screen reader:
376
+ - When changing slides while paused, the carousel name and slide position are announced without duplicate announcements.