From 8dd9190573d6616024ef01b9b34bf90b72960f44 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 21 Sep 2026 08:45:10 +0300 Subject: [PATCH] feat(ui): add spatial clipping scale and compact record actions --- apps/catalog/src/CatalogApp.tsx | 15 ++++++- docs/COMPONENTS.md | 18 ++++++++ docs/ICONS.md | 2 + package.json | 2 +- packages/ui-core/styles.css | 49 ++++++++++++++++++++++ packages/ui-react/src/ApplicationShell.tsx | 3 ++ packages/ui-react/src/Icon.tsx | 2 + packages/ui-react/src/RangeControl.tsx | 24 +++++++++++ packages/ui-react/src/Select.tsx | 48 ++++++++++++++++----- packages/ui-react/src/StatusBadge.tsx | 7 ++-- registry/components.json | 12 ++++-- registry/icons.json | 2 +- scripts/range-control-contract.test.mjs | 43 +++++++++++++++++++ scripts/select-actions-contract.test.mjs | 39 +++++++++++++++++ scripts/status-pulse-contract.test.mjs | 18 ++++++++ 15 files changed, 263 insertions(+), 21 deletions(-) create mode 100644 scripts/select-actions-contract.test.mjs create mode 100644 scripts/status-pulse-contract.test.mjs diff --git a/apps/catalog/src/CatalogApp.tsx b/apps/catalog/src/CatalogApp.tsx index 891cf56..ca05cdf 100644 --- a/apps/catalog/src/CatalogApp.tsx +++ b/apps/catalog/src/CatalogApp.tsx @@ -216,7 +216,7 @@ const iconGroups: Array<{ title: string; note: string; icons: IconName[] }> = [ { title: "Состояние и доступ", note: "Вся платформа", - icons: ["check", "alert", "activity", "lock", "key", "shield", "circle", "eye", "eye-off"], + icons: ["check", "alert", "info", "activity", "lock", "key", "shield", "circle", "eye", "eye-off"], }, { title: "Сущности", @@ -257,6 +257,7 @@ const iconLabels: Record = { grid: "Обзор", image: "Изображение", inbox: "Уведомления", + info: "Информация", key: "Ключ", layers: "Слои", list: "Список задач", @@ -1294,6 +1295,8 @@ export function CatalogApp() { <> `${value}%`} onChange={setBrightness} /> +
`${value.toFixed(1).replace('.', ',')} м`} formatLimit={(value) => value.toFixed(1).replace('.', ',')} onChange={(value) => setBrightness((value + 1.2) / 52.7 * 100)} />
+
`${value.toFixed(1).replace('.', ',')} м`} formatLimit={(value) => value.toFixed(1).replace('.', ',')} onChange={(value) => setBrightness((value + 1.2) / 52.7 * 100)} />
`${value}%`} onChange={setGlowDistance} /> ), @@ -1345,6 +1348,14 @@ export function CatalogApp() { ({ ...option, action: { + label: `Удалить ${option.label}`, icon: , tone: 'danger' as const, + disabled: option.value === 'active', + onAction: () => setConfirmOpen(true), + } }))} /> + onChange(Number(event.target.value))} /> + {label} · {displayValue} + + + {formatLimit(max)} + {formatLimit(min)} + + ); + return (
{ description?: string; icon?: ReactNode; disabled?: boolean; + action?: { label: string; icon: ReactNode; disabled?: boolean; tone?: "neutral" | "danger"; onAction: () => void }; } export type SelectVariant = "integrated" | "split" | "inline"; @@ -52,7 +54,11 @@ export function Select({ const inspectorSplitRequired = useContext(InspectorSelectPolicyContext); const resolvedVariant: SelectVariant = inspectorSplitRequired ? "split" : variant; const [query, setQuery] = useState(""); + const triggerButton = useRef(null); const selected = options.find((option) => option.value === value) ?? options[0]; + // Row actions are siblings, never nested interactive listbox options. + const withActions = options.some((option) => option.action); + const popupRole = withActions ? "dialog" : "listbox"; const visibleOptions = useMemo(() => { const normalized = query.trim().toLocaleLowerCase(); if (!normalized) return options; @@ -66,10 +72,15 @@ export function Select({ minWidth={minMenuWidth} width={menuWidth} disabled={disabled} - surfaceRole="listbox" + surfaceRole={popupRole} surfaceClassName={cn(resolvedVariant === "split" && "nodedc-select__menu", menuClassName)} trigger={({ open, toggle, setAnchorRef, setTriggerRef, surfaceId }) => { + const handleClick = (event: MouseEvent) => { + triggerButton.current = event.currentTarget; + toggle(); + }; const handleKeyDown = (event: KeyboardEvent) => { + triggerButton.current = event.currentTarget; if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") { event.preventDefault(); if (!open) toggle(); @@ -88,11 +99,11 @@ export function Select({ type="button" className="nodedc-select__toggle" aria-label={label} - aria-haspopup="listbox" + aria-haspopup={popupRole} aria-controls={surfaceId} aria-expanded={open} disabled={disabled} - onClick={toggle} + onClick={handleClick} onKeyDown={handleKeyDown} >