blume 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,8 +104,14 @@ Tabs also **scope the sidebar**: when the current route falls under a tab's
104
104
  `path`, the sidebar shows only that section's pages — so `/adapters/*` lists the
105
105
  adapters and nothing else. The folder at a tab's `path` becomes the section, so
106
106
  this needs no extra config beyond the tabs themselves; structure your content
107
- into a folder per tab and point each tab at it. A route under no tab (or a tab
108
- whose `path` is `/`) shows the full sidebar.
107
+ into a folder per tab and point each tab at it.
108
+
109
+ On a route under no tab (or a tab whose `path` is `/`), the sidebar shows the
110
+ pages that _don't_ belong to a tab — each tab's folder is hidden from it, since
111
+ that section already has its own tab in the header. So a root landing page lists
112
+ your loose top-level pages while the sectioned content stays behind its tab,
113
+ mirroring Fumadocs' root folders. If a route has no pages of its own to show
114
+ this way, the full tree is shown instead, so the sidebar is never left blank.
109
115
 
110
116
  ## Explicit sidebar
111
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -114,13 +114,56 @@ const sectionChildren = (nodes: NavNode[], base: string): NavNode[] | null => {
114
114
  return null;
115
115
  };
116
116
 
117
+ /** Whether a group maps to a header tab (matched on its path or link route). */
118
+ const isTabSection = (node: NavNode, tabPaths: Set<string>): boolean =>
119
+ node.kind === "group" &&
120
+ ((node.path !== undefined && tabPaths.has(node.path)) ||
121
+ (node.route !== undefined && tabPaths.has(node.route)));
122
+
123
+ /**
124
+ * Drop the groups that already own a header tab from the tree, at any depth —
125
+ * so a root/un-tabbed route lists only the pages outside every tab's section
126
+ * instead of duplicating each tab as a sidebar group. A container left empty by
127
+ * this pruning is dropped too, so no bare heading is stranded. The root tab
128
+ * (`/`) spans everything, so it never removes anything.
129
+ */
130
+ const withoutTabSections = (nodes: NavNode[], tabs: NavTab[]): NavNode[] => {
131
+ const tabPaths = new Set(
132
+ tabs.filter((tab) => tab.path !== "/").map((tab) => tab.path)
133
+ );
134
+ if (tabPaths.size === 0) {
135
+ return nodes;
136
+ }
137
+ const prune = (items: NavNode[]): NavNode[] => {
138
+ const kept: NavNode[] = [];
139
+ for (const item of items) {
140
+ if (isTabSection(item, tabPaths)) {
141
+ continue;
142
+ }
143
+ if (item.kind === "group") {
144
+ const children = prune(item.children);
145
+ if (children.length === 0) {
146
+ continue;
147
+ }
148
+ kept.push({ ...item, children });
149
+ } else {
150
+ kept.push(item);
151
+ }
152
+ }
153
+ return kept;
154
+ };
155
+ return prune(nodes);
156
+ };
157
+
117
158
  /**
118
159
  * Scope the sidebar to the active tab's section. With tabs configured, a route
119
160
  * under one tab shows only that tab's group — so a multi-section site (e.g.
120
161
  * Adapters / API / AI tabs) drills each tab into its own pages instead of one
121
- * global tree, the way Fumadocs' root folders do. Falls back to the full
122
- * sidebar when no tab matches (or the tab maps to no group), so a route is
123
- * never left with a blank sidebar.
162
+ * global tree, the way Fumadocs' root folders do. On a route under no tab (or
163
+ * the root `/` tab), the tab-owned groups are hidden so the root sidebar shows
164
+ * only pages that don't belong to a tab. Falls back to the full sidebar when a
165
+ * matched tab maps to no group, or when hiding the tab sections would blank the
166
+ * sidebar, so a route is never left empty.
124
167
  */
125
168
  export const sidebarForRoute = (
126
169
  sidebar: NavNode[],
@@ -128,10 +171,11 @@ export const sidebarForRoute = (
128
171
  route: string
129
172
  ): NavNode[] => {
130
173
  const tab = activeTab(tabs, route);
131
- if (!tab) {
132
- return sidebar;
174
+ if (tab) {
175
+ return sectionChildren(sidebar, tab.path) ?? sidebar;
133
176
  }
134
- return sectionChildren(sidebar, tab.path) ?? sidebar;
177
+ const scoped = withoutTabSections(sidebar, tabs);
178
+ return scoped.length > 0 ? scoped : sidebar;
135
179
  };
136
180
 
137
181
  /** Resolve previous/next pages around the current route. */