nextworks 0.2.0-alpha.11 → 0.2.0-alpha.13
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 +283 -282
- package/dist/cli_manifests/blocks_manifest.json +198 -175
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +101 -100
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +105 -104
- package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +1 -1
- package/dist/kits/blocks/app/templates/aiworkflow/PresetThemeVars.tsx +58 -0
- package/dist/kits/blocks/app/templates/aiworkflow/README.md +46 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/CTA.tsx +44 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Contact.tsx +105 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/FAQ.tsx +63 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Features.tsx +65 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Footer.tsx +109 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Hero.tsx +636 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Navbar.tsx +90 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Pricing.tsx +86 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/ProcessTimeline.tsx +103 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Testimonials.tsx +56 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/TrustBadges.tsx +59 -0
- package/dist/kits/blocks/app/templates/aiworkflow/page.tsx +43 -0
- package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
- package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -42
- package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
- package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
- package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
- package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
- package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
- package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
- package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +84 -84
- package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -75
- package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -62
- package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -84
- package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -50
- package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
- package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -86
- package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -83
- package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
- package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -88
- package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -116
- package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
- package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
- package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
- package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
- package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -90
- package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
- package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
- package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
- package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -44
- package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -129
- package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
- package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
- package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -90
- package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
- package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -104
- package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -126
- package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -117
- package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
- package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -96
- package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
- package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
- package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
- package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -195
- package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
- package/dist/kits/blocks/components/sections/About.tsx +291 -291
- package/dist/kits/blocks/components/sections/CTA.tsx +257 -257
- package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
- package/dist/kits/blocks/components/sections/FAQ.tsx +214 -214
- package/dist/kits/blocks/components/sections/Features.tsx +268 -268
- package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
- package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
- package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
- package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +236 -0
- package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
- package/dist/kits/blocks/components/sections/Navbar.tsx +350 -350
- package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -549
- package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
- package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
- package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
- package/dist/kits/blocks/components/sections/Team.tsx +309 -309
- package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
- package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
- package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +125 -0
- package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +397 -0
- package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +128 -0
- package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +127 -0
- package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +150 -0
- package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +191 -0
- package/dist/kits/blocks/components/sections/product-demo/types.ts +193 -0
- package/dist/kits/blocks/components/theme-provider.tsx +1 -1
- package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
- package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
- package/dist/kits/blocks/components/ui/button.tsx +122 -122
- package/dist/kits/blocks/components/ui/card.tsx +95 -95
- package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
- package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
- package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
- package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
- package/dist/kits/blocks/components/ui/input.tsx +27 -27
- package/dist/kits/blocks/components/ui/label.tsx +29 -29
- package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
- package/dist/kits/blocks/components/ui/select.tsx +25 -25
- package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
- package/dist/kits/blocks/components/ui/table.tsx +98 -98
- package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
- package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
- package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -243
- package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
- package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
- package/dist/kits/blocks/lib/themes.ts +400 -400
- package/dist/kits/blocks/lib/utils.ts +6 -6
- package/dist/kits/blocks/package-deps.json +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import type {
|
|
4
|
+
ProductDemoApprovalInboxState,
|
|
5
|
+
ProductDemoStatusTone,
|
|
6
|
+
} from "./types";
|
|
7
|
+
|
|
8
|
+
export interface ApprovalInboxPanelProps {
|
|
9
|
+
state: ProductDemoApprovalInboxState;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
|
|
13
|
+
neutral: "border-border/60 bg-muted/55 text-muted-foreground",
|
|
14
|
+
info: "border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-300",
|
|
15
|
+
success:
|
|
16
|
+
"border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300",
|
|
17
|
+
warning:
|
|
18
|
+
"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
19
|
+
danger: "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-300",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getStatusClass(tone: ProductDemoStatusTone = "neutral") {
|
|
23
|
+
return STATUS_TONE_CLASSES[tone];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ApprovalInboxPanel({ state }: ApprovalInboxPanelProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex h-full flex-col gap-4">
|
|
29
|
+
<div className="space-y-1.5">
|
|
30
|
+
{state.title && (
|
|
31
|
+
<h4 className="text-sm font-semibold text-card-foreground">
|
|
32
|
+
{state.title}
|
|
33
|
+
</h4>
|
|
34
|
+
)}
|
|
35
|
+
{state.subtitle && (
|
|
36
|
+
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
37
|
+
{state.subtitle}
|
|
38
|
+
</p>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{state.counts?.length ? (
|
|
43
|
+
<div className="flex flex-wrap gap-2">
|
|
44
|
+
{state.counts.map((count) => (
|
|
45
|
+
<div
|
|
46
|
+
key={count.id}
|
|
47
|
+
className={cn(
|
|
48
|
+
"rounded-xl border px-3 py-2",
|
|
49
|
+
getStatusClass(count.tone),
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<div className="text-[10px] font-medium uppercase tracking-[0.14em] opacity-80">
|
|
53
|
+
{count.label}
|
|
54
|
+
</div>
|
|
55
|
+
<div className="mt-1 text-sm font-semibold">{count.value}</div>
|
|
56
|
+
</div>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
|
|
61
|
+
<div className="space-y-3">
|
|
62
|
+
{state.items.map((item) => {
|
|
63
|
+
const isActive = item.id === state.activeItemId || item.highlighted;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
key={item.id}
|
|
68
|
+
className={cn(
|
|
69
|
+
"rounded-2xl border border-border/60 bg-background/80 p-3",
|
|
70
|
+
isActive && "border-primary/45 bg-primary/6 shadow-sm",
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<div className="flex items-start justify-between gap-3">
|
|
74
|
+
<div>
|
|
75
|
+
<div className="text-sm font-semibold text-card-foreground">
|
|
76
|
+
{item.title}
|
|
77
|
+
</div>
|
|
78
|
+
{item.description && (
|
|
79
|
+
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">
|
|
80
|
+
{item.description}
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
{item.status && (
|
|
85
|
+
<span
|
|
86
|
+
className={cn(
|
|
87
|
+
"rounded-full border px-2 py-1 text-[10px] font-medium uppercase tracking-[0.14em]",
|
|
88
|
+
getStatusClass(item.status),
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
{item.status}
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{(item.requester || item.priorityLabel || item.dueLabel) && (
|
|
97
|
+
<div className="mt-3 flex flex-wrap gap-2 text-[11px] text-muted-foreground">
|
|
98
|
+
{item.requester && <span>By {item.requester}</span>}
|
|
99
|
+
{item.priorityLabel && <span>{item.priorityLabel}</span>}
|
|
100
|
+
{item.dueLabel && <span>{item.dueLabel}</span>}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{item.actions?.length ? (
|
|
105
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
106
|
+
{item.actions.map((action) => (
|
|
107
|
+
<span
|
|
108
|
+
key={action.id}
|
|
109
|
+
className={cn(
|
|
110
|
+
"rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.14em]",
|
|
111
|
+
getStatusClass(action.tone),
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
{action.label}
|
|
115
|
+
</span>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { motion } from "motion/react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { DemoWindow } from "./DemoWindow";
|
|
7
|
+
import { ApprovalInboxPanel } from "./ApprovalInboxPanel";
|
|
8
|
+
import { KnowledgePanel } from "./KnowledgePanel";
|
|
9
|
+
import { RunConsolePanel } from "./RunConsolePanel";
|
|
10
|
+
import { WorkflowStudioPanel } from "./WorkflowStudioPanel";
|
|
11
|
+
import type {
|
|
12
|
+
ProductDemoHighlightTarget,
|
|
13
|
+
ProductDemoHighlightTone,
|
|
14
|
+
ProductDemoScenario,
|
|
15
|
+
ProductDemoStatusTone,
|
|
16
|
+
ProductDemoWindowKey,
|
|
17
|
+
ProductDemoWindowMeta,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
export interface DemoStageProps {
|
|
21
|
+
scenarios?: ProductDemoScenario[];
|
|
22
|
+
initialScenarioIndex?: number;
|
|
23
|
+
activeScenarioKey?: string;
|
|
24
|
+
autoCycle?: boolean;
|
|
25
|
+
cycleIntervalMs?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
enableMotion?: boolean;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type WindowRenderData = {
|
|
32
|
+
key: ProductDemoWindowKey;
|
|
33
|
+
meta: ProductDemoWindowMeta;
|
|
34
|
+
content: React.ReactNode;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const WINDOW_ORDER: ProductDemoWindowKey[] = [
|
|
38
|
+
"workflowStudio",
|
|
39
|
+
"knowledgePanel",
|
|
40
|
+
"runConsole",
|
|
41
|
+
"approvalInbox",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const HIGHLIGHT_TONE_CLASSES: Record<ProductDemoHighlightTone, string> = {
|
|
45
|
+
neutral: "border-border/60 bg-muted/60 text-muted-foreground",
|
|
46
|
+
info: "border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-300",
|
|
47
|
+
success:
|
|
48
|
+
"border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300",
|
|
49
|
+
warning:
|
|
50
|
+
"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
51
|
+
danger: "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-300",
|
|
52
|
+
accent: "border-primary/35 bg-primary/10 text-primary",
|
|
53
|
+
muted: "border-border/60 bg-background/70 text-muted-foreground",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
|
|
57
|
+
neutral: "border-border/60 bg-muted/55 text-muted-foreground",
|
|
58
|
+
info: "border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-300",
|
|
59
|
+
success:
|
|
60
|
+
"border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300",
|
|
61
|
+
warning:
|
|
62
|
+
"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
63
|
+
danger: "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-300",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function clampInitialScenarioIndex(index: number | undefined, count: number) {
|
|
67
|
+
if (count <= 0) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Math.max(0, Math.min(index ?? 0, count - 1));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getHighlightClass(tone: ProductDemoHighlightTone = "accent") {
|
|
75
|
+
return HIGHLIGHT_TONE_CLASSES[tone];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getStatusClass(tone: ProductDemoStatusTone = "neutral") {
|
|
79
|
+
return STATUS_TONE_CLASSES[tone];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function useActiveScenario({
|
|
83
|
+
scenarios,
|
|
84
|
+
initialScenarioIndex,
|
|
85
|
+
activeScenarioKey,
|
|
86
|
+
autoCycle,
|
|
87
|
+
cycleIntervalMs,
|
|
88
|
+
}: {
|
|
89
|
+
scenarios: ProductDemoScenario[];
|
|
90
|
+
initialScenarioIndex?: number;
|
|
91
|
+
activeScenarioKey?: string;
|
|
92
|
+
autoCycle?: boolean;
|
|
93
|
+
cycleIntervalMs?: number;
|
|
94
|
+
}) {
|
|
95
|
+
const fallbackIndex = React.useMemo(
|
|
96
|
+
() => clampInitialScenarioIndex(initialScenarioIndex, scenarios.length),
|
|
97
|
+
[initialScenarioIndex, scenarios.length],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const keyedIndex = React.useMemo(() => {
|
|
101
|
+
if (!activeScenarioKey) {
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return scenarios.findIndex(
|
|
106
|
+
(scenario) => scenario.key === activeScenarioKey,
|
|
107
|
+
);
|
|
108
|
+
}, [activeScenarioKey, scenarios]);
|
|
109
|
+
|
|
110
|
+
const controlledIndex = keyedIndex >= 0 ? keyedIndex : fallbackIndex;
|
|
111
|
+
const [internalIndex, setInternalIndex] = React.useState(controlledIndex);
|
|
112
|
+
const activeIndex = activeScenarioKey ? controlledIndex : internalIndex;
|
|
113
|
+
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (activeScenarioKey) {
|
|
116
|
+
setInternalIndex(controlledIndex);
|
|
117
|
+
}
|
|
118
|
+
}, [activeScenarioKey, controlledIndex]);
|
|
119
|
+
|
|
120
|
+
React.useEffect(() => {
|
|
121
|
+
if (activeScenarioKey || !autoCycle || scenarios.length <= 1) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const intervalMs = Math.max(cycleIntervalMs ?? 4500, 1200);
|
|
126
|
+
const timer = window.setInterval(() => {
|
|
127
|
+
setInternalIndex((currentIndex) => (currentIndex + 1) % scenarios.length);
|
|
128
|
+
}, intervalMs);
|
|
129
|
+
|
|
130
|
+
return () => window.clearInterval(timer);
|
|
131
|
+
}, [activeScenarioKey, autoCycle, cycleIntervalMs, scenarios.length]);
|
|
132
|
+
|
|
133
|
+
return scenarios[activeIndex] ?? scenarios[0];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function HighlightPills({
|
|
137
|
+
highlights,
|
|
138
|
+
}: {
|
|
139
|
+
highlights?: ProductDemoHighlightTarget[];
|
|
140
|
+
}) {
|
|
141
|
+
if (!highlights?.length) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="flex flex-wrap gap-2">
|
|
147
|
+
{highlights.map((highlight) => (
|
|
148
|
+
<span
|
|
149
|
+
key={highlight.id}
|
|
150
|
+
className={cn(
|
|
151
|
+
"rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.16em]",
|
|
152
|
+
getHighlightClass(highlight.tone),
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
{highlight.label ?? highlight.id}
|
|
156
|
+
</span>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getWindowRenderData(
|
|
163
|
+
scenario: ProductDemoScenario,
|
|
164
|
+
): WindowRenderData[] {
|
|
165
|
+
return [
|
|
166
|
+
{
|
|
167
|
+
key: "workflowStudio",
|
|
168
|
+
meta: scenario.workflowStudio.window,
|
|
169
|
+
content: (
|
|
170
|
+
<>
|
|
171
|
+
<HighlightPills highlights={scenario.workflowStudio.highlights} />
|
|
172
|
+
<WorkflowStudioPanel state={scenario.workflowStudio} />
|
|
173
|
+
</>
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
key: "knowledgePanel",
|
|
178
|
+
meta: scenario.knowledgePanel.window,
|
|
179
|
+
content: (
|
|
180
|
+
<>
|
|
181
|
+
<HighlightPills highlights={scenario.knowledgePanel.highlights} />
|
|
182
|
+
<KnowledgePanel state={scenario.knowledgePanel} />
|
|
183
|
+
</>
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "runConsole",
|
|
188
|
+
meta: scenario.runConsole.window,
|
|
189
|
+
content: (
|
|
190
|
+
<>
|
|
191
|
+
<HighlightPills highlights={scenario.runConsole.highlights} />
|
|
192
|
+
<RunConsolePanel state={scenario.runConsole} />
|
|
193
|
+
</>
|
|
194
|
+
),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
key: "approvalInbox",
|
|
198
|
+
meta: scenario.approvalInbox.window,
|
|
199
|
+
content: (
|
|
200
|
+
<>
|
|
201
|
+
<HighlightPills highlights={scenario.approvalInbox.highlights} />
|
|
202
|
+
<ApprovalInboxPanel state={scenario.approvalInbox} />
|
|
203
|
+
</>
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getWindowShellClass(key: ProductDemoWindowKey) {
|
|
210
|
+
switch (key) {
|
|
211
|
+
case "workflowStudio":
|
|
212
|
+
return "lg:absolute lg:left-[2%] lg:top-[9%] lg:h-[66%] lg:w-[46%]";
|
|
213
|
+
case "knowledgePanel":
|
|
214
|
+
return "lg:absolute lg:left-[50%] lg:top-[10%] lg:h-[34%] lg:w-[30%] xl:left-[49%] xl:w-[29%]";
|
|
215
|
+
case "runConsole":
|
|
216
|
+
return "lg:absolute lg:left-[50%] lg:bottom-[10%] lg:h-[33%] lg:w-[32%] xl:left-[49%] xl:w-[31%]";
|
|
217
|
+
case "approvalInbox":
|
|
218
|
+
return "lg:absolute lg:right-[1%] lg:top-[17%] lg:h-[45%] lg:w-[24%] xl:right-[2%] xl:h-[44%] xl:w-[23%]";
|
|
219
|
+
default:
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getTransformStyle(
|
|
225
|
+
meta: ProductDemoWindowMeta,
|
|
226
|
+
): React.CSSProperties | undefined {
|
|
227
|
+
const layoutHint = meta.layoutHint;
|
|
228
|
+
|
|
229
|
+
if (!layoutHint) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const translateX = layoutHint.x ?? 0;
|
|
234
|
+
const translateY = layoutHint.y ?? 0;
|
|
235
|
+
const rotate = layoutHint.rotateDeg ?? 0;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
zIndex: layoutHint.zIndex,
|
|
239
|
+
transform: `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg)`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function DemoStage({
|
|
244
|
+
scenarios = [],
|
|
245
|
+
initialScenarioIndex,
|
|
246
|
+
activeScenarioKey,
|
|
247
|
+
autoCycle = false,
|
|
248
|
+
cycleIntervalMs = 4500,
|
|
249
|
+
className,
|
|
250
|
+
enableMotion = true,
|
|
251
|
+
ariaLabel = "Product demo stage",
|
|
252
|
+
}: DemoStageProps) {
|
|
253
|
+
const activeScenario = useActiveScenario({
|
|
254
|
+
scenarios,
|
|
255
|
+
initialScenarioIndex,
|
|
256
|
+
activeScenarioKey,
|
|
257
|
+
autoCycle,
|
|
258
|
+
cycleIntervalMs,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const activeIndex = React.useMemo(() => {
|
|
262
|
+
if (!activeScenario) {
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return scenarios.findIndex(
|
|
267
|
+
(scenario) => scenario.key === activeScenario.key,
|
|
268
|
+
);
|
|
269
|
+
}, [activeScenario, scenarios]);
|
|
270
|
+
|
|
271
|
+
if (!activeScenario) {
|
|
272
|
+
return (
|
|
273
|
+
<div
|
|
274
|
+
data-product-demo-stage
|
|
275
|
+
className={cn(
|
|
276
|
+
"relative isolate min-h-[24rem] w-full overflow-hidden rounded-[2rem] border border-border/60 bg-gradient-to-br from-background via-background to-muted/40 shadow-2xl",
|
|
277
|
+
enableMotion &&
|
|
278
|
+
"transition-transform duration-300 hover:-translate-y-1 motion-reduce:transition-none",
|
|
279
|
+
className,
|
|
280
|
+
)}
|
|
281
|
+
aria-label={ariaLabel}
|
|
282
|
+
>
|
|
283
|
+
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(59,130,246,0.12),transparent_38%),linear-gradient(135deg,rgba(148,163,184,0.08),transparent_55%)]" />
|
|
284
|
+
<div className="absolute inset-4 rounded-[1.5rem] border border-dashed border-border/60 bg-background/70" />
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const windows = getWindowRenderData(activeScenario).sort(
|
|
290
|
+
(a, b) => WINDOW_ORDER.indexOf(a.key) - WINDOW_ORDER.indexOf(b.key),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div
|
|
295
|
+
data-product-demo-stage
|
|
296
|
+
data-auto-cycle={autoCycle ? "true" : "false"}
|
|
297
|
+
data-enable-motion={enableMotion ? "true" : "false"}
|
|
298
|
+
data-scenario-count={scenarios.length}
|
|
299
|
+
data-active-scenario-key={activeScenario.key}
|
|
300
|
+
data-active-scenario-index={activeIndex}
|
|
301
|
+
data-cycle-interval-ms={cycleIntervalMs}
|
|
302
|
+
className={cn(
|
|
303
|
+
"relative isolate min-h-[30rem] w-full overflow-hidden rounded-[2rem] border border-border/60 bg-[radial-gradient(circle_at_top,rgba(59,130,246,0.13),transparent_35%),linear-gradient(135deg,rgba(15,23,42,0.04),transparent_55%)] shadow-2xl",
|
|
304
|
+
className,
|
|
305
|
+
)}
|
|
306
|
+
aria-label={ariaLabel}
|
|
307
|
+
>
|
|
308
|
+
<div className="absolute inset-0 bg-gradient-to-br from-background via-background/96 to-muted/45" />
|
|
309
|
+
<div className="absolute inset-x-0 top-0 h-24 bg-gradient-to-b from-primary/8 to-transparent" />
|
|
310
|
+
|
|
311
|
+
<div className="relative z-10 flex min-h-[30rem] flex-col gap-4 p-4 sm:p-5 lg:block lg:p-6">
|
|
312
|
+
<div className="flex flex-wrap items-center justify-between gap-3 pb-1">
|
|
313
|
+
<div>
|
|
314
|
+
{activeScenario.label && (
|
|
315
|
+
<div className="text-[11px] font-medium uppercase tracking-[0.18em] text-primary/80">
|
|
316
|
+
{activeScenario.label}
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
{activeScenario.description && (
|
|
320
|
+
<p className="mt-1 max-w-xl text-xs leading-relaxed text-muted-foreground">
|
|
321
|
+
{activeScenario.description}
|
|
322
|
+
</p>
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<HighlightPills highlights={activeScenario.highlights} />
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<div className="grid gap-4 lg:hidden">
|
|
330
|
+
{windows.map((windowData) => {
|
|
331
|
+
const activeWindow =
|
|
332
|
+
activeScenario.activeWindow === windowData.key ||
|
|
333
|
+
(!activeScenario.activeWindow &&
|
|
334
|
+
windowData.key === "workflowStudio");
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<DemoWindow
|
|
338
|
+
key={windowData.key}
|
|
339
|
+
window={windowData.meta}
|
|
340
|
+
active={activeWindow}
|
|
341
|
+
enableMotion={enableMotion}
|
|
342
|
+
>
|
|
343
|
+
{windowData.content}
|
|
344
|
+
</DemoWindow>
|
|
345
|
+
);
|
|
346
|
+
})}
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<div className="hidden lg:block lg:h-[34rem] xl:h-[36rem]">
|
|
350
|
+
{windows.map((windowData) => {
|
|
351
|
+
const activeWindow =
|
|
352
|
+
activeScenario.activeWindow === windowData.key ||
|
|
353
|
+
(!activeScenario.activeWindow &&
|
|
354
|
+
windowData.key === "workflowStudio");
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<motion.div
|
|
358
|
+
key={windowData.key}
|
|
359
|
+
initial={
|
|
360
|
+
enableMotion ? { opacity: 0, y: 18, scale: 0.98 } : false
|
|
361
|
+
}
|
|
362
|
+
animate={{
|
|
363
|
+
opacity: 1,
|
|
364
|
+
y: 0,
|
|
365
|
+
scale: activeWindow ? 1 : 0.985,
|
|
366
|
+
}}
|
|
367
|
+
transition={
|
|
368
|
+
enableMotion
|
|
369
|
+
? {
|
|
370
|
+
type: "tween",
|
|
371
|
+
duration: 0.45,
|
|
372
|
+
}
|
|
373
|
+
: { duration: 0 }
|
|
374
|
+
}
|
|
375
|
+
className={cn(
|
|
376
|
+
"will-change-transform",
|
|
377
|
+
getWindowShellClass(windowData.key),
|
|
378
|
+
)}
|
|
379
|
+
style={getTransformStyle(windowData.meta)}
|
|
380
|
+
>
|
|
381
|
+
<DemoWindow
|
|
382
|
+
window={windowData.meta}
|
|
383
|
+
active={activeWindow}
|
|
384
|
+
dimmed={!activeWindow}
|
|
385
|
+
enableMotion={enableMotion}
|
|
386
|
+
className="h-full"
|
|
387
|
+
>
|
|
388
|
+
{windowData.content}
|
|
389
|
+
</DemoWindow>
|
|
390
|
+
</motion.div>
|
|
391
|
+
);
|
|
392
|
+
})}
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import type { ProductDemoStatusTone, ProductDemoWindowMeta } from "./types";
|
|
6
|
+
|
|
7
|
+
export interface DemoWindowProps {
|
|
8
|
+
window: ProductDemoWindowMeta;
|
|
9
|
+
className?: string;
|
|
10
|
+
contentClassName?: string;
|
|
11
|
+
chromeClassName?: string;
|
|
12
|
+
bodyClassName?: string;
|
|
13
|
+
active?: boolean;
|
|
14
|
+
dimmed?: boolean;
|
|
15
|
+
enableMotion?: boolean;
|
|
16
|
+
showControls?: boolean;
|
|
17
|
+
showResizeHandle?: boolean;
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const STATUS_TONE_CLASSES: Record<ProductDemoStatusTone, string> = {
|
|
22
|
+
neutral: "border-border/60 bg-muted/60 text-muted-foreground",
|
|
23
|
+
info: "border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-300",
|
|
24
|
+
success:
|
|
25
|
+
"border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-300",
|
|
26
|
+
warning:
|
|
27
|
+
"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
28
|
+
danger: "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-300",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function DemoWindow({
|
|
32
|
+
window,
|
|
33
|
+
className,
|
|
34
|
+
contentClassName,
|
|
35
|
+
chromeClassName,
|
|
36
|
+
bodyClassName,
|
|
37
|
+
active = false,
|
|
38
|
+
dimmed = false,
|
|
39
|
+
enableMotion = true,
|
|
40
|
+
showControls = true,
|
|
41
|
+
showResizeHandle = true,
|
|
42
|
+
children,
|
|
43
|
+
}: DemoWindowProps) {
|
|
44
|
+
const statusTone = window.status?.tone ?? "neutral";
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<section
|
|
48
|
+
className={cn(
|
|
49
|
+
"group relative flex h-full min-h-[14rem] flex-col overflow-hidden rounded-[1.5rem] border border-border/60 bg-card/92 shadow-[0_18px_48px_-24px_rgba(15,23,42,0.45)] backdrop-blur-xl",
|
|
50
|
+
enableMotion &&
|
|
51
|
+
"transition-[transform,opacity,box-shadow,border-color] duration-500 ease-out motion-reduce:transition-none",
|
|
52
|
+
active &&
|
|
53
|
+
"border-primary/45 shadow-[0_24px_60px_-26px_rgba(59,130,246,0.45)]",
|
|
54
|
+
dimmed && "opacity-90",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
data-product-demo-window={window.key}
|
|
58
|
+
data-active={active ? "true" : "false"}
|
|
59
|
+
aria-label={window.title}
|
|
60
|
+
>
|
|
61
|
+
<header
|
|
62
|
+
className={cn(
|
|
63
|
+
"relative flex items-start justify-between gap-3 border-b border-border/50 px-4 py-3 sm:px-5",
|
|
64
|
+
chromeClassName,
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<div className="flex items-start gap-3">
|
|
68
|
+
{showControls && (
|
|
69
|
+
<div className="mt-1 flex items-center gap-1.5 opacity-75 transition-opacity duration-200 group-hover:opacity-100">
|
|
70
|
+
<span className="h-2.5 w-2.5 rounded-full bg-rose-400/90" />
|
|
71
|
+
<span className="h-2.5 w-2.5 rounded-full bg-amber-400/90" />
|
|
72
|
+
<span className="h-2.5 w-2.5 rounded-full bg-emerald-400/90" />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<div className="min-w-0">
|
|
77
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
78
|
+
<h3 className="truncate text-sm font-semibold text-card-foreground sm:text-[0.95rem]">
|
|
79
|
+
{window.title}
|
|
80
|
+
</h3>
|
|
81
|
+
{window.badge && (
|
|
82
|
+
<span className="rounded-full border border-border/60 bg-muted/70 px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.16em] text-muted-foreground">
|
|
83
|
+
{window.badge}
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
{window.subtitle && (
|
|
88
|
+
<p className="mt-1 truncate text-xs text-muted-foreground sm:text-[0.8rem]">
|
|
89
|
+
{window.subtitle}
|
|
90
|
+
</p>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{window.status?.label && (
|
|
96
|
+
<span
|
|
97
|
+
className={cn(
|
|
98
|
+
"shrink-0 rounded-full border px-2.5 py-1 text-[10px] font-medium uppercase tracking-[0.16em]",
|
|
99
|
+
STATUS_TONE_CLASSES[statusTone],
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{window.status.label}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</header>
|
|
106
|
+
|
|
107
|
+
<div
|
|
108
|
+
className={cn(
|
|
109
|
+
"relative flex-1 px-4 py-4 sm:px-5 sm:py-5",
|
|
110
|
+
bodyClassName,
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
<div className={cn("h-full", contentClassName)}>{children}</div>
|
|
114
|
+
|
|
115
|
+
{showResizeHandle && (
|
|
116
|
+
<div
|
|
117
|
+
aria-hidden="true"
|
|
118
|
+
className="pointer-events-none absolute bottom-3 right-3 h-4 w-4 opacity-0 transition-opacity duration-200 group-hover:opacity-70"
|
|
119
|
+
>
|
|
120
|
+
<span className="absolute bottom-0 right-0 h-px w-3 rotate-45 bg-border/80" />
|
|
121
|
+
<span className="absolute bottom-1 right-0 h-px w-2 rotate-45 bg-border/70" />
|
|
122
|
+
<span className="absolute bottom-0 right-1 h-px w-2 rotate-45 bg-border/60" />
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
);
|
|
128
|
+
}
|