From 07c0a7a17662fbe095895600c4e420dcf240bdf3 Mon Sep 17 00:00:00 2001 From: Alexey Morozov Date: Fri, 29 May 2026 01:21:50 +0300 Subject: [PATCH 1/3] Fix small input width overflow in `UnifiedSearch` when resized to the minimum size --- CHANGELOG.md | 1 + src/widgets/unifiedSearch/unifiedSearch.tsx | 1 - styles/widgets/_unifiedSearch.scss | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da0350a..b099ba40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p #### 🐛 Fixed - Fix partially or fully hidden outlines for `WorkspaceLayoutItem` headers and `Navigator` toggle button. - Fix undo/redo not adding or removing graph elements or links after `DataDiagramModel.discardLayout()` call (e.g. by a reload from `useLoadedWorkspace()`). +- Fix small input width overflow in `UnifiedSearch` when resized to the minimum size. #### ⏱ Performance - Fix canvas panning optimization not being applied due to incorrect `z-index` value. diff --git a/src/widgets/unifiedSearch/unifiedSearch.tsx b/src/widgets/unifiedSearch/unifiedSearch.tsx index afb7d138..b4000810 100644 --- a/src/widgets/unifiedSearch/unifiedSearch.tsx +++ b/src/widgets/unifiedSearch/unifiedSearch.tsx @@ -396,7 +396,6 @@ function SearchToggle(props: { role='searchbox' type='text' className={`${CLASS_NAME}__search-input`} - style={{minWidth}} placeholder={ t.textOptional('unified_search.input.placeholder') ?? t.text('search_defaults.input.placeholder') diff --git a/styles/widgets/_unifiedSearch.scss b/styles/widgets/_unifiedSearch.scss index 4606a8ee..5095e384 100644 --- a/styles/widgets/_unifiedSearch.scss +++ b/styles/widgets/_unifiedSearch.scss @@ -40,6 +40,7 @@ $searchButtonTotal: $searchButtonWidth + $searchButtonPadding * 2; display: block; margin: 0; padding: 6px #{12px + $searchButtonTotal} 6px 12px; + min-width: 0; color: theme.$input-color; background-color: theme.$background-color-surface; border: none; From 7f67b22ffcd71cb7125d02c1bcf623f22e570403 Mon Sep 17 00:00:00 2001 From: Alexey Morozov Date: Fri, 29 May 2026 01:30:18 +0300 Subject: [PATCH 2/3] Improve `WorkspaceLayout*` item resize handing: * Enable size transition on expand/collapse `WorkspaceLayoutRow` items; * Set a default minimum size on item width via `--reactodia-accordion-item-min-width`; * Auto-collapse items after resize if the final size is less than `minSize` for that item; * Prevent toggle buttons on `WorkspaceLayoutRow` children from being partially hidden when corresponding item is collapsed. --- CHANGELOG.md | 5 +++++ src/workspace/accordion.tsx | 34 +++++++++++++++++++----------- src/workspace/accordionItem.tsx | 7 ++++++- styles/theme/_common.scss | 1 + styles/theme/_theme.scss | 1 + styles/workspace/_accordion.scss | 36 ++++++++++++++++++++++++-------- 6 files changed, 62 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b099ba40..f3f9771e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Fix canvas panning optimization not being applied due to incorrect `z-index` value. #### 💅 Polish +- Improve `WorkspaceLayout*` item resize handing: + * Enable size transition on expand/collapse `WorkspaceLayoutRow` items; + * Set a default minimum non-wrapping size on item width via `--reactodia-accordion-item-min-width`; + * Auto-collapse items after resize if the final size is less than `minSize` for that item; + * Prevent toggle buttons on `WorkspaceLayoutRow` children from being partially hidden when corresponding item is collapsed. - Export `TranslationProvider` and `DefaultTranslation` to be able to use `useTranslation()` outside the workspace component: * Remove deprecated `Translation.formatIri()` method (use `DataLocaleProvider.formatIri()` instead). - Always display ungroup buttons on `StandardGroup` when the element is single-selected. diff --git a/src/workspace/accordion.tsx b/src/workspace/accordion.tsx index bdff0042..6a315c53 100644 --- a/src/workspace/accordion.tsx +++ b/src/workspace/accordion.tsx @@ -56,15 +56,13 @@ export class Accordion extends React.Component { private defaultProps!: ReadonlyMap; - private isScrollable = false; - constructor(props: AccordionProps) { super(props); const childCount = React.Children.count(this.props.children); this.state = { sizes: [], collapsed: [], - resizing: false, + resizing: true, percents: React.Children.map(this.props.children, () => `${100 / childCount}%`) as string[], }; } @@ -111,10 +109,6 @@ export class Accordion extends React.Component { if (typeof child !== 'object') { return; } if (child) { const {defaultSize, defaultCollapsed, collapsedSize, minSize} = child.props; - // enables the scrollbar in the accordion if at least one item has min size - if (minSize !== undefined) { - this.isScrollable = true; - } defaultProps.set(index, {defaultSize, defaultCollapsed, collapsedSize, minSize}); } else { defaultProps.set(index, {}); @@ -169,6 +163,8 @@ export class Accordion extends React.Component { percents: newPercents, collapsed: newCollapsed, }; + }, () => { + this.setState({resizing: false}); }); } @@ -181,7 +177,6 @@ export class Accordion extends React.Component { CLASS_NAME, `${CLASS_NAME}--${direction}`, resizing ? `${CLASS_NAME}--resizing` : undefined, - this.isScrollable ? `${CLASS_NAME}--scrollable` : undefined, className )} style={style}> @@ -239,9 +234,22 @@ export class Accordion extends React.Component { } private onEndDragHandle = () => { - this.setState({resizing: false}); + this.setState( + {resizing: false}, + () => this.tryCollapseTooSmallItem() + ); }; + private tryCollapseTooSmallItem() { + const collapsedIndex = this.state.sizes.findIndex((size, index) => { + const defaults = this.defaultProps.get(index); + return defaults && defaults.minSize && size < defaults.minSize; + }); + if (collapsedIndex >= 0) { + this.onItemChangeCollapsed({itemIndex: collapsedIndex, itemCollapsed: true}); + } + } + private computeEffectiveItemSizes(): number[] { const sizes: Array = []; this.items.forEach((item, index) => { @@ -275,7 +283,7 @@ export class Accordion extends React.Component { const originTotalSize = this.dragOrigin!.totalSize; new SizeDistributor( - sizes, collapsed, originTotalSize, this.sizeWhenCollapsed, + sizes, collapsed, originTotalSize, this.sizeWhenCollapsed ).distribute(itemIndex + 1, this.isVertical ? dy : dx); const percents = sizes.map(size => `${100 * size / originTotalSize}%`); @@ -298,7 +306,8 @@ export class Accordion extends React.Component { const collapsedSize = this.sizeWhenCollapsed(itemIndex); const distributor = new SizeDistributor( - sizes, collapsed, totalSize, this.sizeWhenCollapsed); + sizes, collapsed, totalSize, this.sizeWhenCollapsed + ); if (itemCollapsed) { const splitShift = Math.max(effectiveSize - collapsedSize, 0); @@ -396,9 +405,10 @@ class SizeDistributor { expand(shift: number, index: number) { if (shift <= 0) { return 0; } const oldSize = this.sizes[index]; + const collapsedSize = this.sizeWhenCollapsed(index); const newSize = Math.round(oldSize + shift); this.sizes[index] = newSize; - this.collapsed[index] = newSize <= this.sizeWhenCollapsed(index); + this.collapsed[index] = newSize <= collapsedSize; return newSize - oldSize; } diff --git a/src/workspace/accordionItem.tsx b/src/workspace/accordionItem.tsx index 1097ae17..7de31430 100644 --- a/src/workspace/accordionItem.tsx +++ b/src/workspace/accordionItem.tsx @@ -73,7 +73,12 @@ export class AccordionItem extends React.Component { } = this.props; const {resizing} = this.state; const shouldRenderHandle = onBeginDragHandle && onDragHandle && onEndDragHandle; - const providedStyle: React.CSSProperties = this.isVertical ? {height: size} : {width: size}; + const providedStyle: React.CSSProperties = this.isVertical + ? {height: size} + : { + width: size, + '--reactodia-accordion-item-size': typeof size === 'number' ? `${size}px` : undefined, + } as React.CSSProperties; // unmount child component when the accordion item is collapsed and has dockSide const isMounted = !(collapsed && dockSide); diff --git a/styles/theme/_common.scss b/styles/theme/_common.scss index 09bfaf9b..1e2d7f82 100644 --- a/styles/theme/_common.scss +++ b/styles/theme/_common.scss @@ -121,5 +121,6 @@ --reactodia-toolbar-height: 30px; + --reactodia-item-min-width: 150px; --reactodia-accordion-transition-duration: var(--reactodia-transition-duration); } diff --git a/styles/theme/_theme.scss b/styles/theme/_theme.scss index 9c542756..0cbc0271 100644 --- a/styles/theme/_theme.scss +++ b/styles/theme/_theme.scss @@ -188,4 +188,5 @@ $navigator-overflow-stroke-dash: var(--reactodia-navigator-overflow-stroke-dash) $toolbar-height: var(--reactodia-toolbar-height); /* Accordion */ +$accordion-item-min-width: var(--reactodia-item-min-width); $accordion-transition-duration: var(--reactodia-accordion-transition-duration); diff --git a/styles/workspace/_accordion.scss b/styles/workspace/_accordion.scss index 11f84a49..ffc5848b 100644 --- a/styles/workspace/_accordion.scss +++ b/styles/workspace/_accordion.scss @@ -7,10 +7,6 @@ height: 100%; width: 100%; - &--scrollable { - overflow: auto; - } - &--vertical { flex-direction: column; } @@ -18,9 +14,16 @@ &--vertical:not(&--resizing):not(:has(.reactodia-accordion-item__handle:focus)) > .reactodia-accordion-item { transition: height theme.$accordion-transition-duration ease-in-out; } + + &--horizontal:not(&--resizing):not(:has(.reactodia-accordion-item__handle:focus)) > .reactodia-accordion-item { + transition: width theme.$accordion-transition-duration ease-in-out; + } } .reactodia-accordion-item { + $buttonSize: 20px; + $buttonAreaMargin: 4px; + display: flex; position: relative; flex: auto; @@ -68,10 +71,10 @@ &__handle-btn { position: absolute; - width: 20px; - height: 20px; + width: $buttonSize; + height: $buttonSize; top: 50%; - margin-top: -10px; + margin-top: -0.5 * $buttonSize; padding: 0; background-color: theme.$button-default-background-color; @@ -110,7 +113,7 @@ &__handle-btn-left { left: 100%; - margin-left: -10px; + margin-left: -0.5 * $buttonSize; &::before { -webkit-mask-image: url("@codicons/chevron-left.svg"); @@ -118,9 +121,16 @@ } } + &--collapsed &__handle-btn-left { + margin-left: calc(-1 * min( + 0.5 * $buttonSize, + var(--reactodia-accordion-item-size, 0px) - $buttonAreaMargin + )); + } + &__handle-btn-right { right: 100%; - margin-right: -10px; + margin-right: -0.5 * $buttonSize; &:before { -webkit-mask-image: url("@codicons/chevron-right.svg"); @@ -128,6 +138,13 @@ } } + &--collapsed &__handle-btn-right { + margin-right: calc(-1 * min( + 0.5 * $buttonSize, + var(--reactodia-accordion-item-size, 0px) - $buttonAreaMargin + )); + } + &--collapsed &__handle-btn:before { transform: rotate(180deg); } @@ -144,6 +161,7 @@ display: flex; flex-direction: column; background-color: theme.$background-color; + min-width: theme.$accordion-item-min-width; } &__header { From 853d41c0510940836231b5a0e51d77b577b5b258 Mon Sep 17 00:00:00 2001 From: Alexey Morozov Date: Sat, 30 May 2026 00:28:02 +0300 Subject: [PATCH 3/3] Fix `Navigator` to avoid animating expand/collapse transition on the initial mount --- CHANGELOG.md | 1 + src/widgets/navigator.tsx | 33 +++++++++++++++++++++++++++++---- styles/widgets/_navigator.scss | 8 +++++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f9771e..036c5d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Fix partially or fully hidden outlines for `WorkspaceLayoutItem` headers and `Navigator` toggle button. - Fix undo/redo not adding or removing graph elements or links after `DataDiagramModel.discardLayout()` call (e.g. by a reload from `useLoadedWorkspace()`). - Fix small input width overflow in `UnifiedSearch` when resized to the minimum size. +- Fix `Navigator` to avoid animating expand/collapse transition on the initial mount. #### ⏱ Performance - Fix canvas panning optimization not being applied due to incorrect `z-index` value. diff --git a/src/widgets/navigator.tsx b/src/widgets/navigator.tsx index 21025017..2925eecd 100644 --- a/src/widgets/navigator.tsx +++ b/src/widgets/navigator.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import cx from 'clsx'; import { useColorScheme } from '../coreUtils/colorScheme'; import { EventObserver } from '../coreUtils/events'; @@ -166,6 +167,7 @@ interface NavigatorInnerProps extends NavigatorProps { } interface State { + initialized: boolean; expanded: boolean; autoToggle: boolean; allowExpand: boolean; @@ -208,6 +210,7 @@ class NavigatorInner extends React.Component { super(props); const {expanded = DEFAULT_EXPANDED} = this.props; this.state = { + initialized: false, expanded: Boolean(expanded), autoToggle: expanded === 'auto', allowExpand: true, @@ -471,12 +474,29 @@ class NavigatorInner extends React.Component { this.setState({ expanded: autoExpanded, allowExpand: strictExpanded, - }, this.scheduleRedraw); + }, () => { + this.scheduleRedraw(); + void this.setInitialized(); + }); } else if (strictExpanded !== allowExpand) { - this.setState({allowExpand: strictExpanded}); + this.setState( + {allowExpand: strictExpanded}, + () => { + void this.setInitialized(); + } + ); + } else { + void this.setInitialized(); } }; + private async setInitialized(): Promise { + // Perform state update in a microtask to allow expanded state to settle + // and avoid running any expand/collapse transitions on the initial mount + await Promise.resolve(); + this.setState({initialized: true}); + } + render() { const { dock = 'se', dockOffsetX, dockOffsetY, @@ -484,13 +504,18 @@ class NavigatorInner extends React.Component { height = DEFAULT_HEIGHT, translation: t, } = this.props; - const {expanded, allowExpand} = this.state; + const {initialized, expanded, allowExpand} = this.state; const expandedWhenAllowed = expanded && allowExpand; return ( -