From 54cf5631620ea44d1a6d0d6f6a180390602c2e33 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 11 May 2026 23:51:03 +0300 Subject: [PATCH] feat(ui): row click opens edit + wide-mode left section rail in drawer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two improvements per user feedback ('по клику открывается редактирование и в развернутом виде слева навигация'): ## 1. Row click → edit drawer TableRow в /dictionaries/$name теперь имеет onClick={setEdit({kind:'edit', record:r})} когда canMutate. cursor-pointer + bubbling guards: - Checkbox cell и Actions cell имеют onClick stopPropagation чтобы внутренние controls не открывали drawer случайно. - Anonymous users (no canMutate) — row не clickable (read-only, no drawer). ## 2. Wide-mode left rail в drawer SchemaDrivenForm в sections layout теперь умеет ResizeObserver-based detection своей ширины. Когда контейнер ≥720px (drawer wide mode = 880px) → render'ит 180px sticky left rail с section list + per-section field counts. Click rail item → smooth scroll к section anchor. Иначе chip strip сверху, как раньше. Container-based (не viewport-based) чтобы narrow drawer на wide viewport не рендерил rail (520px не fits 180+1fr grid). --- .../src/components/form/SchemaDrivenForm.tsx | 89 ++++++++++++++----- .../src/routes/dictionaries.$name.tsx | 14 ++- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index e2905cf..b5ce054 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -276,7 +276,9 @@ export const SchemaDrivenForm = ({ const showAllSections = layout === 'sections' const sectionVisible = (id: TabId) => showAllSections || activeTab === id - // Scroll-to-section для chip click (только в sections mode). + // Scroll-to-section для chip click + rail click. Использует form container + // (scrollable parent = drawer body) вместо window — section header sticky + // считается от form container coordinate space. const scrollToSection = (id: TabId) => { document.getElementById(`form-section-${formId ?? 'default'}-${id}`)?.scrollIntoView({ behavior: 'smooth', @@ -284,30 +286,76 @@ export const SchemaDrivenForm = ({ }) } + // Wide-mode left rail: detection через ResizeObserver. Когда form container + // ≥720px (drawer wide mode = 880px) — показываем 180px nav rail слева + // с section list + counts. Иначе chip strip на top sufficient. Container- + // based вместо viewport-based чтобы narrow drawer на wide viewport не + // рендерил rail (drawer = 520px не fits). + const formContainerRef = useRef(null) + const [showRail, setShowRail] = useState(false) + useEffect(() => { + if (!showAllSections || !formContainerRef.current) return + const node = formContainerRef.current + const ro = new ResizeObserver((entries) => { + const width = entries[0]?.contentRect.width ?? 0 + setShowRail(width >= 720) + }) + ro.observe(node) + return () => ro.disconnect() + }, [showAllSections]) + + // Body content (sections + form fields) — рендерится либо напрямую, + // либо в grid 180px+1fr когда showRail (wide mode). + const sectionsChipStrip = showAllSections ? ( +
+ {visibleTabs.map((tab) => ( + + ))} +
+ ) : null + + // Left rail для wide mode — sticky nav с section list + per-section + // field counts. Click → scrollToSection (smooth scroll к anchor). + const leftRail = showRail && showAllSections ? ( + + ) : null + return (
- {/* Section chips strip per handoff (sections mode) OR tab bar (legacy). */} - {showAllSections ? ( -
- {visibleTabs.map((tab) => ( - - ))} -
- ) : ( + {leftRail} +
+ {showAllSections ? sectionsChipStrip : ( setActiveTab(id as TabId)} /> )} @@ -448,6 +496,7 @@ export const SchemaDrivenForm = ({ )} +
) } diff --git a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx index 8e13245..7ef1c1b 100644 --- a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx +++ b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx @@ -905,10 +905,18 @@ function DictionaryDetail() { setEdit({ kind: 'edit', record: r }) : undefined} + className={[ + canMutate ? 'cursor-pointer' : '', + rowCursor === idx ? 'bg-accent-bg/40 outline outline-1 outline-accent/30' : '', + ].filter(Boolean).join(' ') || undefined} > {canMutate && ( - + e.stopPropagation()}> - + e.stopPropagation()}>