@techsio/ui-kit 0.44.1 → 0.46.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/dist/atoms/hotkeys.js +52 -0
- package/dist/hotkeys.js +108 -0
- package/dist/internal/molecules/command.context.js +14 -0
- package/dist/molecules/command.js +177 -0
- package/dist/molecules/tour.js +318 -0
- package/dist/src/atoms/hotkeys.d.ts +29 -0
- package/dist/src/atoms/hotkeys.d.ts.map +1 -0
- package/dist/src/hotkeys.d.ts +19 -0
- package/dist/src/hotkeys.d.ts.map +1 -0
- package/dist/src/internal/molecules/command.context.d.ts +6 -0
- package/dist/src/internal/molecules/command.context.d.ts.map +1 -0
- package/dist/src/molecules/command.d.ts +51 -0
- package/dist/src/molecules/command.d.ts.map +1 -0
- package/dist/src/molecules/toast.d.ts +9 -9
- package/dist/src/molecules/tour.d.ts +72 -0
- package/dist/src/molecules/tour.d.ts.map +1 -0
- package/dist/src/types/zag.d.ts +2 -0
- package/dist/src/types/zag.d.ts.map +1 -1
- package/dist/stories/atoms/hotkeys-hydration.fixture.d.ts +6 -0
- package/dist/stories/atoms/hotkeys-hydration.fixture.d.ts.map +1 -0
- package/dist/stories/atoms/hotkeys.stories.d.ts +336 -0
- package/dist/stories/atoms/hotkeys.stories.d.ts.map +1 -0
- package/dist/stories/changelog/changelog.stories.d.ts +1 -1
- package/dist/stories/changelog/changelog.stories.d.ts.map +1 -1
- package/dist/stories/molecules/command-hotkeys.stories.d.ts +38 -0
- package/dist/stories/molecules/command-hotkeys.stories.d.ts.map +1 -0
- package/dist/stories/molecules/command.stories.d.ts +74 -0
- package/dist/stories/molecules/command.stories.d.ts.map +1 -0
- package/dist/stories/molecules/tour.stories.d.ts +58 -0
- package/dist/stories/molecules/tour.stories.d.ts.map +1 -0
- package/dist/test/command-hotkeys.spec.d.ts +2 -0
- package/dist/test/command-hotkeys.spec.d.ts.map +1 -0
- package/dist/test/command.spec.d.ts +2 -0
- package/dist/test/command.spec.d.ts.map +1 -0
- package/dist/test/hotkeys.spec.d.ts +2 -0
- package/dist/test/hotkeys.spec.d.ts.map +1 -0
- package/dist/test/tour.playwright.config.d.ts +4 -0
- package/dist/test/tour.playwright.config.d.ts.map +1 -0
- package/dist/test/tour.spec.d.ts +2 -0
- package/dist/test/tour.spec.d.ts.map +1 -0
- package/dist/test/tour.typecheck.d.ts +11 -0
- package/dist/test/tour.typecheck.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/tokens/components/atoms/_hotkeys.css +36 -0
- package/src/tokens/components/components.css +3 -0
- package/src/tokens/components/molecules/_command.css +43 -0
- package/src/tokens/components/molecules/_tour.css +121 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFormatHotkey } from "../hotkeys.js";
|
|
3
|
+
import { tv } from "../utils.js";
|
|
4
|
+
const hotkeysStyles = tv({
|
|
5
|
+
slots: {
|
|
6
|
+
root: "inline-flex items-center gap-hotkeys whitespace-nowrap font-hotkeys text-hotkeys-sm",
|
|
7
|
+
key: "hotkeys-key-border-width inline-flex items-center justify-center rounded-hotkeys-key border-hotkeys-key-border bg-hotkeys-key-bg px-hotkeys-key-x py-hotkeys-key-y text-hotkeys-key-fg",
|
|
8
|
+
separator: "text-hotkeys-separator-fg"
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
function Hotkeys({ hotkey, formatOptions, children, className, ref, ...props }) {
|
|
12
|
+
const format = useFormatHotkey();
|
|
13
|
+
return /*#__PURE__*/ jsx("span", {
|
|
14
|
+
...props,
|
|
15
|
+
className: hotkeysStyles().root({
|
|
16
|
+
className
|
|
17
|
+
}),
|
|
18
|
+
"data-part": "root",
|
|
19
|
+
"data-scope": "hotkeys",
|
|
20
|
+
ref: ref,
|
|
21
|
+
children: children ?? (void 0 !== hotkey ? /*#__PURE__*/ jsx(Hotkeys.Key, {
|
|
22
|
+
children: format(hotkey, formatOptions)
|
|
23
|
+
}) : null)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
Hotkeys.Key = function({ className, ref, ...props }) {
|
|
27
|
+
return /*#__PURE__*/ jsx("kbd", {
|
|
28
|
+
...props,
|
|
29
|
+
className: hotkeysStyles().key({
|
|
30
|
+
className
|
|
31
|
+
}),
|
|
32
|
+
"data-part": "key",
|
|
33
|
+
"data-scope": "hotkeys",
|
|
34
|
+
ref: ref
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
Hotkeys.Separator = function({ children = "+", className, ref, ...props }) {
|
|
38
|
+
return /*#__PURE__*/ jsx("span", {
|
|
39
|
+
...props,
|
|
40
|
+
"aria-hidden": "true",
|
|
41
|
+
className: hotkeysStyles().separator({
|
|
42
|
+
className
|
|
43
|
+
}),
|
|
44
|
+
"data-part": "separator",
|
|
45
|
+
"data-scope": "hotkeys",
|
|
46
|
+
ref: ref,
|
|
47
|
+
children: children
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
Hotkeys.Root = Hotkeys;
|
|
51
|
+
Hotkeys.displayName = "Hotkeys";
|
|
52
|
+
export { Hotkeys };
|
package/dist/hotkeys.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createHotkeyStore, formatHotkey } from "@zag-js/hotkeys";
|
|
2
|
+
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
function useHotkey({ store, id, ...command }) {
|
|
4
|
+
const generatedId = useId();
|
|
5
|
+
useHotkeys({
|
|
6
|
+
store,
|
|
7
|
+
commands: [
|
|
8
|
+
{
|
|
9
|
+
...command,
|
|
10
|
+
id: id ?? generatedId
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function sameValues(left, right) {
|
|
16
|
+
if (Object.is(left, right)) return true;
|
|
17
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index)=>Object.is(value, right[index]));
|
|
18
|
+
}
|
|
19
|
+
function sameOptions(left, right) {
|
|
20
|
+
return left?.capture === right?.capture && left?.eventType === right?.eventType && left?.preventDefault === right?.preventDefault && left?.stopPropagation === right?.stopPropagation && left?.requireReset === right?.requireReset && left?.enableOnContentEditable === right?.enableOnContentEditable && sameValues(left?.enableOnFormTags, right?.enableOnFormTags) && left?.target === right?.target;
|
|
21
|
+
}
|
|
22
|
+
function sameRegistration(left, right) {
|
|
23
|
+
return left.hotkey === right.hotkey && left.label === right.label && left.description === right.description && left.category === right.category && sameValues(left.scopes, right.scopes) && sameValues(left.keywords, right.keywords) && sameOptions(left.options, right.options);
|
|
24
|
+
}
|
|
25
|
+
function readEnabled(command) {
|
|
26
|
+
const value = command?.enabled;
|
|
27
|
+
return "function" == typeof value ? value() : value ?? false;
|
|
28
|
+
}
|
|
29
|
+
function syncCommand(store, command, previous, getCurrent) {
|
|
30
|
+
const enabled = "function" == typeof command.enabled ? ()=>readEnabled(getCurrent()) : command.enabled ?? true;
|
|
31
|
+
const enabledUnchanged = previous?.enabled === command.enabled || "function" == typeof previous?.enabled && "function" == typeof command.enabled;
|
|
32
|
+
if (previous && sameRegistration(previous, command)) {
|
|
33
|
+
if (!enabledUnchanged) store.setEnabled(command.id, enabled);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (previous) store.unregister(command.id);
|
|
37
|
+
store.register({
|
|
38
|
+
...command,
|
|
39
|
+
action: (event)=>getCurrent()?.action(event),
|
|
40
|
+
enabled
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function useHotkeys({ store, commands }) {
|
|
44
|
+
const current = useRef(commands);
|
|
45
|
+
const registered = useRef(new Map());
|
|
46
|
+
useLayoutEffect(()=>{
|
|
47
|
+
current.current = commands;
|
|
48
|
+
});
|
|
49
|
+
useEffect(()=>{
|
|
50
|
+
const owned = registered.current;
|
|
51
|
+
return ()=>{
|
|
52
|
+
for (const id of owned.keys())store.unregister(id);
|
|
53
|
+
owned.clear();
|
|
54
|
+
};
|
|
55
|
+
}, [
|
|
56
|
+
store
|
|
57
|
+
]);
|
|
58
|
+
useEffect(()=>{
|
|
59
|
+
const owned = registered.current;
|
|
60
|
+
for (const id of owned.keys())if (!commands.some((command)=>command.id === id)) {
|
|
61
|
+
owned.delete(id);
|
|
62
|
+
store.unregister(id);
|
|
63
|
+
}
|
|
64
|
+
for (const command of commands){
|
|
65
|
+
const previous = owned.get(command.id);
|
|
66
|
+
owned.set(command.id, command);
|
|
67
|
+
syncCommand(store, command, previous, ()=>current.current.find((item)=>item.id === command.id));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const emptyRegistrations = [];
|
|
72
|
+
const getServerRegistrations = ()=>emptyRegistrations;
|
|
73
|
+
function useHotkeyRegistrations({ store }) {
|
|
74
|
+
const subscription = useMemo(()=>{
|
|
75
|
+
let snapshot = [];
|
|
76
|
+
const getSnapshot = ()=>{
|
|
77
|
+
const commands = [
|
|
78
|
+
...store.getState().commands.values()
|
|
79
|
+
];
|
|
80
|
+
if (commands.length !== snapshot.length || commands.some((command, index)=>{
|
|
81
|
+
const previous = snapshot[index];
|
|
82
|
+
return !previous || command._registrationOrder !== previous._registrationOrder || command.enabled !== previous.enabled || command.options !== previous.options || command._parsed !== previous._parsed;
|
|
83
|
+
})) snapshot = commands.map((command)=>({
|
|
84
|
+
...command
|
|
85
|
+
}));
|
|
86
|
+
return snapshot;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
getSnapshot,
|
|
90
|
+
subscribe: (notify)=>store.subscribe(getSnapshot, notify)
|
|
91
|
+
};
|
|
92
|
+
}, [
|
|
93
|
+
store
|
|
94
|
+
]);
|
|
95
|
+
return useSyncExternalStore(subscription.subscribe, subscription.getSnapshot, getServerRegistrations);
|
|
96
|
+
}
|
|
97
|
+
const subscribeToHydration = ()=>()=>{};
|
|
98
|
+
const getClientHydration = ()=>true;
|
|
99
|
+
const getServerHydration = ()=>false;
|
|
100
|
+
function useFormatHotkey() {
|
|
101
|
+
const hydrated = useSyncExternalStore(subscribeToHydration, getClientHydration, getServerHydration);
|
|
102
|
+
const defaultPlatform = hydrated ? "auto" : "windows";
|
|
103
|
+
return (hotkey, options)=>formatHotkey(hotkey, {
|
|
104
|
+
...options,
|
|
105
|
+
platform: options?.platform && "auto" !== options.platform ? options.platform : defaultPlatform
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export { createHotkeyStore, useFormatHotkey, useHotkey, useHotkeyRegistrations, useHotkeys };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const CommandContext = createContext(null);
|
|
3
|
+
const CommandItemContext = createContext(null);
|
|
4
|
+
function useCommandContext() {
|
|
5
|
+
const api = useContext(CommandContext);
|
|
6
|
+
if (!api) throw new Error("Command components must be used within Command.Root");
|
|
7
|
+
return api;
|
|
8
|
+
}
|
|
9
|
+
function useCommandItemContext() {
|
|
10
|
+
const item = useContext(CommandItemContext);
|
|
11
|
+
if (!item) throw new Error("Command.ItemText must be used within Command.Item");
|
|
12
|
+
return item;
|
|
13
|
+
}
|
|
14
|
+
export { CommandContext, CommandItemContext, useCommandContext, useCommandItemContext };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createFilter } from "@zag-js/i18n-utils";
|
|
3
|
+
import { mergeProps, normalizeProps, useMachine } from "@zag-js/react";
|
|
4
|
+
import { useEffect, useId, useMemo, useState } from "react";
|
|
5
|
+
import { Input } from "../atoms/input.js";
|
|
6
|
+
import { Label } from "../atoms/label.js";
|
|
7
|
+
import { CommandContext, CommandItemContext, useCommandContext, useCommandItemContext } from "../internal/molecules/command.context.js";
|
|
8
|
+
import { tv } from "../utils.js";
|
|
9
|
+
import * as __rspack_external__zag_js_combobox_6371d768 from "@zag-js/combobox";
|
|
10
|
+
const commandVariants = tv({
|
|
11
|
+
slots: {
|
|
12
|
+
root: "command-border-width flex w-full flex-col gap-command-gap rounded-command border-command-border bg-command-bg p-command text-command-fg text-command-md",
|
|
13
|
+
control: "relative flex items-center",
|
|
14
|
+
list: "max-h-command-list-max overflow-y-auto overscroll-contain p-command-list",
|
|
15
|
+
item: "flex w-full cursor-pointer select-none items-center gap-command-item-gap rounded-command-item px-command-item-x py-command-item-y data-disabled:cursor-not-allowed data-highlighted:bg-command-item-bg-highlighted data-disabled:text-command-item-fg-disabled",
|
|
16
|
+
itemText: "min-w-0 flex-1",
|
|
17
|
+
itemGroup: "",
|
|
18
|
+
itemGroupLabel: "px-command-item-x py-command-item-y font-command-group-label text-command-group-label-fg",
|
|
19
|
+
empty: "text-center text-command-empty-fg data-empty:p-command-empty"
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
function Command({ items, children, id, className, ref, disabled, loopFocus = true, onSelect, locale, inputValue, defaultInputValue = "", onInputValueChange, onEscape }) {
|
|
23
|
+
const generatedId = useId();
|
|
24
|
+
const [uncontrolledQuery, setUncontrolledQuery] = useState(defaultInputValue);
|
|
25
|
+
const query = inputValue ?? uncontrolledQuery;
|
|
26
|
+
const collection = useMemo(()=>{
|
|
27
|
+
const filter = createFilter({
|
|
28
|
+
sensitivity: "base",
|
|
29
|
+
locale
|
|
30
|
+
});
|
|
31
|
+
return __rspack_external__zag_js_combobox_6371d768.collection({
|
|
32
|
+
items: items.filter((item)=>filter.contains(item.label, query) || item.keywords?.some((keyword)=>filter.contains(keyword, query))),
|
|
33
|
+
itemToValue: (item)=>item.value,
|
|
34
|
+
itemToString: (item)=>item.label,
|
|
35
|
+
isItemDisabled: (item)=>!!item.disabled
|
|
36
|
+
});
|
|
37
|
+
}, [
|
|
38
|
+
items,
|
|
39
|
+
query,
|
|
40
|
+
locale
|
|
41
|
+
]);
|
|
42
|
+
const service = useMachine(__rspack_external__zag_js_combobox_6371d768.machine, {
|
|
43
|
+
id: id ?? generatedId,
|
|
44
|
+
collection,
|
|
45
|
+
disabled,
|
|
46
|
+
loopFocus,
|
|
47
|
+
onSelect,
|
|
48
|
+
inputValue: query,
|
|
49
|
+
onInputValueChange (details) {
|
|
50
|
+
if (void 0 === inputValue) setUncontrolledQuery(details.inputValue);
|
|
51
|
+
onInputValueChange?.(details);
|
|
52
|
+
},
|
|
53
|
+
onOpenChange (details) {
|
|
54
|
+
if (!details.open && "escape-key" === details.reason) onEscape?.();
|
|
55
|
+
},
|
|
56
|
+
open: true,
|
|
57
|
+
disableLayer: true,
|
|
58
|
+
inputBehavior: "autohighlight",
|
|
59
|
+
selectionBehavior: "preserve",
|
|
60
|
+
closeOnSelect: false
|
|
61
|
+
});
|
|
62
|
+
const api = __rspack_external__zag_js_combobox_6371d768.connect(service, normalizeProps);
|
|
63
|
+
useEffect(()=>{
|
|
64
|
+
if (api.collection.find(api.highlightedValue)?.disabled) api.clearHighlightValue();
|
|
65
|
+
}, [
|
|
66
|
+
api
|
|
67
|
+
]);
|
|
68
|
+
return /*#__PURE__*/ jsx(CommandContext.Provider, {
|
|
69
|
+
value: api,
|
|
70
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
71
|
+
...api.getRootProps(),
|
|
72
|
+
className: commandVariants().root({
|
|
73
|
+
className
|
|
74
|
+
}),
|
|
75
|
+
ref: ref,
|
|
76
|
+
children: children
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
Command.Root = Command;
|
|
81
|
+
Command.Label = function({ children, ...props }) {
|
|
82
|
+
const api = useCommandContext();
|
|
83
|
+
return /*#__PURE__*/ jsx(Label, {
|
|
84
|
+
...mergeProps(props, api.getLabelProps()),
|
|
85
|
+
children: children
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
Command.Control = function({ className, ...props }) {
|
|
89
|
+
const api = useCommandContext();
|
|
90
|
+
return /*#__PURE__*/ jsx("div", {
|
|
91
|
+
...mergeProps(props, api.getControlProps()),
|
|
92
|
+
className: commandVariants().control({
|
|
93
|
+
className
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
Command.Input = function({ size, ...props }) {
|
|
98
|
+
const api = useCommandContext();
|
|
99
|
+
return /*#__PURE__*/ jsx(Input, {
|
|
100
|
+
...mergeProps(props, api.getInputProps()),
|
|
101
|
+
size: size
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
Command.List = function({ className, ...props }) {
|
|
105
|
+
const api = useCommandContext();
|
|
106
|
+
return /*#__PURE__*/ jsx("div", {
|
|
107
|
+
...mergeProps(props, api.getContentProps()),
|
|
108
|
+
className: commandVariants().list({
|
|
109
|
+
className
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
Command.ItemGroup = function({ id, className, ...props }) {
|
|
114
|
+
const api = useCommandContext();
|
|
115
|
+
return /*#__PURE__*/ jsx("div", {
|
|
116
|
+
...mergeProps(props, api.getItemGroupProps({
|
|
117
|
+
id
|
|
118
|
+
})),
|
|
119
|
+
className: commandVariants().itemGroup({
|
|
120
|
+
className
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
Command.ItemGroupLabel = function({ htmlFor, className, ...props }) {
|
|
125
|
+
const api = useCommandContext();
|
|
126
|
+
return /*#__PURE__*/ jsx("div", {
|
|
127
|
+
...mergeProps(props, api.getItemGroupLabelProps({
|
|
128
|
+
htmlFor
|
|
129
|
+
})),
|
|
130
|
+
className: commandVariants().itemGroupLabel({
|
|
131
|
+
className
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
Command.Item = function({ item, className, ...props }) {
|
|
136
|
+
const api = useCommandContext();
|
|
137
|
+
return /*#__PURE__*/ jsx(CommandItemContext.Provider, {
|
|
138
|
+
value: item,
|
|
139
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
140
|
+
...mergeProps(props, api.getItemProps({
|
|
141
|
+
item
|
|
142
|
+
})),
|
|
143
|
+
className: commandVariants().item({
|
|
144
|
+
className
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
Command.ItemText = function({ className, children, ...props }) {
|
|
150
|
+
const api = useCommandContext();
|
|
151
|
+
const item = useCommandItemContext();
|
|
152
|
+
return /*#__PURE__*/ jsx("span", {
|
|
153
|
+
...mergeProps(props, api.getItemTextProps({
|
|
154
|
+
item
|
|
155
|
+
})),
|
|
156
|
+
className: commandVariants().itemText({
|
|
157
|
+
className
|
|
158
|
+
}),
|
|
159
|
+
children: children ?? item.label
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
Command.Context = function({ children }) {
|
|
163
|
+
return children(useCommandContext());
|
|
164
|
+
};
|
|
165
|
+
Command.Empty = function({ children, className, ...props }) {
|
|
166
|
+
const api = useCommandContext();
|
|
167
|
+
const empty = 0 === api.collection.size;
|
|
168
|
+
return /*#__PURE__*/ jsx("output", {
|
|
169
|
+
...props,
|
|
170
|
+
className: commandVariants().empty({
|
|
171
|
+
className
|
|
172
|
+
}),
|
|
173
|
+
"data-empty": empty || void 0,
|
|
174
|
+
children: empty ? children : null
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
export { Command };
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Portal, mergeProps, normalizeProps, useMachine } from "@zag-js/react";
|
|
3
|
+
import { connect, machine } from "@zag-js/tour";
|
|
4
|
+
import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef } from "react";
|
|
5
|
+
import { ActionIcon } from "../atoms/action-icon.js";
|
|
6
|
+
import { Button } from "../atoms/button.js";
|
|
7
|
+
import { tv } from "../utils.js";
|
|
8
|
+
const tourStyles = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
backdrop: "tour-layer bg-tour-backdrop-bg",
|
|
11
|
+
spotlight: "tour-layer tour-spotlight-border",
|
|
12
|
+
positioner: "tour-layer tour-positioner",
|
|
13
|
+
content: "tour-layer tour-content tour-border relative flex flex-col gap-tour-content rounded-tour bg-tour-bg p-tour text-tour-fg shadow-tour outline-none",
|
|
14
|
+
arrow: "tour-arrow",
|
|
15
|
+
arrowTip: "bg-tour-arrow-bg",
|
|
16
|
+
title: "pe-tour-title font-tour-title text-tour-title-size",
|
|
17
|
+
description: "text-tour-body-size text-tour-description-fg",
|
|
18
|
+
progressText: "text-tour-progress-fg text-tour-progress-size",
|
|
19
|
+
actions: "flex flex-wrap items-center justify-end gap-tour-actions pt-tour-actions",
|
|
20
|
+
closeTrigger: "absolute end-tour-close top-tour-close"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const TourContext = /*#__PURE__*/ createContext(null);
|
|
24
|
+
const useIsomorphicLayoutEffect = "u" < typeof window ? useEffect : useLayoutEffect;
|
|
25
|
+
function useTourContext() {
|
|
26
|
+
const api = useContext(TourContext);
|
|
27
|
+
if (!api) throw new Error("Tour components must be used within Tour.Root");
|
|
28
|
+
return api;
|
|
29
|
+
}
|
|
30
|
+
function Tour({ children, getRootNode, id, onStatusChange, steps, ...props }) {
|
|
31
|
+
const generatedId = useId();
|
|
32
|
+
const previousFocus = useRef(null);
|
|
33
|
+
const effectCleanup = useRef(void 0);
|
|
34
|
+
const service = useMachine(machine, {
|
|
35
|
+
...props,
|
|
36
|
+
preventInteraction: false,
|
|
37
|
+
getRootNode,
|
|
38
|
+
id: id ?? generatedId,
|
|
39
|
+
steps: steps.map(prepareStep),
|
|
40
|
+
onStatusChange (details) {
|
|
41
|
+
if ("not-found" === details.status) effectCleanup.current?.();
|
|
42
|
+
if ("started" === details.status) {
|
|
43
|
+
const rootNode = getRootNode?.() ?? document;
|
|
44
|
+
const activeElement = "activeElement" in rootNode ? rootNode.activeElement : rootNode.ownerDocument?.activeElement;
|
|
45
|
+
previousFocus.current = activeElement instanceof HTMLElement ? activeElement : null;
|
|
46
|
+
} else if ("idle" !== details.status) {
|
|
47
|
+
const target = previousFocus.current;
|
|
48
|
+
previousFocus.current = null;
|
|
49
|
+
requestAnimationFrame(()=>{
|
|
50
|
+
if (target?.isConnected) target.focus({
|
|
51
|
+
preventScroll: true
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
onStatusChange?.(details);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
function prepareStep(step) {
|
|
59
|
+
return {
|
|
60
|
+
...step,
|
|
61
|
+
effect: step.effect && prepareEffect(step.effect)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function prepareEffect(effect) {
|
|
65
|
+
return (args)=>{
|
|
66
|
+
const cleanup = effect(args);
|
|
67
|
+
let cleaned = false;
|
|
68
|
+
const cleanupOnce = ()=>{
|
|
69
|
+
if (cleaned) return;
|
|
70
|
+
cleaned = true;
|
|
71
|
+
if (effectCleanup.current === cleanupOnce) effectCleanup.current = void 0;
|
|
72
|
+
cleanup?.();
|
|
73
|
+
};
|
|
74
|
+
effectCleanup.current = cleanupOnce;
|
|
75
|
+
return cleanupOnce;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const zag = connect(service, normalizeProps);
|
|
79
|
+
const handleKeyDown = (event)=>{
|
|
80
|
+
if (event.defaultPrevented || false === props.keyboardNavigation) return;
|
|
81
|
+
if (event.target instanceof Element && event.target.closest("input, textarea, select, [contenteditable='true']")) return;
|
|
82
|
+
const [nextKey, prevKey] = "rtl" === props.dir ? [
|
|
83
|
+
"ArrowLeft",
|
|
84
|
+
"ArrowRight"
|
|
85
|
+
] : [
|
|
86
|
+
"ArrowRight",
|
|
87
|
+
"ArrowLeft"
|
|
88
|
+
];
|
|
89
|
+
if (event.key === nextKey && zag.hasNextStep) {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
zag.next();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (event.key === prevKey && zag.hasPrevStep) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
zag.prev();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const api = {
|
|
100
|
+
...zag,
|
|
101
|
+
addStep: (step)=>zag.addStep(prepareStep(step)),
|
|
102
|
+
updateStep: (stepId, step)=>zag.updateStep(stepId, "effect" in step ? {
|
|
103
|
+
...step,
|
|
104
|
+
effect: step.effect && prepareEffect(step.effect)
|
|
105
|
+
} : step),
|
|
106
|
+
setSteps: (nextSteps)=>zag.setSteps(nextSteps.map(prepareStep)),
|
|
107
|
+
getContentProps () {
|
|
108
|
+
return {
|
|
109
|
+
...zag.getContentProps(),
|
|
110
|
+
onKeyDown: handleKeyDown
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const activeTarget = api.step?.target?.();
|
|
115
|
+
useIsomorphicLayoutEffect(()=>{
|
|
116
|
+
if (!(api.open && props.preventInteraction && activeTarget) || activeTarget.inert) return;
|
|
117
|
+
activeTarget.inert = true;
|
|
118
|
+
let changedExternally = false;
|
|
119
|
+
const observer = new MutationObserver(()=>{
|
|
120
|
+
changedExternally = true;
|
|
121
|
+
});
|
|
122
|
+
observer.observe(activeTarget, {
|
|
123
|
+
attributes: true,
|
|
124
|
+
attributeFilter: [
|
|
125
|
+
"inert"
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
return ()=>{
|
|
129
|
+
const hasExternalWrite = changedExternally || observer.takeRecords().length > 0;
|
|
130
|
+
observer.disconnect();
|
|
131
|
+
if (!hasExternalWrite) activeTarget.inert = false;
|
|
132
|
+
};
|
|
133
|
+
}, [
|
|
134
|
+
api.open,
|
|
135
|
+
props.preventInteraction,
|
|
136
|
+
activeTarget
|
|
137
|
+
]);
|
|
138
|
+
useEffect(()=>()=>{
|
|
139
|
+
const target = previousFocus.current;
|
|
140
|
+
previousFocus.current = null;
|
|
141
|
+
queueMicrotask(()=>{
|
|
142
|
+
if (target?.isConnected) target.focus({
|
|
143
|
+
preventScroll: true
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}, []);
|
|
147
|
+
return /*#__PURE__*/ jsx(TourContext.Provider, {
|
|
148
|
+
value: {
|
|
149
|
+
...api,
|
|
150
|
+
isRunning: !service.state.matches("tourInactive")
|
|
151
|
+
},
|
|
152
|
+
children: children
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
Tour.Context = function({ children }) {
|
|
156
|
+
return children(useTourContext());
|
|
157
|
+
};
|
|
158
|
+
Tour.Trigger = function({ disabled, onClick, stepId, type = "button", ...props }) {
|
|
159
|
+
const api = useTourContext();
|
|
160
|
+
return /*#__PURE__*/ jsx(Button, {
|
|
161
|
+
...props,
|
|
162
|
+
disabled: disabled || api.isRunning || 0 === api.totalSteps,
|
|
163
|
+
onClick: (event)=>{
|
|
164
|
+
onClick?.(event);
|
|
165
|
+
if (!event.defaultPrevented) api.start(stepId);
|
|
166
|
+
},
|
|
167
|
+
type: type
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
Tour.Portal = Portal;
|
|
171
|
+
Tour.Backdrop = function({ className, ref, ...props }) {
|
|
172
|
+
const api = useTourContext();
|
|
173
|
+
if (!(api.open && api.step?.backdrop)) return null;
|
|
174
|
+
return /*#__PURE__*/ jsx("div", {
|
|
175
|
+
...mergeProps(props, api.getBackdropProps()),
|
|
176
|
+
className: tourStyles().backdrop({
|
|
177
|
+
className
|
|
178
|
+
}),
|
|
179
|
+
ref: ref
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
Tour.Spotlight = function({ className, ref, ...props }) {
|
|
183
|
+
const api = useTourContext();
|
|
184
|
+
if (!api.open || api.step?.type !== "tooltip") return null;
|
|
185
|
+
return /*#__PURE__*/ jsx("div", {
|
|
186
|
+
...mergeProps(props, api.getSpotlightProps()),
|
|
187
|
+
className: tourStyles().spotlight({
|
|
188
|
+
className
|
|
189
|
+
}),
|
|
190
|
+
ref: ref
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
Tour.Positioner = function({ className, ref, ...props }) {
|
|
194
|
+
const api = useTourContext();
|
|
195
|
+
if (!api.open) return null;
|
|
196
|
+
return /*#__PURE__*/ jsx("div", {
|
|
197
|
+
...mergeProps(props, api.getPositionerProps()),
|
|
198
|
+
className: tourStyles().positioner({
|
|
199
|
+
className
|
|
200
|
+
}),
|
|
201
|
+
ref: ref
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
Tour.Content = function({ className, ref, ...props }) {
|
|
205
|
+
const api = useTourContext();
|
|
206
|
+
return /*#__PURE__*/ jsx("div", {
|
|
207
|
+
...mergeProps(props, api.getContentProps()),
|
|
208
|
+
className: tourStyles().content({
|
|
209
|
+
className
|
|
210
|
+
}),
|
|
211
|
+
ref: ref
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
Tour.Arrow = function({ children, className, ref, ...props }) {
|
|
215
|
+
const api = useTourContext();
|
|
216
|
+
if (!api.step?.arrow || "tooltip" !== api.step.type) return null;
|
|
217
|
+
return /*#__PURE__*/ jsx("div", {
|
|
218
|
+
...mergeProps(props, api.getArrowProps()),
|
|
219
|
+
className: tourStyles().arrow({
|
|
220
|
+
className
|
|
221
|
+
}),
|
|
222
|
+
ref: ref,
|
|
223
|
+
children: children ?? /*#__PURE__*/ jsx(Tour.ArrowTip, {})
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
Tour.ArrowTip = function({ className, ref, ...props }) {
|
|
227
|
+
const api = useTourContext();
|
|
228
|
+
return /*#__PURE__*/ jsx("div", {
|
|
229
|
+
...mergeProps(props, api.getArrowTipProps()),
|
|
230
|
+
className: tourStyles().arrowTip({
|
|
231
|
+
className
|
|
232
|
+
}),
|
|
233
|
+
ref: ref
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
Tour.Title = function({ children, className, ref, ...props }) {
|
|
237
|
+
const api = useTourContext();
|
|
238
|
+
return /*#__PURE__*/ jsx("div", {
|
|
239
|
+
...mergeProps(props, api.getTitleProps()),
|
|
240
|
+
className: tourStyles().title({
|
|
241
|
+
className
|
|
242
|
+
}),
|
|
243
|
+
ref: ref,
|
|
244
|
+
children: children ?? api.step?.title
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
Tour.Description = function({ children, className, ref, ...props }) {
|
|
248
|
+
const api = useTourContext();
|
|
249
|
+
return /*#__PURE__*/ jsx("div", {
|
|
250
|
+
...mergeProps(props, api.getDescriptionProps()),
|
|
251
|
+
className: tourStyles().description({
|
|
252
|
+
className
|
|
253
|
+
}),
|
|
254
|
+
ref: ref,
|
|
255
|
+
children: children ?? api.step?.description
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
Tour.ProgressText = function({ children, className, ref, ...props }) {
|
|
259
|
+
const api = useTourContext();
|
|
260
|
+
return /*#__PURE__*/ jsx("span", {
|
|
261
|
+
...mergeProps(props, api.getProgressTextProps()),
|
|
262
|
+
className: tourStyles().progressText({
|
|
263
|
+
className
|
|
264
|
+
}),
|
|
265
|
+
ref: ref,
|
|
266
|
+
children: children ?? api.getProgressText()
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
Tour.Actions = function({ className, ref, ...props }) {
|
|
270
|
+
useTourContext();
|
|
271
|
+
return /*#__PURE__*/ jsx("div", {
|
|
272
|
+
...props,
|
|
273
|
+
className: tourStyles().actions({
|
|
274
|
+
className
|
|
275
|
+
}),
|
|
276
|
+
ref: ref
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
Tour.ActionTrigger = function({ action, children, disabled, onClick, type = "button", ...props }) {
|
|
280
|
+
const api = useTourContext();
|
|
281
|
+
const { onClick: onMachineClick, disabled: machineDisabled, "aria-label": machineLabel, ...machineProps } = api.getActionTriggerProps({
|
|
282
|
+
action: {
|
|
283
|
+
action,
|
|
284
|
+
label: ""
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return /*#__PURE__*/ jsx(Button, {
|
|
288
|
+
...mergeProps(props, machineProps),
|
|
289
|
+
"aria-label": props["aria-label"] ?? (null != children ? void 0 : machineLabel),
|
|
290
|
+
disabled: disabled || machineDisabled,
|
|
291
|
+
onClick: (event)=>{
|
|
292
|
+
onClick?.(event);
|
|
293
|
+
if (!event.defaultPrevented) onMachineClick?.(event);
|
|
294
|
+
},
|
|
295
|
+
type: type,
|
|
296
|
+
children: children
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
Tour.CloseTrigger = function({ className, icon = "token-icon-close", onClick, ref, ...props }) {
|
|
300
|
+
const api = useTourContext();
|
|
301
|
+
const { onClick: onMachineClick, "aria-label": machineLabel, ...machineProps } = api.getCloseTriggerProps();
|
|
302
|
+
return /*#__PURE__*/ jsx(ActionIcon, {
|
|
303
|
+
...mergeProps(props, machineProps),
|
|
304
|
+
"aria-label": props["aria-label"] ?? machineLabel,
|
|
305
|
+
className: tourStyles().closeTrigger({
|
|
306
|
+
className
|
|
307
|
+
}),
|
|
308
|
+
icon: icon,
|
|
309
|
+
onClick: (event)=>{
|
|
310
|
+
onClick?.(event);
|
|
311
|
+
if (!event.defaultPrevented) onMachineClick?.(event);
|
|
312
|
+
},
|
|
313
|
+
ref: ref
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
Tour.Root = Tour;
|
|
317
|
+
Tour.displayName = "Tour";
|
|
318
|
+
export { Tour };
|