Persist catalog layout and add inspector toolbar
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import { useState, type CSSProperties } from "react";
|
||||
import { cn } from "./cn.js";
|
||||
import { Icon, type IconName } from "./Icon.js";
|
||||
|
||||
export type ToolbarPlacement = "left" | "right" | "bottom";
|
||||
|
||||
export interface ToolbarItem<T extends string = string> {
|
||||
id: T;
|
||||
label: string;
|
||||
icon: IconName;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
onSelect: (id: T) => void;
|
||||
}
|
||||
|
||||
export interface ToolbarProps<T extends string = string> {
|
||||
items: Array<ToolbarItem<T>>;
|
||||
placement?: ToolbarPlacement;
|
||||
background?: string;
|
||||
border?: string;
|
||||
outline?: string;
|
||||
accent?: string;
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
lensCount?: number;
|
||||
autoHide?: boolean;
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Toolbar<T extends string = string>({
|
||||
items,
|
||||
placement = "left",
|
||||
background = "#111115",
|
||||
border = "#111117",
|
||||
outline = "#1c1c1c",
|
||||
accent = "#ff2f92",
|
||||
minSize = 25,
|
||||
maxSize = 87,
|
||||
lensCount = 5,
|
||||
autoHide = false,
|
||||
label = "Панель инструментов",
|
||||
className,
|
||||
}: ToolbarProps<T>) {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
const [visible, setVisible] = useState(!autoHide);
|
||||
const vertical = placement !== "bottom";
|
||||
const safeMin = Math.max(18, Math.min(42, minSize));
|
||||
const safeMax = Math.max(safeMin, Math.min(88, maxSize));
|
||||
const safeLens = Math.max(1, Math.min(13, lensCount % 2 === 0 ? lensCount + 1 : lensCount));
|
||||
const radius = Math.max(9, Math.round(safeMin * 0.42));
|
||||
|
||||
const style = {
|
||||
"--nodedc-toolbar-bg": background,
|
||||
"--nodedc-toolbar-border": border,
|
||||
"--nodedc-toolbar-outline": outline,
|
||||
"--nodedc-toolbar-accent": accent,
|
||||
"--nodedc-toolbar-min-size": `${safeMin}px`,
|
||||
"--nodedc-toolbar-max-size": `${safeMax}px`,
|
||||
"--nodedc-toolbar-radius": `${radius}px`,
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("nodedc-toolbar-wrap", className)}
|
||||
data-placement={placement}
|
||||
data-visible={visible ? "true" : undefined}
|
||||
data-autohide={autoHide ? "true" : undefined}
|
||||
style={style}
|
||||
onPointerEnter={() => setVisible(true)}
|
||||
onPointerLeave={() => {
|
||||
setHoveredIndex(null);
|
||||
if (autoHide) setVisible(false);
|
||||
}}
|
||||
>
|
||||
{autoHide ? <div className="nodedc-toolbar__hotzone" aria-hidden="true" onPointerEnter={() => setVisible(true)} /> : null}
|
||||
<div className="nodedc-toolbar" role="toolbar" aria-label={label}>
|
||||
{items.map((item, index) => {
|
||||
const distance = hoveredIndex === null ? Number.POSITIVE_INFINITY : Math.abs(index - hoveredIndex);
|
||||
const lensRadius = Math.floor(safeLens / 2);
|
||||
const influence = distance > lensRadius ? 0 : 1 - distance / Math.max(1, lensRadius + 1);
|
||||
const size = safeMin + (safeMax - safeMin) * influence;
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className="nodedc-toolbar__button"
|
||||
data-active={item.active ? "true" : undefined}
|
||||
aria-label={item.label}
|
||||
aria-pressed={item.active}
|
||||
disabled={item.disabled}
|
||||
style={{
|
||||
"--nodedc-toolbar-item-size": `${Math.round(size)}px`,
|
||||
"--nodedc-toolbar-item-shift": `${Math.round((size - safeMin) / (vertical ? 2 : 2))}px`,
|
||||
} as CSSProperties}
|
||||
onPointerEnter={() => setHoveredIndex(index)}
|
||||
onFocus={() => setHoveredIndex(index)}
|
||||
onBlur={() => setHoveredIndex(null)}
|
||||
onClick={() => item.onSelect(item.id)}
|
||||
>
|
||||
<Icon name={item.icon} size={Math.max(12, Math.min(18, Math.round(safeMin * 0.62)))} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user