artcraft: move OnboardingHelper + Help icon kit into ui-pagescene

OnboardingHelper had been left in apps because it depended on the
Help.tsx icon kit (Key, KeyGroup, Mouse, Plus etc.) under
ControlsTopButtons/Help. Help.tsx itself has no host coupling --
it imports only @storyteller/ui-label -- so both move into the lib
together:

- src/lib/comps/ControlsTopButtons/Help/{Help,index}.tsx — keyboard
  + mouse icon visual primitives (Key, KeyGroup, Mouse, Plus,
  ShortcutsGroup, Shortcut, Help itself).
- src/lib/comps/OnboardingHelper.tsx — the WASD/click hint that
  fades out on first key/mouse interaction.

Apps copies become re-export shims. Public API exposes the icon
primitives so future host-side help overlays can reuse them.

Verified: artcraft typecheck at 70 errors (one fewer than the 71
prior; one of OnboardingHelper's pre-existing unused-import errors
disappeared with the migration).
This commit is contained in:
Tanish Manku committed 2026-05-08 15:07:57 -07:00
1 parent e2f1b218bc
commit e01b02e463
7 files changed
+267 -248

No files matched your search

@@ -1,144 +1,8 @@
import { Label } from "@storyteller/ui-label";
export const ShortcutsGroup = (props: {
label: string;
children?: React.ReactNode;
}) => (
<div className="flex flex-col gap-1">
<Label className="text-md font-semibold" {...props}>
{props.label}
</Label>
<div className="relative flex flex-col gap-2.5">
{props.children}
<div className="absolute top-0 h-full w-0.5 bg-white/15" />
</div>
</div>
);
export const Shortcut = (props: {
label: string;
children: React.ReactNode;
}) => (
<div className="flex items-center gap-2 ps-4">
<span className="text-md w-[280px] font-medium opacity-80">
{props.label}
</span>
{props.children}
</div>
);
export const Key = (props: { button: string }) => (
<div className="flex h-[30px] w-auto min-w-[30px] items-center justify-center rounded-md border-b-2 border-[#9E9EA6] bg-white px-2 text-center text-sm font-bold text-ui-background">
{props.button}
</div>
);
export const KeyGroup = (props: { children: React.ReactNode }) => (
<div className="flex gap-1">{props.children}</div>
);
export const Mouse = (props: {
button: "left" | "middle" | "right";
label?: string;
}) => (
<div className="flex items-center gap-2.5">
<img
alt={`${props.button} mouse`}
src={`/resources/icons/mouse_${props.button === "left" ? "lmb" : props.button === "middle" ? "mmb" : "rmb"}.png`}
className="h-7 w-auto object-contain"
/>
<span className="text-sm font-normal opacity-60">{props.label}</span>
</div>
);
export const Plus = () => <div className="text-xl font-medium">+</div>;
const ShortcutsView = () => (
<div className="grid select-none grid-cols-2 gap-12 ps-4 mt-6">
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Navigation">
<Shortcut label="Orbit View">
<KeyGroup>
<Mouse button="left" label="(Hold)" />
</KeyGroup>
</Shortcut>
<Shortcut label="Forward Backward">
<KeyGroup>
<Key button="W" />
<Key button="S" />
</KeyGroup>
</Shortcut>
<Shortcut label="Left Right">
<KeyGroup>
<Key button="A" />
<Key button="D" />
</KeyGroup>
</Shortcut>
<Shortcut label="Up Down">
<KeyGroup>
<Key button="E" />
<Key button="Q" />
</KeyGroup>
</Shortcut>
<Shortcut label="Speed Boost">
<KeyGroup>
<Key button="Shift" />
</KeyGroup>
</Shortcut>
</ShortcutsGroup>
<ShortcutsGroup label="Interaction">
<Shortcut label="Select Object">
<Mouse button="left" label="(Click)" />
</Shortcut>
<Shortcut label="Delete Selection">
<Key button="Del" />
</Shortcut>
</ShortcutsGroup>
</div>
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Shortcuts">
<Shortcut label="Transform">
<Key button="T" />
</Shortcut>
<Shortcut label="Rotate">
<Key button="R" />
</Shortcut>
<Shortcut label="Scale">
<Key button="G" />
</Shortcut>
<Shortcut label="Focus">
<Key button="F" />
</Shortcut>
<Shortcut label="Copy">
<Key button="Ctrl" />
<Plus></Plus>
<Key button="C" />
</Shortcut>
<Shortcut label="Paste">
<Key button="Ctrl" />
<Plus></Plus>
<Key button="Shift" />
<Plus></Plus>
<Key button="V" />
</Shortcut>
<Shortcut label="Open 3D Asset Modal">
<Key button="B" />
</Shortcut>
</ShortcutsGroup>
</div>
</div>
);
export const Help = () => {
return (
<div className="flex flex-col h-full">
<div className="flex-1">
<ShortcutsView />
</div>
</div>
);
};
export {
Help,
Key,
KeyGroup,
Mouse,
Plus,
ShortcutsGroup,
} from "@storyteller/ui-pagescene";
@@ -1,103 +1 @@
import { useState, useEffect } from "react";
import { Transition } from "@headlessui/react";
import { Key, KeyGroup, Mouse, Plus } from "./ControlsTopButtons/Help/Help";
import { CloseButton } from "@storyteller/ui-close-button";
const STORAGE_KEY = "onboarding_helper_dismissed";
const EXPIRATION_DAYS = 3;
export const OnboardingHelper = () => {
const [actionPerformed, setActionPerformed] = useState(false);
const [keyPressed, setKeyPressed] = useState(false);
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
// Check if the helper was dismissed
/*
const dismissedData = localStorage.getItem(STORAGE_KEY);
if (dismissedData) {
const { timestamp } = JSON.parse(dismissedData);
const expirationTime = timestamp + EXPIRATION_DAYS * 24 * 60 * 60 * 1000; // 3 days in milliseconds
if (Date.now() < expirationTime) {
setIsVisible(false);
} else {
// Clear expired data
localStorage.removeItem(STORAGE_KEY);
}
}
*/
}, []);
const handleClose = () => {
setIsVisible(false);
// Store dismissal timestamp
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
timestamp: Date.now(),
}),
);
};
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (["W", "A", "S", "D"].includes(event.key.toUpperCase())) {
setTimeout(() => setKeyPressed(true), 200);
}
};
const handleAction = () => {
setTimeout(() => setActionPerformed(true), 200);
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("mousedown", handleAction);
window.addEventListener("dragstart", handleAction);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("mousedown", handleAction);
window.removeEventListener("dragstart", handleAction);
};
}, []);
return (
<Transition
show={isVisible && !actionPerformed && !keyPressed}
enter="transition-opacity duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="glass pointer-events-none absolute left-1/2 bottom-56 z-10 flex -translate-x-1/2 flex-col items-center justify-center gap-1 rounded-xl border border-brand-primary !bg-brand-primary/10 px-3 pr-4 py-2">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="scale-75 origin-center -mx-3">
<KeyGroup>
<Key button="W" />
<Key button="A" />
<Key button="S" />
<Key button="D" />
</KeyGroup>
</div>
<p className="text-sm font-semibold">Move</p>
</div>
<div className="h-4 w-[1px] bg-white/20" />
<div className="flex items-center gap-2">
<div className="scale-75 origin-center -mx-1">
<Mouse button="left" />
</div>
<p className="text-sm font-semibold">Look</p>
</div>
</div>
<div className="pointer-events-auto absolute -top-2 -right-2">
<CloseButton onClick={handleClose} className="h-5 w-5 bg-black/50 hover:bg-black/80 rounded-full border border-white/10" />
</div>
</div>
</Transition>
);
};
export default OnboardingHelper;
export { OnboardingHelper, OnboardingHelper as default } from "@storyteller/ui-pagescene";
+1 -1
View File
@@ -1 +1 @@
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
@@ -144,6 +144,15 @@ export { ControlPanelSceneObject } from "./lib/comps/ControlPanelSceneObject";
export { SceneContainer } from "./lib/comps/SceneContainer";
export { PreviewEngineCamera } from "./lib/comps/PreviewEngineCamera";
export { PreviewImages } from "./lib/comps/PreviewImages";
export { OnboardingHelper } from "./lib/comps/OnboardingHelper";
export {
Help,
Key,
KeyGroup,
Mouse,
Plus,
ShortcutsGroup,
} from "./lib/comps/ControlsTopButtons/Help/Help";
// Viewport-size hook (also useful to host code that wants the same
// reactivity contract the lib comps use).
@@ -0,0 +1,144 @@
import { Label } from "@storyteller/ui-label";
export const ShortcutsGroup = (props: {
label: string;
children?: React.ReactNode;
}) => (
<div className="flex flex-col gap-1">
<Label className="text-md font-semibold" {...props}>
{props.label}
</Label>
<div className="relative flex flex-col gap-2.5">
{props.children}
<div className="absolute top-0 h-full w-0.5 bg-white/15" />
</div>
</div>
);
export const Shortcut = (props: {
label: string;
children: React.ReactNode;
}) => (
<div className="flex items-center gap-2 ps-4">
<span className="text-md w-[280px] font-medium opacity-80">
{props.label}
</span>
{props.children}
</div>
);
export const Key = (props: { button: string }) => (
<div className="flex h-[30px] w-auto min-w-[30px] items-center justify-center rounded-md border-b-2 border-[#9E9EA6] bg-white px-2 text-center text-sm font-bold text-ui-background">
{props.button}
</div>
);
export const KeyGroup = (props: { children: React.ReactNode }) => (
<div className="flex gap-1">{props.children}</div>
);
export const Mouse = (props: {
button: "left" | "middle" | "right";
label?: string;
}) => (
<div className="flex items-center gap-2.5">
<img
alt={`${props.button} mouse`}
src={`/resources/icons/mouse_${props.button === "left" ? "lmb" : props.button === "middle" ? "mmb" : "rmb"}.png`}
className="h-7 w-auto object-contain"
/>
<span className="text-sm font-normal opacity-60">{props.label}</span>
</div>
);
export const Plus = () => <div className="text-xl font-medium">+</div>;
const ShortcutsView = () => (
<div className="grid select-none grid-cols-2 gap-12 ps-4 mt-6">
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Navigation">
<Shortcut label="Orbit View">
<KeyGroup>
<Mouse button="left" label="(Hold)" />
</KeyGroup>
</Shortcut>
<Shortcut label="Forward Backward">
<KeyGroup>
<Key button="W" />
<Key button="S" />
</KeyGroup>
</Shortcut>
<Shortcut label="Left Right">
<KeyGroup>
<Key button="A" />
<Key button="D" />
</KeyGroup>
</Shortcut>
<Shortcut label="Up Down">
<KeyGroup>
<Key button="E" />
<Key button="Q" />
</KeyGroup>
</Shortcut>
<Shortcut label="Speed Boost">
<KeyGroup>
<Key button="Shift" />
</KeyGroup>
</Shortcut>
</ShortcutsGroup>
<ShortcutsGroup label="Interaction">
<Shortcut label="Select Object">
<Mouse button="left" label="(Click)" />
</Shortcut>
<Shortcut label="Delete Selection">
<Key button="Del" />
</Shortcut>
</ShortcutsGroup>
</div>
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Shortcuts">
<Shortcut label="Transform">
<Key button="T" />
</Shortcut>
<Shortcut label="Rotate">
<Key button="R" />
</Shortcut>
<Shortcut label="Scale">
<Key button="G" />
</Shortcut>
<Shortcut label="Focus">
<Key button="F" />
</Shortcut>
<Shortcut label="Copy">
<Key button="Ctrl" />
<Plus></Plus>
<Key button="C" />
</Shortcut>
<Shortcut label="Paste">
<Key button="Ctrl" />
<Plus></Plus>
<Key button="Shift" />
<Plus></Plus>
<Key button="V" />
</Shortcut>
<Shortcut label="Open 3D Asset Modal">
<Key button="B" />
</Shortcut>
</ShortcutsGroup>
</div>
</div>
);
export const Help = () => {
return (
<div className="flex flex-col h-full">
<div className="flex-1">
<ShortcutsView />
</div>
</div>
);
};
@@ -0,0 +1 @@
export { Help } from "./Help";
@@ -0,0 +1,103 @@
import { useState, useEffect } from "react";
import { Transition } from "@headlessui/react";
import { Key, KeyGroup, Mouse, Plus } from "./ControlsTopButtons/Help/Help";
import { CloseButton } from "@storyteller/ui-close-button";
const STORAGE_KEY = "onboarding_helper_dismissed";
const EXPIRATION_DAYS = 3;
export const OnboardingHelper = () => {
const [actionPerformed, setActionPerformed] = useState(false);
const [keyPressed, setKeyPressed] = useState(false);
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
// Check if the helper was dismissed
/*
const dismissedData = localStorage.getItem(STORAGE_KEY);
if (dismissedData) {
const { timestamp } = JSON.parse(dismissedData);
const expirationTime = timestamp + EXPIRATION_DAYS * 24 * 60 * 60 * 1000; // 3 days in milliseconds
if (Date.now() < expirationTime) {
setIsVisible(false);
} else {
// Clear expired data
localStorage.removeItem(STORAGE_KEY);
}
}
*/
}, []);
const handleClose = () => {
setIsVisible(false);
// Store dismissal timestamp
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
timestamp: Date.now(),
}),
);
};
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (["W", "A", "S", "D"].includes(event.key.toUpperCase())) {
setTimeout(() => setKeyPressed(true), 200);
}
};
const handleAction = () => {
setTimeout(() => setActionPerformed(true), 200);
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("mousedown", handleAction);
window.addEventListener("dragstart", handleAction);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("mousedown", handleAction);
window.removeEventListener("dragstart", handleAction);
};
}, []);
return (
<Transition
show={isVisible && !actionPerformed && !keyPressed}
enter="transition-opacity duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="glass pointer-events-none absolute left-1/2 bottom-56 z-10 flex -translate-x-1/2 flex-col items-center justify-center gap-1 rounded-xl border border-brand-primary !bg-brand-primary/10 px-3 pr-4 py-2">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="scale-75 origin-center -mx-3">
<KeyGroup>
<Key button="W" />
<Key button="A" />
<Key button="S" />
<Key button="D" />
</KeyGroup>
</div>
<p className="text-sm font-semibold">Move</p>
</div>
<div className="h-4 w-[1px] bg-white/20" />
<div className="flex items-center gap-2">
<div className="scale-75 origin-center -mx-1">
<Mouse button="left" />
</div>
<p className="text-sm font-semibold">Look</p>
</div>
</div>
<div className="pointer-events-auto absolute -top-2 -right-2">
<CloseButton onClick={handleClose} className="h-5 w-5 bg-black/50 hover:bg-black/80 rounded-full border border-white/10" />
</div>
</div>
</Transition>
);
};
export default OnboardingHelper;