@dreamlake/dockit 0.2.9 → 0.2.11

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.
@@ -35,7 +35,6 @@ const ICON_BUTTON_SIZE = 28;
35
35
  const SEARCH_MAX_W = 860;
36
36
  const SEARCH_GUTTER = 24;
37
37
  const SEARCH_INSET = `max(${SEARCH_GUTTER}px, calc((100vw - ${SEARCH_MAX_W}px) / 2))`;
38
- const TABS_EDGE_MAX_W = 420;
39
38
  function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
40
39
  const { urlPathname } = usePageContext();
41
40
  const current = findPage(urlPathname);
@@ -153,32 +152,54 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
153
152
  transition: "opacity 0.25s ease"
154
153
  });
155
154
  const iconButtonMode = isBelowLg && !searchOpen;
156
- const clusterRef = useRef(null);
155
+ const closedWidth = isXlUp ? CLOSED_WIDTH_XL : isLgUp ? CLOSED_WIDTH_LG : CLOSED_WIDTH_MD;
156
+ const brandRef = useRef(null);
157
+ const brandMaxRight = useRef(0);
157
158
  const tabsRef = useRef(null);
158
159
  const actionsRef = useRef(null);
159
160
  const [tabsFit, setTabsFit] = useState(false);
161
+ const [tabsW, setTabsW] = useState(0);
162
+ const [pinLeft, setPinLeft] = useState(0);
163
+ const [searchPinRight, setSearchPinRight] = useState(null);
160
164
  useLayoutEffect(() => {
161
165
  const tabsEl = tabsRef.current;
162
166
  if (!tabsEl) return;
167
+ const content = document.querySelector("main.doc-content");
163
168
  const measure = () => {
164
- const tabsW = tabsEl.getBoundingClientRect().width;
165
- const inset = Math.max(SEARCH_GUTTER, (window.innerWidth - SEARCH_MAX_W) / 2);
169
+ const w = tabsEl.getBoundingClientRect().width;
170
+ const rect = content?.getBoundingClientRect();
171
+ const cs = content ? getComputedStyle(content) : null;
172
+ const fallbackInset = Math.max(SEARCH_GUTTER, (window.innerWidth - SEARCH_MAX_W) / 2);
173
+ const textLeft = rect ? rect.left + parseFloat(cs.paddingLeft) : fallbackInset;
174
+ const textRight = rect ? rect.right - parseFloat(cs.paddingRight) : window.innerWidth - fallbackInset;
175
+ const pin = textLeft - 10;
176
+ const brand = brandRef.current?.getBoundingClientRect();
177
+ if (brand) brandMaxRight.current = Math.max(brandMaxRight.current, brand.right);
178
+ const brandClear = brandMaxRight.current + 16 <= pin;
166
179
  const actions = actionsRef.current?.getBoundingClientRect();
167
- const actionsW = actions ? window.innerWidth - actions.left : 0;
168
- setTabsFit(tabsW > 0 && tabsW <= TABS_EDGE_MAX_W && inset >= actionsW + 16);
180
+ const actionsLeft = actions ? actions.left : window.innerWidth;
181
+ const pinnedSearchLeft = textRight - closedWidth;
182
+ const packedSearchLeft = actionsLeft - 8 - closedWidth;
183
+ const fits = (searchLeft) => w > 0 && brandClear && pin + w + 16 <= searchLeft;
184
+ const fullPin = textRight + 16 <= actionsLeft && fits(pinnedSearchLeft);
185
+ setTabsW(w);
186
+ setPinLeft(pin);
187
+ setSearchPinRight(fullPin ? window.innerWidth - textRight : null);
188
+ setTabsFit(fullPin || fits(packedSearchLeft));
169
189
  };
170
190
  measure();
171
191
  const ro = new ResizeObserver(measure);
172
192
  ro.observe(tabsEl);
193
+ if (content) ro.observe(content);
194
+ if (brandRef.current) ro.observe(brandRef.current);
173
195
  if (actionsRef.current) ro.observe(actionsRef.current);
174
196
  window.addEventListener("resize", measure);
175
197
  return () => {
176
198
  ro.disconnect();
177
199
  window.removeEventListener("resize", measure);
178
200
  };
179
- }, []);
180
- const edgeAligned = isLgUp && !iconButtonMode && !searchOpen && tabsFit;
181
- const closedWidth = isXlUp ? CLOSED_WIDTH_XL : isLgUp ? CLOSED_WIDTH_LG : CLOSED_WIDTH_MD;
201
+ }, [closedWidth]);
202
+ const edgePinned = isLgUp && !iconButtonMode && tabsFit;
182
203
  const searchStyle = iconButtonMode ? {
183
204
  // In-flow grid item — sits at the end of column 2 next to TabStrip.
184
205
  // Square footprint, icon-only, click opens the palette directly.
@@ -223,6 +244,22 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
223
244
  placeSelf: "stretch",
224
245
  boxShadow: "0 4px 14px rgb(0 0 0 / 0.06)",
225
246
  transition: `left ${EXPAND_MS}ms ${EXPAND_EASE}, right ${EXPAND_MS}ms ${EXPAND_EASE}, background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease`
247
+ } : searchPinRight != null ? {
248
+ // Closed, right-aligned to the content column's right
249
+ // edge (mirrors the pinned tab strip on the left edge).
250
+ // (gridArea/placeSelf: see the note above about
251
+ // fixed-positioned grid items.)
252
+ position: "fixed",
253
+ top: 6,
254
+ right: searchPinRight,
255
+ width: closedWidth,
256
+ maxWidth: closedWidth,
257
+ height: 28,
258
+ padding: "6px 10px",
259
+ borderRadius: 12,
260
+ gridArea: "auto",
261
+ placeSelf: "stretch",
262
+ transition: `width ${EXPAND_MS}ms ${EXPAND_EASE}, background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease`
226
263
  } : {
227
264
  gridColumn: 2,
228
265
  gridRow: 1,
@@ -256,6 +293,7 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
256
293
  /* @__PURE__ */ jsxs(
257
294
  "a",
258
295
  {
296
+ ref: brandRef,
259
297
  href: "/",
260
298
  className: "flex items-center gap-2.5 pl-[14px] md:pl-[18px] no-underline text-doc-template-ink hover:opacity-80 min-w-[72px] shrink-0",
261
299
  style: {
@@ -354,10 +392,19 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
354
392
  "aria-label": "Breadcrumb",
355
393
  className: "hidden md:flex items-center gap-1.5 text-doc-template-muted uppercase pointer-events-none overflow-hidden",
356
394
  style: {
357
- gridColumn: 2,
358
- gridRow: 1,
359
- justifySelf: "start",
360
- marginLeft: 8,
395
+ // With the tab strip pinned to the content column's left
396
+ // edge, the breadcrumb moves out of the grid too and starts
397
+ // just right of the strip — otherwise it would fade in
398
+ // underneath the tabs. (gridArea/placeSelf: see the note on
399
+ // `searchStyle` about fixed-positioned grid items.)
400
+ ...edgePinned ? {
401
+ position: "fixed",
402
+ top: 0,
403
+ height: 40,
404
+ left: pinLeft + tabsW + 24,
405
+ gridArea: "auto",
406
+ placeSelf: "stretch"
407
+ } : { gridColumn: 2, gridRow: 1, justifySelf: "start", marginLeft: 8 },
361
408
  fontFamily: "var(--font-doc-template-mono)",
362
409
  fontSize: 10,
363
410
  fontWeight: 500,
@@ -383,16 +430,9 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
383
430
  /* @__PURE__ */ jsxs(
384
431
  "div",
385
432
  {
386
- ref: clusterRef,
387
433
  style: {
388
- ...edgeAligned ? {
389
- position: "fixed",
390
- top: 0,
391
- right: SEARCH_INSET,
392
- height: 40,
393
- gridArea: "auto",
394
- placeSelf: "stretch"
395
- } : { gridColumn: 2, gridRow: 1 },
434
+ gridColumn: 2,
435
+ gridRow: 1,
396
436
  display: "flex",
397
437
  alignItems: "center",
398
438
  justifyContent: "flex-end",
@@ -405,7 +445,10 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
405
445
  {
406
446
  ref: tabsRef,
407
447
  className: "flex items-stretch",
408
- style: { ...palFade(searchOpen), order: edgeAligned ? 1 : 0 },
448
+ style: {
449
+ ...palFade(searchOpen),
450
+ ...edgePinned ? { position: "fixed", top: 0, left: pinLeft, height: 40 } : {}
451
+ },
409
452
  children: /* @__PURE__ */ jsx(TabStrip, {})
410
453
  }
411
454
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamlake/dockit",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "The DreamLake documentation shell — Vike + React + Tailwind v4 + MDX docs framework with sidebar, TOC, search, and theming",
5
5
  "license": "MIT",
6
6
  "type": "module",