47 lines
3.5 KiB
TypeScript
47 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { ChevronDown, Search, X } from "lucide-react";
|
|
import { XMIND_MARKER_GROUPS, type XmindMarker } from "@/lib/flow-board/xmind-markers";
|
|
|
|
export function XmindMarkerPanel({ selectedCount, activeIds, mixedIds, onApply, onClear, onClose }: {
|
|
selectedCount: number;
|
|
activeIds: string[];
|
|
mixedIds: string[];
|
|
onApply: (marker: XmindMarker) => void;
|
|
onClear: () => void;
|
|
onClose: () => void;
|
|
}) {
|
|
const [query, setQuery] = useState("");
|
|
const [showAll, setShowAll] = useState(false);
|
|
const [collapsed, setCollapsed] = useState<string[]>([]);
|
|
const needle = query.trim().toLowerCase();
|
|
const groups = XMIND_MARKER_GROUPS.filter(group => showAll || needle || !group.hidden).map(group => ({
|
|
...group, markers: group.markers.filter(marker => (showAll || needle || !marker.hidden) && (!needle || `${group.label} ${marker.name} ${marker.id}`.toLowerCase().includes(needle))),
|
|
})).filter(group => group.markers.length);
|
|
return <aside className="xmind-format-panel xmind-marker-panel" aria-label="标记面板" data-flow-board-shell-control="true"
|
|
onPointerDownCapture={event => event.stopPropagation()} onMouseDownCapture={event => event.stopPropagation()}>
|
|
<div className="xmind-panel-heading"><span>标记</span><button type="button" aria-label="关闭标记面板" onClick={onClose}><X size={16} /></button></div>
|
|
<div className="xmind-marker-search"><Search size={14} /><input aria-label="搜索标记" placeholder="搜索标记" value={query} onChange={event => setQuery(event.target.value)} /></div>
|
|
<p className="xmind-marker-selection" role="status">{selectedCount ? `已选中 ${selectedCount} 个主题` : "先选中主题,再添加标记"}</p>
|
|
<div className="xmind-panel-content">
|
|
{groups.map(group => <section className="xmind-marker-group" key={group.id}>
|
|
<button type="button" className="xmind-marker-group-heading" aria-expanded={!collapsed.includes(group.id) || Boolean(needle)} onClick={() => setCollapsed(ids => ids.includes(group.id) ? ids.filter(id => id !== group.id) : [...ids, group.id])}>
|
|
<ChevronDown size={13} /><span>{group.label}</span><span>{group.markers.length}</span>
|
|
</button>
|
|
{!collapsed.includes(group.id) || needle ? <div className="xmind-marker-grid">
|
|
{group.markers.map(marker => <button type="button" key={marker.id} data-marker-id={marker.id}
|
|
aria-label={marker.id.startsWith("priority-") ? marker.name : `${group.label} ${marker.name}`}
|
|
title={`${group.label} · ${marker.name}`} aria-pressed={activeIds.includes(marker.id) ? true : mixedIds.includes(marker.id) ? "mixed" : false}
|
|
disabled={!selectedCount} onPointerDown={event => event.preventDefault()} onMouseDown={event => event.preventDefault()} onClick={() => onApply(marker)}>
|
|
<img src={marker.asset} alt="" draggable={false} loading="lazy" />
|
|
</button>)}
|
|
</div> : null}
|
|
</section>)}
|
|
{!groups.length ? <p className="xmind-marker-empty">没有匹配的标记</p> : null}
|
|
{!needle ? <button type="button" className="xmind-marker-more" aria-expanded={showAll} onClick={() => setShowAll(value => !value)}>{showAll ? "收起更多标记" : "更多标记"}</button> : null}
|
|
</div>
|
|
<div className="xmind-marker-footer"><button type="button" disabled={!selectedCount} onPointerDown={event => event.preventDefault()} onMouseDown={event => event.preventDefault()} onClick={onClear}>清空所选节点标记</button></div>
|
|
</aside>;
|
|
}
|