diff --git a/.changeset/truncate-long-tab-titles.md b/.changeset/truncate-long-tab-titles.md new file mode 100644 index 000000000..767917479 --- /dev/null +++ b/.changeset/truncate-long-tab-titles.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index c571899b5..4aef68fe0 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -141,7 +141,8 @@ const TabItemList = memo(function TabItemList(props: { onSelect: (tabId: string) => void; }) { const { tabs, activeTabId, onSelect } = props; - const { containerRef, itemRef, overflowing, isMeasuring } = useListOverflow(); + // The active tab's width decides how many others fit, so a new selection re-measures. + const { containerRef, itemRef, overflowing, isMeasuring } = useListOverflow(activeTabId); const overflowingTabs = useMemo( () => Array.from(overflowing, (id) => { @@ -181,6 +182,7 @@ const TabItemList = memo(function TabItemList(props: { key={tab.id} ref={itemRef} isActive={tab.id === activeTabId} + isMeasuring={isMeasuring} tab={tab} onSelect={onSelect} /> @@ -239,10 +241,11 @@ function TabsDropdownMenu(props: { const TabItem = memo(function TabItem(props: { ref: React.Ref; isActive: boolean; + isMeasuring: boolean; tab: TabsItem; onSelect: (tabId: string) => void; }) { - const { ref, tab, isActive, onSelect } = props; + const { ref, tab, isActive, isMeasuring, onSelect } = props; return ( onSelect(tab.id)} + wrapperClassName={ + isActive + ? // Keeps room for the overflow menu button. + 'max-w-[calc(100%-3rem)]' + : isMeasuring + ? // Measured after the active tab, at their narrowest, so the menu only takes + // the tabs that can't fit even truncated. + 'order-1 max-w-28' + : // Equal shares capped at their own width, so no tab drops below that + // narrowest size; the high grow leaves the strip's filler only the leftover. + 'min-w-0 max-w-max basis-0 grow-[1000]' + } > {tab.icon && } {tab.title} @@ -264,9 +280,10 @@ const TabItem = memo(function TabItem(props: { function TabButton( props: Omit, 'type'> & { isActive?: boolean; + wrapperClassName?: string; } ) { - const { isActive, ...rest } = props; + const { isActive, wrapperClassName, ...rest } = props; return (