diff --git a/frontend/src/App.css b/frontend/src/App.css index 6d59d91..df35257 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -81,8 +81,8 @@ samp { } .sidebar-tree-scroll-shell .ant-tree .ant-tree-node-content-wrapper { - width: auto !important; - min-width: 0; + width: max-content !important; + min-width: 100%; display: flex !important; align-items: center; gap: 8px; @@ -106,7 +106,7 @@ samp { .sidebar-tree-scroll-shell .ant-tree .ant-tree-title { flex: 0 0 auto; - min-width: 0; + min-width: max-content; overflow: visible; text-overflow: clip; } diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index a13ac2a..7c7ca87 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -70,6 +70,12 @@ import { } from './V2TableContextMenu'; const readSourceFile = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), 'utf8'); +const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +const readCssRuleBlock = (css: string, selector: string) => { + const match = css.match(new RegExp(`${escapeRegExp(selector)}\\s*\\{(?[^}]*)\\}`, 's')); + expect(match, `Missing CSS rule for ${selector}`).not.toBeNull(); + return match?.groups?.body ?? ''; +}; const readSidebarSource = () => [ readSourceFile('./Sidebar.tsx'), readSourceFile('./sidebar/sidebarHelpers.ts'), @@ -1100,14 +1106,30 @@ describe('Sidebar locate toolbar', () => { expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*height: 12px !important;[^}]*bottom: calc\(\(var\(--gn-v2-tree-horizontal-scroll-reserve\) - 12px\) \* -1\) !important;/s); expect(css).not.toContain('.gn-v2-tree-horizontal-scroll-spacer'); expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \.ant-tree-list-scrollbar-thumb \{[^}]*height: 8px !important;/s); - expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-node-content-wrapper \{[^}]*display: flex !important;/s); + const treeContentWrapperCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wrapper'); + expect(treeContentWrapperCss).toContain('min-width: 100%;'); + expect(treeContentWrapperCss).toContain('width: max-content !important;'); + expect(treeContentWrapperCss).toContain('display: flex !important;'); expect(css).toMatch(/\.gn-v2-tree-title\.is-connection \{[^}]*align-items:\s*center;/s); - expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-title \{[^}]*flex: 1 1 auto;[^}]*overflow: visible;/s); - expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-title > \.gn-v2-tree-title \{[^}]*overflow: visible;/s); + const antTreeTitleCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title'); + expect(antTreeTitleCss).toContain('min-width: max-content;'); + expect(antTreeTitleCss).toContain('flex: 0 0 auto;'); + expect(antTreeTitleCss).toContain('overflow: visible;'); + const antTreeTitleSpanCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title > span'); + expect(antTreeTitleSpanCss).toContain('min-width: max-content;'); + expect(antTreeTitleSpanCss).toContain('overflow: visible;'); + expect(antTreeTitleSpanCss).toContain('text-overflow: clip;'); + const v2TreeTitleCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title > .gn-v2-tree-title'); + expect(v2TreeTitleCss).toContain('width: max-content;'); + expect(v2TreeTitleCss).toContain('min-width: 100%;'); + expect(v2TreeTitleCss).toContain('overflow: visible;'); expect(css).toMatch(/\.gn-v2-tree-status \{[^}]*width: 14px;[^}]*height: 14px;[^}]*flex: 0 0 14px;[^}]*overflow: visible;/s); expect(css).toMatch(/\.gn-v2-tree-status::before \{[^}]*width: 7px;[^}]*height: 7px;[^}]*border-radius: 50%;/s); expect(css).toMatch(/\.gn-v2-tree-status\.is-success::before \{[^}]*background: #22c55e;[^}]*box-shadow: 0 0 0 4px rgba\(34, 197, 94, 0\.18\);/s); - expect(css).toMatch(/\.gn-v2-tree-label \{[^}]*overflow: hidden;[^}]*text-overflow: ellipsis;/s); + const treeLabelCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-tree-label'); + expect(treeLabelCss).toContain('flex: 0 0 auto;'); + expect(treeLabelCss).toContain('overflow: visible;'); + expect(treeLabelCss).toContain('text-overflow: clip;'); expect(css).toMatch(/\.gn-v2-tree-title\.is-mono \{[^}]*width: max-content;[^}]*min-width: 100%;[^}]*flex: 0 0 auto;/s); expect(css).toMatch(/\.gn-v2-tree-title\.is-mono \.gn-v2-tree-label \{[^}]*flex: 0 0 auto;[^}]*overflow: visible;[^}]*text-overflow: clip;/s); expect(css).toMatch(/\.gn-v2-tree-folder-icon \{[^}]*width: 22px;[^}]*height: 22px;[^}]*flex: 0 0 22px;/s); diff --git a/frontend/src/sidebarTreeScrollCss.test.ts b/frontend/src/sidebarTreeScrollCss.test.ts index 96c507c..5350f52 100644 --- a/frontend/src/sidebarTreeScrollCss.test.ts +++ b/frontend/src/sidebarTreeScrollCss.test.ts @@ -8,20 +8,20 @@ const __dirname = path.dirname(__filename); const appCss = readFileSync(path.resolve(__dirname, './App.css'), 'utf8'); describe('sidebar tree horizontal scroll css', () => { - it('keeps the virtual tree width anchored to the sidebar by default', () => { + it('lets sidebar tree titles keep their full content width for horizontal scrolling', () => { expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-list-holder,\s*\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-list-holder-inner\s*\{[^}]*min-width:\s*100%;/s); expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-list-holder,\s*\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-list-holder-inner\s*\{[^}]*max-content/s); expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-treenode\s*\{[^}]*width:\s*auto;[^}]*min-width:\s*100%;/s); expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-treenode\s*\{[^}]*width:\s*max-content/s); - expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-node-content-wrapper\s*\{[^}]*width:\s*auto\s*!important;[^}]*min-width:\s*0;/s); - expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-node-content-wrapper\s*\{[^}]*max-content/s); + expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-node-content-wrapper\s*\{[^}]*width:\s*max-content\s*!important;[^}]*min-width:\s*100%;/s); + expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-node-content-wrapper\s*\{[^}]*min-width:\s*0;/s); expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-switcher\s*\{[^}]*width:\s*24px;[^}]*min-width:\s*24px;/s); expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-iconEle\s*\{[^}]*width:\s*16px;[^}]*min-width:\s*16px;/s); - expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-title\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*visible;/s); - expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-title\s*\{[^}]*max-content/s); + expect(appCss).toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-title\s*\{[^}]*min-width:\s*max-content;[^}]*overflow:\s*visible;[^}]*text-overflow:\s*clip;/s); + expect(appCss).not.toMatch(/\.sidebar-tree-scroll-shell\s+\.ant-tree\s+\.ant-tree-title\s*\{[^}]*ellipsis/s); }); }); diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index 9633269..e460c56 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -2661,7 +2661,8 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-indent-unit { } body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wrapper { - min-width: 0; + min-width: 100%; + width: max-content !important; min-height: 26px !important; padding: 0 6px !important; display: flex !important; @@ -2720,27 +2721,30 @@ body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .gn-v2-tree-folder-icon .a } body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title { - min-width: 0; - flex: 1 1 auto; + min-width: max-content; + flex: 0 0 auto; display: inline-flex; align-items: center; overflow: visible; } body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title > span { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; + min-width: max-content; + overflow: visible; + text-overflow: clip; white-space: nowrap; } body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-title > .gn-v2-tree-title { + width: max-content; + min-width: 100%; overflow: visible; } body[data-ui-version="v2"] .gn-v2-tree-title { - width: 100%; - min-width: 0; + width: max-content; + min-width: 100%; + flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; @@ -2824,18 +2828,18 @@ body[data-ui-version="v2"] .gn-v2-tree-status.is-error::before { } body[data-ui-version="v2"] .gn-v2-tree-label { - min-width: 0; - flex: 1 1 auto; - overflow: hidden; - text-overflow: ellipsis; + min-width: max-content; + flex: 0 0 auto; + overflow: visible; + text-overflow: clip; white-space: nowrap; letter-spacing: 0; font-weight: 400 !important; } body[data-ui-version="v2"] .gn-v2-tree-connection-copy { - min-width: 0; - flex: 1 1 auto; + min-width: max-content; + flex: 0 0 auto; color: var(--gn-fg-1); font-weight: 400 !important; }