nextworks 0.2.0-alpha.14 → 0.2.0-alpha.16
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/cli_manifests/blocks_manifest.json +5 -0
- package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +19 -18
- package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +204 -5
- package/dist/kits/blocks/components/sections/product-demo/TaskListPanel.tsx +267 -60
- package/dist/kits/blocks/package-deps.json +3 -3
- package/package.json +1 -1
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"components/sections/product-demo/KnowledgePanel.tsx",
|
|
63
63
|
"components/sections/product-demo/RunConsolePanel.tsx",
|
|
64
64
|
"components/sections/product-demo/types.ts",
|
|
65
|
+
"components/sections/product-demo/TaskListPanel.tsx",
|
|
65
66
|
"components/sections/product-demo/WorkflowStudioPanel.tsx",
|
|
66
67
|
"components/sections/Newsletter.tsx",
|
|
67
68
|
"components/sections/PortfolioSimple.tsx",
|
|
@@ -144,6 +145,10 @@
|
|
|
144
145
|
"app/templates/aiworkflow/components/ProcessTimeline.tsx",
|
|
145
146
|
"app/templates/aiworkflow/components/Testimonials.tsx",
|
|
146
147
|
"app/templates/aiworkflow/components/TrustBadges.tsx",
|
|
148
|
+
"app/templates/aiworkflow/themes/default.tsx",
|
|
149
|
+
"public/placeholders/aiworkflow/live.svg",
|
|
150
|
+
"public/placeholders/aiworkflow/review.svg",
|
|
151
|
+
"public/placeholders/aiworkflow/task.svg",
|
|
147
152
|
"public/placeholders/gallery/hero-pexels-broken-9945014.avif",
|
|
148
153
|
"public/placeholders/gallery/pexels-googledeepmind-25626431.jpg",
|
|
149
154
|
"public/placeholders/gallery/pexels-googledeepmind-25626432.jpg",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { motion } from "motion/react";
|
|
5
4
|
import { cn } from "@/lib/utils";
|
|
6
5
|
import { DemoWindow } from "./DemoWindow";
|
|
7
6
|
import { KnowledgePanel } from "./KnowledgePanel";
|
|
@@ -295,6 +294,19 @@ function getWindowShellClass(key: ProductDemoWindowKey) {
|
|
|
295
294
|
}
|
|
296
295
|
}
|
|
297
296
|
|
|
297
|
+
function getMobileWindowClass(key: ProductDemoWindowKey) {
|
|
298
|
+
switch (key) {
|
|
299
|
+
case "taskList":
|
|
300
|
+
return "h-[15rem] sm:h-[16rem] lg:h-full";
|
|
301
|
+
case "workflowStudio":
|
|
302
|
+
return "h-[18rem] sm:h-[19rem] lg:h-full";
|
|
303
|
+
case "runConsole":
|
|
304
|
+
return "h-[18rem] sm:h-[19rem] lg:h-full";
|
|
305
|
+
default:
|
|
306
|
+
return "lg:h-full";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
298
310
|
export function DemoStage({
|
|
299
311
|
scenarios = [],
|
|
300
312
|
initialScenarioIndex,
|
|
@@ -391,8 +403,8 @@ export function DemoStage({
|
|
|
391
403
|
)}
|
|
392
404
|
aria-label={ariaLabel}
|
|
393
405
|
>
|
|
394
|
-
<div className="relative z-10 flex
|
|
395
|
-
<div className="grid gap-4 lg:hidden">
|
|
406
|
+
<div className="relative z-10 flex min-h-[36rem] flex-col gap-4 lg:h-full lg:min-h-0 lg:gap-0">
|
|
407
|
+
<div className="grid auto-rows-max gap-4 lg:hidden">
|
|
396
408
|
{windows.map((windowData) => {
|
|
397
409
|
if (getWindowShellClass(windowData.key) === "hidden") {
|
|
398
410
|
return null;
|
|
@@ -412,6 +424,8 @@ export function DemoStage({
|
|
|
412
424
|
showControls={false}
|
|
413
425
|
showResizeHandle={false}
|
|
414
426
|
showHeader={false}
|
|
427
|
+
className={getMobileWindowClass(windowData.key)}
|
|
428
|
+
bodyClassName="px-0 py-0 sm:px-0 sm:py-0"
|
|
415
429
|
>
|
|
416
430
|
{windowData.content}
|
|
417
431
|
</DemoWindow>
|
|
@@ -472,20 +486,7 @@ export function DemoStage({
|
|
|
472
486
|
windowData.key === "workflowStudio");
|
|
473
487
|
|
|
474
488
|
return (
|
|
475
|
-
<
|
|
476
|
-
key={windowData.key}
|
|
477
|
-
initial={enableMotion ? { opacity: 0 } : false}
|
|
478
|
-
animate={{ opacity: 1 }}
|
|
479
|
-
transition={
|
|
480
|
-
enableMotion
|
|
481
|
-
? {
|
|
482
|
-
type: "tween",
|
|
483
|
-
duration: 0.24,
|
|
484
|
-
}
|
|
485
|
-
: { duration: 0 }
|
|
486
|
-
}
|
|
487
|
-
className={cn("min-h-0", shellClass)}
|
|
488
|
-
>
|
|
489
|
+
<div key={windowData.key} className={cn("min-h-0", shellClass)}>
|
|
489
490
|
<DemoWindow
|
|
490
491
|
window={windowData.meta}
|
|
491
492
|
active={activeWindow}
|
|
@@ -507,7 +508,7 @@ export function DemoStage({
|
|
|
507
508
|
>
|
|
508
509
|
{windowData.content}
|
|
509
510
|
</DemoWindow>
|
|
510
|
-
</
|
|
511
|
+
</div>
|
|
511
512
|
);
|
|
512
513
|
})}
|
|
513
514
|
</div>
|
|
@@ -7,6 +7,15 @@ export interface RunConsolePanelProps {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
10
|
+
const scrollViewportRef = React.useRef<HTMLDivElement | null>(null);
|
|
11
|
+
const scrollbarTrackRef = React.useRef<HTMLDivElement | null>(null);
|
|
12
|
+
const dragStateRef = React.useRef<{
|
|
13
|
+
pointerId: number;
|
|
14
|
+
startY: number;
|
|
15
|
+
startScrollTop: number;
|
|
16
|
+
scrollRatio: number;
|
|
17
|
+
pointerOffsetY: number;
|
|
18
|
+
} | null>(null);
|
|
10
19
|
const playbackMs = state.playbackMs ?? 1800;
|
|
11
20
|
const playbackStepEntryIndices = state.playbackStepEntryIndices ?? [];
|
|
12
21
|
const playbackStepVisibleLineCounts =
|
|
@@ -74,6 +83,11 @@ export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
|
74
83
|
const [visibleLineCount, setVisibleLineCount] = React.useState(
|
|
75
84
|
Math.max(1, Math.min(2, activeCode.length || 1)),
|
|
76
85
|
);
|
|
86
|
+
const [scrollMetrics, setScrollMetrics] = React.useState({
|
|
87
|
+
scrollTop: 0,
|
|
88
|
+
scrollHeight: 1,
|
|
89
|
+
clientHeight: 1,
|
|
90
|
+
});
|
|
77
91
|
|
|
78
92
|
React.useEffect(() => {
|
|
79
93
|
if (typeof state.playbackStep === "number") {
|
|
@@ -124,13 +138,179 @@ export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
|
124
138
|
state.playbackStep,
|
|
125
139
|
]);
|
|
126
140
|
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
const viewport = scrollViewportRef.current;
|
|
143
|
+
|
|
144
|
+
if (!viewport) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const updateScrollMetrics = () => {
|
|
149
|
+
setScrollMetrics({
|
|
150
|
+
scrollTop: viewport.scrollTop,
|
|
151
|
+
scrollHeight: viewport.scrollHeight,
|
|
152
|
+
clientHeight: viewport.clientHeight,
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
updateScrollMetrics();
|
|
157
|
+
|
|
158
|
+
viewport.addEventListener("scroll", updateScrollMetrics, {
|
|
159
|
+
passive: true,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
163
|
+
updateScrollMetrics();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
resizeObserver.observe(viewport);
|
|
167
|
+
|
|
168
|
+
if (viewport.firstElementChild instanceof HTMLElement) {
|
|
169
|
+
resizeObserver.observe(viewport.firstElementChild);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
window.addEventListener("resize", updateScrollMetrics);
|
|
173
|
+
|
|
174
|
+
return () => {
|
|
175
|
+
viewport.removeEventListener("scroll", updateScrollMetrics);
|
|
176
|
+
resizeObserver.disconnect();
|
|
177
|
+
window.removeEventListener("resize", updateScrollMetrics);
|
|
178
|
+
};
|
|
179
|
+
}, [visibleLineCount, activeEntry?.id, activeCode.length]);
|
|
180
|
+
|
|
127
181
|
const visibleCode = activeCode.slice(0, visibleLineCount);
|
|
182
|
+
const hasOverflow =
|
|
183
|
+
scrollMetrics.scrollHeight > scrollMetrics.clientHeight + 1;
|
|
184
|
+
const thumbHeight = hasOverflow
|
|
185
|
+
? Math.max(
|
|
186
|
+
36,
|
|
187
|
+
(scrollMetrics.clientHeight / scrollMetrics.scrollHeight) *
|
|
188
|
+
scrollMetrics.clientHeight,
|
|
189
|
+
)
|
|
190
|
+
: 0;
|
|
191
|
+
const maxThumbOffset = Math.max(scrollMetrics.clientHeight - thumbHeight, 0);
|
|
192
|
+
const maxScrollTop = Math.max(
|
|
193
|
+
scrollMetrics.scrollHeight - scrollMetrics.clientHeight,
|
|
194
|
+
1,
|
|
195
|
+
);
|
|
196
|
+
const thumbOffset = hasOverflow
|
|
197
|
+
? (scrollMetrics.scrollTop / maxScrollTop) * maxThumbOffset
|
|
198
|
+
: 0;
|
|
199
|
+
|
|
200
|
+
const updateScrollTopFromPointer = React.useCallback(
|
|
201
|
+
(clientY: number) => {
|
|
202
|
+
const viewport = scrollViewportRef.current;
|
|
203
|
+
const track = scrollbarTrackRef.current;
|
|
204
|
+
|
|
205
|
+
if (!viewport || !track || !hasOverflow) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const trackRect = track.getBoundingClientRect();
|
|
210
|
+
const nextThumbTop = Math.min(
|
|
211
|
+
Math.max(
|
|
212
|
+
clientY - trackRect.top - dragStateRef.current!.pointerOffsetY,
|
|
213
|
+
0,
|
|
214
|
+
),
|
|
215
|
+
maxThumbOffset,
|
|
216
|
+
);
|
|
217
|
+
const nextScrollTop =
|
|
218
|
+
maxThumbOffset > 0 ? (nextThumbTop / maxThumbOffset) * maxScrollTop : 0;
|
|
219
|
+
|
|
220
|
+
viewport.scrollTop = nextScrollTop;
|
|
221
|
+
},
|
|
222
|
+
[hasOverflow, maxScrollTop, maxThumbOffset],
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const handleScrollbarPointerDown = React.useCallback(
|
|
226
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
227
|
+
const viewport = scrollViewportRef.current;
|
|
228
|
+
const track = scrollbarTrackRef.current;
|
|
229
|
+
|
|
230
|
+
if (!viewport || !track || !hasOverflow) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const trackRect = track.getBoundingClientRect();
|
|
235
|
+
const targetElement = event.target as HTMLElement | null;
|
|
236
|
+
const clickedThumb = targetElement?.dataset.scrollbarThumb === "true";
|
|
237
|
+
const pointerOffsetY = clickedThumb
|
|
238
|
+
? event.clientY - trackRect.top - thumbOffset
|
|
239
|
+
: thumbHeight / 2;
|
|
240
|
+
|
|
241
|
+
dragStateRef.current = {
|
|
242
|
+
pointerId: event.pointerId,
|
|
243
|
+
startY: event.clientY,
|
|
244
|
+
startScrollTop: viewport.scrollTop,
|
|
245
|
+
scrollRatio: maxThumbOffset > 0 ? maxScrollTop / maxThumbOffset : 0,
|
|
246
|
+
pointerOffsetY,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
if (!clickedThumb) {
|
|
250
|
+
updateScrollTopFromPointer(event.clientY);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
(event.currentTarget as HTMLDivElement).setPointerCapture(
|
|
254
|
+
event.pointerId,
|
|
255
|
+
);
|
|
256
|
+
event.preventDefault();
|
|
257
|
+
},
|
|
258
|
+
[
|
|
259
|
+
hasOverflow,
|
|
260
|
+
maxScrollTop,
|
|
261
|
+
maxThumbOffset,
|
|
262
|
+
thumbHeight,
|
|
263
|
+
thumbOffset,
|
|
264
|
+
updateScrollTopFromPointer,
|
|
265
|
+
],
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const handleScrollbarPointerMove = React.useCallback(
|
|
269
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
270
|
+
const dragState = dragStateRef.current;
|
|
271
|
+
|
|
272
|
+
if (!dragState || dragState.pointerId !== event.pointerId) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const viewport = scrollViewportRef.current;
|
|
277
|
+
const track = scrollbarTrackRef.current;
|
|
278
|
+
|
|
279
|
+
if (!viewport || !track || !hasOverflow) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const deltaY = event.clientY - dragState.startY;
|
|
284
|
+
const nextScrollTop = Math.min(
|
|
285
|
+
Math.max(dragState.startScrollTop + deltaY * dragState.scrollRatio, 0),
|
|
286
|
+
maxScrollTop,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
viewport.scrollTop = nextScrollTop;
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
},
|
|
292
|
+
[hasOverflow, maxScrollTop],
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const handleScrollbarPointerUp = React.useCallback(
|
|
296
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
297
|
+
const dragState = dragStateRef.current;
|
|
298
|
+
|
|
299
|
+
if (dragState?.pointerId === event.pointerId) {
|
|
300
|
+
dragStateRef.current = null;
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
[],
|
|
304
|
+
);
|
|
128
305
|
|
|
129
306
|
return (
|
|
130
307
|
<div className="flex h-full min-h-0 flex-col text-[var(--demo-fg)] [font-synthesis:none] antialiased">
|
|
131
308
|
<div className="flex min-h-0 flex-1 overflow-hidden rounded-none border border-[var(--demo-border)] bg-[var(--demo-code-bg)] shadow-none">
|
|
132
|
-
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
133
|
-
<div
|
|
309
|
+
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col">
|
|
310
|
+
<div
|
|
311
|
+
ref={scrollViewportRef}
|
|
312
|
+
className="grid min-h-0 flex-1 grid-cols-[3.5rem_minmax(0,1fr)] overflow-y-auto bg-[var(--demo-code-bg)] pr-5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:overflow-hidden lg:pr-0"
|
|
313
|
+
>
|
|
134
314
|
<div className="border-r border-[var(--demo-border)] bg-[var(--demo-code-gutter-bg)] px-2 py-3 font-mono text-[11px] leading-7 text-[var(--demo-subtle-fg)]">
|
|
135
315
|
{visibleCode.map((line, index) => {
|
|
136
316
|
const isAdded = line.trimStart().startsWith("+");
|
|
@@ -151,7 +331,7 @@ export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
|
151
331
|
})}
|
|
152
332
|
</div>
|
|
153
333
|
|
|
154
|
-
<div className="relative
|
|
334
|
+
<div className="relative min-h-full bg-[var(--demo-code-bg)] px-3 py-3 font-mono text-[12px] leading-7 text-[var(--demo-fg)]">
|
|
155
335
|
<div>
|
|
156
336
|
{visibleCode.map((line, index) => {
|
|
157
337
|
const isAdded = line.trimStart().startsWith("+");
|
|
@@ -202,10 +382,29 @@ export function RunConsolePanel({ state }: RunConsolePanelProps) {
|
|
|
202
382
|
</div>
|
|
203
383
|
) : null}
|
|
204
384
|
</div>
|
|
205
|
-
|
|
206
|
-
<div className="flex-1" />
|
|
207
385
|
</div>
|
|
208
386
|
</div>
|
|
387
|
+
|
|
388
|
+
{hasOverflow ? (
|
|
389
|
+
<div
|
|
390
|
+
ref={scrollbarTrackRef}
|
|
391
|
+
aria-hidden="true"
|
|
392
|
+
onPointerDown={handleScrollbarPointerDown}
|
|
393
|
+
onPointerMove={handleScrollbarPointerMove}
|
|
394
|
+
onPointerUp={handleScrollbarPointerUp}
|
|
395
|
+
onPointerCancel={handleScrollbarPointerUp}
|
|
396
|
+
className="absolute inset-y-3 right-1.5 w-[10px] cursor-pointer overflow-hidden rounded-full bg-[var(--demo-scroll-track)] lg:hidden"
|
|
397
|
+
>
|
|
398
|
+
<div
|
|
399
|
+
data-scrollbar-thumb="true"
|
|
400
|
+
className="absolute inset-x-1 rounded-full bg-[var(--demo-scroll-thumb)] shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] will-change-transform"
|
|
401
|
+
style={{
|
|
402
|
+
height: `${thumbHeight}px`,
|
|
403
|
+
transform: `translateY(${thumbOffset}px)`,
|
|
404
|
+
}}
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
) : null}
|
|
209
408
|
</div>
|
|
210
409
|
|
|
211
410
|
{state.metrics?.length ? (
|
|
@@ -9,6 +9,185 @@ export interface TaskListPanelProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function TaskListPanel({ state, onSelect }: TaskListPanelProps) {
|
|
12
|
+
const scrollViewportRef = React.useRef<HTMLDivElement | null>(null);
|
|
13
|
+
const scrollbarTrackRef = React.useRef<HTMLDivElement | null>(null);
|
|
14
|
+
const dragStateRef = React.useRef<{
|
|
15
|
+
pointerId: number;
|
|
16
|
+
startY: number;
|
|
17
|
+
startScrollTop: number;
|
|
18
|
+
scrollRatio: number;
|
|
19
|
+
pointerOffsetY: number;
|
|
20
|
+
} | null>(null);
|
|
21
|
+
const [scrollMetrics, setScrollMetrics] = React.useState({
|
|
22
|
+
scrollTop: 0,
|
|
23
|
+
scrollHeight: 1,
|
|
24
|
+
clientHeight: 1,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
const viewport = scrollViewportRef.current;
|
|
29
|
+
|
|
30
|
+
if (!viewport) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const updateScrollMetrics = () => {
|
|
35
|
+
setScrollMetrics({
|
|
36
|
+
scrollTop: viewport.scrollTop,
|
|
37
|
+
scrollHeight: viewport.scrollHeight,
|
|
38
|
+
clientHeight: viewport.clientHeight,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
updateScrollMetrics();
|
|
43
|
+
|
|
44
|
+
viewport.addEventListener("scroll", updateScrollMetrics, {
|
|
45
|
+
passive: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
49
|
+
updateScrollMetrics();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
resizeObserver.observe(viewport);
|
|
53
|
+
|
|
54
|
+
if (viewport.firstElementChild instanceof HTMLElement) {
|
|
55
|
+
resizeObserver.observe(viewport.firstElementChild);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
window.addEventListener("resize", updateScrollMetrics);
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
viewport.removeEventListener("scroll", updateScrollMetrics);
|
|
62
|
+
resizeObserver.disconnect();
|
|
63
|
+
window.removeEventListener("resize", updateScrollMetrics);
|
|
64
|
+
};
|
|
65
|
+
}, [state.items.length, state.activeItemId]);
|
|
66
|
+
|
|
67
|
+
const hasOverflow =
|
|
68
|
+
scrollMetrics.scrollHeight > scrollMetrics.clientHeight + 1;
|
|
69
|
+
const thumbHeight = hasOverflow
|
|
70
|
+
? Math.max(
|
|
71
|
+
36,
|
|
72
|
+
(scrollMetrics.clientHeight / scrollMetrics.scrollHeight) *
|
|
73
|
+
scrollMetrics.clientHeight,
|
|
74
|
+
)
|
|
75
|
+
: 0;
|
|
76
|
+
const maxThumbOffset = Math.max(scrollMetrics.clientHeight - thumbHeight, 0);
|
|
77
|
+
const maxScrollTop = Math.max(
|
|
78
|
+
scrollMetrics.scrollHeight - scrollMetrics.clientHeight,
|
|
79
|
+
1,
|
|
80
|
+
);
|
|
81
|
+
const thumbOffset = hasOverflow
|
|
82
|
+
? (scrollMetrics.scrollTop / maxScrollTop) * maxThumbOffset
|
|
83
|
+
: 0;
|
|
84
|
+
|
|
85
|
+
const updateScrollTopFromPointer = React.useCallback(
|
|
86
|
+
(clientY: number) => {
|
|
87
|
+
const viewport = scrollViewportRef.current;
|
|
88
|
+
const track = scrollbarTrackRef.current;
|
|
89
|
+
|
|
90
|
+
if (!viewport || !track || !hasOverflow) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const trackRect = track.getBoundingClientRect();
|
|
95
|
+
const nextThumbTop = Math.min(
|
|
96
|
+
Math.max(
|
|
97
|
+
clientY - trackRect.top - dragStateRef.current!.pointerOffsetY,
|
|
98
|
+
0,
|
|
99
|
+
),
|
|
100
|
+
maxThumbOffset,
|
|
101
|
+
);
|
|
102
|
+
const nextScrollTop =
|
|
103
|
+
maxThumbOffset > 0 ? (nextThumbTop / maxThumbOffset) * maxScrollTop : 0;
|
|
104
|
+
|
|
105
|
+
viewport.scrollTop = nextScrollTop;
|
|
106
|
+
},
|
|
107
|
+
[hasOverflow, maxScrollTop, maxThumbOffset],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const handleScrollbarPointerDown = React.useCallback(
|
|
111
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
112
|
+
const viewport = scrollViewportRef.current;
|
|
113
|
+
const track = scrollbarTrackRef.current;
|
|
114
|
+
|
|
115
|
+
if (!viewport || !track || !hasOverflow) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const trackRect = track.getBoundingClientRect();
|
|
120
|
+
const targetElement = event.target as HTMLElement | null;
|
|
121
|
+
const clickedThumb = targetElement?.dataset.scrollbarThumb === "true";
|
|
122
|
+
const pointerOffsetY = clickedThumb
|
|
123
|
+
? event.clientY - trackRect.top - thumbOffset
|
|
124
|
+
: thumbHeight / 2;
|
|
125
|
+
|
|
126
|
+
dragStateRef.current = {
|
|
127
|
+
pointerId: event.pointerId,
|
|
128
|
+
startY: event.clientY,
|
|
129
|
+
startScrollTop: viewport.scrollTop,
|
|
130
|
+
scrollRatio: maxThumbOffset > 0 ? maxScrollTop / maxThumbOffset : 0,
|
|
131
|
+
pointerOffsetY,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if (!clickedThumb) {
|
|
135
|
+
updateScrollTopFromPointer(event.clientY);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
(event.currentTarget as HTMLDivElement).setPointerCapture(
|
|
139
|
+
event.pointerId,
|
|
140
|
+
);
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
},
|
|
143
|
+
[
|
|
144
|
+
hasOverflow,
|
|
145
|
+
maxScrollTop,
|
|
146
|
+
maxThumbOffset,
|
|
147
|
+
thumbHeight,
|
|
148
|
+
thumbOffset,
|
|
149
|
+
updateScrollTopFromPointer,
|
|
150
|
+
],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const handleScrollbarPointerMove = React.useCallback(
|
|
154
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
155
|
+
const dragState = dragStateRef.current;
|
|
156
|
+
|
|
157
|
+
if (!dragState || dragState.pointerId !== event.pointerId) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const viewport = scrollViewportRef.current;
|
|
162
|
+
const track = scrollbarTrackRef.current;
|
|
163
|
+
|
|
164
|
+
if (!viewport || !track || !hasOverflow) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const deltaY = event.clientY - dragState.startY;
|
|
169
|
+
const nextScrollTop = Math.min(
|
|
170
|
+
Math.max(dragState.startScrollTop + deltaY * dragState.scrollRatio, 0),
|
|
171
|
+
maxScrollTop,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
viewport.scrollTop = nextScrollTop;
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
},
|
|
177
|
+
[hasOverflow, maxScrollTop],
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const handleScrollbarPointerUp = React.useCallback(
|
|
181
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
182
|
+
const dragState = dragStateRef.current;
|
|
183
|
+
|
|
184
|
+
if (dragState?.pointerId === event.pointerId) {
|
|
185
|
+
dragStateRef.current = null;
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[],
|
|
189
|
+
);
|
|
190
|
+
|
|
12
191
|
return (
|
|
13
192
|
<div className="flex h-full min-h-0 flex-col bg-[var(--demo-panel-muted-bg)] text-[var(--demo-fg)]">
|
|
14
193
|
<div className="border-b border-[var(--demo-border)] px-3 py-2.5">
|
|
@@ -20,75 +199,103 @@ export function TaskListPanel({ state, onSelect }: TaskListPanelProps) {
|
|
|
20
199
|
</div>
|
|
21
200
|
</div>
|
|
22
201
|
|
|
23
|
-
<div className="
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)}
|
|
38
|
-
>
|
|
39
|
-
{isActive ? (
|
|
40
|
-
<span
|
|
41
|
-
aria-hidden="true"
|
|
42
|
-
className="pointer-events-none absolute inset-y-0 left-0 w-px bg-[var(--demo-accent)]"
|
|
43
|
-
/>
|
|
44
|
-
) : null}
|
|
45
|
-
|
|
46
|
-
<div className="relative z-10 flex items-start gap-3">
|
|
47
|
-
<span
|
|
202
|
+
<div className="relative min-h-0 flex-1">
|
|
203
|
+
<div
|
|
204
|
+
ref={scrollViewportRef}
|
|
205
|
+
className="h-full overflow-y-auto pr-5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
206
|
+
>
|
|
207
|
+
<div className="space-y-0">
|
|
208
|
+
{state.items.map((item, index) => {
|
|
209
|
+
const isActive = item.id === state.activeItemId;
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<button
|
|
213
|
+
key={item.id}
|
|
214
|
+
type="button"
|
|
215
|
+
onClick={() => onSelect?.(item.id)}
|
|
48
216
|
className={cn(
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
217
|
+
"relative isolate w-full overflow-hidden rounded-none border-x-0 border-y border-b-0 px-3 py-3 text-left transition-colors duration-200 first:border-t-0",
|
|
218
|
+
"border-[var(--demo-border)] bg-[var(--demo-panel-bg)] hover:border-[var(--demo-border-strong)] hover:bg-[var(--demo-shell-strong-bg)]",
|
|
219
|
+
isActive &&
|
|
220
|
+
"border-[var(--demo-border-strong)] bg-[var(--demo-shell-strong-bg)]",
|
|
53
221
|
)}
|
|
54
222
|
>
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
</div>
|
|
65
|
-
<div
|
|
223
|
+
{isActive ? (
|
|
224
|
+
<span
|
|
225
|
+
aria-hidden="true"
|
|
226
|
+
className="pointer-events-none absolute inset-y-0 left-0 w-px bg-[var(--demo-accent)]"
|
|
227
|
+
/>
|
|
228
|
+
) : null}
|
|
229
|
+
|
|
230
|
+
<div className="relative z-10 flex items-start gap-3">
|
|
231
|
+
<span
|
|
66
232
|
className={cn(
|
|
67
|
-
"flex h-5 w-
|
|
233
|
+
"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold",
|
|
68
234
|
isActive
|
|
69
|
-
? "text-[var(--demo-
|
|
70
|
-
: "
|
|
235
|
+
? "border-[var(--demo-border-strong)] bg-[var(--demo-shell-strong-bg)] text-[var(--demo-fg)]"
|
|
236
|
+
: "border-[var(--demo-border)] bg-[var(--demo-panel-subtle-bg)] text-[var(--demo-muted-fg)]",
|
|
71
237
|
)}
|
|
72
238
|
>
|
|
73
|
-
{
|
|
239
|
+
{index + 1}
|
|
240
|
+
</span>
|
|
241
|
+
|
|
242
|
+
<div className="min-w-0 flex-1">
|
|
243
|
+
<div className="flex items-start justify-between gap-3">
|
|
244
|
+
<div className="min-w-0">
|
|
245
|
+
<div className="text-sm font-semibold text-[var(--demo-fg)]">
|
|
246
|
+
{item.title}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
<div
|
|
250
|
+
className={cn(
|
|
251
|
+
"flex h-5 w-[4.5rem] shrink-0 items-center justify-end text-[9px] uppercase tracking-[0.14em]",
|
|
252
|
+
isActive
|
|
253
|
+
? "text-[var(--demo-muted-fg)]"
|
|
254
|
+
: "text-[var(--demo-subtle-fg)]",
|
|
255
|
+
)}
|
|
256
|
+
>
|
|
257
|
+
{isActive ? "Open" : "Queued"}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
{item.description && (
|
|
261
|
+
<p className="mt-1.5 max-w-[22ch] text-xs leading-relaxed text-[var(--demo-muted-fg)]">
|
|
262
|
+
{item.description}
|
|
263
|
+
</p>
|
|
264
|
+
)}
|
|
265
|
+
{item.meta && (
|
|
266
|
+
<div className="mt-2.5 flex items-center gap-2 text-[11px] text-[var(--demo-muted-fg)]">
|
|
267
|
+
<span className="h-1 w-1 rounded-full bg-[var(--demo-border-strong)]" />
|
|
268
|
+
<span>{item.meta}</span>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
74
271
|
</div>
|
|
75
272
|
</div>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
273
|
+
</button>
|
|
274
|
+
);
|
|
275
|
+
})}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
{hasOverflow ? (
|
|
280
|
+
<div
|
|
281
|
+
ref={scrollbarTrackRef}
|
|
282
|
+
aria-hidden="true"
|
|
283
|
+
onPointerDown={handleScrollbarPointerDown}
|
|
284
|
+
onPointerMove={handleScrollbarPointerMove}
|
|
285
|
+
onPointerUp={handleScrollbarPointerUp}
|
|
286
|
+
onPointerCancel={handleScrollbarPointerUp}
|
|
287
|
+
className="absolute inset-y-3 right-1.5 w-[10px] cursor-pointer overflow-hidden rounded-full bg-[var(--demo-scroll-track)]"
|
|
288
|
+
>
|
|
289
|
+
<div
|
|
290
|
+
data-scrollbar-thumb="true"
|
|
291
|
+
className="absolute inset-x-1 rounded-full bg-[var(--demo-scroll-thumb)] shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] will-change-transform"
|
|
292
|
+
style={{
|
|
293
|
+
height: `${thumbHeight}px`,
|
|
294
|
+
transform: `translateY(${thumbOffset}px)`,
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
) : null}
|
|
92
299
|
</div>
|
|
93
300
|
</div>
|
|
94
301
|
);
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"next-themes": "^0.4.6",
|
|
14
14
|
"motion": "^12.24.10",
|
|
15
15
|
"sonner": "^2.0.7",
|
|
16
|
-
"@nextworks/blocks-core": "0.2.0-alpha.
|
|
17
|
-
"@nextworks/blocks-sections": "0.2.0-alpha.
|
|
18
|
-
"@nextworks/blocks-templates": "0.2.0-alpha.
|
|
16
|
+
"@nextworks/blocks-core": "0.2.0-alpha.16",
|
|
17
|
+
"@nextworks/blocks-sections": "0.2.0-alpha.16",
|
|
18
|
+
"@nextworks/blocks-templates": "0.2.0-alpha.16"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"tailwindcss": "^4.1.12"
|