@carlonicora/nextjs-jsonapi 3.8.2 → 3.10.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/{BlockNoteEditor-KRGDA5RX.js → BlockNoteEditor-4UAIVF3G.js} +19 -19
- package/dist/{BlockNoteEditor-KRGDA5RX.js.map → BlockNoteEditor-4UAIVF3G.js.map} +1 -1
- package/dist/BlockNoteEditor-K55A4FS2.css +30 -0
- package/dist/BlockNoteEditor-K55A4FS2.css.map +1 -0
- package/dist/{BlockNoteEditor-34AZITDR.mjs → BlockNoteEditor-PFCHUFTD.mjs} +4 -4
- package/dist/{BlockNoteViewer-6N2GQWTI.js → BlockNoteViewer-C2UUOBAF.js} +17 -3
- package/dist/BlockNoteViewer-C2UUOBAF.js.map +1 -0
- package/dist/{BlockNoteViewer-IBQRJ3WM.mjs → BlockNoteViewer-TYLVTIUD.mjs} +17 -3
- package/dist/BlockNoteViewer-TYLVTIUD.mjs.map +1 -0
- package/dist/billing/index.css +22 -21
- package/dist/billing/index.css.map +1 -1
- package/dist/billing/index.js +398 -398
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-DGAVM2LZ.js → chunk-46CQ3RRQ.js} +1315 -1069
- package/dist/chunk-46CQ3RRQ.js.map +1 -0
- package/dist/{chunk-UYLS3LJW.js → chunk-6J2SQZ3G.js} +11 -1
- package/dist/chunk-6J2SQZ3G.js.map +1 -0
- package/dist/{chunk-2D4MFVDV.mjs → chunk-IMSIVIIC.mjs} +2 -2
- package/dist/{chunk-HDZ2BL6D.mjs → chunk-M5QAHSCY.mjs} +11 -1
- package/dist/{chunk-HDZ2BL6D.mjs.map → chunk-M5QAHSCY.mjs.map} +1 -1
- package/dist/{chunk-TULJCLZA.js → chunk-V6CGCARD.js} +7 -7
- package/dist/{chunk-TULJCLZA.js.map → chunk-V6CGCARD.js.map} +1 -1
- package/dist/{chunk-GXSXD4OR.mjs → chunk-XZJJ4SZW.mjs} +2073 -1827
- package/dist/chunk-XZJJ4SZW.mjs.map +1 -0
- package/dist/client/index.css +22 -21
- package/dist/client/index.css.map +1 -1
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.css +22 -21
- package/dist/components/index.css.map +1 -1
- package/dist/components/index.d.mts +77 -15
- package/dist/components/index.d.ts +77 -15
- package/dist/components/index.js +8 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +7 -3
- package/dist/contexts/index.css +22 -21
- package/dist/contexts/index.css.map +1 -1
- package/dist/contexts/index.d.mts +17 -4
- package/dist/contexts/index.d.ts +17 -4
- package/dist/contexts/index.js +10 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +9 -3
- package/dist/core/index.d.mts +13 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/features/help/index.css +22 -21
- package/dist/features/help/index.css.map +1 -1
- package/dist/features/help/index.d.mts +6 -1
- package/dist/features/help/index.d.ts +6 -1
- package/dist/features/help/index.js +39 -38
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +4 -3
- package/dist/features/tokenusage/index.css +22 -21
- package/dist/features/tokenusage/index.css.map +1 -1
- package/dist/features/tokenusage/index.js +102 -102
- package/dist/features/tokenusage/index.js.map +1 -1
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/features/tokenusage/index.mjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/dist/{onboarding.interface-Djyl9qYu.d.mts → onboarding.interface-DVUOUC4y.d.mts} +4 -1
- package/dist/{onboarding.interface-Djyl9qYu.d.ts → onboarding.interface-DVUOUC4y.d.ts} +4 -1
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +2 -2
- package/src/components/containers/RoundPageContainer.tsx +38 -16
- package/src/components/containers/RoundPageContainerTitle.tsx +11 -3
- package/src/components/containers/TabsContainer.tsx +11 -0
- package/src/components/forms/CommonDeleter.tsx +14 -1
- package/src/components/forms/EditorSheet.tsx +66 -47
- package/src/components/grids/ContentListGrid.tsx +1 -1
- package/src/components/navigations/MobileNavigationBar.tsx +1 -0
- package/src/components/tables/ContentListTable.tsx +1 -1
- package/src/components/tables/ContentTableSearch.tsx +1 -0
- package/src/components/viewers/BlockNoteViewer.tsx +13 -2
- package/src/components/viewers/BlockNoteViewerContainer.tsx +4 -1
- package/src/contexts/ActionBarContext.tsx +23 -0
- package/src/contexts/index.ts +1 -0
- package/src/features/assistant/components/index.ts +2 -0
- package/src/features/assistant/components/parts/AssistantComposer.tsx +1 -1
- package/src/features/assistant/components/parts/__tests__/AssistantComposer.spec.tsx +2 -2
- package/src/features/assistant-action/components/ApprovalActionCard.tsx +325 -4
- package/src/features/assistant-action/components/__tests__/ApprovalActionCard.spec.tsx +268 -0
- package/src/features/assistant-action/data/AssistantAction.ts +12 -0
- package/src/features/assistant-action/data/AssistantActionInterface.ts +9 -0
- package/src/features/assistant-action/data/__tests__/AssistantAction.spec.ts +11 -0
- package/src/features/help/index.ts +1 -0
- package/src/features/onboarding/components/OnboardingCard.tsx +1 -2
- package/src/features/onboarding/contexts/OnboardingContext.spec.tsx +191 -0
- package/src/features/onboarding/contexts/OnboardingContext.tsx +176 -125
- package/src/features/onboarding/interfaces/onboarding.interface.ts +4 -0
- package/src/features/onboarding/styles/onboarding.css +25 -30
- package/src/features/tokenusage/components/TokenUsageReportFilterBar.tsx +1 -1
- package/src/shadcnui/ui/command.tsx +9 -0
- package/dist/BlockNoteEditor-YOAJRPWU.css +0 -29
- package/dist/BlockNoteEditor-YOAJRPWU.css.map +0 -1
- package/dist/BlockNoteViewer-6N2GQWTI.js.map +0 -1
- package/dist/BlockNoteViewer-IBQRJ3WM.mjs.map +0 -1
- package/dist/chunk-DGAVM2LZ.js.map +0 -1
- package/dist/chunk-GXSXD4OR.mjs.map +0 -1
- package/dist/chunk-UYLS3LJW.js.map +0 -1
- /package/dist/{BlockNoteEditor-34AZITDR.mjs.map → BlockNoteEditor-PFCHUFTD.mjs.map} +0 -0
- /package/dist/{chunk-2D4MFVDV.mjs.map → chunk-IMSIVIIC.mjs.map} +0 -0
|
@@ -1,20 +1,61 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { flushSync } from "react-dom";
|
|
4
5
|
import { createRoot, Root } from "react-dom/client";
|
|
5
|
-
import
|
|
6
|
-
import "
|
|
6
|
+
import { driver, type Driver, type DriveStep, type PopoverDOM } from "driver.js";
|
|
7
|
+
import "driver.js/dist/driver.css";
|
|
7
8
|
import { OnboardingCard } from "../components/OnboardingCard";
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_ONBOARDING_LABELS,
|
|
10
11
|
OnboardingContextValue,
|
|
11
12
|
OnboardingProviderProps,
|
|
12
13
|
OnboardingStepConfig,
|
|
14
|
+
OnboardingStepSide,
|
|
15
|
+
OnboardingTourEndReason,
|
|
13
16
|
} from "../interfaces";
|
|
14
17
|
import "../styles/onboarding.css";
|
|
15
18
|
|
|
16
19
|
const OnboardingContext = createContext<OnboardingContextValue | null>(null);
|
|
17
20
|
|
|
21
|
+
type DriverSide = "top" | "right" | "bottom" | "left";
|
|
22
|
+
type DriverAlign = "start" | "center" | "end";
|
|
23
|
+
|
|
24
|
+
/** "bottom-start" → { side: "bottom", align: "start" }; bare side → align "center". */
|
|
25
|
+
export function mapOnboardingSide(side?: OnboardingStepSide): { side: DriverSide; align: DriverAlign } {
|
|
26
|
+
if (!side) return { side: "bottom", align: "center" };
|
|
27
|
+
const [s, a] = side.split("-") as [DriverSide, DriverAlign | undefined];
|
|
28
|
+
return { side: s, align: a ?? "center" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolves a step's target, treating a hidden element as a missing one.
|
|
33
|
+
*
|
|
34
|
+
* driver.js only honours `skipMissingElement` when the element resolves to
|
|
35
|
+
* nothing. A selector string cannot express "present in the DOM but not
|
|
36
|
+
* displayed", so targets that exist yet are `display: none` (the section rail
|
|
37
|
+
* below `md`, for instance) would otherwise highlight an empty box. Returning
|
|
38
|
+
* `undefined` from the resolver takes the exact same path as an absent
|
|
39
|
+
* selector, so the step is skipped.
|
|
40
|
+
*
|
|
41
|
+
* The check is `getClientRects()`, not the element's own computed style: an
|
|
42
|
+
* element inherits invisibility from a `display: none` ancestor, whose own
|
|
43
|
+
* style says nothing. An element with no client rects generates no boxes, so
|
|
44
|
+
* it is not displayed, whatever its own `display` value is.
|
|
45
|
+
*
|
|
46
|
+
* The cast exists because driver.js types the resolver as `() => Element`,
|
|
47
|
+
* while its runtime (`typeof e === "function" ? e() : ...`) treats any falsy
|
|
48
|
+
* result as missing.
|
|
49
|
+
*/
|
|
50
|
+
function resolveVisibleElement(selector: string): DriveStep["element"] {
|
|
51
|
+
return (() => {
|
|
52
|
+
const element = document.querySelector(selector);
|
|
53
|
+
if (!element) return undefined;
|
|
54
|
+
if (element.getClientRects().length === 0) return undefined;
|
|
55
|
+
return element;
|
|
56
|
+
}) as DriveStep["element"];
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
export function OnboardingProvider({
|
|
19
60
|
children,
|
|
20
61
|
tours = [],
|
|
@@ -22,72 +63,78 @@ export function OnboardingProvider({
|
|
|
22
63
|
labels = DEFAULT_ONBOARDING_LABELS,
|
|
23
64
|
renderCard,
|
|
24
65
|
zIndex: _zIndex = 9999,
|
|
66
|
+
onTourEnd,
|
|
25
67
|
}: OnboardingProviderProps) {
|
|
26
68
|
const [isTourActive, setIsTourActive] = useState(false);
|
|
27
69
|
const [activeTourId, setActiveTourId] = useState<string | null>(null);
|
|
28
70
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
29
71
|
const [totalSteps, setTotalSteps] = useState(0);
|
|
30
72
|
|
|
31
|
-
const
|
|
32
|
-
const rootsRef = useRef<Map<
|
|
73
|
+
const driverRef = useRef<Driver | null>(null);
|
|
74
|
+
const rootsRef = useRef<Map<number, Root>>(new Map());
|
|
75
|
+
const endReasonRef = useRef<OnboardingTourEndReason>("dismissed");
|
|
76
|
+
const activeTourIdRef = useRef<string | null>(null);
|
|
77
|
+
|
|
78
|
+
// Unmount on the next tick: a root cannot unmount synchronously from inside
|
|
79
|
+
// an event handler it rendered (React warns and skips the unmount).
|
|
80
|
+
const unmountRoot = (root: Root) => setTimeout(() => root.unmount(), 0);
|
|
33
81
|
|
|
34
82
|
const cleanupRoots = useCallback(() => {
|
|
35
|
-
rootsRef.current.forEach((root) =>
|
|
36
|
-
try {
|
|
37
|
-
root.unmount();
|
|
38
|
-
} catch (_e) {
|
|
39
|
-
// Root may already be unmounted
|
|
40
|
-
}
|
|
41
|
-
});
|
|
83
|
+
rootsRef.current.forEach((root) => unmountRoot(root));
|
|
42
84
|
rootsRef.current.clear();
|
|
43
85
|
}, []);
|
|
44
86
|
|
|
45
|
-
const
|
|
46
|
-
if (tourRef.current) {
|
|
47
|
-
tourRef.current.cancel();
|
|
48
|
-
tourRef.current = null;
|
|
49
|
-
}
|
|
50
|
-
cleanupRoots();
|
|
87
|
+
const resetState = useCallback(() => {
|
|
51
88
|
setIsTourActive(false);
|
|
52
89
|
setActiveTourId(null);
|
|
53
90
|
setCurrentStepIndex(0);
|
|
54
91
|
setTotalSteps(0);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const nextStep = useCallback(() => {
|
|
58
|
-
if (tourRef.current) {
|
|
59
|
-
tourRef.current.next();
|
|
60
|
-
}
|
|
92
|
+
driverRef.current = null;
|
|
93
|
+
activeTourIdRef.current = null;
|
|
61
94
|
}, []);
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Ends the tour on our side: unmount the cards, reset the state, report the reason.
|
|
98
|
+
*
|
|
99
|
+
* It cannot be left to driver.js's `onDestroyed`: driver only fires that hook when
|
|
100
|
+
* it holds both an active element and an active step, and both are set by a step's
|
|
101
|
+
* 400ms transition callback. A Next/Finish click that lands before the animation
|
|
102
|
+
* settles therefore destroys the DOM without ever calling the hook, and the provider
|
|
103
|
+
* would stay `isTourActive` forever. Every path that calls `destroy()` calls this
|
|
104
|
+
* straight after; `resetState()` nulls `driverRef.current`, so a later `onDestroyed`
|
|
105
|
+
* is a no-op.
|
|
106
|
+
*/
|
|
107
|
+
const teardown = useCallback(
|
|
108
|
+
(reason: OnboardingTourEndReason) => {
|
|
109
|
+
if (!driverRef.current) return;
|
|
110
|
+
const id = activeTourIdRef.current;
|
|
111
|
+
cleanupRoots();
|
|
112
|
+
resetState();
|
|
113
|
+
if (id) onTourEnd?.(id, reason);
|
|
114
|
+
},
|
|
115
|
+
[cleanupRoots, resetState, onTourEnd],
|
|
116
|
+
);
|
|
68
117
|
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
118
|
+
const closeTour = useCallback(() => {
|
|
119
|
+
if (driverRef.current) {
|
|
120
|
+
driverRef.current.destroy();
|
|
121
|
+
teardown("dismissed");
|
|
122
|
+
return;
|
|
72
123
|
}
|
|
73
|
-
|
|
124
|
+
cleanupRoots();
|
|
125
|
+
resetState();
|
|
126
|
+
}, [cleanupRoots, resetState, teardown]);
|
|
127
|
+
|
|
128
|
+
const nextStep = useCallback(() => driverRef.current?.moveNext(), []);
|
|
129
|
+
const previousStep = useCallback(() => driverRef.current?.movePrevious(), []);
|
|
130
|
+
const goToStep = useCallback((index: number) => driverRef.current?.moveTo(index), []);
|
|
74
131
|
|
|
75
132
|
const startTour = useCallback(
|
|
76
133
|
(tourId: string, steps?: OnboardingStepConfig[]) => {
|
|
77
134
|
if (typeof window === "undefined") return;
|
|
135
|
+
if (driverRef.current) closeTour();
|
|
78
136
|
|
|
79
|
-
|
|
80
|
-
if (tourRef.current) {
|
|
81
|
-
closeTour();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Get steps from provided steps or find tour by ID
|
|
85
|
-
let tourSteps = steps;
|
|
86
|
-
if (!tourSteps) {
|
|
87
|
-
const tour = tours.find((t) => t.id === tourId);
|
|
88
|
-
tourSteps = tour?.steps;
|
|
89
|
-
}
|
|
90
|
-
|
|
137
|
+
const tourSteps = steps ?? tours.find((t) => t.id === tourId)?.steps;
|
|
91
138
|
if (!tourSteps || tourSteps.length === 0) {
|
|
92
139
|
console.warn(`No steps found for tour: ${tourId}`);
|
|
93
140
|
return;
|
|
@@ -95,96 +142,102 @@ export function OnboardingProvider({
|
|
|
95
142
|
|
|
96
143
|
setTotalSteps(tourSteps.length);
|
|
97
144
|
setActiveTourId(tourId);
|
|
145
|
+
activeTourIdRef.current = tourId;
|
|
146
|
+
endReasonRef.current = "dismissed";
|
|
98
147
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
},
|
|
106
|
-
});
|
|
148
|
+
const move = (direction: "next" | "previous", from: number) => {
|
|
149
|
+
const target = tourSteps[direction === "next" ? from + 1 : from - 1];
|
|
150
|
+
const go = () => (direction === "next" ? driverRef.current?.moveNext() : driverRef.current?.movePrevious());
|
|
151
|
+
if (target?.showDelay) setTimeout(go, target.showDelay);
|
|
152
|
+
else go();
|
|
153
|
+
};
|
|
107
154
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
setActiveTourId(null);
|
|
114
|
-
setCurrentStepIndex(0);
|
|
115
|
-
setTotalSteps(0);
|
|
116
|
-
tourRef.current = null;
|
|
117
|
-
});
|
|
118
|
-
tour.on("complete", () => {
|
|
119
|
-
cleanupRoots();
|
|
120
|
-
setIsTourActive(false);
|
|
121
|
-
setActiveTourId(null);
|
|
122
|
-
setCurrentStepIndex(0);
|
|
123
|
-
setTotalSteps(0);
|
|
124
|
-
tourRef.current = null;
|
|
125
|
-
});
|
|
155
|
+
const end = (reason: OnboardingTourEndReason) => {
|
|
156
|
+
endReasonRef.current = reason;
|
|
157
|
+
driverRef.current?.destroy();
|
|
158
|
+
teardown(reason);
|
|
159
|
+
};
|
|
126
160
|
|
|
127
|
-
tourSteps.
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
stepConfig.onShow?.();
|
|
168
|
-
},
|
|
169
|
-
hide: () => {
|
|
170
|
-
stepConfig.onHide?.();
|
|
161
|
+
const driveSteps: DriveStep[] = tourSteps.map((stepConfig, index) => {
|
|
162
|
+
const { side, align } = mapOnboardingSide(stepConfig.side);
|
|
163
|
+
return {
|
|
164
|
+
element: stepConfig.selector ? resolveVisibleElement(stepConfig.selector) : undefined,
|
|
165
|
+
disableActiveInteraction: !(stepConfig.canClickTarget ?? false),
|
|
166
|
+
popover: {
|
|
167
|
+
side,
|
|
168
|
+
align,
|
|
169
|
+
showButtons: [],
|
|
170
|
+
popoverClass: ["onboarding-popover", stepConfig.className].filter(Boolean).join(" "),
|
|
171
|
+
onPopoverRender: (popover: PopoverDOM) => {
|
|
172
|
+
popover.wrapper.querySelectorAll("[data-onboarding-root]").forEach((n) => n.remove());
|
|
173
|
+
const container = document.createElement("div");
|
|
174
|
+
container.setAttribute("data-onboarding-root", "");
|
|
175
|
+
const root = createRoot(container);
|
|
176
|
+
/* driver.js re-renders the popover on refresh(), so the same step can
|
|
177
|
+
render twice; drop the root the previous render left behind. */
|
|
178
|
+
const prev = rootsRef.current.get(index);
|
|
179
|
+
if (prev) unmountRoot(prev);
|
|
180
|
+
rootsRef.current.set(index, root);
|
|
181
|
+
const cardProps = {
|
|
182
|
+
step: stepConfig,
|
|
183
|
+
currentIndex: index,
|
|
184
|
+
totalSteps: tourSteps.length,
|
|
185
|
+
labels,
|
|
186
|
+
onNext: () => move("next", index),
|
|
187
|
+
onPrevious: () => move("previous", index),
|
|
188
|
+
onClose: () => end(index === tourSteps.length - 1 ? "finished" : "skipped"),
|
|
189
|
+
onSkip: () => end("skipped"),
|
|
190
|
+
isFirst: index === 0,
|
|
191
|
+
isLast: index === tourSteps.length - 1,
|
|
192
|
+
};
|
|
193
|
+
/* driver.js positions the popover right after this hook returns (renderPopover
|
|
194
|
+
calls onRender, then repositionPopover), so the card has to be in the DOM at
|
|
195
|
+
its real size by then — an async render would be measured as an empty box and
|
|
196
|
+
the card would overflow the viewport near a page edge. */
|
|
197
|
+
flushSync(() => {
|
|
198
|
+
root.render(renderCard ? renderCard(cardProps) : <OnboardingCard {...cardProps} />);
|
|
199
|
+
});
|
|
200
|
+
popover.wrapper.appendChild(container);
|
|
171
201
|
},
|
|
172
202
|
},
|
|
173
|
-
|
|
203
|
+
onHighlightStarted: () => {
|
|
204
|
+
setIsTourActive(true);
|
|
205
|
+
setCurrentStepIndex(index);
|
|
206
|
+
stepConfig.onShow?.();
|
|
207
|
+
},
|
|
208
|
+
onDeselected: () => {
|
|
209
|
+
stepConfig.onHide?.();
|
|
210
|
+
const root = rootsRef.current.get(index);
|
|
211
|
+
if (root) {
|
|
212
|
+
rootsRef.current.delete(index);
|
|
213
|
+
unmountRoot(root);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const instance = driver({
|
|
220
|
+
steps: driveSteps,
|
|
221
|
+
animate: true,
|
|
222
|
+
allowClose: true,
|
|
223
|
+
overlayOpacity: 0.75,
|
|
224
|
+
stagePadding: 8,
|
|
225
|
+
stageRadius: 8,
|
|
226
|
+
smoothScroll: true,
|
|
227
|
+
skipMissingElement: true,
|
|
228
|
+
showButtons: [],
|
|
229
|
+
onDestroyed: () => teardown(endReasonRef.current),
|
|
174
230
|
});
|
|
175
231
|
|
|
176
|
-
|
|
177
|
-
|
|
232
|
+
driverRef.current = instance;
|
|
233
|
+
instance.drive();
|
|
178
234
|
},
|
|
179
|
-
[tours, labels, renderCard, closeTour,
|
|
235
|
+
[tours, labels, renderCard, closeTour, teardown],
|
|
180
236
|
);
|
|
181
237
|
|
|
182
|
-
// Cleanup on unmount
|
|
183
238
|
useEffect(() => {
|
|
184
239
|
return () => {
|
|
185
|
-
|
|
186
|
-
tourRef.current.cancel();
|
|
187
|
-
}
|
|
240
|
+
driverRef.current?.destroy();
|
|
188
241
|
cleanupRoots();
|
|
189
242
|
};
|
|
190
243
|
}, [cleanupRoots]);
|
|
@@ -206,8 +259,6 @@ export function OnboardingProvider({
|
|
|
206
259
|
|
|
207
260
|
export function useOnboarding(): OnboardingContextValue {
|
|
208
261
|
const context = useContext(OnboardingContext);
|
|
209
|
-
if (!context)
|
|
210
|
-
throw new Error("useOnboarding must be used within an OnboardingProvider");
|
|
211
|
-
}
|
|
262
|
+
if (!context) throw new Error("useOnboarding must be used within an OnboardingProvider");
|
|
212
263
|
return context;
|
|
213
264
|
}
|
|
@@ -70,6 +70,8 @@ export interface OnboardingContextValue {
|
|
|
70
70
|
totalSteps: number;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export type OnboardingTourEndReason = "finished" | "skipped" | "dismissed";
|
|
74
|
+
|
|
73
75
|
export interface OnboardingProviderProps {
|
|
74
76
|
children: React.ReactNode;
|
|
75
77
|
tours?: OnboardingTourConfig[];
|
|
@@ -77,6 +79,8 @@ export interface OnboardingProviderProps {
|
|
|
77
79
|
labels?: OnboardingLabels;
|
|
78
80
|
renderCard?: (props: OnboardingCardRenderProps) => ReactNode;
|
|
79
81
|
zIndex?: number;
|
|
82
|
+
/** Fires once when a tour ends. "finished" = last-step Finish, "skipped" = card Skip/close, "dismissed" = overlay click or Escape. */
|
|
83
|
+
onTourEnd?: (tourId: string, reason: OnboardingTourEndReason) => void;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
export interface OnboardingCardRenderProps {
|
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
/*
|
|
2
|
-
.
|
|
3
|
-
|
|
1
|
+
/* driver.js chrome is hidden: the React card mounted by OnboardingContext is the whole popover. */
|
|
2
|
+
.driver-popover.onboarding-popover {
|
|
3
|
+
background: transparent;
|
|
4
|
+
box-shadow: none;
|
|
5
|
+
padding: 0;
|
|
6
|
+
border-radius: 0;
|
|
7
|
+
max-width: none;
|
|
8
|
+
width: auto;
|
|
9
|
+
/* The card is the popover; driver.js's own clipping would cut it off. */
|
|
10
|
+
overflow: visible;
|
|
11
|
+
color: inherit;
|
|
4
12
|
}
|
|
5
13
|
|
|
6
|
-
.
|
|
7
|
-
|
|
14
|
+
.driver-popover.onboarding-popover .driver-popover-title,
|
|
15
|
+
.driver-popover.onboarding-popover .driver-popover-description,
|
|
16
|
+
.driver-popover.onboarding-popover .driver-popover-footer,
|
|
17
|
+
.driver-popover.onboarding-popover .driver-popover-close-btn {
|
|
18
|
+
display: none;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
/*
|
|
11
|
-
.
|
|
12
|
-
|
|
13
|
-
border: none !important;
|
|
14
|
-
box-shadow: none !important;
|
|
15
|
-
padding: 0 !important;
|
|
16
|
-
max-width: none !important;
|
|
21
|
+
/* Arrow colour follows the card surface so it reads as part of it. */
|
|
22
|
+
.driver-popover.onboarding-popover .driver-popover-arrow-side-top {
|
|
23
|
+
border-top-color: var(--card);
|
|
17
24
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.shepherd-text {
|
|
21
|
-
padding: 0 !important;
|
|
25
|
+
.driver-popover.onboarding-popover .driver-popover-arrow-side-bottom {
|
|
26
|
+
border-bottom-color: var(--card);
|
|
22
27
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.shepherd-arrow::before {
|
|
26
|
-
background: hsl(var(--card)) !important;
|
|
28
|
+
.driver-popover.onboarding-popover .driver-popover-arrow-side-left {
|
|
29
|
+
border-left-color: var(--card);
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.shepherd-header,
|
|
31
|
-
.shepherd-footer {
|
|
32
|
-
display: none !important;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* Dark mode support */
|
|
36
|
-
.dark .shepherd-arrow::before {
|
|
37
|
-
background: hsl(var(--card)) !important;
|
|
31
|
+
.driver-popover.onboarding-popover .driver-popover-arrow-side-right {
|
|
32
|
+
border-right-color: var(--card);
|
|
38
33
|
}
|
|
@@ -22,7 +22,7 @@ type Props = {
|
|
|
22
22
|
*/
|
|
23
23
|
export function TokenUsageReportFilterBar({ onChange }: Props) {
|
|
24
24
|
return (
|
|
25
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
25
|
+
<div data-help="tokenusage.filters" className="flex flex-wrap items-center gap-2">
|
|
26
26
|
<DateRangeSelector
|
|
27
27
|
onDateChange={(range) => {
|
|
28
28
|
if (!range?.from || !range?.to) return;
|
|
@@ -8,12 +8,21 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f
|
|
|
8
8
|
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
|
|
9
9
|
import { SearchIcon, CheckIcon } from "lucide-react";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Inside a dialog the Command must NOT paint `bg-popover`: `DialogContent` is
|
|
13
|
+
* `bg-background`, and the two tokens are only identical in the light theme
|
|
14
|
+
* (both white). In dark they differ — `--background` oklch(0.148) vs
|
|
15
|
+
* `--popover` oklch(0.218) — so the command block renders as a lighter panel
|
|
16
|
+
* seamed against the dialog's own header. Inheriting the dialog surface keeps a
|
|
17
|
+
* command dialog one colour in both themes.
|
|
18
|
+
*/
|
|
11
19
|
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
|
12
20
|
return (
|
|
13
21
|
<CommandPrimitive
|
|
14
22
|
data-slot="command"
|
|
15
23
|
className={cn(
|
|
16
24
|
"bg-popover text-popover-foreground rounded-xl p-1 flex size-full flex-col overflow-hidden",
|
|
25
|
+
"[[data-slot=dialog-content]_&]:bg-transparent [[data-slot=dialog-content]_&]:text-foreground",
|
|
17
26
|
className,
|
|
18
27
|
)}
|
|
19
28
|
{...props}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/* src/features/onboarding/styles/onboarding.css */
|
|
2
|
-
.shepherd-modal-overlay-container.shepherd-modal-is-visible {
|
|
3
|
-
opacity: 1 !important;
|
|
4
|
-
}
|
|
5
|
-
.shepherd-modal-overlay-container path {
|
|
6
|
-
fill: rgba(0, 0, 0, 0.75) !important;
|
|
7
|
-
}
|
|
8
|
-
.shepherd-element {
|
|
9
|
-
background: transparent !important;
|
|
10
|
-
border: none !important;
|
|
11
|
-
box-shadow: none !important;
|
|
12
|
-
padding: 0 !important;
|
|
13
|
-
max-width: none !important;
|
|
14
|
-
}
|
|
15
|
-
.shepherd-content,
|
|
16
|
-
.shepherd-text {
|
|
17
|
-
padding: 0 !important;
|
|
18
|
-
}
|
|
19
|
-
.shepherd-arrow::before {
|
|
20
|
-
background: hsl(var(--card)) !important;
|
|
21
|
-
}
|
|
22
|
-
.shepherd-header,
|
|
23
|
-
.shepherd-footer {
|
|
24
|
-
display: none !important;
|
|
25
|
-
}
|
|
26
|
-
.dark .shepherd-arrow::before {
|
|
27
|
-
background: hsl(var(--card)) !important;
|
|
28
|
-
}
|
|
29
|
-
/*# sourceMappingURL=BlockNoteEditor-YOAJRPWU.css.map */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/features/onboarding/styles/onboarding.css"],"sourcesContent":["/* Shepherd Modal Overlay */\n.shepherd-modal-overlay-container.shepherd-modal-is-visible {\n opacity: 1 !important;\n}\n\n.shepherd-modal-overlay-container path {\n fill: rgba(0, 0, 0, 0.75) !important;\n}\n\n/* Remove default shepherd styling - we use custom React card */\n.shepherd-element {\n background: transparent !important;\n border: none !important;\n box-shadow: none !important;\n padding: 0 !important;\n max-width: none !important;\n}\n\n.shepherd-content,\n.shepherd-text {\n padding: 0 !important;\n}\n\n/* Arrow styling to match card background */\n.shepherd-arrow::before {\n background: hsl(var(--card)) !important;\n}\n\n/* Hide default Shepherd header and footer - we render custom ones */\n.shepherd-header,\n.shepherd-footer {\n display: none !important;\n}\n\n/* Dark mode support */\n.dark .shepherd-arrow::before {\n background: hsl(var(--card)) !important;\n}\n"],"mappings":";AACA,CAAC,gCAAgC,CAAC;AAChC,WAAS;AACX;AAEA,CAJC,iCAIiC;AAChC,QAAM,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACtB;AAGA,CAAC;AACC,cAAY;AACZ,UAAQ;AACR,cAAY;AACZ,WAAS;AACT,aAAW;AACb;AAEA,CAAC;AACD,CAAC;AACC,WAAS;AACX;AAGA,CAAC,cAAc;AACb,cAAY,IAAI,IAAI;AACtB;AAGA,CAAC;AACD,CAAC;AACC,WAAS;AACX;AAGA,CAAC,KAAK,CAXL,cAWoB;AACnB,cAAY,IAAI,IAAI;AACtB;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/BlockNoteViewer-6N2GQWTI.js","../src/components/viewers/BlockNoteViewer.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACE;AACF,sDAA4B;AAC5B;AACA;ACHA,yCAAmC;AAKnC,2CAA8B;AAC9B,uCAAO;AACP,yCAAyB;AAoBhB,+CAAA;AAlBT,SAAS,SAAA,CAAU,OAAA,EAA8C;AAC/D,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAA,GAAK,OAAA,CAAQ,OAAA,EAAS,CAAA,EAAG,OAAO,OAAA;AACzD,EAAA,GAAA,CAAI,OAAO,QAAA,IAAY,SAAA,GAAY,OAAA,CAAQ,IAAA,CAAK,CAAA,EAAG;AACjD,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AACjC,MAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,EAAA,GAAK,MAAA,CAAO,OAAA,EAAS,CAAA,EAAG,OAAO,MAAA;AAAA,IACzD,EAAA,UAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,KAAA,CAAA;AACT;AAXS,qCAAA,SAAA,EAAA,WAAA,CAAA;AAaF,SAAS,eAAA,CAAgB,EAAE,QAAQ,CAAA,EAAyB;AACjE,EAAA,MAAM,OAAA,EAAS,uCAAA,EAAqB,cAAA,EAAgB,SAAA,CAAU,OAAO,EAAE,CAAC,CAAA;AAGxE,EAAA,MAAM,EAAE,cAAc,EAAA,EAAI,kCAAA,CAAS;AACnC,EAAA,uBAAO,6BAAA,qBAAC,EAAA,EAAc,MAAA,EAAgB,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,cAAA,IAAkB,OAAA,EAAS,OAAA,EAAS,QAAA,CAAS,CAAA;AAC7G;AANgB,qCAAA,eAAA,EAAA,iBAAA,CAAA;ADKhB;AACE;AACF,0CAAC","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/BlockNoteViewer-6N2GQWTI.js","sourcesContent":[null,"\"use client\";\n\nimport { useCreateBlockNote } from \"@blocknote/react\";\nimport type { PartialBlock } from \"@blocknote/core\";\n// Mirror BlockNoteEditor.tsx's BlockNoteView import + style.css. Default schema\n// only — no useCurrentUserContext, uploadImage, AI, or custom inline specs, so\n// this can render on auth-free public pages.\nimport { BlockNoteView } from \"@blocknote/shadcn\";\nimport \"@blocknote/shadcn/style.css\";\nimport { useTheme } from \"next-themes\";\n\nfunction normalize(content: unknown): PartialBlock[] | undefined {\n if (Array.isArray(content) && content.length > 0) return content as PartialBlock[];\n if (typeof content === \"string\" && content.trim()) {\n try {\n const parsed = JSON.parse(content);\n if (Array.isArray(parsed) && parsed.length > 0) return parsed as PartialBlock[];\n } catch {\n /* plain string — fall through to undefined (rendered as empty) */\n }\n }\n return undefined;\n}\n\nexport function BlockNoteViewer({ content }: { content: unknown }) {\n const editor = useCreateBlockNote({ initialContent: normalize(content) });\n // Outside a next-themes ThemeProvider (public pages) resolvedTheme is\n // undefined, so this falls back to \"light\".\n const { resolvedTheme } = useTheme();\n return <BlockNoteView editor={editor} editable={false} theme={resolvedTheme === \"dark\" ? \"dark\" : \"light\"} />;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/viewers/BlockNoteViewer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCreateBlockNote } from \"@blocknote/react\";\nimport type { PartialBlock } from \"@blocknote/core\";\n// Mirror BlockNoteEditor.tsx's BlockNoteView import + style.css. Default schema\n// only — no useCurrentUserContext, uploadImage, AI, or custom inline specs, so\n// this can render on auth-free public pages.\nimport { BlockNoteView } from \"@blocknote/shadcn\";\nimport \"@blocknote/shadcn/style.css\";\nimport { useTheme } from \"next-themes\";\n\nfunction normalize(content: unknown): PartialBlock[] | undefined {\n if (Array.isArray(content) && content.length > 0) return content as PartialBlock[];\n if (typeof content === \"string\" && content.trim()) {\n try {\n const parsed = JSON.parse(content);\n if (Array.isArray(parsed) && parsed.length > 0) return parsed as PartialBlock[];\n } catch {\n /* plain string — fall through to undefined (rendered as empty) */\n }\n }\n return undefined;\n}\n\nexport function BlockNoteViewer({ content }: { content: unknown }) {\n const editor = useCreateBlockNote({ initialContent: normalize(content) });\n // Outside a next-themes ThemeProvider (public pages) resolvedTheme is\n // undefined, so this falls back to \"light\".\n const { resolvedTheme } = useTheme();\n return <BlockNoteView editor={editor} editable={false} theme={resolvedTheme === \"dark\" ? \"dark\" : \"light\"} />;\n}\n"],"mappings":";;;;;;AAEA,SAAS,0BAA0B;AAKnC,SAAS,qBAAqB;AAC9B,OAAO;AACP,SAAS,gBAAgB;AAoBhB;AAlBT,SAAS,UAAU,SAA8C;AAC/D,MAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,EAAG,QAAO;AACzD,MAAI,OAAO,YAAY,YAAY,QAAQ,KAAK,GAAG;AACjD,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,EAAG,QAAO;AAAA,IACzD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAXS;AAaF,SAAS,gBAAgB,EAAE,QAAQ,GAAyB;AACjE,QAAM,SAAS,mBAAmB,EAAE,gBAAgB,UAAU,OAAO,EAAE,CAAC;AAGxE,QAAM,EAAE,cAAc,IAAI,SAAS;AACnC,SAAO,oBAAC,iBAAc,QAAgB,UAAU,OAAO,OAAO,kBAAkB,SAAS,SAAS,SAAS;AAC7G;AANgB;","names":[]}
|