speak-friend-react 0.1.0-alpha.1 → 0.1.0-alpha.2
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 +1 -0
- package/dist/guide.d.ts +3 -1
- package/dist/guide.js +2 -2
- package/dist/ring.d.ts +2 -1
- package/dist/ring.js +2 -2
- package/dist/styles.css +17 -20
- package/dist/wait-for-target.d.ts +1 -1
- package/dist/wait-for-target.js +3 -3
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ await tours.start("welcome");
|
|
|
58
58
|
| --------------- | -------- | ----------------------------------------------------------- |
|
|
59
59
|
| `labels` | required | Every string: `counter`, `back`, `next`, `finish`, `close`. |
|
|
60
60
|
| `position` | `end` | `start` or `end`. |
|
|
61
|
+
| `theme` | | `light` or `dark` forces a theme. Default: follows the OS. |
|
|
61
62
|
| `headingLevel` | `2` | Heading level of the step title. |
|
|
62
63
|
| `announce` | `focus` | `focus` or `live`. |
|
|
63
64
|
| `closeOnEscape` | `true` | |
|
package/dist/guide.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface TourGuideProps {
|
|
|
13
13
|
labels: GuideLabels;
|
|
14
14
|
/** Horizontal side of the dock. Logical, so it mirrors in right-to-left layouts. Default `end`. */
|
|
15
15
|
position?: "start" | "end";
|
|
16
|
+
/** `light` or `dark` forces a theme. By default the guide follows the operating system. */
|
|
17
|
+
theme?: "light" | "dark";
|
|
16
18
|
/** Heading level of the step title. Default 2. */
|
|
17
19
|
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
18
20
|
/** `focus` moves focus to the step title; `live` keeps focus and announces the step. Default `focus`. */
|
|
@@ -22,4 +24,4 @@ export interface TourGuideProps {
|
|
|
22
24
|
className?: string;
|
|
23
25
|
}
|
|
24
26
|
/** The docked guide with its highlight ring. Renders only while a tour is running. */
|
|
25
|
-
export declare function TourGuide({ labels, position, headingLevel, announce, closeOnEscape, className, }: TourGuideProps): JSX.Element | null;
|
|
27
|
+
export declare function TourGuide({ labels, position, theme, headingLevel, announce, closeOnEscape, className, }: TourGuideProps): JSX.Element | null;
|
package/dist/guide.js
CHANGED
|
@@ -7,7 +7,7 @@ import { findTarget } from "./wait-for-target.js";
|
|
|
7
7
|
const noSubscription = () => () => undefined;
|
|
8
8
|
const reducedMotion = () => typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
9
9
|
/** The docked guide with its highlight ring. Renders only while a tour is running. */
|
|
10
|
-
export function TourGuide({ labels, position = "end", headingLevel = 2, announce = "focus", closeOnEscape = true, className, }) {
|
|
10
|
+
export function TourGuide({ labels, position = "end", theme, headingLevel = 2, announce = "focus", closeOnEscape = true, className, }) {
|
|
11
11
|
const tours = useTours();
|
|
12
12
|
const run = useRun();
|
|
13
13
|
const state = useTourState();
|
|
@@ -74,5 +74,5 @@ export function TourGuide({ labels, position = "end", headingLevel = 2, announce
|
|
|
74
74
|
const { step, index, total } = state;
|
|
75
75
|
const last = index === total - 1;
|
|
76
76
|
const counter = labels.counter(index + 1, total);
|
|
77
|
-
return (_jsxs(_Fragment, { children: [_jsx(Ring, { target: step.target }, step.id), createPortal(_jsxs("section", { ref: guideRef, role: "dialog", "aria-labelledby": id, className: className ? `sf-guide ${className}` : "sf-guide", "data-position": position, children: [_jsxs("div", { className: "sf-band", children: [_jsx("span", { className: "sf-count", children: counter }), _jsx("span", { className: "sf-progress", "aria-hidden": "true", children: Array.from({ length: total }, (_, i) => (_jsx("i", { className: i < index ? "sf-done" : i === index ? "sf-current" : undefined }, i))) })] }), _jsxs("div", { className: "sf-body", children: [createElement(`h${headingLevel}`, { id, ref: titleRef, tabIndex: -1, className: "sf-title" }, step.title), _jsx("p", { className: "sf-text", children: step.body }), _jsxs("div", { className: "sf-actions", children: [_jsx("button", { type: "button", className: "sf-button", onClick: () => run(tours.back()), disabled: index === 0, children: labels.back }), _jsx("button", { type: "button", className: "sf-button sf-primary", onClick: () => run(tours.next()), children: last ? labels.finish : labels.next }), _jsx("button", { type: "button", className: "sf-button sf-quiet", onClick: () => run(tours.close()), children: labels.close })] })] }), announce === "live" && (_jsx("div", { className: "sf-visually-hidden", role: "status", children: `${counter}: ${step.title}` }))] }), document.body)] }));
|
|
77
|
+
return (_jsxs(_Fragment, { children: [_jsx(Ring, { target: step.target, theme: theme }, step.id), createPortal(_jsxs("section", { ref: guideRef, role: "dialog", "aria-labelledby": id, className: className ? `sf-guide ${className}` : "sf-guide", "data-position": position, "data-theme": theme, children: [_jsxs("div", { className: "sf-band", children: [_jsx("span", { className: "sf-count", children: counter }), _jsx("span", { className: "sf-progress", "aria-hidden": "true", children: Array.from({ length: total }, (_, i) => (_jsx("i", { className: i < index ? "sf-done" : i === index ? "sf-current" : undefined }, i))) })] }), _jsxs("div", { className: "sf-body", children: [createElement(`h${headingLevel}`, { id, ref: titleRef, tabIndex: -1, className: "sf-title" }, step.title), _jsx("p", { className: "sf-text", children: step.body }), _jsxs("div", { className: "sf-actions", children: [_jsx("button", { type: "button", className: "sf-button", onClick: () => run(tours.back()), disabled: index === 0, children: labels.back }), _jsx("button", { type: "button", className: "sf-button sf-primary", onClick: () => run(tours.next()), children: last ? labels.finish : labels.next }), _jsx("button", { type: "button", className: "sf-button sf-quiet", onClick: () => run(tours.close()), children: labels.close })] })] }), announce === "live" && (_jsx("div", { className: "sf-visually-hidden", role: "status", children: `${counter}: ${step.title}` }))] }), document.body)] }));
|
|
78
78
|
}
|
package/dist/ring.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JSX } from "react";
|
|
2
2
|
/** A ring around the element marked `data-tour="<target>"`. It never blocks pointer events. */
|
|
3
|
-
export declare function Ring({ target }: {
|
|
3
|
+
export declare function Ring({ target, theme }: {
|
|
4
4
|
target: string;
|
|
5
|
+
theme?: "light" | "dark";
|
|
5
6
|
}): JSX.Element | null;
|
package/dist/ring.js
CHANGED
|
@@ -37,9 +37,9 @@ function useBox(target) {
|
|
|
37
37
|
return box;
|
|
38
38
|
}
|
|
39
39
|
/** A ring around the element marked `data-tour="<target>"`. It never blocks pointer events. */
|
|
40
|
-
export function Ring({ target }) {
|
|
40
|
+
export function Ring({ target, theme }) {
|
|
41
41
|
const box = useBox(target);
|
|
42
42
|
if (!box || typeof document === "undefined")
|
|
43
43
|
return null;
|
|
44
|
-
return createPortal(_jsx("div", { className: "sf-ring", "aria-hidden": "true", style: { top: box.top, left: box.left, width: box.width, height: box.height } }), document.body);
|
|
44
|
+
return createPortal(_jsx("div", { className: "sf-ring", "data-theme": theme, "aria-hidden": "true", style: { top: box.top, left: box.left, width: box.width, height: box.height } }), document.body);
|
|
45
45
|
}
|
package/dist/styles.css
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
.sf-guide,
|
|
2
2
|
.sf-ring {
|
|
3
|
-
|
|
4
|
-
--sf-
|
|
5
|
-
--sf-
|
|
6
|
-
--sf-
|
|
7
|
-
--sf-accent
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
--sf-surface: light-dark(oklch(100% 0 0), oklch(23% 0.035 285));
|
|
5
|
+
--sf-ink: light-dark(oklch(23% 0.03 285), oklch(95% 0.012 95));
|
|
6
|
+
--sf-line: light-dark(oklch(62% 0.02 285), oklch(62% 0.04 285));
|
|
7
|
+
--sf-accent: light-dark(oklch(44% 0.19 290), oklch(80% 0.11 300));
|
|
8
|
+
--sf-accent-ink: light-dark(oklch(100% 0 0), oklch(19% 0.02 285));
|
|
8
9
|
--sf-primary: oklch(87% 0.2 125);
|
|
9
10
|
--sf-primary-ink: oklch(19% 0.02 285);
|
|
10
|
-
--sf-primary-edge: oklch(53% 0.13 125);
|
|
11
|
-
--sf-done: oklch(53% 0.13 125);
|
|
12
|
-
--sf-halo: oklch(100% 0 0);
|
|
11
|
+
--sf-primary-edge: light-dark(oklch(53% 0.13 125), transparent);
|
|
12
|
+
--sf-done: light-dark(oklch(53% 0.13 125), oklch(81% 0.16 125));
|
|
13
|
+
--sf-halo: light-dark(oklch(100% 0 0), oklch(19% 0.02 285));
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
--sf-primary-edge: transparent;
|
|
24
|
-
--sf-done: oklch(81% 0.16 125);
|
|
25
|
-
--sf-halo: oklch(19% 0.02 285);
|
|
26
|
-
}
|
|
16
|
+
.sf-guide[data-theme="light"],
|
|
17
|
+
.sf-ring[data-theme="light"] {
|
|
18
|
+
color-scheme: light;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.sf-guide[data-theme="dark"],
|
|
22
|
+
.sf-ring[data-theme="dark"] {
|
|
23
|
+
color-scheme: dark;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
.sf-visually-hidden {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The visible element marked `data-tour="<target>"`, or null. */
|
|
1
|
+
/** The first visible element marked `data-tour="<target>"`, or null. */
|
|
2
2
|
export declare const findTarget: (target: string) => HTMLElement | null;
|
|
3
3
|
/** Resolves true when the target is on the page, waiting up to `timeout` ms for it to appear. */
|
|
4
4
|
export declare function waitForTarget(target: string, timeout: number): Promise<boolean>;
|
package/dist/wait-for-target.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const escapeValue = (value) => typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
2
2
|
const find = (target) => {
|
|
3
|
-
const
|
|
4
|
-
return el
|
|
3
|
+
const candidates = document.querySelectorAll(`[data-tour="${escapeValue(target)}"]`);
|
|
4
|
+
return Array.from(candidates).find((el) => el.getClientRects().length > 0) ?? null;
|
|
5
5
|
};
|
|
6
|
-
/** The visible element marked `data-tour="<target>"`, or null. */
|
|
6
|
+
/** The first visible element marked `data-tour="<target>"`, or null. */
|
|
7
7
|
export const findTarget = (target) => typeof document === "undefined" ? null : find(target);
|
|
8
8
|
/** Resolves true when the target is on the page, waiting up to `timeout` ms for it to appear. */
|
|
9
9
|
export function waitForTarget(target, timeout) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speak-friend-react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "React bindings for speak-friend: an accessible docked tour guide with a highlight ring.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"react-dom": ">=18"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"speak-friend": "0.1.0-alpha.
|
|
36
|
+
"speak-friend": "0.1.0-alpha.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@testing-library/react": "16.3.0",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
"react"
|
|
53
53
|
],
|
|
54
54
|
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
"tag": "next"
|
|
55
|
+
"access": "public"
|
|
57
56
|
},
|
|
58
57
|
"scripts": {
|
|
59
58
|
"build": "tsc -p tsconfig.build.json && cp src/styles.css dist/styles.css",
|
|
60
59
|
"typecheck": "tsc --noEmit",
|
|
61
60
|
"test": "vitest run",
|
|
62
|
-
"smoke": "node scripts/smoke.mjs"
|
|
61
|
+
"smoke": "node scripts/smoke.mjs",
|
|
62
|
+
"test:coverage": "vitest run --coverage"
|
|
63
63
|
}
|
|
64
64
|
}
|