@bundu/ui

中文文档
0.13.0发布于 2026/11/05MITTypeScriptES Module构建来源已验证近 30 天下载 3735

The marketing UI kit for the Bundu Ecosystem — Nyuchi's implementation of the Mzizi design system. All 21 Mzizi colour families (7 minerals, 7 heritage, 7 experimental) under one --color-* namespace, the nine-step surface ladder, the connectivity status t

原始使用文档
文档目录10 节
  1. What it is
  2. Generated, not transcribed
  3. Install
  4. Quick usage
  5. Hero
  6. App patterns (0.3.0)
  7. The product-dashboard shell
  8. Discover patterns (the Mzizi Discover Standard)
  9. Component contracts
  10. Licence

@bundu/ui

The marketing UI kit for the Bundu ecosystem — editorial Astro building blocks plus shadcn-style React primitives on the Mzizi tokens (all 21 colour families).

npm Lint License: MIT

Repo: mzizi-dev/packages-npm | Architecture: mzizi.dev


What it is

The shared component layer behind the marketing sites (bundu, nyuchi, mukoko). It is Nyuchi's implementation of the Mzizi design system — an open-architecture project of the Bundu Foundation, operated and developed by Nyuchi. This package is not Mzizi itself.

  • Astro marketing components — Hero, Section, SectionHeader, Container, MineralStrip, Icon, SocialIcon, and Breadcrumb (emits valid schema.org BreadcrumbList JSON-LD for Google rich results).

  • Astro form and media components — SegmentedControl (native radios), NativeSelect, Toaster (window.toast() in a polite live region) and SafeAreaFrame (geometry: safeAreaBands from the package root), imported as @bundu/ui/<Name>.astro, beside the registry primitives Alert, Button, Card, Input, Label, Skeleton and StatusBadge. Each keeps its registry contract (contracts/ui/); the app patterns are under @bundu/ui/app/* (below).

  • Markdown, safe by construction — MarkdownRenderer (@bundu/ui/MarkdownRenderer.astro, contract ui/markdown-renderer). It parses content into a typed tree and draws it as elements, never through an HTML string. Raw HTML in the source shows as text, and links pass a scheme allow-list (links="safe", the default, keeps http, https, mailto, tel and relative addresses; links="https" keeps https only); a refused link keeps its words. from="html" or "auto" reads an editor's rich text, and headingBase sets the level a # heading renders at. The parser it draws from is @bundu/ui/markdown-parse: markdownBlocks(), safeHref() and the block and inline types, for code that needs the tree, not the markup.

    ---
    import MarkdownRenderer from "@bundu/ui/MarkdownRenderer.astro";
    ---
    <MarkdownRenderer content={post.body} headingBase={2} />
    
  • shadcn CVA + cn() React primitives — Button, Card, Badge, Input, Textarea, Select, Label, Alert, Avatar, Separator, Skeleton, Switch, Checkbox, Tabs, Tooltip.

  • The whole Mzizi palette — styles/tokens.css carries all 21 colour families under one --color-* namespace: 7 minerals, 7 heritage tones, 7 experimental tones. Plus the nine-step surface ladder (--pitch, --void, --base, --surface, --container, --overlay, --raised, --scrim, --wash) and the connectivity status trio (--syncing, --offline, --neutral).

  • Tailwind v3 and v4 — styles/theme.css is a native v4 @theme entrypoint; tailwind-preset.mjs is the v3-shape preset, still shipped and still working (v4 loads it through @config).

  • Brand overlays — brand-bundu, brand-nyuchi, brand-mukoko, brand-shamwari, brand-mzizi, and the sub-apps brand-events (Mukoko Events), brand-lingo, brand-bushtrade, brand-campfire, brand-news, brand-weather, brand-kweli, brand-learning and brand-circles (Mukoko Circles). brand-nhimbe is a deprecated alias that re-exports brand-events (the nhimbe brand is retired; the events platform is Mukoko Events). Each repoints --primary and --ring and nothing else; the mineral comes from canon's ecosystem table. brand-circles also sets --brand-accent (terracotta), the one brand whose canon row names an accent.

  • tokens.json — the same values machine-readable, including every custom property resolved to a literal hex per mode, for the surfaces that cannot consume CSS at all: Expo (mukoko-weather-mobile) and Satori-based OG-image / email / PDF generators.

Generated, not transcribed

styles/tokens.css, styles/theme.css, tokens.json, tailwind-palette.mjs and every styles/brand-*.css are generated by scripts/generate-tokens.mjs from tokens/canon.snapshot.json, which is itself machine-written by scripts/fetch-canon.mjs from canon:

SourceSupplies
https://api.mzizi.dev/api/v1/brandthe 21 families, semantic tokens, the background ladder, radii, the brand→mineral table
mzizi-dev/mzizi-registry → lib/tokens/palette.source.tsthe minerals' onContainer pairs, which /v1/brand does not project

The two are cross-checked against each other on every fetch: if they disagree about a hex, the fetch fails rather than silently preferring one. That disagreement is how an estate ends up with four different terracottas.

The hex values do not live in a database. Mzizi holds no brand or primitive token data in Supabase or anywhere else — 0.1.1's tokens.css header said otherwise and was wrong.

Three gates, none of which can pass vacuously:

CommandNetworkRunsFails when
pnpm tokens:checknoevery CI job, prepacka generated file disagrees with the generator
pnpm canon:parityyesCI onlythe committed snapshot has drifted from live canon
pnpm canon:fetchyeson demandthe API and palette.source.ts disagree

canon:parity refuses to run outside CI without --force, so it cannot creep into a build or a runtime path. A token package that phones home to render a page is a page that goes blank the afternoon the API is unwell.

Every value flows through CSS custom properties / Tailwind tokens — never a raw hex in source.

Install

pnpm add @bundu/ui
# peer deps (for the React primitives)
pnpm add react react-dom

Quick usage

1a. Tailwind v4 (recommended) — one stylesheet, no tailwind.config.mjs at all:

@import "tailwindcss";
@import "@bundu/ui/styles/theme.css"; /* @imports tokens.css, adds @theme */
@import "@bundu/ui/styles/globals.css"; /* the @layer rules the Astro components use, and density.css */
@import "@bundu/ui/styles/color-scheme.css"; /* optional: follow the OS dark setting */
@import "@bundu/ui/styles/brand-nyuchi.css";
@source "../../node_modules/@bundu/ui/src"; /* v4 does not scan node_modules itself */

color-scheme.css is opt-in. tokens.css only turns dark under .dark or [data-theme="dark"], and this file adds the same dark values under prefers-color-scheme: dark, for sites with no theme script. An explicit data-theme="light" still wins. Import it before the brand overlay.

theme.css carries the palette, the type scale (text-display … text-caption), the reading widths (max-w-narrow) and the named spacing, so globals.css's @apply rules and the components' classes resolve with no preset. Tailwind imports tokens.css once even though both files reference it. The Astro components render the React primitives (for example Hero's buttons), so an Astro site also needs @astrojs/react. With no client:* directive they render to static HTML and ship no JavaScript.

The Bundu floor and density. globals.css imports styles/density.css, generated from the registry: body text 17px, captions 14px, a 49px hit region on every control (a control may be drawn 28 to 40px at desktop density, and a hit-area overlay keeps the 49px hit region), and the size roles the components use as utilities (h-control, h-control-sm, h-control-lg, size-control-icon, h-field, min-h-touch-min). Those utilities resolve to nothing without it. An app that imports the pieces separately (theme.css and tokens.css, no globals.css) must add it after them:

@import "@bundu/ui/styles/theme.css";
@import "@bundu/ui/styles/density.css";

<html data-mzizi-sizes="legacy"> restores the sizes before the floor (16px body, 12px captions, 48/56px controls) until an app has migrated. Migration table: mzizi-registry docs/sizes-and-density.md.

1b. Tailwind v3, or v4 via @config — unchanged from 0.1.x, still supported:

@import "tailwindcss";
@import "@bundu/ui/styles/tokens.css"; /* or globals.css for the @layer rules */
@import "@bundu/ui/styles/brand-nyuchi.css";
@config "../../tailwind.config.mjs";
// tailwind.config.mjs
import preset from "@bundu/ui/tailwind-preset";

export default {
  presets: [preset],
  content: ["./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx,vue}"],
};

2. Outside the browser — Expo, Satori, PDF:

import tokens from "@bundu/ui/tokens.json" with { type: "json" };

tokens.resolved.dark["--color-sodalite"]; // "#3d5afe"
tokens.color.heritage.savanna.dark; // "#e5c158"
tokens.surface.base.light; // "#f3f3f1"

3. Components:

---
import Hero from "@bundu/ui/Hero.astro";
import Container from "@bundu/ui/Container.astro";
import MineralStrip from "@bundu/ui/MineralStrip.astro";
import Breadcrumb from "@bundu/ui/Breadcrumb.astro";
import { deriveBreadcrumbs } from "@bundu/ui";

const crumbs = deriveBreadcrumbs(Astro.url.pathname, { about: "About" }, "Nyuchi");
---

<MineralStrip />
<Hero title="Build in the open" subtitle="Bundu Ecosystem" />
<Container>
  <Breadcrumb items={crumbs} origin={Astro.site?.toString()} />
</Container>
import { Button } from "@bundu/ui/ui/button";
import { Alert, AlertTitle } from "@bundu/ui/ui/alert";

export function CTA() {
  return (
    <>
      <Alert variant="success">
        <AlertTitle>Saved</AlertTitle>
      </Alert>
      <Button variant="primary" href="/start" arrow>
        Get started
      </Button>
    </>
  );
}

Hero

---
import Hero from "@bundu/ui/Hero.astro";
---

<!-- 0.1.x usage, unchanged: a single text column -->
<Hero
  title="Build in the open"
  subtitle="Bundu Ecosystem"
  description="One line that says what this is."
  primaryCTA={{ text: "Get started", href: "/start" }}
  secondaryCTA={{ text: "Read the docs", href: "https://docs.example.org", external: true }}
/>

<!-- Split: text beside a live demo at lg, stacked (text first) below it -->
<Hero
  layout="split"
  variant="showcase"
  badge="Beta"
  title="A short, bold headline"
  description="One line of subtext."
  primaryCTA={{ text: "Try it", href: "#demo" }}
  mediaLabel="Live demo"
>
  <div slot="media"><!-- your demo, screenshot or widget --></div>
</Hero>
PropTypeDefaultNotes
titlestring—The h1.
subtitlestring—Eyebrow above the headline.
descriptionstring—One paragraph of subtext.
primaryCTA{ text, href, external? }—Pill button.
secondaryCTA{ text, href, external? }—Ghost button with an arrow.
variant"default" | "gradient" | "light" | "showcase""default"showcase washes the brand --primary and cobalt over --background; tokens only.
align"start" | "center""start"
layout"stack" | "split""stack"split sets text and the media slot side by side from lg.
badgestring—Status pill above the headline. It is read as a claim, so keep it accurate.
mediaLabelstring—Accessible name for the media region (role="group").
media (slot)markup—Rendered beside (split) or below (stack) the text column.

With no media slot, no badge, layout left at stack and a 0.1.x variant, Hero renders byte-identical markup to 0.1.1. That was checked by building every combination of the 0.1.x props against the published 0.1.1 tarball.

See BUILDING.md for the full toolchain — the mzizi MCP, the shadcn CLI, the 21 colour families, and the no-raw-hex rule.

App patterns (0.3.0)

Server-rendered building blocks for signed-in apps and consoles, under @bundu/ui/app/*. They are pure Astro: no React (or any framework) under them and no client JavaScript, as the Mzizi doctrine has it for Astro. Filters are GET forms, paging is links, menus are <details>, and a toast is dismissed with a label for a hidden checkbox. Every colour is a token, so light and dark follow tokens.css / color-scheme.css. The class recipes are shared with the React primitives through @bundu/ui/ui/variants, so both builds look the same.

ComponentWhat it does
AppShell.astroSticky top bar (brand, account slots), sidebar navigation from lg and a disclosure below, main#main
SideNav.astroSection list with label and summary; aria-current="page" on the current item; 48px targets
AccountMenu.astroWho is signed in, a slot for account actions (an appearance form, links), and a sign-out POST form
PageHeader.astroThe page's one <h1>, description, breadcrumbs, actions slot
DataTable.astroA real <table> with caption and scoped headers; one card per row on narrow screens; cells are data, not markup
FilterBar.astroSearch and select filters as a role="search" GET form; "Clear" when anything is set
Pagination.astro"Showing 26–50 of 112" and previous, numbered and next links; hidden when one page is enough
DetailPanel.astroOne record as a description list, an optional "Full record" JSON disclosure, an actions slot
FormLayout.astroA titled form card; fields in two columns when the card is wide (container query); form-level alert; enctype for files
FormField.astroVisible label and an input (text, number, date, file…), textarea or select, with hint and error wired with aria-describedby and aria-invalid; wide spans both columns
StateMessage.astroEmpty, error, not-configured, unavailable and loading (skeleton) states with fixed wording
Toast.astroThe last action's result in a polite live region; stays until dismissed (WCAG 2.2.3)
StatTile.astroOne headline figure in a <dl>: label, value ("Not available", never 0), the trend written in words
BarChart.astroColumns or rows of bars as SVG, a captioned <figure>, axis labels that never crowd, and the exact figures in a real table under a disclosure
Button.astro<a> with href, else <button>; appButtonVariants, incl. destructive and destructive-outline
Badge.astroThe Mzizi registry badge contract: default, secondary, destructive, outline, ghost, link
Card.astro, Alert.astro, Input.astro, Label.astro, Skeleton.astroThe primitives the patterns are built from, pure Astro

@bundu/ui/lib/table holds the pure helpers behind DataTable, FilterBar and Pagination: parseTableQuery (URL → query, clamped), filterRows (every word, case- and accent-insensitive, plus exact-match filters), paginate and withParams.

---
import AppShell from "@bundu/ui/app/AppShell.astro";
import SideNav from "@bundu/ui/app/SideNav.astro";
import AccountMenu from "@bundu/ui/app/AccountMenu.astro";
import PageHeader from "@bundu/ui/app/PageHeader.astro";
import FilterBar from "@bundu/ui/app/FilterBar.astro";
import DataTable from "@bundu/ui/app/DataTable.astro";
import Pagination from "@bundu/ui/app/Pagination.astro";
import { filterRows, paginate, parseTableQuery, withParams } from "@bundu/ui/lib/table";

const query = parseTableQuery(Astro.url.searchParams, ["role"]);
const rows = filterRows(people, query, (p) => [p.name], (p, f) => (f === "role" ? p.role : null));
const page = paginate(rows, query.page, query.perPage);
---

<AppShell>
  <a slot="brand" href="/">Your app</a>
  <AccountMenu slot="account" name="Tendai Moyo" email="[email protected]" signOutAction="/auth/logout" />
  <SideNav slot="nav" items={[{ href: "/people", label: "People", summary: "Everyone in your family" }]} />

  <PageHeader title="People" description="Everyone in your family." />
  <FilterBar searchLabel="Search people" q={query.q} values={query.filters} clearHref={withParams(Astro.url, { q: null, role: null, page: null })} />
  <DataTable
    caption={`People, ${page.total} in all`}
    columns={[{ key: "name", label: "Name" }, { key: "role", label: "Role" }]}
    rows={page.rows.map((p) => ({ name: { text: p.name, href: `/people/${p.id}` }, role: { text: p.role, badge: "outline" } }))}
  />
  <Pagination url={Astro.url} {...page} noun="people" />
</AppShell>

They need only Astro and the Tailwind v4 setup above, including the @source line so the classes inside the package are generated. BarChart sizes its bars as SVG geometry, never inline style, so a Content-Security-Policy needs no style-src-attr 'unsafe-inline'.

DataTable badges ({ text, badge }) take the registry badge variants. Before 0.3.0 the console's copy used the mineral variants of the React Badge; primary there is default here. For a record's state, a cell's tone ({ text, tone }: neutral, success, warning, info, accent, premium) is a status pill in a fixed status colour (statusToneClasses in @bundu/ui/ui/variants); tone wins over badge.

The product-dashboard shell

The app patterns make a full-width product dashboard, dense like a console rather than roomy like a marketing page.

ComponentWhat it does
AppShell.astroThe whole viewport: a fixed 16.25rem sidebar (workspace, search, nav slots, a collapse toggle at the bottom) and a main column filling the rest (a thin top bar with brand, actions, account; the page; footerLinks / footer). No centred page container. Collapses to a 3.5rem icon rail (a real checkbox; collapsed and persist keep it across pages). Below 64rem the sidebar is an off-canvas drawer: the same element as a Popover API popover, so Escape and a click outside close it. overlay slot for a CommandPalette
SideNav.astrogroups under quiet labels (or 0.3's flat items), an icon per item, badge ("New"), nested items under a chevron disclosure (open when a child is current), the current item filled with a gold indicator. Label only: an item's description is a tooltip on hover and keyboard focus and its aria-describedby; in the collapsed rail the tooltip shows the label too
WorkspaceSwitcher.astroThe product mark (mark slot) over the current workspace; a <details> list of workspaces and links, or a plain link home when there is nothing to switch to
QuickSearch.astro + CommandPalette.astroThe sidebar's search button with its ⌘K hint opens the palette (popovertarget): every nav item, grouped, as links, and with action a GET search form for the server
TopBarAction.astroAn icon-and-label link or button for the top bar ("Ask AI", "Support"); icon only below sm with the label as its name
Toolbar.astro + ToolbarMenu.astroA full-width GET search box joined to a button group; ToolbarMenu is a <details> list of links (a date range)
StatTiles.astroOne bordered row of equal StatTiles with hairline dividers; StatTile gains info
InfoTip.astroA focusable info icon whose tooltip is its accessible description
EmptyState.astroA bordered card: a heading, help text and actions; StateMessage kind="empty" uses it
BrandMark.astroThe official Nyuchi mark (the bee) or Mukoko mark (brand="mukoko", the honeycomb): the Mzizi registry's light/dark icon pair, scaled, never redrawn, with the lowercase serif wordmark and a muted suffix when asked; the right variant per theme by CSS

@bundu/ui/app/nav (also from the root) holds the pure helpers: groupNav turns flat one-line registrations (group, parent) into ordered, nested groups; currentHref picks the longest matching href; searchNav / matchesQuery match every word, case- and accent-insensitively.

Density. Button, Input, FilterBar, DataTable, Pagination, DetailPanel, FormLayout, PageHeader and StatTile take the app density (appButtonVariants, appInputClasses in @bundu/ui/ui/variants): 36px controls, 14px text and ~40px table rows with a fine pointer; 44–48px targets and 16px inputs on touch (pointer-coarse:). Headings inside AppShell use the body sans. The marketing primitives are unchanged.

Script. Two components carry one small module each, and both work without it: AppShell (it writes the collapse choice to the persist cookie and lets Escape hide a tooltip) and CommandPalette (the ⌘K / Ctrl+K shortcut, live filtering with the count announced, Enter and the arrow keys). A keyboard shortcut and live filtering cannot be done in CSS. Astro bundles them as files, so a script-src 'self' CSP works if the app does not inline small scripts (set vite.build.assetsInlineLimit: 0).

---
import AppShell from "@bundu/ui/app/AppShell.astro";
import BrandMark from "@bundu/ui/app/BrandMark.astro";
import CommandPalette from "@bundu/ui/app/CommandPalette.astro";
import QuickSearch from "@bundu/ui/app/QuickSearch.astro";
import SideNav from "@bundu/ui/app/SideNav.astro";
import TopBarAction from "@bundu/ui/app/TopBarAction.astro";
import WorkspaceSwitcher from "@bundu/ui/app/WorkspaceSwitcher.astro";
import { groupNav } from "@bundu/ui/app/nav";

const groups = groupNav(
  [
    { href: "/", label: "Home", icon: "home" },
    { href: "/people", label: "People", icon: "users", group: "Identity & people", description: "Everyone in your organisation" },
    { href: "/keys", label: "API keys", group: "Developer", parent: "/dev" },
    { href: "/dev", label: "Developer", icon: "terminal", group: "Developer" },
  ],
  ["Identity & people", "Developer"],
);
---

<AppShell collapsed={Astro.cookies.get("sidebar")?.value === "collapsed"} persist="sidebar" footerLinks={[{ label: "Status", href: "https://status.example" }]}>
  <WorkspaceSwitcher slot="workspace" name="Nyuchi Africa" href="/"><BrandMark slot="mark" wordmark suffix="console" /></WorkspaceSwitcher>
  <QuickSearch slot="search" target="palette" />
  <SideNav slot="nav" groups={groups} />
  <TopBarAction slot="actions" label="Support" icon="help" href="/support" />
  <slot />
  <CommandPalette slot="overlay" id="palette" groups={groups} action="/search" />
</AppShell>

Discover patterns (the Mzizi Discover Standard)

One design for every public discover and browse page in the Mukoko family: circles, news, events, weather, and the super-app on the web. Pure Astro, no client JavaScript, no inline styles. Eleven components under @bundu/ui/discover/*:

ComponentWhat it is
DiscoverShellSkip link, mineral strip, header (brand, main nav, one action), <main id="main">, footer
DiscoverMeta<head> tags: title, robots, canonical, Open Graph, X card, JSON-LD
DiscoverHeroBreadcrumbs, eyebrow, the one <h1>, lead, then the search and actions slots
DiscoverSearchA GET search form, 48px on every pointer
CategoryChipsA named nav of CategoryChip links that wraps
CategoryChipOne category link with a count; current marks it
DiscoverSectionA named band: eyebrow, heading, description, "See all", content
ResultGridStatus line, cards, empty state and "load more"; layouts grid, list, rail
DiscoverCardOne card, variants article, event, circle, place, sharing one anatomy
LoadMoreCursor pagination: one rel="next" link to ?cursor=…
OpenInAppThe "Open in Mukoko" action: an https link and one line saying what happens
---
import DiscoverShell from "@bundu/ui/discover/DiscoverShell.astro";
import DiscoverHero from "@bundu/ui/discover/DiscoverHero.astro";
import DiscoverSearch from "@bundu/ui/discover/DiscoverSearch.astro";
import DiscoverSection from "@bundu/ui/discover/DiscoverSection.astro";
import ResultGrid from "@bundu/ui/discover/ResultGrid.astro";
import DiscoverCard from "@bundu/ui/discover/DiscoverCard.astro";
import LoadMore from "@bundu/ui/discover/LoadMore.astro";
---
<DiscoverShell homeLabel="mukoko circles, home" nav={[{ label: "All circles", href: "/circles" }]}>
  <span slot="brand">mukoko circles</span>
  <DiscoverHero size="home" eyebrow="Mukoko Circles" title="Find your people.">
    <DiscoverSearch slot="search" label="Search circles" />
  </DiscoverHero>
  <DiscoverSection id="featured" title="Circles worth joining" seeAllHref="/circles">
    <ResultGrid label="Featured circles" summary={`${circles.length} circles`}>
      {circles.map((c) => <DiscoverCard variant="circle" href={c.href} title={c.name} initial={c.name[0]} meta={[c.members]} />)}
      <LoadMore slot="more" href={next} label="More circles" />
    </ResultGrid>
  </DiscoverSection>
</DiscoverShell>

Tailwind must see the components' classes: add @source "../../node_modules/@bundu/ui/src"; (the path from your CSS file) after the @bundu/ui imports.

Server-filled shells. Every Discover component also works when the page is built once with {{placeholders}} and a server fills it (as circles.mukoko.com's Rust Worker does): text props are plain strings, empty text hides itself, and state switches (ResultGrid state, LoadMore state, DiscoverCard badgeTone, CategoryChip current) are attributes styled by classes, so the template needs no logic.

Component contracts

Every app and Discover component carries a contract, as everything built in Mzizi does. The contracts are authored in the Mzizi registry (mzizi-dev/mzizi-registry, contracts/) and shipped here, one JSON file per component:

import contract from "@bundu/ui/contracts/app/side-nav.contract.json" with { type: "json" };

Each states the component's props and slots with types, its behaviour and named states, accessibility (roles, keyboard, focus, ARIA), density for a fine and a coarse pointer, theming (brand overlay tokens only; layout never varies by brand), the no-JavaScript fallback and the responsive rules, plus a contract … end block in the Mzizi language's clause grammar. The schema is @bundu/ui/contracts/schema/component-contract.schema.json.

src/app/contracts.test.ts renders every component in every named state and evaluates its contract: the clauses, the selector checks, the density table and the brand-overlay rule. A clause the runner cannot evaluate fails, and so does any inline style attribute (a page's CSP never needs style-src-attr 'unsafe-inline'). contracts/app/ is the Mzizi Dashboard Standard and contracts/discover/ the Mzizi Discover Standard (docs.mzizi.dev).

Upstream first. A component an app needs that is not here, or a change to one that is, comes here (and its contract to the registry) straight away. An app keeps a local copy only while that PR is open, marked TODO(mzizi): <PR URL>.

Do not edit contracts/ here. Change the registry, then pnpm contracts:fetch (or pnpm contracts:check [ref] to compare).

Licence

MIT © Nyuchi Africa (Pvt) Ltd.

版本与发布记录

@bundu/ui 共发布 14 个版本,首次发布于 2026/07/03;过去 12 个月发布了 14 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.13.0latest2026/11/053 天前
0.12.02026/11/044 天前
0.11.02026/11/044 天前
0.10.02026/11/044 天前
0.9.02026/11/035 天前
0.8.02026/11/035 天前

关于 @bundu/ui 的常见问题

@bundu/ui 是什么?

The marketing UI kit for the Bundu Ecosystem — Nyuchi's implementation of the Mzizi design system. All 21 Mzizi colour families (7 minerals, 7 heritage, 7 experimental) under one --color-* namespace, the nine-step surface ladder, the connectivity status t

如何安装 @bundu/ui?

在项目目录运行 npm install @bundu/ui,或使用 pnpm add / yarn add / bun add。

@bundu/ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bundu/ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bundu/ui 可以商用吗?

@bundu/ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bundu/ui 的下载量有多少?

根据 npm 官方下载统计,@bundu/ui 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,735 次,日均约 125 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bundu/ui 还在维护吗?

最新版本 0.13.0 发布于 2026/11/05(3 天前);至今共发布 14 个版本,其中过去 12 个月发布了 14 个;首次发布于 2026/07/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。