@bitbaum/sitekit 0.4.2 → 0.5.0

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.
package/README.md CHANGED
@@ -160,3 +160,9 @@ semantic navigation groups. Existing flat `links` remain supported. Consumers
160
160
  can configure `--sk-header-height`, `--sk-mobile-header-height`,
161
161
  `--sk-display-weight`, `--sk-display-leading`, `--sk-display-tracking` and
162
162
  `--sk-footer-gap` through their theme adapter, without replacing shared markup.
163
+
164
+ ## Mobile tab bar
165
+
166
+ `SiteTabBar` renders a fixed bottom navigation for small screens (hidden from
167
+ 48rem up, where the masthead nav takes over). Pass 3–5 `{ path, label, icon }`
168
+ items and the current path; it renders its own spacer so content is not hidden.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The mobile bottom tab bar: three to five destinations within thumb reach.
3
+ *
4
+ * A server component like SiteNav (the injected Link is a function). It is
5
+ * hidden from the desktop breakpoint up by styles.css (`--sk-tabbar-hide`,
6
+ * default 48rem) so the masthead nav stays the one desktop navigation.
7
+ * Consumers add `padding-bottom: var(--sk-tabbar-height)` to their page body
8
+ * on small screens, or use the `.sk-tabbar-spacer` the component renders.
9
+ */
10
+ import type { ReactNode } from 'react';
11
+ import { type LinkLike } from './link.js';
12
+ export interface SiteTabItem {
13
+ path: string;
14
+ label: string;
15
+ /** Decorative glyph or SVG; hidden from assistive tech. */
16
+ icon: ReactNode;
17
+ }
18
+ interface Props {
19
+ items: SiteTabItem[];
20
+ currentPath: string;
21
+ Link?: LinkLike;
22
+ label?: string;
23
+ }
24
+ export declare function SiteTabBar({ items, currentPath, Link, label }: Props): import("react").JSX.Element | null;
25
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { href } from '../model.js';
3
+ import { DefaultLink } from './link.js';
4
+ const isCurrent = (path, current) => {
5
+ const a = href(path);
6
+ const b = href(current);
7
+ return a === '/' ? b === '/' : b === a || b.startsWith(a + '/');
8
+ };
9
+ export function SiteTabBar({ items, currentPath, Link = DefaultLink, label = 'Main' }) {
10
+ if (items.length === 0)
11
+ return null;
12
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sk-tabbar-spacer", "aria-hidden": "true" }), _jsx("nav", { className: "sk-tabbar", "aria-label": label, children: items.slice(0, 5).map((item) => {
13
+ const current = isCurrent(item.path, currentPath);
14
+ return (_jsxs(Link, { href: href(item.path), "aria-current": current ? 'page' : undefined, className: "sk-tab", children: [_jsx("span", { className: "sk-tab-icon", "aria-hidden": "true", children: item.icon }), _jsx("span", { className: "sk-tab-label", children: item.label })] }, item.path));
15
+ }) })] }));
16
+ }
@@ -9,6 +9,7 @@
9
9
  export { SiteSections } from './SiteSections.js';
10
10
  export { SiteMasthead, SiteFooter } from './SiteChrome.js';
11
11
  export { SiteNav } from './SiteNav.js';
12
+ export { SiteTabBar, type SiteTabItem } from './SiteTabBar.js';
12
13
  export { NavAutoScroll } from './NavAutoScroll.js';
13
14
  export { DefaultLink, type LinkLike, type LinkProps } from './link.js';
14
15
  export { SectionHeading, Blurb, SectionBody } from './sections/Primitives.js';
@@ -9,6 +9,7 @@
9
9
  export { SiteSections } from './SiteSections.js';
10
10
  export { SiteMasthead, SiteFooter } from './SiteChrome.js';
11
11
  export { SiteNav } from './SiteNav.js';
12
+ export { SiteTabBar } from './SiteTabBar.js';
12
13
  export { NavAutoScroll } from './NavAutoScroll.js';
13
14
  export { DefaultLink } from './link.js';
14
15
  export { SectionHeading, Blurb, SectionBody } from './sections/Primitives.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbaum/sitekit",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "A website as data: a closed union of section shapes validated at runtime, one set of React renderers, and per-field provenance so a generated site can prove it fabricated nothing.",
5
5
  "license": "MIT",
6
6
  "author": "Mao Nakamoto",
package/styles.css CHANGED
@@ -306,3 +306,57 @@
306
306
  gap: 1.5rem;
307
307
  }
308
308
  }
309
+ /* Mobile bottom tab bar (SiteTabBar). Hidden from --sk-tabbar-breakpoint up. */
310
+ .sk-tabbar {
311
+ position: fixed;
312
+ inset: auto 0 0 0;
313
+ z-index: 45;
314
+ display: flex;
315
+ justify-content: space-around;
316
+ gap: 2px;
317
+ padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
318
+ background: var(--sk-bg, Canvas);
319
+ border-top: 1px solid var(--sk-line, #d6d6d6);
320
+ font-family: var(--sk-font, system-ui, sans-serif);
321
+ }
322
+ .sk-tab {
323
+ flex: 1;
324
+ min-width: 0;
325
+ display: flex;
326
+ flex-direction: column;
327
+ align-items: center;
328
+ gap: 2px;
329
+ padding: 6px 2px;
330
+ border-radius: 10px;
331
+ color: var(--sk-muted, #555);
332
+ font-size: 11px;
333
+ font-weight: 600;
334
+ text-decoration: none;
335
+ }
336
+ .sk-tab-icon {
337
+ font-size: 20px;
338
+ line-height: 1;
339
+ }
340
+ .sk-tab-label {
341
+ white-space: nowrap;
342
+ overflow: hidden;
343
+ text-overflow: ellipsis;
344
+ max-width: 100%;
345
+ }
346
+ .sk-tab[aria-current='page'] {
347
+ color: var(--sk-fg, CanvasText);
348
+ background: var(--sk-tab-active, color-mix(in srgb, currentColor 8%, transparent));
349
+ }
350
+ .sk-tab:focus-visible {
351
+ outline: 2px solid var(--sk-focus, Highlight);
352
+ outline-offset: 2px;
353
+ }
354
+ .sk-tabbar-spacer {
355
+ height: var(--sk-tabbar-height, 68px);
356
+ }
357
+ @media (min-width: 48rem) {
358
+ .sk-tabbar,
359
+ .sk-tabbar-spacer {
360
+ display: none;
361
+ }
362
+ }