80 lines
3.3 KiB
TypeScript
80 lines
3.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import type { Notebook } from '@shared/types';
|
|
|
|
interface Props {
|
|
notebooks: Notebook[];
|
|
selectedId: string | null;
|
|
onSelect: (id: string) => void;
|
|
onCreate: () => void;
|
|
onReorder?: (id: string, direction: 'up' | 'down') => Promise<void>;
|
|
}
|
|
|
|
export function NotebookList({ notebooks, selectedId, onSelect, onCreate, onReorder }: Props): React.ReactElement {
|
|
const [hoverId, setHoverId] = useState<string | null>(null);
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
{notebooks.map((nb, idx) => {
|
|
const active = nb.id === selectedId;
|
|
const hover = nb.id === hoverId;
|
|
const isFirst = idx === 0;
|
|
const isLast = idx === notebooks.length - 1;
|
|
return (
|
|
<div
|
|
key={nb.id}
|
|
onMouseEnter={() => setHoverId(nb.id)}
|
|
onMouseLeave={() => setHoverId(null)}
|
|
style={{ position: 'relative', display: 'flex' }}
|
|
>
|
|
<button
|
|
onClick={() => onSelect(nb.id)}
|
|
style={{
|
|
display: 'flex', alignItems: 'center', gap: 8,
|
|
padding: '6px 12px', background: active ? '#eaf3ff' : 'transparent',
|
|
border: 'none', cursor: 'pointer', textAlign: 'left',
|
|
color: active ? '#0a4b80' : '#333', fontSize: 13, flex: 1
|
|
}}
|
|
>
|
|
<span style={{
|
|
width: 8, height: 8, borderRadius: '50%',
|
|
background: nb.color ?? '#bbb', flexShrink: 0
|
|
}} />
|
|
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
{nb.name}
|
|
</span>
|
|
<span style={{ fontSize: 11, color: '#888' }}>{nb.noteCount}</span>
|
|
</button>
|
|
{hover && onReorder && notebooks.length > 1 && (
|
|
<div style={{ position: 'absolute', right: 4, top: 2, display: 'flex', gap: 2 }}>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); if (!isFirst) void onReorder(nb.id, 'up'); }}
|
|
disabled={isFirst}
|
|
aria-label={`${nb.name} 위로`}
|
|
title="위로"
|
|
style={{ background: 'rgba(255,255,255,0.9)', border: '1px solid #ccc', borderRadius: 3, fontSize: 10, padding: '0 4px', cursor: isFirst ? 'not-allowed' : 'pointer', opacity: isFirst ? 0.3 : 1 }}
|
|
>▲</button>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); if (!isLast) void onReorder(nb.id, 'down'); }}
|
|
disabled={isLast}
|
|
aria-label={`${nb.name} 아래로`}
|
|
title="아래로"
|
|
style={{ background: 'rgba(255,255,255,0.9)', border: '1px solid #ccc', borderRadius: 3, fontSize: 10, padding: '0 4px', cursor: isLast ? 'not-allowed' : 'pointer', opacity: isLast ? 0.3 : 1 }}
|
|
>▼</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
<button
|
|
onClick={onCreate}
|
|
style={{
|
|
padding: '6px 12px', background: 'transparent', border: 'none',
|
|
cursor: 'pointer', textAlign: 'left', color: '#888', fontSize: 12
|
|
}}
|
|
>
|
|
+ 새 노트북
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|