From 8e75e3bec579a0586f0b98ed94fca006e8d88aa9 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Wed, 11 Jun 2025 16:50:09 +0200 Subject: [PATCH 1/9] First iteration --- .../DocumentView/Tabs/DynamicTabs.tsx | 142 +++++++++++------- 1 file changed, 84 insertions(+), 58 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index e4465b3032..8c69669753 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -84,6 +84,8 @@ export function DynamicTabs( ); }, [id, tabs, tabsState]); + const position: string = 'top'; + // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. // Otherwise because of the streaming/suspense approach, tabs can be first-rendered at different time @@ -143,13 +145,14 @@ export function DynamicTabs( return (
{tabs.map((tab) => ( @@ -174,49 +180,41 @@ export function DynamicTabs( hashLinkButtonWrapperStyles, 'flex', 'items-center', - 'gap-3.5', + 'relative', + position === 'left' && 'pr-1.5', + position === 'right' && 'pl-1.5', + position === 'top' && 'max-w-full overflow-hidden pb-1.5', + 'min-w-16', //prev from active-tab - '[&:has(+_.active-tab)]:rounded-br-md', + // '[&:has(+_.active-tab)]:rounded-br-md', //next from active-tab - '[.active-tab_+_&]:rounded-bl-md', + // '[.active-tab_+_&]:rounded-bl-md', //next from active-tab - '[.active-tab_+_:after]:rounded-br-md', - - 'after:transition-colors', - 'after:border-r', - 'after:absolute', - 'after:left-[unset]', - 'after:right-0', - 'after:border-tint', - 'after:top-[15%]', - 'after:h-[70%]', - 'after:w-[1px]', + // '[.active-tab_+_:after]:rounded-br-md', - 'px-3.5', - 'py-2', + // 'after:transition-colors', + // 'after:border-r', + // 'after:absolute', + // 'after:left-[unset]', + // 'after:right-0', + // 'after:border-tint', + // 'after:top-[15%]', + // 'after:h-[70%]', + // 'after:w-[1px]', - 'last:after:border-transparent', + // 'last:after:border-transparent', 'text-tint', - 'bg-tint-12/1', - 'hover:text-tint-strong', - 'max-w-full', - 'truncate', - - active.id === tab.id - ? [ - 'shrink-0', - 'active-tab', - 'text-tint-strong', - 'bg-transparent', - 'after:[&.active-tab]:border-transparent', - 'after:[:has(+_&.active-tab)]:border-transparent', - 'after:[:has(&_+)]:border-transparent', - ] - : null + // 'bg-tint-subtle', + // 'bg-tint-12/1', + // 'hover:text-tint-strong', + 'text-sm', + 'font-medium', + + active.id === tab.id ? 'active-tab text-primary-subtle' : '' )} > - + {active.id === tab.id ? ( +
+ ) : null}
))}
@@ -255,7 +271,17 @@ export function DynamicTabs( role="tabpanel" id={getTabPanelId(tab.id)} aria-labelledby={getTabButtonId(tab.id)} - className={tcls('p-4', tab.id !== active.id ? 'hidden' : null)} + className={tcls( + 'p-4', + 'rounded-lg', + 'straight-corners:rounded-none', + 'circular-corners:rounded-2xl', + 'grow', + 'ring-1', + 'ring-inset', + 'ring-tint-subtle', + tab.id !== active.id ? 'hidden' : null + )} > {tabsBody[index]} From 8e3e5147cf4be97d87100d613960b1df57bead25 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Thu, 12 Jun 2025 19:38:31 +0200 Subject: [PATCH 2/9] Second iteration, ready --- .../DocumentView/Tabs/DynamicTabs.tsx | 175 +++++++++--------- 1 file changed, 87 insertions(+), 88 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index 8c69669753..0638e5ba12 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -84,7 +84,8 @@ export function DynamicTabs( ); }, [id, tabs, tabsState]); - const position: string = 'top'; + const position: string = 'top'; // TODO: Get position from tab block options + const description = null; // TODO: Get description from tabs // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. @@ -145,16 +146,23 @@ export function DynamicTabs( return (
3 ? 'md:flex-wrap' : '', + + 'overflow-x-auto', + 'md:overflow-hidden', + + 'gap-1', + 'pb-2', + 'flex-row', + + position === 'left' || position === 'right' + ? 'gap-2 md:max-w-[40%] md:flex-col' + : '', + 'snap-x', + 'snap-mandatory', + + // We need to inset the tablist to make edge-to-edge scrolling work. + '-mx-4', + 'px-4', + 'scroll-px-4', + 'sm:-mx-6', + 'sm:px-6', + 'sm:scroll-px-6', + 'md:mx-0', + 'md:px-0', + 'md:scroll-px-0' )} > {tabs.map((tab) => ( - + ))}
{tabs.map((tab, index) => ( @@ -274,6 +272,7 @@ export function DynamicTabs( className={tcls( 'p-4', 'rounded-lg', + 'flex-1', 'straight-corners:rounded-none', 'circular-corners:rounded-2xl', 'grow', From 60015f58078be6548c8a7ccb7d6aaec5b3b8cd96 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Thu, 12 Jun 2025 19:39:12 +0200 Subject: [PATCH 3/9] Changeset --- .changeset/tough-lobsters-perform.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tough-lobsters-perform.md diff --git a/.changeset/tough-lobsters-perform.md b/.changeset/tough-lobsters-perform.md new file mode 100644 index 0000000000..ea3debd6a4 --- /dev/null +++ b/.changeset/tough-lobsters-perform.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +New design for tab blocks From 34a11d37a739ba344a9a6d5227526b4b7015fdaf Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Wed, 11 Jun 2025 16:50:09 +0200 Subject: [PATCH 4/9] First iteration --- .../DocumentView/Tabs/DynamicTabs.tsx | 142 +++++++++++------- 1 file changed, 84 insertions(+), 58 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index e4465b3032..8c69669753 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -84,6 +84,8 @@ export function DynamicTabs( ); }, [id, tabs, tabsState]); + const position: string = 'top'; + // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. // Otherwise because of the streaming/suspense approach, tabs can be first-rendered at different time @@ -143,13 +145,14 @@ export function DynamicTabs( return (
{tabs.map((tab) => ( @@ -174,49 +180,41 @@ export function DynamicTabs( hashLinkButtonWrapperStyles, 'flex', 'items-center', - 'gap-3.5', + 'relative', + position === 'left' && 'pr-1.5', + position === 'right' && 'pl-1.5', + position === 'top' && 'max-w-full overflow-hidden pb-1.5', + 'min-w-16', //prev from active-tab - '[&:has(+_.active-tab)]:rounded-br-md', + // '[&:has(+_.active-tab)]:rounded-br-md', //next from active-tab - '[.active-tab_+_&]:rounded-bl-md', + // '[.active-tab_+_&]:rounded-bl-md', //next from active-tab - '[.active-tab_+_:after]:rounded-br-md', - - 'after:transition-colors', - 'after:border-r', - 'after:absolute', - 'after:left-[unset]', - 'after:right-0', - 'after:border-tint', - 'after:top-[15%]', - 'after:h-[70%]', - 'after:w-[1px]', + // '[.active-tab_+_:after]:rounded-br-md', - 'px-3.5', - 'py-2', + // 'after:transition-colors', + // 'after:border-r', + // 'after:absolute', + // 'after:left-[unset]', + // 'after:right-0', + // 'after:border-tint', + // 'after:top-[15%]', + // 'after:h-[70%]', + // 'after:w-[1px]', - 'last:after:border-transparent', + // 'last:after:border-transparent', 'text-tint', - 'bg-tint-12/1', - 'hover:text-tint-strong', - 'max-w-full', - 'truncate', - - active.id === tab.id - ? [ - 'shrink-0', - 'active-tab', - 'text-tint-strong', - 'bg-transparent', - 'after:[&.active-tab]:border-transparent', - 'after:[:has(+_&.active-tab)]:border-transparent', - 'after:[:has(&_+)]:border-transparent', - ] - : null + // 'bg-tint-subtle', + // 'bg-tint-12/1', + // 'hover:text-tint-strong', + 'text-sm', + 'font-medium', + + active.id === tab.id ? 'active-tab text-primary-subtle' : '' )} > - + {active.id === tab.id ? ( +
+ ) : null}
))}
@@ -255,7 +271,17 @@ export function DynamicTabs( role="tabpanel" id={getTabPanelId(tab.id)} aria-labelledby={getTabButtonId(tab.id)} - className={tcls('p-4', tab.id !== active.id ? 'hidden' : null)} + className={tcls( + 'p-4', + 'rounded-lg', + 'straight-corners:rounded-none', + 'circular-corners:rounded-2xl', + 'grow', + 'ring-1', + 'ring-inset', + 'ring-tint-subtle', + tab.id !== active.id ? 'hidden' : null + )} > {tabsBody[index]}
From 17df1be3ef1620e9d5e35d05c4213feefb6de83e Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Thu, 12 Jun 2025 19:38:31 +0200 Subject: [PATCH 5/9] Second iteration, ready --- .../DocumentView/Tabs/DynamicTabs.tsx | 175 +++++++++--------- 1 file changed, 87 insertions(+), 88 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index 8c69669753..0638e5ba12 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -84,7 +84,8 @@ export function DynamicTabs( ); }, [id, tabs, tabsState]); - const position: string = 'top'; + const position: string = 'top'; // TODO: Get position from tab block options + const description = null; // TODO: Get description from tabs // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. @@ -145,16 +146,23 @@ export function DynamicTabs( return (
3 ? 'md:flex-wrap' : '', + + 'overflow-x-auto', + 'md:overflow-hidden', + + 'gap-1', + 'pb-2', + 'flex-row', + + position === 'left' || position === 'right' + ? 'gap-2 md:max-w-[40%] md:flex-col' + : '', + 'snap-x', + 'snap-mandatory', + + // We need to inset the tablist to make edge-to-edge scrolling work. + '-mx-4', + 'px-4', + 'scroll-px-4', + 'sm:-mx-6', + 'sm:px-6', + 'sm:scroll-px-6', + 'md:mx-0', + 'md:px-0', + 'md:scroll-px-0' )} > {tabs.map((tab) => ( - + ))}
{tabs.map((tab, index) => ( @@ -274,6 +272,7 @@ export function DynamicTabs( className={tcls( 'p-4', 'rounded-lg', + 'flex-1', 'straight-corners:rounded-none', 'circular-corners:rounded-2xl', 'grow', From e2dd8a92882e8ae7b7400e953cdff89fec033b2d Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Thu, 12 Jun 2025 19:39:12 +0200 Subject: [PATCH 6/9] Changeset --- .changeset/tough-lobsters-perform.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tough-lobsters-perform.md diff --git a/.changeset/tough-lobsters-perform.md b/.changeset/tough-lobsters-perform.md new file mode 100644 index 0000000000..ea3debd6a4 --- /dev/null +++ b/.changeset/tough-lobsters-perform.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +New design for tab blocks From 2e5d9418d4517b1beae4e556adef706d74bb16d3 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Mon, 23 Jun 2025 16:41:39 +0200 Subject: [PATCH 7/9] New design --- .../src/components/Cookies/CookiesToast.tsx | 2 +- .../DocumentView/Tabs/DynamicTabs.tsx | 241 ++++++++++++------ 2 files changed, 158 insertions(+), 85 deletions(-) diff --git a/packages/gitbook/src/components/Cookies/CookiesToast.tsx b/packages/gitbook/src/components/Cookies/CookiesToast.tsx index 281e6ad911..f3cc52a449 100644 --- a/packages/gitbook/src/components/Cookies/CookiesToast.tsx +++ b/packages/gitbook/src/components/Cookies/CookiesToast.tsx @@ -43,7 +43,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) { aria-describedby={describedById} className={tcls( 'fixed', - 'z-10', + 'z-50', 'bg-tint-base', 'rounded', 'straight-corners:rounded-none', diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index 0638e5ba12..0ce0ae7048 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -84,8 +84,10 @@ export function DynamicTabs( ); }, [id, tabs, tabsState]); - const position: string = 'top'; // TODO: Get position from tab block options - const description = null; // TODO: Get description from tabs + const orientation: string = 'horizontal'; // TODO: Get orientation from tab block options + const position: string = 'start'; // TODO: Get position from tab block options + + const description = null; //'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; // null; // TODO: Get description from tabs // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. @@ -148,21 +150,16 @@ export function DynamicTabs( className={tcls( 'flex', 'flex-col', - position === 'left' && 'gap-4 md:flex-row', - position === 'right' && 'gap-4 md:flex-row-reverse', 'overflow-hidden', + 'pb-1', + + orientation === 'horizontal' && position === 'start' && 'flex-col', + orientation === 'vertical' && position === 'start' && 'md:flex-row', + orientation === 'vertical' && position === 'end' && 'md:flex-row-reverse', - style, + style // We need to inset the tabs container to make edge-to-edge scrolling work, since this container has overflow:hidden // Also important to put this after the `style` to override those. - '-mx-4', - 'px-4', - 'sm:-mx-6', - 'sm:px-6', - 'md:mx-auto', - 'md:px-0', - 'w-auto', - 'md:w-full' )} >
{tabs.map((tab) => ( - + > +
+ {tab.title} +
+ +
+ {description ? ( +

{description}

+ ) : null} + +
))} {tabs.map((tab, index) => ( @@ -271,14 +336,22 @@ export function DynamicTabs( aria-labelledby={getTabButtonId(tab.id)} className={tcls( 'p-4', - 'rounded-lg', - 'flex-1', + 'rounded-md', 'straight-corners:rounded-none', 'circular-corners:rounded-2xl', - 'grow', + 'z-10', + 'bg-tint-base', + position === 'start' && '!rounded-tl-none', + position === 'end' && '!rounded-tr-none', + // index === 0 && active.id === tab.id + // ? '!rounded-tl-none' + // : '[.peer:has(.tab:first-child:hover)~&]:rounded-tl-none', + 'transition-all', 'ring-1', 'ring-inset', 'ring-tint-subtle', + 'grow', + 'depth-subtle:shadow-sm', tab.id !== active.id ? 'hidden' : null )} > From d0b93fcae07f84aac4e457d71a15546d3fba7ab0 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Mon, 23 Jun 2025 16:44:39 +0200 Subject: [PATCH 8/9] Cleanup --- .../DocumentView/Tabs/DynamicTabs.tsx | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx index 0ce0ae7048..075de3ccd0 100644 --- a/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx +++ b/packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx @@ -87,11 +87,11 @@ export function DynamicTabs( const orientation: string = 'horizontal'; // TODO: Get orientation from tab block options const position: string = 'start'; // TODO: Get position from tab block options - const description = null; //'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; // null; // TODO: Get description from tabs + const description = null; // TODO: Get description from tabs // To avoid issue with hydration, we only use the state from localStorage // once the component has been mounted. - // Otherwise because of the streaming/suspense approach, tabs can be first-rendered at different time + // Otherwise because of the streaming/suspense approach, tabs can be first-rendered at different time // and get stuck into an inconsistent state. const mounted = useIsMounted(); const active = mounted ? activeState : tabs[0]; @@ -158,8 +158,6 @@ export function DynamicTabs( orientation === 'vertical' && position === 'end' && 'md:flex-row-reverse', style - // We need to inset the tabs container to make edge-to-edge scrolling work, since this container has overflow:hidden - // Also important to put this after the `style` to override those. )} >
3 ? 'md:flex-wrap' : '', - 'overflow-x-auto', 'md:overflow-hidden', - // 'items-end', - 'gap-x-1', 'flex-row', - orientation === 'vertical' && 'gap-1.5 md:max-w-[40%] md:flex-col', 'snap-x', 'snap-mandatory', '-mb-px', - 'peer', - orientation === 'vertical' && position === 'start' && 'md:-mr-px md:mb-0', orientation === 'vertical' && position === 'end' && 'md:-ml-px md:mb-0' - - // We need to inset the tablist to make edge-to-edge scrolling work. - // '-mx-4', - // 'px-4', - // 'scroll-px-4', - // 'sm:-mx-6', - // 'sm:px-6', - // 'sm:scroll-px-6', - // 'md:mx-0', - // 'md:px-0', - // 'md:scroll-px-0', )} > {tabs.map((tab) => ( @@ -213,7 +194,7 @@ export function DynamicTabs(