Stories renaming
This commit is contained in:
parent
061f79c473
commit
b274d0d018
|
|
@ -24,21 +24,79 @@
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.itemWrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 0;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--bg-active);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
background: transparent;
|
||||||
|
border: none;
|
||||||
.active {
|
outline: none;
|
||||||
color: var(--text);
|
cursor: pointer;
|
||||||
background: var(--bg-active);
|
color: inherit;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--bg-active);
|
background: var(--bg-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active .item {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
|
.itemWrapper:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButton {
|
||||||
|
padding: 4px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: var(--bg-hover);
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Sidebar } from "./sidebar";
|
||||||
import { useAppState } from "../contexts/state";
|
import { useAppState } from "../contexts/state";
|
||||||
import type { Story } from "../contexts/state";
|
import type { Story } from "../contexts/state";
|
||||||
import styles from '../assets/menu-sidebar.module.css';
|
import styles from '../assets/menu-sidebar.module.css';
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
// ─── Story Item ───────────────────────────────────────────────────────────────
|
// ─── Story Item ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -10,16 +11,68 @@ interface StoryItemProps {
|
||||||
story: Story;
|
story: Story;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
|
onRename: (newTitle: string) => void;
|
||||||
|
onDelete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StoryItem = ({ story, active, onSelect }: StoryItemProps) => (
|
const StoryItem = ({ story, active, onSelect, onRename, onDelete }: 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
|
<button
|
||||||
class={clsx(styles.item, active && styles.active)}
|
class={clsx(styles.item, active && styles.active)}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
>
|
>
|
||||||
{story.title}
|
{story.title}
|
||||||
</button>
|
</button>
|
||||||
);
|
<div class={styles.actions}>
|
||||||
|
<button class={styles.actionButton} onClick={() => setIsEditing(true)} title="Rename">
|
||||||
|
✎
|
||||||
|
</button>
|
||||||
|
<button class={styles.actionButton} onClick={onDelete} title="Delete">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// ─── Menu Sidebar ─────────────────────────────────────────────────────────────
|
// ─── Menu Sidebar ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -34,6 +87,18 @@ export const MenuSidebar = () => {
|
||||||
dispatch({ type: 'SELECT_STORY', id });
|
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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar side="left">
|
<Sidebar side="left">
|
||||||
<div class={styles.menu}>
|
<div class={styles.menu}>
|
||||||
|
|
@ -47,6 +112,8 @@ export const MenuSidebar = () => {
|
||||||
story={story}
|
story={story}
|
||||||
active={story.id === currentStory?.id}
|
active={story.id === currentStory?.id}
|
||||||
onSelect={() => handleSelect(story.id)}
|
onSelect={() => handleSelect(story.id)}
|
||||||
|
onRename={(newTitle) => handleRename(story.id, newTitle)}
|
||||||
|
onDelete={() => handleDelete(story.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue