1
0
Fork 0
tsgames/src/games/storywriter/components/menu-sidebar.tsx

153 lines
5.5 KiB
TypeScript

import clsx from "clsx";
import { Sidebar } from "./sidebar";
import { ConnectionSettingsModal } from "./connection-settings-modal";
import { SettingsModal } from "./settings-modal";
import { useAppState } from "../contexts/state";
import { useBool } from "@common/hooks/useBool";
import type { Story } from "../contexts/state";
import styles from '../assets/menu-sidebar.module.css';
import { useState } from "preact/hooks";
import { Pencil, X, Plus, Plug, Settings, Copy } from "lucide-preact";
// ─── Story Item ───────────────────────────────────────────────────────────────
interface StoryItemProps {
story: Story;
active: boolean;
onSelect: () => void;
onRename: (newTitle: string) => void;
onDelete: () => void;
onDuplicate: () => void;
}
const StoryItem = ({ story, active, onSelect, onRename, onDelete, onDuplicate }: StoryItemProps) => {
const [isEditing, setIsEditing] = useState(false);
const [editTitle, setEditTitle] = useState(story.title);
const handleSubmit = () => {
if (editTitle.trim()) {
onRename(editTitle.trim());
}
setIsEditing(false);
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
handleSubmit();
} else if (e.key === 'Escape') {
setEditTitle(story.title);
setIsEditing(false);
}
};
const handleBlur = () => {
handleSubmit();
};
if (isEditing) {
return (
<div class={clsx(styles.itemWrapper, active && styles.active)}>
<input
class={styles.input}
value={editTitle}
onInput={(e) => setEditTitle((e.target as HTMLInputElement).value)}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
autoFocus
/>
</div>
);
}
return (
<div class={clsx(styles.itemWrapper, active && styles.active)}>
<button
class={clsx(styles.item, active && styles.active)}
onClick={onSelect}
>
{story.title}
</button>
<div class={styles.actions}>
<button class={styles.actionButton} onClick={() => setIsEditing(true)} title="Rename">
<Pencil size={14} />
</button>
<button class={styles.actionButton} onClick={onDuplicate} title="Duplicate">
<Copy size={14} />
</button>
<button class={styles.actionButton} onClick={onDelete} title="Delete">
<X size={14} />
</button>
</div>
</div>
);
};
// ─── Menu Sidebar ─────────────────────────────────────────────────────────────
export const MenuSidebar = () => {
const { stories, currentStory, dispatch } = useAppState();
const isConnectionSettingsOpen = useBool(false);
const isSettingsOpen = useBool(false);
const handleCreate = () => {
dispatch({ type: 'CREATE_STORY', title: 'New Story' });
};
const handleSelect = (id: string) => {
dispatch({ type: 'SELECT_STORY', id });
};
const handleRename = (id: string, newTitle: string) => {
dispatch({ type: 'RENAME_STORY', id, title: newTitle });
};
const handleDelete = (id: string) => {
const story = stories.find(s => s.id === id);
if (!story) return;
if (confirm(`Delete "${story.title}"?`)) {
dispatch({ type: 'DELETE_STORY', id });
}
};
const handleDuplicate = (id: string) => {
dispatch({ type: 'DUPLICATE_STORY', id });
};
return (
<Sidebar side="left">
<div class={styles.menu}>
<button class={styles.newButton} onClick={handleCreate}>
<Plus size={16} /> New Story
</button>
<div class={styles.list}>
{stories.map(story => (
<StoryItem
key={story.id}
story={story}
active={story.id === currentStory?.id}
onSelect={() => handleSelect(story.id)}
onRename={(newTitle) => handleRename(story.id, newTitle)}
onDelete={() => handleDelete(story.id)}
onDuplicate={() => handleDuplicate(story.id)}
/>
))}
</div>
<div class={styles.bottomButtons}>
<button class={styles.settingsButton} onClick={isSettingsOpen.toggle}>
<Settings size={16} /> Settings
</button>
<button class={styles.settingsButton} onClick={isConnectionSettingsOpen.toggle}>
<Plug size={16} /> Connection Settings
</button>
</div>
</div>
{isSettingsOpen.value && (
<SettingsModal onClose={isSettingsOpen.toggle} />
)}
{isConnectionSettingsOpen.value && (
<ConnectionSettingsModal onClose={isConnectionSettingsOpen.toggle} />
)}
</Sidebar>
);
};