@krizaka/ui 3.13.0 → 3.14.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/dist/nav-bar.js +2 -2
- package/dist/nav-bar.js.map +1 -1
- package/dist/site-footer.d.ts +8 -1
- package/dist/site-footer.js +12 -2
- package/dist/site-footer.js.map +1 -1
- package/package.json +4 -4
- package/registry/examples/site-footer/product.js +26 -12
- package/registry/examples/site-footer/product.tsx +15 -2
- package/registry/index.json +6 -6
- package/registry/map.json +1 -1
- package/registry/nav-bar.json +2 -2
- package/registry/site-footer.json +25 -10
package/dist/nav-bar.js
CHANGED
|
@@ -173,8 +173,8 @@ function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, classNam
|
|
|
173
173
|
return /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": `${id}-family`, className: s.mobileFamily({ className }), children: [
|
|
174
174
|
/* @__PURE__ */ jsx("p", { id: `${id}-family`, className: s.familyGroupTitle(), children: labels.family }),
|
|
175
175
|
groups.map(({ group, sites }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
176
|
-
/* @__PURE__ */ jsx("p", { id: `${id}-${group}`, className: s.familyGroupTitle(), children: labels.groups[group] }),
|
|
177
|
-
/* @__PURE__ */ jsx("ul", { "aria-labelledby": `${id}-${group}`, className: "grid gap-0.5", children: sites.map((site) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: site.url, "data-current": site.id === current ? "" : void 0, "aria-current": site.id === current ? "true" : void 0, className: s.mobileFamilyLink(), children: [
|
|
176
|
+
group !== "krizaka" && /* @__PURE__ */ jsx("p", { id: `${id}-${group}`, className: s.familyGroupTitle(), children: labels.groups[group] }),
|
|
177
|
+
/* @__PURE__ */ jsx("ul", { "aria-labelledby": group !== "krizaka" ? `${id}-${group}` : `${id}-family`, className: "grid gap-0.5", children: sites.map((site) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: site.url, "data-current": site.id === current ? "" : void 0, "aria-current": site.id === current ? "true" : void 0, className: s.mobileFamilyLink(), children: [
|
|
178
178
|
/* @__PURE__ */ jsx("span", { className: s.familyMark(), children: /* @__PURE__ */ jsx(FamilyMark, { mark: site.mark, size: 24 }) }),
|
|
179
179
|
/* @__PURE__ */ jsx(FamilyName, { site, current: site.id === current, labels })
|
|
180
180
|
] }) }, site.id)) })
|
package/dist/nav-bar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/nav-bar/nav-bar.tsx"],"sourcesContent":["// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { FamilyMark, GitHubGlyph } from \"../family-mark\";\nimport { type FamilyLabels, familyLabels, type Site, type SiteId, sitesByGroup } from \"../sites/sites\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n // The family switcher: a chevron attached to the brand, its panel aligned on the bar's left edge.\n family: \"hidden md:block\",\n familyTrigger:\n \"group/trigger -ml-2 mr-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=open]:bg-surface-3 data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n familyViewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n familyPanel: \"grid w-[min(92vw,42rem)] gap-x-2 gap-y-3 p-3 sm:grid-cols-2\",\n familyGroupTitle: \"px-3 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n familyLink:\n \"group/link flex items-start gap-3 rounded-xl px-3 py-2.5 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n familyMark: \"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center text-fg\",\n familyName: \"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm font-semibold text-fg\",\n familyText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n familyTag: \"rounded-full px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider\",\n mobileFamily: \"mt-1 grid gap-2 border-t border-border-subtle px-1 pb-1 pt-3\",\n mobileFamilyLink:\n \"flex min-h-11 items-center gap-3 rounded-xl px-2 py-2 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n badge:\n \"hidden h-6 shrink-0 items-center gap-1 rounded-full border border-border-subtle bg-surface-2 px-2 font-mono text-[11px] font-medium text-fg-secondary sm:inline-flex\",\n repo:\n \"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\n/** True inside `NavBar.Mobile`: the family renders as the panel's last group instead of a switcher. */\nconst MobileContext = React.createContext(false);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n <MobileContext.Provider value>{children}</MobileContext.Provider>\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n\nconst tagTone = {\n upcoming: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40\",\n construction: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40\",\n current: \"bg-surface-3 text-fg-secondary\",\n};\n\n/** A site's name and its tags: status when not released, “You are here” on the current one. */\nfunction FamilyName({ site, current, labels }: { site: Site; current: boolean; labels: FamilyLabels }) {\n return (\n <span className={s.familyName()}>\n {site.name}\n {site.status !== \"released\" && <span className={cn(s.familyTag(), tagTone[site.status])}>{labels.status[site.status]}</span>}\n {current && <span className={cn(s.familyTag(), tagTone.current)}>{labels.current}</span>}\n </span>\n );\n}\n\nexport type NavBarFamilyProps = {\n /** The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** Opens the panel at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group). */\n className?: string;\n};\n\n/**\n * The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\n * Krizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\n * or ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n * `NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.\n */\nexport function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, className }: NavBarFamilyProps) {\n const inMobile = React.useContext(MobileContext);\n const id = React.useId();\n const groups = sitesByGroup();\n if (inMobile) {\n return (\n <div role=\"group\" aria-labelledby={`${id}-family`} className={s.mobileFamily({ className })}>\n <p id={`${id}-family`} className={s.familyGroupTitle()}>\n {labels.family}\n </p>\n {groups.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <a href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.mobileFamilyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={24} />\n </span>\n <FamilyName site={site} current={site.id === current} labels={labels} />\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n }\n // Click only: a pointer passing over the brand never opens the family (Radix opens its menus on hover by default).\n const noHover = (e: React.PointerEvent) => e.preventDefault();\n const columns = [groups.filter((g) => g.group === \"krizaka\" || g.group === \"products\"), groups.filter((g) => g.group === \"open-source\" || g.group === \"github\")];\n return (\n <Nav.Root aria-label={labels.family} defaultValue={defaultOpen ? \"family\" : undefined} className={s.family({ className })}>\n <Nav.List className=\"flex\">\n <Nav.Item value=\"family\">\n <Nav.Trigger aria-label={labels.family} onPointerMove={noHover} onPointerLeave={noHover} className={s.familyTrigger()}>\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content onPointerLeave={noHover} className={s.familyPanel()}>\n {columns.map((column, i) => (\n <div key={i} className=\"grid content-start gap-3\">\n {column.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <Nav.Link href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.familyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={28} />\n </span>\n <span className=\"min-w-0\">\n <FamilyName site={site} current={site.id === current} labels={labels} />\n <span className={s.familyText()}>{labels.descriptions[site.id]}</span>\n </span>\n </Nav.Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n ))}\n </Nav.Content>\n </Nav.Item>\n </Nav.List>\n <Nav.Viewport onPointerLeave={noHover} className={s.familyViewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarBadgeProps = React.ComponentProps<\"span\"> & {\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n};\n\n/** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */\nexport function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps) {\n const parts = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean);\n return (\n <span className={s.badge({ className })} {...props}>\n {parts.join(\" · \")}\n {children}\n </span>\n );\n}\n\nexport type NavBarRepoProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The repository the site is built from. */\n href: string;\n /** The link's accessible name (“Source on GitHub”) — passed translated. */\n label: string;\n};\n\n/** The site's repository on GitHub, an icon link of `NavBar.Actions`. */\nexport function NavBarRepo({ label, className, ...props }: NavBarRepoProps) {\n return (\n <a aria-label={label} className={s.repo({ className })} {...props}>\n <GitHubGlyph size={18} />\n </a>\n );\n}\n\n/**\n * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileLink` — the Krizaka navigation bar,\n * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light\n * gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Family: NavBarFamily,\n Badge: NavBarBadge,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Repo: NavBarRepo,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAIA,SAAS,kBAAkB,KAAK,YAAY;AAC5C,YAAY,WAAW;AACvB,SAAS,UAAU;AAmIb,SAyFF,UAzFE,KAuBF,YAvBE;AA5HC,IAAM,SAAS,GAAG;AAAA,EACvB,OAAO;AAAA,IACL,MACE;AAAA,IAGF,OACE;AAAA,IAEF,OAAO;AAAA,IACP,MACE;AAAA,IAKF,MACE;AAAA,IAIF,SAAS;AAAA,IACT,UACE;AAAA,IAEF,OAAO;AAAA,IACP,UACE;AAAA,IAEF,UACE;AAAA,IAEF,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT,QACE;AAAA,IAEF,QAAQ;AAAA,IACR,YACE;AAAA;AAAA,IAGF,QAAQ;AAAA,IACR,eACE;AAAA,IAEF,gBACE;AAAA,IAEF,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,YACE;AAAA,IAEF,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,cAAc;AAAA,IACd,kBACE;AAAA,IAEF,OACE;AAAA,IACF,MACE;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,MACR,MAAM,EAAE,MAAM,iEAAiE;AAAA,MAC/E,OAAO,EAAE,MAAM,wBAAwB;AAAA,IACzC;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,UAAU,KAAK;AACpC,CAAC;AACD,IAAM,IAAI,OAAO;AAQjB,IAAM,UAAgB,oBAAoC,IAAI;AAE9D,IAAM,gBAAsB,oBAAc,KAAK;AAC/C,IAAM,YAAY,MAAM;AACtB,QAAM,MAAY,iBAAW,OAAO;AACpC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4CAA4C;AACtE,SAAO;AACT;AAkBO,SAAS,WAAW,EAAE,OAAO,eAAe,IAAI,WAAW,MAAM,WAAW,UAAU,GAAG,MAAM,GAAoB;AACxH,QAAM,CAAC,SAAS,UAAU,IAAU,eAAS,KAAK;AAClD,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,KAAK;AAC5C,QAAM,UAAgB,YAAM;AAC5B,EAAM,gBAAU,MAAM;AACpB,QAAI,iBAAiB,MAAO;AAC5B,UAAM,WAAW,MAAM,WAAW,OAAO,UAAU,YAAY;AAC/D,aAAS;AACT,WAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAC7D,WAAO,MAAM,OAAO,oBAAoB,UAAU,QAAQ;AAAA,EAC5D,GAAG,CAAC,YAAY,CAAC;AACjB,QAAM,MAAY,cAAQ,OAAO,EAAE,OAAO,MAAM,SAAS,QAAQ,IAAI,CAAC,OAAO,MAAM,OAAO,CAAC;AAC3F,SACE,oBAAC,QAAQ,UAAR,EAAiB,OAAO,KACvB,8BAAC,YAAO,gBAAc,UAAU,KAAK,QAAW,iBAAe,UAAU,WAAW,OAAO,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAC/H,UACH,GACF;AAEJ;AAQO,SAAS,YAAY,EAAE,SAAS,WAAW,GAAG,MAAM,GAAqB;AAC9E,QAAM,OAAO,UAAU,KAAK,OAAO;AACnC,SAAO,oBAAC,QAAK,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC7D;AAGO,SAAS,YAAY,EAAE,WAAW,UAAU,GAAG,MAAM,GAA+D;AACzH,QAAM,EAAE,MAAM,IAAI,UAAU;AAC5B,QAAM,EAAE,KAAK,SAAS,IAAI,oBAAsC,EAAE,MAAM,mBAAmB,QAAQ,gCAAgC,QAAQ,KAAK,CAAC;AACjJ,SACE,qBAAC,IAAI,MAAJ,EAAS,cAAY,OAAO,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,eAAe,IAAK,GAAG,OACrF;AAAA,wBAAC,IAAI,MAAJ,EAAS,KAAU,WAAW,EAAE,KAAK,GAAI,GAAG,UAC1C,UACH;AAAA,IACA,oBAAC,IAAI,UAAJ,EAAa,WAAW,EAAE,SAAS,GAAG;AAAA,KACzC;AAEJ;AAQO,SAAS,WAAW,EAAE,QAAQ,WAAW,GAAG,MAAM,GAAoB;AAC3E,SACE,oBAAC,IAAI,MAAJ,EACC,8BAAC,IAAI,MAAJ,EAAS,iBAAc,IAAG,eAAa,SAAS,KAAK,QAAW,QAAgB,gBAAc,SAAS,SAAS,QAAW,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO,GAC3K;AAEJ;AAiBO,SAAS,WAAW,EAAE,OAAO,QAAQ,UAAU,GAAG,UAAU,WAAW,GAAG,MAAM,GAAoB;AACzG,SACE,qBAAC,IAAI,MAAJ,EAAS,WAAuB,GAAG,OAClC;AAAA,yBAAC,IAAI,SAAJ,EAAY,iBAAc,IAAG,eAAa,SAAS,KAAK,QAAW,WAAW,GAAG,EAAE,KAAK,GAAG,eAAe,GACxG;AAAA;AAAA,MACD,oBAAC,SAAI,SAAQ,aAAY,WAAW,EAAE,QAAQ,GAAG,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,eAAW,MAClI,8BAAC,UAAK,GAAE,gBAAe,GACzB;AAAA,OACF;AAAA,IACA,oBAAC,IAAI,SAAJ,EAAY,WAAW,GAAG,EAAE,MAAM,GAAG,YAAY,KAAK,gBAAgB,GAAI,UAAS;AAAA,KACtF;AAEJ;AAcO,SAAS,eAAe,EAAE,OAAO,aAAa,MAAM,QAAQ,MAAM,WAAW,SAAS,UAAU,GAAG,MAAM,GAAwB;AACtI,QAAM,OACJ,iCACG;AAAA,YAAQ,oBAAC,UAAK,WAAW,EAAE,SAAS,GAAI,gBAAK;AAAA,IAC9C,qBAAC,UAAK,WAAU,WACd;AAAA,0BAAC,UAAK,WAAW,EAAE,UAAU,GAAI,iBAAM;AAAA,MACtC,eAAe,oBAAC,UAAK,WAAW,EAAE,SAAS,GAAI,uBAAY;AAAA,OAC9D;AAAA,KACF;AAEF,SACE,oBAAC,IAAI,MAAJ,EAAS,SAAkB,MAAM,UAAU,SAAY,MAAM,QAAgB,gBAAc,SAAS,SAAS,QAAW,eAAa,SAAS,KAAK,QAAW,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC,GAAI,GAAG,OACtM,qBAAiB,qBAA+C,QAAQ,IAAU,mBAAa,UAAU,QAAW,IAAI,IAAI,MAC/H;AAEJ;AAGO,SAAS,cAAc,EAAE,WAAW,GAAG,MAAM,GAAgC;AAClF,SAAO,oBAAC,SAAI,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC9D;AAaO,SAAS,aAAa,EAAE,WAAW,YAAY,WAAW,UAAU,GAAG,MAAM,GAAsB;AACxG,QAAM,EAAE,OAAO,MAAM,SAAS,QAAQ,IAAI,UAAU;AACpD,QAAM,SAAe,aAA0B,IAAI;AACnD,EAAM,gBAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAqB;AAClC,UAAI,EAAE,QAAQ,SAAU;AACxB,cAAQ,KAAK;AACb,aAAO,SAAS,MAAM;AAAA,IACxB;AACA,aAAS,iBAAiB,WAAW,KAAK;AAC1C,WAAO,MAAM,SAAS,oBAAoB,WAAW,KAAK;AAAA,EAC5D,GAAG,CAAC,MAAM,OAAO,CAAC;AAClB,SACE,iCACE;AAAA,wBAAC,YAAO,KAAK,QAAQ,MAAK,UAAS,iBAAe,MAAM,iBAAe,SAAS,cAAY,OAAO,aAAa,WAAW,SAAS,MAAM,QAAQ,CAAC,IAAI,GAAG,WAAW,EAAE,OAAO,GAC5K,8BAAC,SAAI,SAAQ,aAAY,WAAU,eAAc,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,eAAW,MACjI,iBAAO,oBAAC,UAAK,GAAE,wBAAuB,IAAK,oBAAC,UAAK,GAAE,2BAA0B,GAChF,GACF;AAAA,IACC,QACC;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,QACJ,cAAY;AAAA,QACZ,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC;AAAA,QACjC,SAAS,CAAC,MAAM;AACd,cAAK,EAAE,OAAmB,QAAQ,GAAG,EAAG,SAAQ,KAAK;AAAA,QACvD;AAAA,QACC,GAAG;AAAA,QAEJ,8BAAC,cAAc,UAAd,EAAuB,OAAK,MAAE,UAAS;AAAA;AAAA,IAC1C;AAAA,KAEJ;AAEJ;AAUO,SAAS,iBAAiB,EAAE,QAAQ,SAAS,WAAW,GAAG,MAAM,GAA0B;AAChG,QAAM,OAAO,UAAU,KAAK,OAAO;AACnC,SAAO,oBAAC,QAAK,gBAAc,SAAS,SAAS,QAAW,WAAW,EAAE,WAAW,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC7G;AAGA,IAAM,UAAU;AAAA,EACd,UAAU;AAAA,EACV,cAAc;AAAA,EACd,SAAS;AACX;AAGA,SAAS,WAAW,EAAE,MAAM,SAAS,OAAO,GAA2D;AACrG,SACE,qBAAC,UAAK,WAAW,EAAE,WAAW,GAC3B;AAAA,SAAK;AAAA,IACL,KAAK,WAAW,cAAc,oBAAC,UAAK,WAAW,GAAG,EAAE,UAAU,GAAG,QAAQ,KAAK,MAAM,CAAC,GAAI,iBAAO,OAAO,KAAK,MAAM,GAAE;AAAA,IACpH,WAAW,oBAAC,UAAK,WAAW,GAAG,EAAE,UAAU,GAAG,QAAQ,OAAO,GAAI,iBAAO,SAAQ;AAAA,KACnF;AAEJ;AAsBO,SAAS,aAAa,EAAE,SAAS,SAAS,aAAa,IAAI,aAAa,UAAU,GAAsB;AAC7G,QAAM,WAAiB,iBAAW,aAAa;AAC/C,QAAM,KAAW,YAAM;AACvB,QAAM,SAAS,aAAa;AAC5B,MAAI,UAAU;AACZ,WACE,qBAAC,SAAI,MAAK,SAAQ,mBAAiB,GAAG,EAAE,WAAW,WAAW,EAAE,aAAa,EAAE,UAAU,CAAC,GACxF;AAAA,0BAAC,OAAE,IAAI,GAAG,EAAE,WAAW,WAAW,EAAE,iBAAiB,GAClD,iBAAO,QACV;AAAA,MACC,OAAO,IAAI,CAAC,EAAE,OAAO,MAAM,MAC1B,qBAAC,SACC;AAAA,4BAAC,OAAE,IAAI,GAAG,EAAE,IAAI,KAAK,IAAI,WAAW,EAAE,iBAAiB,GACpD,iBAAO,OAAO,KAAK,GACtB;AAAA,QACA,oBAAC,QAAG,mBAAiB,GAAG,EAAE,IAAI,KAAK,IAAI,WAAU,gBAC9C,gBAAM,IAAI,CAAC,SACV,oBAAC,QACC,+BAAC,OAAE,MAAM,KAAK,KAAK,gBAAc,KAAK,OAAO,UAAU,KAAK,QAAW,gBAAc,KAAK,OAAO,UAAU,SAAS,QAAW,WAAW,EAAE,iBAAiB,GAC3J;AAAA,8BAAC,UAAK,WAAW,EAAE,WAAW,GAC5B,8BAAC,cAAW,MAAM,KAAK,MAAM,MAAM,IAAI,GACzC;AAAA,UACA,oBAAC,cAAW,MAAY,SAAS,KAAK,OAAO,SAAS,QAAgB;AAAA,WACxE,KANO,KAAK,EAOd,CACD,GACH;AAAA,WAfQ,KAgBV,CACD;AAAA,OACH;AAAA,EAEJ;AAEA,QAAM,UAAU,CAAC,MAA0B,EAAE,eAAe;AAC5D,QAAM,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,aAAa,EAAE,UAAU,UAAU,GAAG,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,iBAAiB,EAAE,UAAU,QAAQ,CAAC;AAC/J,SACE,qBAAC,IAAI,MAAJ,EAAS,cAAY,OAAO,QAAQ,cAAc,cAAc,WAAW,QAAW,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,GACtH;AAAA,wBAAC,IAAI,MAAJ,EAAS,WAAU,QAClB,+BAAC,IAAI,MAAJ,EAAS,OAAM,UACd;AAAA,0BAAC,IAAI,SAAJ,EAAY,cAAY,OAAO,QAAQ,eAAe,SAAS,gBAAgB,SAAS,WAAW,EAAE,cAAc,GAClH,8BAAC,SAAI,SAAQ,aAAY,WAAW,EAAE,QAAQ,GAAG,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,eAAW,MAClI,8BAAC,UAAK,GAAE,gBAAe,GACzB,GACF;AAAA,MACA,oBAAC,IAAI,SAAJ,EAAY,gBAAgB,SAAS,WAAW,EAAE,YAAY,GAC5D,kBAAQ,IAAI,CAAC,QAAQ,MACpB,oBAAC,SAAY,WAAU,4BACpB,iBAAO,IAAI,CAAC,EAAE,OAAO,MAAM,MAC1B,qBAAC,SACC;AAAA,4BAAC,OAAE,IAAI,GAAG,EAAE,IAAI,KAAK,IAAI,WAAW,EAAE,iBAAiB,GACpD,iBAAO,OAAO,KAAK,GACtB;AAAA,QACA,oBAAC,QAAG,mBAAiB,GAAG,EAAE,IAAI,KAAK,IAAI,WAAU,gBAC9C,gBAAM,IAAI,CAAC,SACV,oBAAC,QACC,+BAAC,IAAI,MAAJ,EAAS,MAAM,KAAK,KAAK,gBAAc,KAAK,OAAO,UAAU,KAAK,QAAW,gBAAc,KAAK,OAAO,UAAU,SAAS,QAAW,WAAW,EAAE,WAAW,GAC5J;AAAA,8BAAC,UAAK,WAAW,EAAE,WAAW,GAC5B,8BAAC,cAAW,MAAM,KAAK,MAAM,MAAM,IAAI,GACzC;AAAA,UACA,qBAAC,UAAK,WAAU,WACd;AAAA,gCAAC,cAAW,MAAY,SAAS,KAAK,OAAO,SAAS,QAAgB;AAAA,YACtE,oBAAC,UAAK,WAAW,EAAE,WAAW,GAAI,iBAAO,aAAa,KAAK,EAAE,GAAE;AAAA,aACjE;AAAA,WACF,KATO,KAAK,EAUd,CACD,GACH;AAAA,WAlBQ,KAmBV,CACD,KAtBO,CAuBV,CACD,GACH;AAAA,OACF,GACF;AAAA,IACA,oBAAC,IAAI,UAAJ,EAAa,gBAAgB,SAAS,WAAW,EAAE,eAAe,GAAG;AAAA,KACxE;AAEJ;AAUO,SAAS,YAAY,EAAE,KAAK,SAAS,WAAW,UAAU,GAAG,MAAM,GAAqB;AAC7F,QAAM,QAAQ,CAAC,KAAK,WAAW,IAAI,QAAQ,QAAQ,MAAM,EAAE,CAAC,EAAE,EAAE,OAAO,OAAO;AAC9E,SACE,qBAAC,UAAK,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAC1C;AAAA,UAAM,KAAK,QAAK;AAAA,IAChB;AAAA,KACH;AAEJ;AAUO,SAAS,WAAW,EAAE,OAAO,WAAW,GAAG,MAAM,GAAoB;AAC1E,SACE,oBAAC,OAAE,cAAY,OAAO,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAC1D,8BAAC,eAAY,MAAM,IAAI,GACzB;AAEJ;AAOO,IAAM,SAAS;AAAA,EACpB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AACd;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/nav-bar/nav-bar.tsx"],"sourcesContent":["// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { FamilyMark, GitHubGlyph } from \"../family-mark\";\nimport { type FamilyLabels, familyLabels, type Site, type SiteId, sitesByGroup } from \"../sites/sites\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n // The family switcher: a chevron attached to the brand, its panel aligned on the bar's left edge.\n family: \"hidden md:block\",\n familyTrigger:\n \"group/trigger -ml-2 mr-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=open]:bg-surface-3 data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n familyViewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n familyPanel: \"grid w-[min(92vw,42rem)] gap-x-2 gap-y-3 p-3 sm:grid-cols-2\",\n familyGroupTitle: \"px-3 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n familyLink:\n \"group/link flex items-start gap-3 rounded-xl px-3 py-2.5 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n familyMark: \"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center text-fg\",\n familyName: \"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm font-semibold text-fg\",\n familyText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n familyTag: \"rounded-full px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider\",\n mobileFamily: \"mt-1 grid gap-2 border-t border-border-subtle px-1 pb-1 pt-3\",\n mobileFamilyLink:\n \"flex min-h-11 items-center gap-3 rounded-xl px-2 py-2 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n badge:\n \"hidden h-6 shrink-0 items-center gap-1 rounded-full border border-border-subtle bg-surface-2 px-2 font-mono text-[11px] font-medium text-fg-secondary sm:inline-flex\",\n repo:\n \"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\n/** True inside `NavBar.Mobile`: the family renders as the panel's last group instead of a switcher. */\nconst MobileContext = React.createContext(false);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n <MobileContext.Provider value>{children}</MobileContext.Provider>\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n\nconst tagTone = {\n upcoming: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40\",\n construction: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40\",\n current: \"bg-surface-3 text-fg-secondary\",\n};\n\n/** A site's name and its tags: status when not released, “You are here” on the current one. */\nfunction FamilyName({ site, current, labels }: { site: Site; current: boolean; labels: FamilyLabels }) {\n return (\n <span className={s.familyName()}>\n {site.name}\n {site.status !== \"released\" && <span className={cn(s.familyTag(), tagTone[site.status])}>{labels.status[site.status]}</span>}\n {current && <span className={cn(s.familyTag(), tagTone.current)}>{labels.current}</span>}\n </span>\n );\n}\n\nexport type NavBarFamilyProps = {\n /** The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** Opens the panel at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group). */\n className?: string;\n};\n\n/**\n * The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\n * Krizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\n * or ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n * `NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.\n */\nexport function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, className }: NavBarFamilyProps) {\n const inMobile = React.useContext(MobileContext);\n const id = React.useId();\n const groups = sitesByGroup();\n if (inMobile) {\n return (\n <div role=\"group\" aria-labelledby={`${id}-family`} className={s.mobileFamily({ className })}>\n <p id={`${id}-family`} className={s.familyGroupTitle()}>\n {labels.family}\n </p>\n {groups.map(({ group, sites }) => (\n <div key={group}>\n {/* The company's own group is named like the family itself: its heading would repeat the panel's. */}\n {group !== \"krizaka\" && (\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n )}\n <ul aria-labelledby={group !== \"krizaka\" ? `${id}-${group}` : `${id}-family`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <a href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.mobileFamilyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={24} />\n </span>\n <FamilyName site={site} current={site.id === current} labels={labels} />\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n }\n // Click only: a pointer passing over the brand never opens the family (Radix opens its menus on hover by default).\n const noHover = (e: React.PointerEvent) => e.preventDefault();\n const columns = [groups.filter((g) => g.group === \"krizaka\" || g.group === \"products\"), groups.filter((g) => g.group === \"open-source\" || g.group === \"github\")];\n return (\n <Nav.Root aria-label={labels.family} defaultValue={defaultOpen ? \"family\" : undefined} className={s.family({ className })}>\n <Nav.List className=\"flex\">\n <Nav.Item value=\"family\">\n <Nav.Trigger aria-label={labels.family} onPointerMove={noHover} onPointerLeave={noHover} className={s.familyTrigger()}>\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content onPointerLeave={noHover} className={s.familyPanel()}>\n {columns.map((column, i) => (\n <div key={i} className=\"grid content-start gap-3\">\n {column.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <Nav.Link href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.familyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={28} />\n </span>\n <span className=\"min-w-0\">\n <FamilyName site={site} current={site.id === current} labels={labels} />\n <span className={s.familyText()}>{labels.descriptions[site.id]}</span>\n </span>\n </Nav.Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n ))}\n </Nav.Content>\n </Nav.Item>\n </Nav.List>\n <Nav.Viewport onPointerLeave={noHover} className={s.familyViewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarBadgeProps = React.ComponentProps<\"span\"> & {\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n};\n\n/** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */\nexport function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps) {\n const parts = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean);\n return (\n <span className={s.badge({ className })} {...props}>\n {parts.join(\" · \")}\n {children}\n </span>\n );\n}\n\nexport type NavBarRepoProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The repository the site is built from. */\n href: string;\n /** The link's accessible name (“Source on GitHub”) — passed translated. */\n label: string;\n};\n\n/** The site's repository on GitHub, an icon link of `NavBar.Actions`. */\nexport function NavBarRepo({ label, className, ...props }: NavBarRepoProps) {\n return (\n <a aria-label={label} className={s.repo({ className })} {...props}>\n <GitHubGlyph size={18} />\n </a>\n );\n}\n\n/**\n * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileLink` — the Krizaka navigation bar,\n * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light\n * gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Family: NavBarFamily,\n Badge: NavBarBadge,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Repo: NavBarRepo,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"],"mappings":"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","names":[]}
|
package/dist/site-footer.d.ts
CHANGED
|
@@ -143,11 +143,18 @@ type SiteFooterProps = Omit<React.ComponentProps<"footer">, "children"> & {
|
|
|
143
143
|
version?: string;
|
|
144
144
|
/** The environment, when it is not production (`DEV`). */
|
|
145
145
|
env?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Shows the family in columns (the Krizaka sites: www, ui., java.). A product is independent once deployed: pass
|
|
148
|
+
* `family={false}` and the footer holds only its own columns, the legal links, the version and one publisher line —
|
|
149
|
+
* “A Krizaka product”, linking to www.krizaka.com.
|
|
150
|
+
* @default true
|
|
151
|
+
*/
|
|
152
|
+
family?: boolean;
|
|
146
153
|
/** The end of the signature line: legal links, a language switch (`SiteFooter.Link`s or any inline content). */
|
|
147
154
|
legal?: React.ReactNode;
|
|
148
155
|
};
|
|
149
156
|
/** The footer of a Krizaka site: its own columns, the family in columns, the signature, the version, Montréal. */
|
|
150
|
-
declare function SiteFooter({ current, labels, children, version, env, legal, className, ...props }: SiteFooterProps): React.JSX.Element;
|
|
157
|
+
declare function SiteFooter({ current, labels, children, version, env, legal, family, className, ...props }: SiteFooterProps): React.JSX.Element;
|
|
151
158
|
declare namespace SiteFooter {
|
|
152
159
|
var Column: typeof SiteFooterColumn;
|
|
153
160
|
var Link: typeof SiteFooterLink;
|
package/dist/site-footer.js
CHANGED
|
@@ -37,7 +37,17 @@ var siteFooter = tv({
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
var s = siteFooter();
|
|
40
|
-
function SiteFooter({
|
|
40
|
+
function SiteFooter({
|
|
41
|
+
current,
|
|
42
|
+
labels = familyLabels.en,
|
|
43
|
+
children,
|
|
44
|
+
version,
|
|
45
|
+
env,
|
|
46
|
+
legal,
|
|
47
|
+
family = true,
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}) {
|
|
41
51
|
const id = React.useId();
|
|
42
52
|
const site = siteById(current);
|
|
43
53
|
const krizaka = siteById("krizaka");
|
|
@@ -53,7 +63,7 @@ function SiteFooter({ current, labels = familyLabels.en, children, version, env,
|
|
|
53
63
|
] }),
|
|
54
64
|
children && /* @__PURE__ */ jsx("div", { className: s.own(), children })
|
|
55
65
|
] }),
|
|
56
|
-
/* @__PURE__ */ jsx("nav", { "aria-label": labels.family, className: s.family(), children: sitesByGroup().map(({ group, sites }) => /* @__PURE__ */ jsxs("div", { className: s.column(), children: [
|
|
66
|
+
family && /* @__PURE__ */ jsx("nav", { "aria-label": labels.family, className: s.family(), children: sitesByGroup().map(({ group, sites }) => /* @__PURE__ */ jsxs("div", { className: s.column(), children: [
|
|
57
67
|
/* @__PURE__ */ jsx("p", { id: `${id}-${group}`, className: s.title(), children: labels.groups[group] }),
|
|
58
68
|
/* @__PURE__ */ jsx("ul", { "aria-labelledby": `${id}-${group}`, className: s.list(), children: sites.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: item.url, "aria-current": item.id === current ? "true" : void 0, className: s.link(), children: [
|
|
59
69
|
/* @__PURE__ */ jsx("span", { className: s.mark(), children: /* @__PURE__ */ jsx(FamilyMark, { mark: item.mark, size: 16 }) }),
|
package/dist/site-footer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/site-footer/site-footer.tsx"],"sourcesContent":["// Client (the marks hold an id). The footer shared by every site of the Krizaka family: the site's own columns, then the\n// family in columns from `@krizaka/ui/sites` (the same groups as the bar's switcher), then the signature line — “A\n// Krizaka product” by the kind of the current site — the version and the place.\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { FamilyMark } from \"../family-mark\";\nimport { KrizakaLogo } from \"../marks/KrizakaLogo\";\nimport { type FamilyLabels, familyLabels, siteById, type SiteId, sitesByGroup } from \"../sites/sites\";\n\nexport const siteFooter = tv({\n slots: {\n root: \"w-full border-t border-border-subtle bg-surface-0 text-fg\",\n inner: \"mx-auto grid w-full max-w-(--kz-page-max,72rem) grid-cols-[minmax(0,1fr)] gap-10 px-(--kz-page-gutter,16px) py-12\",\n top: \"grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]\",\n site: \"grid content-start gap-3\",\n siteName: \"inline-flex items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg\",\n siteText: \"max-w-xs text-sm leading-relaxed text-fg-secondary\",\n own: \"grid grid-cols-2 gap-8 sm:grid-cols-3\",\n family: \"grid grid-cols-2 gap-8 border-t border-border-subtle pt-8 md:grid-cols-4\",\n column: \"grid min-w-0 content-start gap-3\",\n title: \"text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n list: \"grid min-w-0 gap-2\",\n link:\n \"inline-flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 rounded-md text-sm [overflow-wrap:anywhere] text-fg-secondary no-underline transition-colors hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg aria-[current=true]:text-fg aria-[current=true]:font-semibold\",\n mark: \"flex h-4 w-4 shrink-0 items-center justify-center text-fg\",\n tag: \"rounded-full bg-surface-3 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider text-fg-secondary\",\n bottom: \"flex flex-wrap items-center gap-x-5 gap-y-3 border-t border-border-subtle pt-6 text-xs text-fg-secondary\",\n signature:\n \"inline-flex items-center gap-2 rounded-md font-medium text-fg no-underline hover:text-fg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n version: \"rounded-full border border-border-subtle bg-surface-2 px-2 py-0.5 font-mono text-[11px] text-fg-secondary\",\n legal: \"flex flex-wrap items-center gap-x-4 gap-y-2 sm:ml-auto\",\n },\n});\nconst s = siteFooter();\n\nexport type SiteFooterProps = Omit<React.ComponentProps<\"footer\">, \"children\"> & {\n /** The site this footer belongs to (`@krizaka/ui/sites`): its mark and line, its signature, marked in the family. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** The site's own columns (`SiteFooter.Column`), before the family. */\n children?: React.ReactNode;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The end of the signature line: legal links, a language switch (`SiteFooter.Link`s or any inline content). */\n legal?: React.ReactNode;\n};\n\n/** The footer of a Krizaka site: its own columns, the family in columns, the signature, the version, Montréal. */\nexport function SiteFooter({ current, labels = familyLabels.en, children, version, env, legal, className, ...props }: SiteFooterProps) {\n const id = React.useId();\n const site = siteById(current);\n const krizaka = siteById(\"krizaka\");\n const tag = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean).join(\" · \");\n return (\n <footer className={s.root({ className })} {...props}>\n <div className={s.inner()}>\n <div className={s.top()}>\n <div className={s.site()}>\n <a href={site.url} className={s.siteName()}>\n <FamilyMark mark={site.mark} size={24} />\n {site.name}\n </a>\n <p className={s.siteText()}>{labels.descriptions[site.id]}</p>\n </div>\n {children && <div className={s.own()}>{children}</div>}\n </div>\n <nav aria-label={labels.family} className={s.family()}>\n {sitesByGroup().map(({ group, sites }) => (\n <div key={group} className={s.column()}>\n <p id={`${id}-${group}`} className={s.title()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className={s.list()}>\n {sites.map((item) => (\n <li key={item.id}>\n <a href={item.url} aria-current={item.id === current ? \"true\" : undefined} className={s.link()}>\n <span className={s.mark()}>\n <FamilyMark mark={item.mark} size={16} />\n </span>\n {item.name}\n {item.status !== \"released\" && <span className={s.tag()}>{labels.status[item.status]}</span>}\n {item.id === current && <span className=\"sr-only\">({labels.current})</span>}\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n <div className={s.bottom()}>\n <a href={krizaka.url} className={s.signature()}>\n <KrizakaLogo size={18} animated={false} />\n {labels.signature[site.kind]}\n </a>\n {tag && (\n <span className={s.version()}>\n <span className=\"sr-only\">{labels.version} </span>\n {tag}\n </span>\n )}\n <span>{labels.place}</span>\n {legal && <div className={s.legal()}>{legal}</div>}\n </div>\n </div>\n </footer>\n );\n}\n\nexport type SiteFooterColumnProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** The column's heading (“Docs”) — passed translated. */\n title: string;\n /** Its links (`SiteFooter.Link`). */\n children: React.ReactNode;\n};\n\n/** One of the site's own columns: a heading and its links. */\nexport function SiteFooterColumn({ title, children, className, ...props }: SiteFooterColumnProps) {\n const id = React.useId();\n return (\n <div className={s.column({ className })} {...props}>\n <p id={id} className={s.title()}>\n {title}\n </p>\n <ul aria-labelledby={id} className={s.list()}>\n {React.Children.map(children, (child) => (child ? <li>{child}</li> : null))}\n </ul>\n </div>\n );\n}\n\nexport type SiteFooterLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page: `aria-current=\"page\"`. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the footer. */\nexport function SiteFooterLink({ active, asChild, className, ...props }: SiteFooterLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.link({ className })} {...props} />;\n}\n\nSiteFooter.Column = SiteFooterColumn;\nSiteFooter.Link = SiteFooterLink;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/site-footer/site-footer.tsx"],"sourcesContent":["// Client (the marks hold an id). The footer shared by every site of the Krizaka family: the site's own columns, then the\n// family in columns from `@krizaka/ui/sites` (the same groups as the bar's switcher), then the signature line — “A\n// Krizaka product” by the kind of the current site — the version and the place.\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { FamilyMark } from \"../family-mark\";\nimport { KrizakaLogo } from \"../marks/KrizakaLogo\";\nimport { type FamilyLabels, familyLabels, siteById, type SiteId, sitesByGroup } from \"../sites/sites\";\n\nexport const siteFooter = tv({\n slots: {\n root: \"w-full border-t border-border-subtle bg-surface-0 text-fg\",\n inner: \"mx-auto grid w-full max-w-(--kz-page-max,72rem) grid-cols-[minmax(0,1fr)] gap-10 px-(--kz-page-gutter,16px) py-12\",\n top: \"grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]\",\n site: \"grid content-start gap-3\",\n siteName: \"inline-flex items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg\",\n siteText: \"max-w-xs text-sm leading-relaxed text-fg-secondary\",\n own: \"grid grid-cols-2 gap-8 sm:grid-cols-3\",\n family: \"grid grid-cols-2 gap-8 border-t border-border-subtle pt-8 md:grid-cols-4\",\n column: \"grid min-w-0 content-start gap-3\",\n title: \"text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n list: \"grid min-w-0 gap-2\",\n link:\n \"inline-flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 rounded-md text-sm [overflow-wrap:anywhere] text-fg-secondary no-underline transition-colors hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg aria-[current=true]:text-fg aria-[current=true]:font-semibold\",\n mark: \"flex h-4 w-4 shrink-0 items-center justify-center text-fg\",\n tag: \"rounded-full bg-surface-3 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider text-fg-secondary\",\n bottom: \"flex flex-wrap items-center gap-x-5 gap-y-3 border-t border-border-subtle pt-6 text-xs text-fg-secondary\",\n signature:\n \"inline-flex items-center gap-2 rounded-md font-medium text-fg no-underline hover:text-fg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n version:\n \"rounded-full border border-border-subtle bg-surface-2 px-2 py-0.5 font-mono text-[11px] text-fg-secondary\",\n legal: \"flex flex-wrap items-center gap-x-4 gap-y-2 sm:ml-auto\",\n },\n});\nconst s = siteFooter();\n\nexport type SiteFooterProps = Omit<React.ComponentProps<\"footer\">, \"children\"> & {\n /** The site this footer belongs to (`@krizaka/ui/sites`): its mark and line, its signature, marked in the family. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** The site's own columns (`SiteFooter.Column`), before the family. */\n children?: React.ReactNode;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /**\n * Shows the family in columns (the Krizaka sites: www, ui., java.). A product is independent once deployed: pass\n * `family={false}` and the footer holds only its own columns, the legal links, the version and one publisher line —\n * “A Krizaka product”, linking to www.krizaka.com.\n * @default true\n */\n family?: boolean;\n /** The end of the signature line: legal links, a language switch (`SiteFooter.Link`s or any inline content). */\n legal?: React.ReactNode;\n};\n\n/** The footer of a Krizaka site: its own columns, the family in columns, the signature, the version, Montréal. */\nexport function SiteFooter({\n current,\n labels = familyLabels.en,\n children,\n version,\n env,\n legal,\n family = true,\n className,\n ...props\n}: SiteFooterProps) {\n const id = React.useId();\n const site = siteById(current);\n const krizaka = siteById(\"krizaka\");\n const tag = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean).join(\" · \");\n return (\n <footer className={s.root({ className })} {...props}>\n <div className={s.inner()}>\n <div className={s.top()}>\n <div className={s.site()}>\n <a href={site.url} className={s.siteName()}>\n <FamilyMark mark={site.mark} size={24} />\n {site.name}\n </a>\n <p className={s.siteText()}>{labels.descriptions[site.id]}</p>\n </div>\n {children && <div className={s.own()}>{children}</div>}\n </div>\n {family && (\n <nav aria-label={labels.family} className={s.family()}>\n {sitesByGroup().map(({ group, sites }) => (\n <div key={group} className={s.column()}>\n <p id={`${id}-${group}`} className={s.title()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className={s.list()}>\n {sites.map((item) => (\n <li key={item.id}>\n <a href={item.url} aria-current={item.id === current ? \"true\" : undefined} className={s.link()}>\n <span className={s.mark()}>\n <FamilyMark mark={item.mark} size={16} />\n </span>\n {item.name}\n {item.status !== \"released\" && <span className={s.tag()}>{labels.status[item.status]}</span>}\n {item.id === current && <span className=\"sr-only\">({labels.current})</span>}\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n )}\n <div className={s.bottom()}>\n <a href={krizaka.url} className={s.signature()}>\n <KrizakaLogo size={18} animated={false} />\n {labels.signature[site.kind]}\n </a>\n {tag && (\n <span className={s.version()}>\n <span className=\"sr-only\">{labels.version} </span>\n {tag}\n </span>\n )}\n <span>{labels.place}</span>\n {legal && <div className={s.legal()}>{legal}</div>}\n </div>\n </div>\n </footer>\n );\n}\n\nexport type SiteFooterColumnProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** The column's heading (“Docs”) — passed translated. */\n title: string;\n /** Its links (`SiteFooter.Link`). */\n children: React.ReactNode;\n};\n\n/** One of the site's own columns: a heading and its links. */\nexport function SiteFooterColumn({ title, children, className, ...props }: SiteFooterColumnProps) {\n const id = React.useId();\n return (\n <div className={s.column({ className })} {...props}>\n <p id={id} className={s.title()}>\n {title}\n </p>\n <ul aria-labelledby={id} className={s.list()}>\n {React.Children.map(children, (child) => (child ? <li>{child}</li> : null))}\n </ul>\n </div>\n );\n}\n\nexport type SiteFooterLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page: `aria-current=\"page\"`. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the footer. */\nexport function SiteFooterLink({ active, asChild, className, ...props }: SiteFooterLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.link({ className })} {...props} />;\n}\n\nSiteFooter.Column = SiteFooterColumn;\nSiteFooter.Link = SiteFooterLink;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krizaka/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"description": "The Krizaka front-end platform's components: the primitives (button, card, dialog, toast, menu, field, tabs, chip, switch, slider, command palette, theme, countdown…) on the semantic --kz-* tokens, the animated marks (Krizaka, Orazaka, Orochia, Yasakia, Yororis) and the motion signature — web and /native.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"krizaka",
|
|
@@ -615,7 +615,7 @@
|
|
|
615
615
|
],
|
|
616
616
|
"peerDependencies": {
|
|
617
617
|
"@krizaka/query": ">=0.2.1",
|
|
618
|
-
"@krizaka/tailwind": ">=3.
|
|
618
|
+
"@krizaka/tailwind": ">=3.14.0",
|
|
619
619
|
"@maplibre/maplibre-react-native": ">=11",
|
|
620
620
|
"@shopify/flash-list": ">=2",
|
|
621
621
|
"expo-camera": ">=16",
|
|
@@ -2099,8 +2099,8 @@
|
|
|
2099
2099
|
"sonner": "^2.0.8",
|
|
2100
2100
|
"tailwind-variants": "^3.3.1",
|
|
2101
2101
|
"@krizaka/intl": "^0.2.1",
|
|
2102
|
-
"@krizaka/
|
|
2103
|
-
"@krizaka/
|
|
2102
|
+
"@krizaka/pacer": "^0.1.0",
|
|
2103
|
+
"@krizaka/tokens": "^3.14.0"
|
|
2104
2104
|
},
|
|
2105
2105
|
"scripts": {
|
|
2106
2106
|
"build": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true});for(const f of fs.readdirSync('registry/examples',{recursive:true}))if(/\\.(js|d\\.ts)$/.test(f))fs.rmSync('registry/examples/'+f)\" && tsup && node -e \"require('node:fs').copyFileSync('src/motion.css','dist/motion.css');require('node:fs').copyFileSync('src/blocks.css','dist/blocks.css');require('node:fs').copyFileSync('src/story.css','dist/story.css')\" && node scripts/build-registry.mjs",
|
|
@@ -3,19 +3,33 @@
|
|
|
3
3
|
// registry/examples/site-footer/product.tsx
|
|
4
4
|
import { SiteFooter } from "@krizaka/ui/site-footer";
|
|
5
5
|
import { familyLabels } from "@krizaka/ui/sites";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
function SiteFooterProduct() {
|
|
8
|
-
return /* @__PURE__ */ jsxs(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/* @__PURE__ */
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
return /* @__PURE__ */ jsxs(
|
|
9
|
+
SiteFooter,
|
|
10
|
+
{
|
|
11
|
+
current: "orazaka",
|
|
12
|
+
family: false,
|
|
13
|
+
labels: familyLabels.en,
|
|
14
|
+
env: "DEV",
|
|
15
|
+
version: "1.2.3",
|
|
16
|
+
legal: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "#privacy", children: "Privacy" }),
|
|
18
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "#terms", children: "Terms" })
|
|
19
|
+
] }),
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ jsxs(SiteFooter.Column, { title: "Orazaka", children: [
|
|
22
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "#how", children: "How it works" }),
|
|
23
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "#demo", children: "Demo" }),
|
|
24
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "#docs", children: "Docs" })
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ jsxs(SiteFooter.Column, { title: "Talk to us", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "https://www.krizaka.com/contact?topic=orazaka", children: "Contact" }),
|
|
28
|
+
/* @__PURE__ */ jsx(SiteFooter.Link, { href: "https://github.com/krizaka/orazaka", children: "Source" })
|
|
29
|
+
] })
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
);
|
|
19
33
|
}
|
|
20
34
|
export {
|
|
21
35
|
SiteFooterProduct as default
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { SiteFooter } from "@krizaka/ui/site-footer";
|
|
2
2
|
import { familyLabels } from "@krizaka/ui/sites";
|
|
3
3
|
|
|
4
|
-
// A product's footer (dev.orazaka.com): its own columns, the
|
|
4
|
+
// A product's footer (dev.orazaka.com): independent once deployed — its own columns, the legal links, the version it
|
|
5
|
+
// runs, and one publisher line “A Krizaka product” (→ www.krizaka.com). No family columns, no link to another product.
|
|
5
6
|
export default function SiteFooterProduct() {
|
|
6
7
|
return (
|
|
7
|
-
<SiteFooter
|
|
8
|
+
<SiteFooter
|
|
9
|
+
current="orazaka"
|
|
10
|
+
family={false}
|
|
11
|
+
labels={familyLabels.en}
|
|
12
|
+
env="DEV"
|
|
13
|
+
version="1.2.3"
|
|
14
|
+
legal={
|
|
15
|
+
<>
|
|
16
|
+
<SiteFooter.Link href="#privacy">Privacy</SiteFooter.Link>
|
|
17
|
+
<SiteFooter.Link href="#terms">Terms</SiteFooter.Link>
|
|
18
|
+
</>
|
|
19
|
+
}
|
|
20
|
+
>
|
|
8
21
|
<SiteFooter.Column title="Orazaka">
|
|
9
22
|
<SiteFooter.Link href="#how">How it works</SiteFooter.Link>
|
|
10
23
|
<SiteFooter.Link href="#demo">Demo</SiteFooter.Link>
|
package/registry/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krizaka/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"templatePurposes": {
|
|
5
5
|
"convince": {
|
|
6
6
|
"title": "Convince",
|
|
@@ -1731,7 +1731,7 @@
|
|
|
1731
1731
|
"native": 2
|
|
1732
1732
|
},
|
|
1733
1733
|
"dependencies": [
|
|
1734
|
-
"@krizaka/tokens@^3.
|
|
1734
|
+
"@krizaka/tokens@^3.14.0",
|
|
1735
1735
|
"@protomaps/basemaps@5.7.2",
|
|
1736
1736
|
"tailwind-variants@^3.3.1"
|
|
1737
1737
|
],
|
|
@@ -1939,7 +1939,7 @@
|
|
|
1939
1939
|
"native": 0
|
|
1940
1940
|
},
|
|
1941
1941
|
"dependencies": [
|
|
1942
|
-
"@krizaka/tokens@^3.
|
|
1942
|
+
"@krizaka/tokens@^3.14.0",
|
|
1943
1943
|
"radix-ui@^1.7.0",
|
|
1944
1944
|
"tailwind-variants@^3.3.1"
|
|
1945
1945
|
],
|
|
@@ -2445,8 +2445,8 @@
|
|
|
2445
2445
|
"name": "site-footer",
|
|
2446
2446
|
"type": "primitive",
|
|
2447
2447
|
"title": "Site footer",
|
|
2448
|
-
"summary": "The footer
|
|
2449
|
-
"description": "The footer
|
|
2448
|
+
"summary": "The footer of a Krizaka site: the site's mark and line, its own columns, the family in columns on the Krizaka sites (from `@krizaka/ui/sites`; `family={false}` for an independent product), and the signature line — “A Krizaka company / product / open-source project” — with the version and Montréal.",
|
|
2449
|
+
"description": "The footer of a Krizaka site: the site's mark and line, its own columns, the family in columns on the Krizaka sites (from `@krizaka/ui/sites`; `family={false}` for an independent product), and the signature line — “A Krizaka company / product / open-source project” — with the version and Montréal.",
|
|
2450
2450
|
"status": "beta",
|
|
2451
2451
|
"category": "navigation",
|
|
2452
2452
|
"platforms": "web",
|
|
@@ -2455,7 +2455,7 @@
|
|
|
2455
2455
|
"native": 0
|
|
2456
2456
|
},
|
|
2457
2457
|
"dependencies": [
|
|
2458
|
-
"@krizaka/tokens@^3.
|
|
2458
|
+
"@krizaka/tokens@^3.14.0",
|
|
2459
2459
|
"radix-ui@^1.7.0",
|
|
2460
2460
|
"tailwind-variants@^3.3.1"
|
|
2461
2461
|
],
|
package/registry/map.json
CHANGED
package/registry/nav-bar.json
CHANGED
|
@@ -470,7 +470,7 @@
|
|
|
470
470
|
},
|
|
471
471
|
{
|
|
472
472
|
"path": "nav-bar/nav-bar.tsx",
|
|
473
|
-
"content": "// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { FamilyMark, GitHubGlyph } from \"../family-mark\";\nimport { type FamilyLabels, familyLabels, type Site, type SiteId, sitesByGroup } from \"../sites/sites\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n // The family switcher: a chevron attached to the brand, its panel aligned on the bar's left edge.\n family: \"hidden md:block\",\n familyTrigger:\n \"group/trigger -ml-2 mr-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=open]:bg-surface-3 data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n familyViewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n familyPanel: \"grid w-[min(92vw,42rem)] gap-x-2 gap-y-3 p-3 sm:grid-cols-2\",\n familyGroupTitle: \"px-3 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n familyLink:\n \"group/link flex items-start gap-3 rounded-xl px-3 py-2.5 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n familyMark: \"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center text-fg\",\n familyName: \"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm font-semibold text-fg\",\n familyText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n familyTag: \"rounded-full px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider\",\n mobileFamily: \"mt-1 grid gap-2 border-t border-border-subtle px-1 pb-1 pt-3\",\n mobileFamilyLink:\n \"flex min-h-11 items-center gap-3 rounded-xl px-2 py-2 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n badge:\n \"hidden h-6 shrink-0 items-center gap-1 rounded-full border border-border-subtle bg-surface-2 px-2 font-mono text-[11px] font-medium text-fg-secondary sm:inline-flex\",\n repo:\n \"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\n/** True inside `NavBar.Mobile`: the family renders as the panel's last group instead of a switcher. */\nconst MobileContext = React.createContext(false);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n <MobileContext.Provider value>{children}</MobileContext.Provider>\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n\nconst tagTone = {\n upcoming: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40\",\n construction: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40\",\n current: \"bg-surface-3 text-fg-secondary\",\n};\n\n/** A site's name and its tags: status when not released, “You are here” on the current one. */\nfunction FamilyName({ site, current, labels }: { site: Site; current: boolean; labels: FamilyLabels }) {\n return (\n <span className={s.familyName()}>\n {site.name}\n {site.status !== \"released\" && <span className={cn(s.familyTag(), tagTone[site.status])}>{labels.status[site.status]}</span>}\n {current && <span className={cn(s.familyTag(), tagTone.current)}>{labels.current}</span>}\n </span>\n );\n}\n\nexport type NavBarFamilyProps = {\n /** The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** Opens the panel at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group). */\n className?: string;\n};\n\n/**\n * The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\n * Krizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\n * or ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n * `NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.\n */\nexport function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, className }: NavBarFamilyProps) {\n const inMobile = React.useContext(MobileContext);\n const id = React.useId();\n const groups = sitesByGroup();\n if (inMobile) {\n return (\n <div role=\"group\" aria-labelledby={`${id}-family`} className={s.mobileFamily({ className })}>\n <p id={`${id}-family`} className={s.familyGroupTitle()}>\n {labels.family}\n </p>\n {groups.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <a href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.mobileFamilyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={24} />\n </span>\n <FamilyName site={site} current={site.id === current} labels={labels} />\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n }\n // Click only: a pointer passing over the brand never opens the family (Radix opens its menus on hover by default).\n const noHover = (e: React.PointerEvent) => e.preventDefault();\n const columns = [groups.filter((g) => g.group === \"krizaka\" || g.group === \"products\"), groups.filter((g) => g.group === \"open-source\" || g.group === \"github\")];\n return (\n <Nav.Root aria-label={labels.family} defaultValue={defaultOpen ? \"family\" : undefined} className={s.family({ className })}>\n <Nav.List className=\"flex\">\n <Nav.Item value=\"family\">\n <Nav.Trigger aria-label={labels.family} onPointerMove={noHover} onPointerLeave={noHover} className={s.familyTrigger()}>\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content onPointerLeave={noHover} className={s.familyPanel()}>\n {columns.map((column, i) => (\n <div key={i} className=\"grid content-start gap-3\">\n {column.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <Nav.Link href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.familyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={28} />\n </span>\n <span className=\"min-w-0\">\n <FamilyName site={site} current={site.id === current} labels={labels} />\n <span className={s.familyText()}>{labels.descriptions[site.id]}</span>\n </span>\n </Nav.Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n ))}\n </Nav.Content>\n </Nav.Item>\n </Nav.List>\n <Nav.Viewport onPointerLeave={noHover} className={s.familyViewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarBadgeProps = React.ComponentProps<\"span\"> & {\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n};\n\n/** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */\nexport function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps) {\n const parts = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean);\n return (\n <span className={s.badge({ className })} {...props}>\n {parts.join(\" · \")}\n {children}\n </span>\n );\n}\n\nexport type NavBarRepoProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The repository the site is built from. */\n href: string;\n /** The link's accessible name (“Source on GitHub”) — passed translated. */\n label: string;\n};\n\n/** The site's repository on GitHub, an icon link of `NavBar.Actions`. */\nexport function NavBarRepo({ label, className, ...props }: NavBarRepoProps) {\n return (\n <a aria-label={label} className={s.repo({ className })} {...props}>\n <GitHubGlyph size={18} />\n </a>\n );\n}\n\n/**\n * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileLink` — the Krizaka navigation bar,\n * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light\n * gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Family: NavBarFamily,\n Badge: NavBarBadge,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Repo: NavBarRepo,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"
|
|
473
|
+
"content": "// Client (scroll, a sliding indicator, menus). Radix NavigationMenu does the menus' semantics and keyboard (Enter, Space\n// and ↓ open; ↑ ↓ Home End walk; Escape closes and returns the focus; Tab out closes); this file draws the Krizaka bar:\n// a floating pill that tightens once the page scrolls, one light that glides to the item under the pointer or the\n// focus and rests on the current page, and a mobile panel that is a plain disclosure (no focus trap, Escape closes).\nimport { NavigationMenu as Nav, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { FamilyMark, GitHubGlyph } from \"../family-mark\";\nimport { type FamilyLabels, familyLabels, type Site, type SiteId, sitesByGroup } from \"../sites/sites\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const navBar = tv({\n slots: {\n root:\n \"group/nav relative z-40 mx-auto flex h-14 w-full items-center gap-2 border-border-subtle bg-surface-1/75 backdrop-blur-xl backdrop-saturate-150 \" +\n \"transition-[height,background-color,border-color,box-shadow] duration-300 motion-reduce:transition-none \" +\n \"data-compact:h-12 data-compact:border-border-default data-compact:bg-surface-1/90 data-compact:shadow-lg\",\n brand:\n \"mr-2 inline-flex shrink-0 items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg no-underline \" +\n \"rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n items: \"relative hidden md:block\",\n list:\n \"group/list relative isolate flex items-center gap-0.5 \" +\n // The light: under the item that is pointed at or focused, resting on the current page.\n \"before:absolute before:left-0 before:top-0 before:-z-10 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-full before:bg-surface-3 before:opacity-0 before:transition-[translate,width,opacity] before:duration-300 motion-reduce:before:transition-none \" +\n \"data-[indicator=on]:before:opacity-100\",\n item:\n \"inline-flex h-9 select-none items-center gap-1 rounded-full px-3.5 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:text-fg data-active:text-fg data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n // Before the light is ready (server render), the current page carries its own tint.\n \"group-not-data-[indicator=on]/list:data-active:bg-surface-3\",\n chevron: \"h-3.5 w-3.5 transition-transform duration-300 group-data-[state=open]/trigger:rotate-180 motion-reduce:transition-none\",\n viewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n panel: \"grid w-max max-w-[min(92vw,30rem)] gap-1 p-2\",\n menuLink:\n \"group/link flex items-start gap-3 rounded-xl p-3 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-active:bg-accent-soft\",\n menuIcon:\n \"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border-default bg-surface-2 text-fg-accent \" +\n \"transition-[transform,border-color] duration-300 group-hover/link:-translate-y-0.5 group-hover/link:border-accent [&>svg]:h-4.5 [&>svg]:w-4.5 motion-reduce:transform-none\",\n menuTitle: \"block text-sm font-semibold text-fg\",\n menuText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n actions: \"ml-auto flex items-center gap-2\",\n toggle:\n \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-subtle bg-surface-2/60 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n mobile: \"kz-pop absolute inset-x-0 top-full mt-2 grid gap-1 rounded-2xl border border-border-default bg-surface-1 p-2 shadow-lg md:hidden\",\n mobileLink:\n \"flex items-center justify-between rounded-xl px-3 py-3 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg-accent\",\n // The family switcher: a chevron attached to the brand, its panel aligned on the bar's left edge.\n family: \"hidden md:block\",\n familyTrigger:\n \"group/trigger -ml-2 mr-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=open]:bg-surface-3 data-[state=open]:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n familyViewport:\n \"kz-pop absolute left-0 top-full mt-2 h-(--radix-navigation-menu-viewport-height) w-(--radix-navigation-menu-viewport-width) origin-top-left overflow-hidden \" +\n \"rounded-2xl border border-border-default bg-surface-1 shadow-lg transition-[width,height] duration-300 motion-reduce:transition-none\",\n familyPanel: \"grid w-[min(92vw,42rem)] gap-x-2 gap-y-3 p-3 sm:grid-cols-2\",\n familyGroupTitle: \"px-3 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n familyLink:\n \"group/link flex items-start gap-3 rounded-xl px-3 py-2.5 text-left no-underline outline-hidden transition-colors \" +\n \"hover:bg-surface-2 focus-visible:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n familyMark: \"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center text-fg\",\n familyName: \"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm font-semibold text-fg\",\n familyText: \"mt-0.5 block text-xs leading-relaxed text-fg-secondary\",\n familyTag: \"rounded-full px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider\",\n mobileFamily: \"mt-1 grid gap-2 border-t border-border-subtle px-1 pb-1 pt-3\",\n mobileFamilyLink:\n \"flex min-h-11 items-center gap-3 rounded-xl px-2 py-2 text-[15px] font-medium text-fg no-underline hover:bg-surface-2 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-current:bg-accent-soft\",\n badge:\n \"hidden h-6 shrink-0 items-center gap-1 rounded-full border border-border-subtle bg-surface-2 px-2 font-mono text-[11px] font-medium text-fg-secondary sm:inline-flex\",\n repo:\n \"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n floating: {\n true: { root: \"max-w-5xl rounded-full border pl-5 pr-2 data-compact:max-w-4xl\" },\n false: { root: \"border-b px-4 md:px-6\" },\n },\n },\n defaultVariants: { floating: true },\n});\nconst s = navBar();\n\ninterface NavBarContext {\n label: string;\n open: boolean;\n setOpen: (open: boolean) => void;\n panelId: string;\n}\nconst Context = React.createContext<NavBarContext | null>(null);\n/** True inside `NavBar.Mobile`: the family renders as the panel's last group instead of a switcher. */\nconst MobileContext = React.createContext(false);\nconst useNavBar = () => {\n const ctx = React.useContext(Context);\n if (!ctx) throw new Error(\"NavBar parts must be inside <NavBar.Root>.\");\n return ctx;\n};\n\nexport type NavBarRootProps = React.ComponentProps<\"header\"> & {\n /** The accessible name of the navigation (“Main”) — passed translated. */\n label: string;\n /**\n * Pixels of scroll after which the bar tightens (denser, lower, lifted). `false`: never.\n * @default 24\n */\n compactAfter?: number | false;\n /**\n * A floating pill (the Krizaka bar) or a full-width bar with a hairline under it.\n * @default true\n */\n floating?: boolean;\n};\n\n/** The bar. Place it in a `sticky top-3` (floating) or `sticky top-0` wrapper — or let it scroll. */\nexport function NavBarRoot({ label, compactAfter = 24, floating = true, className, children, ...props }: NavBarRootProps) {\n const [compact, setCompact] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const panelId = React.useId();\n React.useEffect(() => {\n if (compactAfter === false) return;\n const onScroll = () => setCompact(window.scrollY > compactAfter);\n onScroll();\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [compactAfter]);\n const ctx = React.useMemo(() => ({ label, open, setOpen, panelId }), [label, open, panelId]);\n return (\n <Context.Provider value={ctx}>\n <header data-compact={compact ? \"\" : undefined} data-floating={floating} className={navBar({ floating }).root({ className })} {...props}>\n {children}\n </header>\n </Context.Provider>\n );\n}\n\nexport type NavBarBrandProps = React.ComponentProps<\"a\"> & {\n /** Renders your link (a router `<Link>`) with the brand's look. */\n asChild?: boolean;\n};\n\n/** The brand: a mark and a name, linking home. */\nexport function NavBarBrand({ asChild, className, ...props }: NavBarBrandProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp className={s.brand({ className })} {...props} />;\n}\n\n/** The desktop navigation: links and menus, with the light that glides between them. Hidden below `md` (see `NavBar.Mobile`). */\nexport function NavBarItems({ className, children, ...props }: Omit<React.ComponentProps<typeof Nav.Root>, \"orientation\">) {\n const { label } = useNavBar();\n const { ref, handlers } = useSlidingIndicator<HTMLUListElement>({ item: \"[data-nav-item]\", active: \"[data-nav-item][data-active]\", follow: true });\n return (\n <Nav.Root aria-label={label} className={s.items({ className })} delayDuration={80} {...props}>\n <Nav.List ref={ref} className={s.list()} {...handlers}>\n {children}\n </Nav.List>\n <Nav.Viewport className={s.viewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"active\"> & {\n /** The current page: `aria-current=\"page\"`, and the light rests on it. */\n active?: boolean;\n};\n\n/** A link of the bar. `asChild` renders your router's `<Link>`. */\nexport function NavBarLink({ active, className, ...props }: NavBarLinkProps) {\n return (\n <Nav.Item>\n <Nav.Link data-nav-item=\"\" data-active={active ? \"\" : undefined} active={active} aria-current={active ? \"page\" : undefined} className={s.item({ className })} {...props} />\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuProps = Omit<React.ComponentProps<typeof Nav.Item>, \"children\"> & {\n /** The trigger's words (“Products”) — passed translated. */\n label: string;\n /** The current page is inside this menu: the light rests on its trigger. */\n active?: boolean;\n /** The menu's links (`NavBar.MenuLink`), laid out in a panel; `columns` sets the grid. */\n children: React.ReactNode;\n /**\n * Columns of the panel.\n * @default 1\n */\n columns?: 1 | 2;\n};\n\n/** A menu of the bar: its trigger, and its links in a panel that opens under the bar. */\nexport function NavBarMenu({ label, active, columns = 1, children, className, ...props }: NavBarMenuProps) {\n return (\n <Nav.Item className={className} {...props}>\n <Nav.Trigger data-nav-item=\"\" data-active={active ? \"\" : undefined} className={cn(s.item(), \"group/trigger\")}>\n {label}\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content className={cn(s.panel(), columns === 2 && \"md:grid-cols-2\")}>{children}</Nav.Content>\n </Nav.Item>\n );\n}\n\nexport type NavBarMenuLinkProps = Omit<React.ComponentProps<typeof Nav.Link>, \"title\" | \"active\"> & {\n /** The link's name — passed translated. */\n title: string;\n /** One line under it — passed translated. */\n description?: string;\n /** An icon (a signature icon of `@krizaka/icons`: its node lights up on hover). */\n icon?: React.ReactNode;\n /** The current page. */\n active?: boolean;\n};\n\n/** A link inside a menu: an icon, a name and a line of text. */\nexport function NavBarMenuLink({ title, description, icon, active, href, className, asChild, children, ...props }: NavBarMenuLinkProps) {\n const body = (\n <>\n {icon && <span className={s.menuIcon()}>{icon}</span>}\n <span className=\"min-w-0\">\n <span className={s.menuTitle()}>{title}</span>\n {description && <span className={s.menuText()}>{description}</span>}\n </span>\n </>\n );\n return (\n <Nav.Link asChild={asChild} href={asChild ? undefined : href} active={active} aria-current={active ? \"page\" : undefined} data-active={active ? \"\" : undefined} className={s.menuLink({ className })} {...props}>\n {asChild && React.isValidElement<{ children?: React.ReactNode }>(children) ? React.cloneElement(children, undefined, body) : body}\n </Nav.Link>\n );\n}\n\n/** The right side: search, theme, language, the call to action. */\nexport function NavBarActions({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.actions({ className })} {...props} />;\n}\n\nexport type NavBarMobileProps = React.ComponentProps<\"nav\"> & {\n /** The toggle's name when closed (“Open the menu”) — passed translated. */\n openLabel: string;\n /** The toggle's name when open (“Close the menu”) — passed translated. */\n closeLabel: string;\n};\n\n/**\n * Below `md`: a toggle, and a panel under the bar with its links (`NavBar.MobileLink`). A disclosure: Escape and a click\n * on a link close it, the focus returns to the toggle.\n */\nexport function NavBarMobile({ openLabel, closeLabel, className, children, ...props }: NavBarMobileProps) {\n const { label, open, setOpen, panelId } = useNavBar();\n const toggle = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n setOpen(false);\n toggle.current?.focus();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, setOpen]);\n return (\n <>\n <button ref={toggle} type=\"button\" aria-expanded={open} aria-controls={panelId} aria-label={open ? closeLabel : openLabel} onClick={() => setOpen(!open)} className={s.toggle()}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4.5 w-4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n {open ? <path d=\"M18 6 6 18M6 6l12 12\" /> : <path d=\"M4 7h16M4 12h16M4 17h10\" />}\n </svg>\n </button>\n {open && (\n <nav\n id={panelId}\n aria-label={label}\n className={s.mobile({ className })}\n onClick={(e) => {\n if ((e.target as Element).closest(\"a\")) setOpen(false);\n }}\n {...props}\n >\n <MobileContext.Provider value>{children}</MobileContext.Provider>\n </nav>\n )}\n </>\n );\n}\n\nexport type NavBarMobileLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the mobile panel. */\nexport function NavBarMobileLink({ active, asChild, className, ...props }: NavBarMobileLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.mobileLink({ className })} {...props} />;\n}\n\n\nconst tagTone = {\n upcoming: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40\",\n construction: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40\",\n current: \"bg-surface-3 text-fg-secondary\",\n};\n\n/** A site's name and its tags: status when not released, “You are here” on the current one. */\nfunction FamilyName({ site, current, labels }: { site: Site; current: boolean; labels: FamilyLabels }) {\n return (\n <span className={s.familyName()}>\n {site.name}\n {site.status !== \"released\" && <span className={cn(s.familyTag(), tagTone[site.status])}>{labels.status[site.status]}</span>}\n {current && <span className={cn(s.familyTag(), tagTone.current)}>{labels.current}</span>}\n </span>\n );\n}\n\nexport type NavBarFamilyProps = {\n /** The site this bar belongs to (`@krizaka/ui/sites`): marked in the family, `aria-current=\"true\"`. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** Opens the panel at first render (a story, a screenshot). */\n defaultOpen?: boolean;\n /** Classes of the switcher (or, inside `NavBar.Mobile`, of the family's group). */\n className?: string;\n};\n\n/**\n * The family switcher, attached to the brand (place it right after `NavBar.Brand`): a chevron that opens every site of\n * Krizaka grouped — Krizaka · Products · Open source · GitHub — the current one marked. It opens on click, Enter, Space\n * or ↓ (never on hover); ↑ ↓ walk the sites, Escape closes and returns the focus. Hidden below `md`: place a second\n * `NavBar.Family` as the last child of `NavBar.Mobile`, where it renders as the panel's last group.\n */\nexport function NavBarFamily({ current, labels = familyLabels.en, defaultOpen, className }: NavBarFamilyProps) {\n const inMobile = React.useContext(MobileContext);\n const id = React.useId();\n const groups = sitesByGroup();\n if (inMobile) {\n return (\n <div role=\"group\" aria-labelledby={`${id}-family`} className={s.mobileFamily({ className })}>\n <p id={`${id}-family`} className={s.familyGroupTitle()}>\n {labels.family}\n </p>\n {groups.map(({ group, sites }) => (\n <div key={group}>\n {/* The company's own group is named like the family itself: its heading would repeat the panel's. */}\n {group !== \"krizaka\" && (\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n )}\n <ul aria-labelledby={group !== \"krizaka\" ? `${id}-${group}` : `${id}-family`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <a href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.mobileFamilyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={24} />\n </span>\n <FamilyName site={site} current={site.id === current} labels={labels} />\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n }\n // Click only: a pointer passing over the brand never opens the family (Radix opens its menus on hover by default).\n const noHover = (e: React.PointerEvent) => e.preventDefault();\n const columns = [groups.filter((g) => g.group === \"krizaka\" || g.group === \"products\"), groups.filter((g) => g.group === \"open-source\" || g.group === \"github\")];\n return (\n <Nav.Root aria-label={labels.family} defaultValue={defaultOpen ? \"family\" : undefined} className={s.family({ className })}>\n <Nav.List className=\"flex\">\n <Nav.Item value=\"family\">\n <Nav.Trigger aria-label={labels.family} onPointerMove={noHover} onPointerLeave={noHover} className={s.familyTrigger()}>\n <svg viewBox=\"0 0 24 24\" className={s.chevron()} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </Nav.Trigger>\n <Nav.Content onPointerLeave={noHover} className={s.familyPanel()}>\n {columns.map((column, i) => (\n <div key={i} className=\"grid content-start gap-3\">\n {column.map(({ group, sites }) => (\n <div key={group}>\n <p id={`${id}-${group}`} className={s.familyGroupTitle()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className=\"grid gap-0.5\">\n {sites.map((site) => (\n <li key={site.id}>\n <Nav.Link href={site.url} data-current={site.id === current ? \"\" : undefined} aria-current={site.id === current ? \"true\" : undefined} className={s.familyLink()}>\n <span className={s.familyMark()}>\n <FamilyMark mark={site.mark} size={28} />\n </span>\n <span className=\"min-w-0\">\n <FamilyName site={site} current={site.id === current} labels={labels} />\n <span className={s.familyText()}>{labels.descriptions[site.id]}</span>\n </span>\n </Nav.Link>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n ))}\n </Nav.Content>\n </Nav.Item>\n </Nav.List>\n <Nav.Viewport onPointerLeave={noHover} className={s.familyViewport()} />\n </Nav.Root>\n );\n}\n\nexport type NavBarBadgeProps = React.ComponentProps<\"span\"> & {\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n};\n\n/** A small mono badge beside the brand: the environment and the version (`DEV · v1.2.3`). Hidden below `sm`. */\nexport function NavBarBadge({ env, version, className, children, ...props }: NavBarBadgeProps) {\n const parts = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean);\n return (\n <span className={s.badge({ className })} {...props}>\n {parts.join(\" · \")}\n {children}\n </span>\n );\n}\n\nexport type NavBarRepoProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The repository the site is built from. */\n href: string;\n /** The link's accessible name (“Source on GitHub”) — passed translated. */\n label: string;\n};\n\n/** The site's repository on GitHub, an icon link of `NavBar.Actions`. */\nexport function NavBarRepo({ label, className, ...props }: NavBarRepoProps) {\n return (\n <a aria-label={label} className={s.repo({ className })} {...props}>\n <GitHubGlyph size={18} />\n </a>\n );\n}\n\n/**\n * `NavBar.Root/Brand/Family/Badge/Items/Link/Menu/MenuLink/Actions/Repo/Mobile/MobileLink` — the Krizaka navigation bar,\n * the same on every site of the family: floating, tightening on scroll, the family switcher on the brand, one light\n * gliding between its items, accessible menus, a mobile panel.\n */\nexport const NavBar = {\n Root: NavBarRoot,\n Brand: NavBarBrand,\n Family: NavBarFamily,\n Badge: NavBarBadge,\n Items: NavBarItems,\n Link: NavBarLink,\n Menu: NavBarMenu,\n MenuLink: NavBarMenuLink,\n Actions: NavBarActions,\n Repo: NavBarRepo,\n Mobile: NavBarMobile,\n MobileLink: NavBarMobileLink,\n};\n"
|
|
474
474
|
},
|
|
475
475
|
{
|
|
476
476
|
"path": "sliding-indicator.ts",
|
|
@@ -478,7 +478,7 @@
|
|
|
478
478
|
}
|
|
479
479
|
],
|
|
480
480
|
"dependencies": [
|
|
481
|
-
"@krizaka/tokens@^3.
|
|
481
|
+
"@krizaka/tokens@^3.14.0",
|
|
482
482
|
"radix-ui@^1.7.0",
|
|
483
483
|
"tailwind-variants@^3.3.1"
|
|
484
484
|
],
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"name": "site-footer",
|
|
3
3
|
"type": "primitive",
|
|
4
4
|
"title": "Site footer",
|
|
5
|
-
"summary": "The footer
|
|
6
|
-
"description": "The footer
|
|
5
|
+
"summary": "The footer of a Krizaka site: the site's mark and line, its own columns, the family in columns on the Krizaka sites (from `@krizaka/ui/sites`; `family={false}` for an independent product), and the signature line — “A Krizaka company / product / open-source project” — with the version and Montréal.",
|
|
6
|
+
"description": "The footer of a Krizaka site: the site's mark and line, its own columns, the family in columns on the Krizaka sites (from `@krizaka/ui/sites`; `family={false}` for an independent product), and the signature line — “A Krizaka company / product / open-source project” — with the version and Montréal.",
|
|
7
7
|
"status": "beta",
|
|
8
8
|
"deprecated": null,
|
|
9
9
|
"category": "navigation",
|
|
10
10
|
"platforms": "web",
|
|
11
11
|
"whenToUse": [
|
|
12
|
-
"The bottom of every page of a Krizaka site: www, ui., java
|
|
12
|
+
"The bottom of every page of a Krizaka site: www, ui., java. (the family in columns).",
|
|
13
|
+
"The bottom of a deployed product (dev.<product>): `family={false}` — its own columns, the legal links, the version and one publisher line, no link to another product.",
|
|
13
14
|
"With `MarketingLayout`'s `footer` slot, under a bar of `@krizaka/ui/nav-bar` with `NavBar.Family`."
|
|
14
15
|
],
|
|
15
16
|
"whenNotToUse": [
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
],
|
|
25
26
|
"bestPractices": [
|
|
26
27
|
"Set `current` to the site's id: it chooses the mark, the line, the signature (company, product, open source) and marks the site in the family.",
|
|
27
|
-
"Two or three own columns at most (`SiteFooter.Column` + `SiteFooter.Link`), never a link to another site of the family: the family columns hold them.",
|
|
28
|
+
"Two or three own columns at most (`SiteFooter.Column` + `SiteFooter.Link`), never a link to another site of the family: on the Krizaka sites the family columns hold them, on a product (`family={false}`) there is none.",
|
|
28
29
|
"Pass the version the site runs (`version`, and `env=\"DEV\"` on a dev environment) and the legal links in `legal`.",
|
|
29
30
|
"Pass the words translated: `labels={familyLabels[locale]}` from `@krizaka/ui/sites`."
|
|
30
31
|
],
|
|
@@ -70,6 +71,13 @@
|
|
|
70
71
|
"description": "The environment, when it is not production (`DEV`).",
|
|
71
72
|
"required": false
|
|
72
73
|
},
|
|
74
|
+
{
|
|
75
|
+
"name": "family",
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"default": "true",
|
|
78
|
+
"description": "Shows the family in columns (the Krizaka sites: www, ui., java.). A product is independent once deployed: pass\n`family={false}` and the footer holds only its own columns, the legal links, the version and one publisher line —\n“A Krizaka product”, linking to www.krizaka.com.",
|
|
79
|
+
"required": false
|
|
80
|
+
},
|
|
73
81
|
{
|
|
74
82
|
"name": "labels",
|
|
75
83
|
"type": "FamilyLabels",
|
|
@@ -164,10 +172,10 @@
|
|
|
164
172
|
{
|
|
165
173
|
"name": "product",
|
|
166
174
|
"title": "A product",
|
|
167
|
-
"description": "dev.orazaka.com: the product's columns, the
|
|
175
|
+
"description": "dev.orazaka.com (`family={false}`): the product's columns, the legal links, “A Krizaka product” → www.krizaka.com, `DEV · v1.2.3` — no family columns.",
|
|
168
176
|
"path": "examples/site-footer/product.tsx",
|
|
169
177
|
"module": "@krizaka/ui/registry/examples/site-footer/product",
|
|
170
|
-
"code": "import { SiteFooter } from \"@krizaka/ui/site-footer\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\n// A product's footer (dev.orazaka.com): its own columns, the
|
|
178
|
+
"code": "import { SiteFooter } from \"@krizaka/ui/site-footer\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\n// A product's footer (dev.orazaka.com): independent once deployed — its own columns, the legal links, the version it\n// runs, and one publisher line “A Krizaka product” (→ www.krizaka.com). No family columns, no link to another product.\nexport default function SiteFooterProduct() {\n return (\n <SiteFooter\n current=\"orazaka\"\n family={false}\n labels={familyLabels.en}\n env=\"DEV\"\n version=\"1.2.3\"\n legal={\n <>\n <SiteFooter.Link href=\"#privacy\">Privacy</SiteFooter.Link>\n <SiteFooter.Link href=\"#terms\">Terms</SiteFooter.Link>\n </>\n }\n >\n <SiteFooter.Column title=\"Orazaka\">\n <SiteFooter.Link href=\"#how\">How it works</SiteFooter.Link>\n <SiteFooter.Link href=\"#demo\">Demo</SiteFooter.Link>\n <SiteFooter.Link href=\"#docs\">Docs</SiteFooter.Link>\n </SiteFooter.Column>\n <SiteFooter.Column title=\"Talk to us\">\n <SiteFooter.Link href=\"https://www.krizaka.com/contact?topic=orazaka\">Contact</SiteFooter.Link>\n <SiteFooter.Link href=\"https://github.com/krizaka/orazaka\">Source</SiteFooter.Link>\n </SiteFooter.Column>\n </SiteFooter>\n );\n}\n"
|
|
171
179
|
},
|
|
172
180
|
{
|
|
173
181
|
"name": "company",
|
|
@@ -259,15 +267,15 @@
|
|
|
259
267
|
},
|
|
260
268
|
{
|
|
261
269
|
"path": "site-footer/meta.ts",
|
|
262
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Site footer\",\n summary:\n \"The footer
|
|
270
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Site footer\",\n summary:\n \"The footer of a Krizaka site: the site's mark and line, its own columns, the family in columns on the Krizaka sites (from `@krizaka/ui/sites`; `family={false}` for an independent product), and the signature line — “A Krizaka company / product / open-source project” — with the version and Montréal.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"web\",\n whenToUse: [\n \"The bottom of every page of a Krizaka site: www, ui., java. (the family in columns).\",\n \"The bottom of a deployed product (dev.<product>): `family={false}` — its own columns, the legal links, the version and one publisher line, no link to another product.\",\n \"With `MarketingLayout`'s `footer` slot, under a bar of `@krizaka/ui/nav-bar` with `NavBar.Family`.\",\n ],\n whenNotToUse: [\n { when: \"Inside an application screen or a console: the app shell holds its own navigation.\", use: \"app-shell\" },\n { when: \"To list the family in the bar.\", use: \"nav-bar\" },\n ],\n bestPractices: [\n \"Set `current` to the site's id: it chooses the mark, the line, the signature (company, product, open source) and marks the site in the family.\",\n \"Two or three own columns at most (`SiteFooter.Column` + `SiteFooter.Link`), never a link to another site of the family: on the Krizaka sites the family columns hold them, on a product (`family={false}`) there is none.\",\n \"Pass the version the site runs (`version`, and `env=\\\"DEV\\\"` on a dev environment) and the legal links in `legal`.\",\n \"Pass the words translated: `labels={familyLabels[locale]}` from `@krizaka/ui/sites`.\",\n ],\n accessibility: {\n keyboard: [],\n notes: [\n \"A `footer` landmark; the family is a `nav` named by `labels.family`, each column a list named by its heading.\",\n \"The current site says `aria-current=\\\"true\\\"` and “You are here” to screen readers; a status is written, never only a colour.\",\n \"The marks are decorative: every site's name is written beside its mark.\",\n ],\n },\n related: [\"sites\", \"nav-bar\", \"marketing-layout\"],\n web: {\n imports: [\"SiteFooter\", \"SiteFooterColumn\", \"SiteFooterLink\"],\n examples: [\n { name: \"product\", title: \"A product\", description: \"dev.orazaka.com (`family={false}`): the product's columns, the legal links, “A Krizaka product” → www.krizaka.com, `DEV · v1.2.3` — no family columns.\" },\n { name: \"company\", title: \"The company\", description: \"www.krizaka.com: “A Krizaka company”, the company's column, the legal links at the end of the signature line.\" },\n { name: \"open-source-fr\", title: \"Open source, in French\", description: \"ui.krizaka.com in French: « Un projet open source de Krizaka », the version of the packages.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
263
271
|
},
|
|
264
272
|
{
|
|
265
273
|
"path": "site-footer/site-footer.tsx",
|
|
266
|
-
"content": "// Client (the marks hold an id). The footer shared by every site of the Krizaka family: the site's own columns, then the\n// family in columns from `@krizaka/ui/sites` (the same groups as the bar's switcher), then the signature line — “A\n// Krizaka product” by the kind of the current site — the version and the place.\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { FamilyMark } from \"../family-mark\";\nimport { KrizakaLogo } from \"../marks/KrizakaLogo\";\nimport { type FamilyLabels, familyLabels, siteById, type SiteId, sitesByGroup } from \"../sites/sites\";\n\nexport const siteFooter = tv({\n slots: {\n root: \"w-full border-t border-border-subtle bg-surface-0 text-fg\",\n inner: \"mx-auto grid w-full max-w-(--kz-page-max,72rem) grid-cols-[minmax(0,1fr)] gap-10 px-(--kz-page-gutter,16px) py-12\",\n top: \"grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]\",\n site: \"grid content-start gap-3\",\n siteName: \"inline-flex items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg\",\n siteText: \"max-w-xs text-sm leading-relaxed text-fg-secondary\",\n own: \"grid grid-cols-2 gap-8 sm:grid-cols-3\",\n family: \"grid grid-cols-2 gap-8 border-t border-border-subtle pt-8 md:grid-cols-4\",\n column: \"grid min-w-0 content-start gap-3\",\n title: \"text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n list: \"grid min-w-0 gap-2\",\n link:\n \"inline-flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 rounded-md text-sm [overflow-wrap:anywhere] text-fg-secondary no-underline transition-colors hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg aria-[current=true]:text-fg aria-[current=true]:font-semibold\",\n mark: \"flex h-4 w-4 shrink-0 items-center justify-center text-fg\",\n tag: \"rounded-full bg-surface-3 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider text-fg-secondary\",\n bottom: \"flex flex-wrap items-center gap-x-5 gap-y-3 border-t border-border-subtle pt-6 text-xs text-fg-secondary\",\n signature:\n \"inline-flex items-center gap-2 rounded-md font-medium text-fg no-underline hover:text-fg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n version
|
|
274
|
+
"content": "// Client (the marks hold an id). The footer shared by every site of the Krizaka family: the site's own columns, then the\n// family in columns from `@krizaka/ui/sites` (the same groups as the bar's switcher), then the signature line — “A\n// Krizaka product” by the kind of the current site — the version and the place.\nimport { Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { FamilyMark } from \"../family-mark\";\nimport { KrizakaLogo } from \"../marks/KrizakaLogo\";\nimport { type FamilyLabels, familyLabels, siteById, type SiteId, sitesByGroup } from \"../sites/sites\";\n\nexport const siteFooter = tv({\n slots: {\n root: \"w-full border-t border-border-subtle bg-surface-0 text-fg\",\n inner: \"mx-auto grid w-full max-w-(--kz-page-max,72rem) grid-cols-[minmax(0,1fr)] gap-10 px-(--kz-page-gutter,16px) py-12\",\n top: \"grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]\",\n site: \"grid content-start gap-3\",\n siteName: \"inline-flex items-center gap-2 font-display text-[15px] font-bold tracking-tight text-fg\",\n siteText: \"max-w-xs text-sm leading-relaxed text-fg-secondary\",\n own: \"grid grid-cols-2 gap-8 sm:grid-cols-3\",\n family: \"grid grid-cols-2 gap-8 border-t border-border-subtle pt-8 md:grid-cols-4\",\n column: \"grid min-w-0 content-start gap-3\",\n title: \"text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n list: \"grid min-w-0 gap-2\",\n link:\n \"inline-flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 rounded-md text-sm [overflow-wrap:anywhere] text-fg-secondary no-underline transition-colors hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:text-fg aria-[current=true]:text-fg aria-[current=true]:font-semibold\",\n mark: \"flex h-4 w-4 shrink-0 items-center justify-center text-fg\",\n tag: \"rounded-full bg-surface-3 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wider text-fg-secondary\",\n bottom: \"flex flex-wrap items-center gap-x-5 gap-y-3 border-t border-border-subtle pt-6 text-xs text-fg-secondary\",\n signature:\n \"inline-flex items-center gap-2 rounded-md font-medium text-fg no-underline hover:text-fg-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n version:\n \"rounded-full border border-border-subtle bg-surface-2 px-2 py-0.5 font-mono text-[11px] text-fg-secondary\",\n legal: \"flex flex-wrap items-center gap-x-4 gap-y-2 sm:ml-auto\",\n },\n});\nconst s = siteFooter();\n\nexport type SiteFooterProps = Omit<React.ComponentProps<\"footer\">, \"children\"> & {\n /** The site this footer belongs to (`@krizaka/ui/sites`): its mark and line, its signature, marked in the family. */\n current: SiteId;\n /**\n * The family's words, passed translated: `familyLabels.fr` from `@krizaka/ui/sites`, or your own of the same shape.\n * @default familyLabels.en\n */\n labels?: FamilyLabels;\n /** The site's own columns (`SiteFooter.Column`), before the family. */\n children?: React.ReactNode;\n /** The version the site runs (`1.2.3`, shown `v1.2.3`). */\n version?: string;\n /** The environment, when it is not production (`DEV`). */\n env?: string;\n /**\n * Shows the family in columns (the Krizaka sites: www, ui., java.). A product is independent once deployed: pass\n * `family={false}` and the footer holds only its own columns, the legal links, the version and one publisher line —\n * “A Krizaka product”, linking to www.krizaka.com.\n * @default true\n */\n family?: boolean;\n /** The end of the signature line: legal links, a language switch (`SiteFooter.Link`s or any inline content). */\n legal?: React.ReactNode;\n};\n\n/** The footer of a Krizaka site: its own columns, the family in columns, the signature, the version, Montréal. */\nexport function SiteFooter({\n current,\n labels = familyLabels.en,\n children,\n version,\n env,\n legal,\n family = true,\n className,\n ...props\n}: SiteFooterProps) {\n const id = React.useId();\n const site = siteById(current);\n const krizaka = siteById(\"krizaka\");\n const tag = [env, version && `v${version.replace(/^v/, \"\")}`].filter(Boolean).join(\" · \");\n return (\n <footer className={s.root({ className })} {...props}>\n <div className={s.inner()}>\n <div className={s.top()}>\n <div className={s.site()}>\n <a href={site.url} className={s.siteName()}>\n <FamilyMark mark={site.mark} size={24} />\n {site.name}\n </a>\n <p className={s.siteText()}>{labels.descriptions[site.id]}</p>\n </div>\n {children && <div className={s.own()}>{children}</div>}\n </div>\n {family && (\n <nav aria-label={labels.family} className={s.family()}>\n {sitesByGroup().map(({ group, sites }) => (\n <div key={group} className={s.column()}>\n <p id={`${id}-${group}`} className={s.title()}>\n {labels.groups[group]}\n </p>\n <ul aria-labelledby={`${id}-${group}`} className={s.list()}>\n {sites.map((item) => (\n <li key={item.id}>\n <a href={item.url} aria-current={item.id === current ? \"true\" : undefined} className={s.link()}>\n <span className={s.mark()}>\n <FamilyMark mark={item.mark} size={16} />\n </span>\n {item.name}\n {item.status !== \"released\" && <span className={s.tag()}>{labels.status[item.status]}</span>}\n {item.id === current && <span className=\"sr-only\">({labels.current})</span>}\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n )}\n <div className={s.bottom()}>\n <a href={krizaka.url} className={s.signature()}>\n <KrizakaLogo size={18} animated={false} />\n {labels.signature[site.kind]}\n </a>\n {tag && (\n <span className={s.version()}>\n <span className=\"sr-only\">{labels.version} </span>\n {tag}\n </span>\n )}\n <span>{labels.place}</span>\n {legal && <div className={s.legal()}>{legal}</div>}\n </div>\n </div>\n </footer>\n );\n}\n\nexport type SiteFooterColumnProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** The column's heading (“Docs”) — passed translated. */\n title: string;\n /** Its links (`SiteFooter.Link`). */\n children: React.ReactNode;\n};\n\n/** One of the site's own columns: a heading and its links. */\nexport function SiteFooterColumn({ title, children, className, ...props }: SiteFooterColumnProps) {\n const id = React.useId();\n return (\n <div className={s.column({ className })} {...props}>\n <p id={id} className={s.title()}>\n {title}\n </p>\n <ul aria-labelledby={id} className={s.list()}>\n {React.Children.map(children, (child) => (child ? <li>{child}</li> : null))}\n </ul>\n </div>\n );\n}\n\nexport type SiteFooterLinkProps = React.ComponentProps<\"a\"> & {\n /** The current page: `aria-current=\"page\"`. */\n active?: boolean;\n /** Renders your router's `<Link>`. */\n asChild?: boolean;\n};\n\n/** A link of the footer. */\nexport function SiteFooterLink({ active, asChild, className, ...props }: SiteFooterLinkProps) {\n const Comp = asChild ? Slot.Root : \"a\";\n return <Comp aria-current={active ? \"page\" : undefined} className={s.link({ className })} {...props} />;\n}\n\nSiteFooter.Column = SiteFooterColumn;\nSiteFooter.Link = SiteFooterLink;\n"
|
|
267
275
|
}
|
|
268
276
|
],
|
|
269
277
|
"dependencies": [
|
|
270
|
-
"@krizaka/tokens@^3.
|
|
278
|
+
"@krizaka/tokens@^3.14.0",
|
|
271
279
|
"radix-ui@^1.7.0",
|
|
272
280
|
"tailwind-variants@^3.3.1"
|
|
273
281
|
],
|
|
@@ -276,7 +284,7 @@
|
|
|
276
284
|
],
|
|
277
285
|
"demo": {
|
|
278
286
|
"path": "examples/site-footer/product.tsx",
|
|
279
|
-
"content": "import { SiteFooter } from \"@krizaka/ui/site-footer\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\n// A product's footer (dev.orazaka.com): its own columns, the
|
|
287
|
+
"content": "import { SiteFooter } from \"@krizaka/ui/site-footer\";\nimport { familyLabels } from \"@krizaka/ui/sites\";\n\n// A product's footer (dev.orazaka.com): independent once deployed — its own columns, the legal links, the version it\n// runs, and one publisher line “A Krizaka product” (→ www.krizaka.com). No family columns, no link to another product.\nexport default function SiteFooterProduct() {\n return (\n <SiteFooter\n current=\"orazaka\"\n family={false}\n labels={familyLabels.en}\n env=\"DEV\"\n version=\"1.2.3\"\n legal={\n <>\n <SiteFooter.Link href=\"#privacy\">Privacy</SiteFooter.Link>\n <SiteFooter.Link href=\"#terms\">Terms</SiteFooter.Link>\n </>\n }\n >\n <SiteFooter.Column title=\"Orazaka\">\n <SiteFooter.Link href=\"#how\">How it works</SiteFooter.Link>\n <SiteFooter.Link href=\"#demo\">Demo</SiteFooter.Link>\n <SiteFooter.Link href=\"#docs\">Docs</SiteFooter.Link>\n </SiteFooter.Column>\n <SiteFooter.Column title=\"Talk to us\">\n <SiteFooter.Link href=\"https://www.krizaka.com/contact?topic=orazaka\">Contact</SiteFooter.Link>\n <SiteFooter.Link href=\"https://github.com/krizaka/orazaka\">Source</SiteFooter.Link>\n </SiteFooter.Column>\n </SiteFooter>\n );\n}\n"
|
|
280
288
|
},
|
|
281
289
|
"props": [
|
|
282
290
|
{
|
|
@@ -304,6 +312,13 @@
|
|
|
304
312
|
"description": "The environment, when it is not production (`DEV`).",
|
|
305
313
|
"required": false
|
|
306
314
|
},
|
|
315
|
+
{
|
|
316
|
+
"name": "family",
|
|
317
|
+
"type": "boolean",
|
|
318
|
+
"default": "true",
|
|
319
|
+
"description": "Shows the family in columns (the Krizaka sites: www, ui., java.). A product is independent once deployed: pass\n`family={false}` and the footer holds only its own columns, the legal links, the version and one publisher line —\n“A Krizaka product”, linking to www.krizaka.com.",
|
|
320
|
+
"required": false
|
|
321
|
+
},
|
|
307
322
|
{
|
|
308
323
|
"name": "labels",
|
|
309
324
|
"type": "FamilyLabels",
|