70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
import React, { useCallback, useEffect, useState } from 'react'
|
|
import type { Frame } from '@/lib/wysiwyg/schema'
|
|
|
|
export type LayersPanelProps = {
|
|
frame: Frame
|
|
onChange?: (next: Frame) => void
|
|
}
|
|
|
|
export function LayersPanel({ frame, onChange }: LayersPanelProps) {
|
|
const [model, setModel] = useState<Frame>(frame)
|
|
|
|
useEffect(() => {
|
|
setModel(frame)
|
|
}, [frame])
|
|
|
|
const move = useCallback((id: string, dir: 1 | -1) => {
|
|
const idx = model.layers.findIndex((l) => l.id === id)
|
|
if (idx < 0) return
|
|
const j = idx + dir
|
|
if (j < 0 || j >= model.layers.length) return
|
|
const nextLayers = model.layers.slice()
|
|
const [it] = nextLayers.splice(idx, 1)
|
|
nextLayers.splice(j, 0, it)
|
|
const next = { ...model, layers: nextLayers }
|
|
setModel(next)
|
|
onChange?.(next)
|
|
}, [model, onChange])
|
|
|
|
const toggle = useCallback((id: string, key: 'locked' | 'visible') => {
|
|
const nextLayers = model.layers.map((l) => l.id === id ? { ...l, [key]: key === 'visible' ? !l.visible : !l.locked } : l)
|
|
const next = { ...model, layers: nextLayers }
|
|
setModel(next)
|
|
onChange?.(next)
|
|
}, [model, onChange])
|
|
|
|
return (
|
|
<div aria-label="Layers Panel">
|
|
{model.layers.map((l) => (
|
|
<div key={l.id} data-testid="layer-item" style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
<span>{l.name}</span>
|
|
<button type="button" data-testid={`layer-up-${l.id}`} onClick={() => move(l.id, -1)} aria-label={`move up ${l.name}`}>
|
|
↑
|
|
</button>
|
|
<button type="button" data-testid={`layer-down-${l.id}`} onClick={() => move(l.id, 1)} aria-label={`move down ${l.name}`}>
|
|
↓
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-testid={`layer-lock-${l.id}`}
|
|
aria-pressed={l.locked ? 'true' : 'false'}
|
|
onClick={() => toggle(l.id, 'locked')}
|
|
aria-label={`lock ${l.name}`}
|
|
>
|
|
🔒
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-testid={`layer-hide-${l.id}`}
|
|
aria-pressed={l.visible ? 'false' : 'true'}
|
|
onClick={() => toggle(l.id, 'visible')}
|
|
aria-label={`toggle ${l.name} visibility`}
|
|
>
|
|
👁
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|