feat(design-system): add portable open-source component library

Signed-off-by: Arjun Mahanti <arjun@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Arjun Mahanti
2026-09-09 08:24:52 -07:00
parent 20927e4355
commit 31b53e1afd
68 changed files with 4039 additions and 26 deletions

View File

@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && tsc --noEmit",
"build": "pnpm --filter @buzz/ui build && vite build && tsc --noEmit",
"typecheck": "tsc --noEmit",
"preview": "vite preview",
"lint": "biome lint .",
@@ -18,13 +18,14 @@
"test": "pnpm --filter @buzz/design-tokens test"
},
"dependencies": {
"@base-ui-components/react": "1.0.0-rc.0",
"@fontsource-variable/inter": "^5.2.8",
"@fontsource/jetbrains-mono": "^5.3.0",
"@tanstack/react-router": "^1.168.10",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"@buzz/design-tokens": "workspace:*"
"@buzz/design-tokens": "workspace:*",
"@buzz/ui": "workspace:*",
"lucide-react": "^1.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.3.0",
@@ -34,6 +35,7 @@
"@vitejs/plugin-react": "^6.0.0",
"tailwindcss": "^4.3.0",
"typescript": "~6.0.0",
"vite": "^8.0.0"
"vite": "^8.0.0",
"@playwright/test": "^1.58.2"
}
}

View File

@@ -1,7 +1,8 @@
import { FOUNDATION_GROUPS } from "@buzz/design-tokens/foundations";
import { PageHeader, Section } from "./primitives";
export function MotionPage() {
const group = FOUNDATION_GROUPS[2];
const group = FOUNDATION_GROUPS.find((group) => group.name === "Motion");
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />

View File

@@ -1,7 +1,8 @@
import { FOUNDATION_GROUPS } from "@buzz/design-tokens/foundations";
import { PageHeader, Section } from "./primitives";
export function RadiusPage() {
const group = FOUNDATION_GROUPS[1];
const group = FOUNDATION_GROUPS.find((group) => group.name === "Radius");
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />

View File

@@ -1,7 +1,8 @@
import { FOUNDATION_GROUPS } from "@buzz/design-tokens/foundations";
import { PageHeader, Section } from "./primitives";
export function SpacingPage() {
const group = FOUNDATION_GROUPS[0];
const group = FOUNDATION_GROUPS.find((group) => group.name === "Spacing");
if (!group) throw new Error("Missing foundation group");
return (
<>
<PageHeader title={group.name} intro={group.description} />

View File

@@ -1,5 +1,14 @@
/* Generated by packages/design-tokens/build.mjs. */
@theme {
--control-sm: 2rem;
--control-md: 2.5rem;
--control-lg: 3.25rem;
--space-action-inset: 1.25rem;
--space-tooltip-block: 0.125rem;
--layer-dialog: 80;
--layer-floating: 100;
--layer-toast: 120;
--overlay-scrim: rgb(0 0 0 / 0.5);
--space-control-gap: 0.5rem;
--space-control-inset: 0.75rem;
--space-section-gap: 1.5rem;

View File

@@ -1,5 +1,20 @@
/** Role-based geometry and motion shared by components and documentation. */
export const FOUNDATION_GROUPS = [
{
name: "Controls",
description: "Shared control dimensions and overlay stacking.",
roles: [
["control-sm", "2rem", "Small actions."],
["control-md", "2.5rem", "Standard actions and inputs."],
["control-lg", "3.25rem", "Prominent actions."],
["space-action-inset", "1.25rem", "Standard button horizontal padding."],
["space-tooltip-block", "0.125rem", "Compact tooltip vertical padding."],
["layer-dialog", "80", "Modal surfaces."],
["layer-floating", "100", "Menus and tooltips above their trigger."],
["layer-toast", "120", "Transient status announcements."],
["overlay-scrim", "rgb(0 0 0 / 0.5)", "A modal backdrop."],
],
},
{
name: "Spacing",
description:

1
packages/ui/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/

68
packages/ui/README.md Normal file
View File

@@ -0,0 +1,68 @@
# Buzz UI
Open-source React components with Buzz's semantic roles, Inter typography, and
Lucide icons. Behavior comes from public Base UI primitives; calendar and resize
behavior come from DayPicker and react-resizable-panels. No internal package,
font CDN, Storybook server, or native Buzz bridge is required.
From the repository root:
```sh
. ./bin/activate-hermit
pnpm install --frozen-lockfile
pnpm --filter buzz-desktop-next dev
# Open http://localhost:5173/design
pnpm --filter @buzz/ui build
```
## Consume
```tsx
import '@fontsource-variable/inter/index.css';
import '@fontsource/jetbrains-mono/index.css';
import { Button, Field, Input } from '@buzz/ui';
<Field.Root name="name">
<Field.Label>Name</Field.Label>
<Input required />
<Field.Error />
</Field.Root>
<Button variant="primary">Continue</Button>
```
The workspace export includes the source CSS; use a Tailwind 4 PostCSS pipeline.
For a consumer without Tailwind, build the package and use `@buzz/ui/dist` plus
`@buzz/ui/dist/styles.css`. Build output keeps React and behavior libraries external.
The package is intentionally private until package naming and release automation
are agreed; it is fully usable from this open-source workspace.
The stylesheet includes the shared theme and Tailwind reset. Load it once at the
application boundary. Set `class="dark"` on the document root for dark mode.
Font packages belong to the host, allowing self-hosted subsets without remote requests.
## Component contract
Compound families retain Base UI's API: `Dialog.Root`, `Dialog.Trigger`,
`Dialog.Portal`, `Dialog.Backdrop`, `Dialog.Popup`, `Dialog.Title`, and
`Dialog.Close`. Use `render={<Button />}` to skin triggers and closes. Supply a
Title and Description for dialogs and popovers. Label inputs with Field.Label;
label standalone controls directly. IconButton requires an accessible name.
Behavioral Root, Portal, and state helpers are direct exports, preserving generics,
refs, controlled state, and events. Styled parts merge both string and state-function
class names. The underlying libraries own focus management, keyboard navigation,
ARIA semantics, and dismissal. Caller-supplied labels and composition still matter.
Button variants: primary, secondary, outline, ghost, danger, link. Sizes: sm, md,
lg. Loading disables duplicate activation while preserving the accessible name.
Color, type, geometry, and motion are roles; components never consume color ramps.
BarChart covers nonnegative categorical bars with a semantic data table, not a
full chart grammar. Carousel is manually advanced, without autoplay or drag.
Naming differences from shadcn: Menu is DropdownMenu; PreviewCard is HoverCard;
OTPField is InputOTP; Field.Label is Label; Toast replaces Sonner's role; BarChart
is the initial Chart treatment. Sheet composes Dialog. No shadcn API compatibility
or proprietary BlockUI package compatibility is implied.
See `../../desktop-next/OPEN_SOURCE.md` for provenance and `THIRD_PARTY_NOTICES.md`
for upstream notices. New source is covered by the repository's Apache-2.0 license.

View File

@@ -0,0 +1,332 @@
# Third-party notices
These notices accompany the public dependencies used by Buzz UI and the catalog.
Transitive dependencies retain their own notices in their npm distributions.
## Inter
```text
Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
```
## JetBrains Mono
```text
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
```
## Lucide
```text
ISC License
Copyright (c) 2026 Lucide Icons and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
The following Lucide icons are derived from the Feather project:
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
The MIT License (MIT) (for the icons listed above)
Copyright (c) 2013-present Cole Bemis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Base UI
```text
MIT License
Copyright (c) 2019 Material-UI SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## DayPicker
```text
The MIT License (MIT)
Copyright (c) 2014-2026 Giampaolo Bellavite <io@gpbl.dev> and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## React Resizable Panels
```text
The MIT License (MIT)
Copyright (c) 2018 Brian Vaughn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

46
packages/ui/package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "@buzz/ui",
"private": true,
"version": "0.1.0",
"type": "module",
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./styles.css": "./src/styles.css",
"./dist/styles.css": "./dist/ui.css",
"./dist": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@base-ui/react": "1.7.0",
"@buzz/design-tokens": "workspace:*",
"@daypicker/react": "^10.0.1",
"lucide-react": "^1.0.0",
"react-resizable-panels": "^4.12.4"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.3.0",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwindcss": "^4.3.0",
"typescript": "~6.0.0",
"vite": "^8.0.0"
},
"files": [
"dist",
"README.md",
"THIRD_PARTY_NOTICES.md"
]
}

View File

@@ -0,0 +1 @@
export default { plugins: { "@tailwindcss/postcss": {} } };

1
packages/ui/src/assets.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module "*.css";

View File

@@ -0,0 +1,42 @@
import type { ComponentProps } from "react";
import { Button as Primitive } from "@base-ui/react/button";
import { LoaderCircle } from "lucide-react";
import { skin } from "./classes";
/** Appearance and behavior of an action. Loading preserves its accessible label. */
export type ButtonProps = ComponentProps<typeof Primitive> & {
variant?: "primary" | "secondary" | "outline" | "ghost" | "danger" | "link";
size?: "sm" | "md" | "lg";
loading?: boolean;
};
/** A pill action with explicit variants, native keyboard semantics, and bounded loading feedback. */
export function Button({
variant = "primary",
size = "md",
loading = false,
disabled,
className,
children,
...props
}: ButtonProps) {
return (
<Primitive
type="button"
{...props}
disabled={disabled || loading}
aria-busy={loading || undefined}
data-loading={loading || undefined}
data-variant={variant}
data-size={size}
className={skin("bui-button", className)}
>
<span className="bui-button-label">{children}</span>
{loading && <LoaderCircle className="bui-loading" aria-hidden="true" />}
</Primitive>
);
}
/** An icon-only action must have a readable accessible name. */
export function IconButton(props: ButtonProps & { "aria-label": string }) {
return (
<Button {...props} className={skin("bui-icon-button", props.className)} />
);
}

View File

@@ -0,0 +1,21 @@
import { DayPicker, type DayPickerProps } from "@daypicker/react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cx } from "./classes";
/** A keyboard-accessible calendar; selection, locale, and disabled dates use DayPicker's API. */
export function Calendar({ className, components, ...props }: DayPickerProps) {
return (
<DayPicker
{...props}
className={cx("bui-calendar", className)}
components={{
Chevron: ({ orientation }) =>
orientation === "left" ? (
<ChevronLeft aria-hidden="true" />
) : (
<ChevronRight aria-hidden="true" />
),
...components,
}}
/>
);
}

View File

@@ -0,0 +1,56 @@
import { useId, useState, type ReactNode } from "react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { IconButton } from "./button";
/** A manually advanced carousel. No autoplay; every slide has a named position. */
export function Carousel({
label,
slides,
}: {
label: string;
slides: readonly ReactNode[];
}) {
const [index, setIndex] = useState(0);
const id = useId();
const current = Math.min(index, Math.max(0, slides.length - 1));
if (!slides.length) return null;
return (
<section
className="bui-stack"
aria-label={label}
aria-roledescription="carousel"
>
{/* biome-ignore lint/a11y/useSemanticElements: WAI-ARIA carousel slides are groups, not form fieldsets. */}
<div
id={id}
role="group"
aria-roledescription="slide"
aria-label={current + 1 + " of " + slides.length}
>
{slides[current]}
</div>
<div className="bui-inline">
<IconButton
aria-label="Previous slide"
aria-controls={id}
variant="outline"
disabled={current === 0}
onClick={() => setIndex(current - 1)}
>
<ArrowLeft aria-hidden="true" />
</IconButton>
<span aria-live="polite" className="bui-description">
{current + 1} / {slides.length}
</span>
<IconButton
aria-label="Next slide"
aria-controls={id}
variant="outline"
disabled={current === slides.length - 1}
onClick={() => setIndex(current + 1)}
>
<ArrowRight aria-hidden="true" />
</IconButton>
</div>
</section>
);
}

53
packages/ui/src/chart.tsx Normal file
View File

@@ -0,0 +1,53 @@
import { useId } from "react";
/** One nonnegative scalar in a categorical bar chart. */
export interface ChartDatum {
label: string;
value: number;
}
/** A compact bar chart whose table is the accessible data source. Invalid values fail explicitly. */
export function BarChart({
title,
data,
formatValue = String,
}: {
title: string;
data: readonly ChartDatum[];
formatValue?: (value: number) => string;
}) {
const id = useId();
if (data.some((item) => !Number.isFinite(item.value) || item.value < 0))
throw new RangeError("BarChart requires finite nonnegative values");
if (new Set(data.map((item) => item.label)).size !== data.length)
throw new Error("Chart labels must be unique");
const max = Math.max(1, ...data.map((item) => item.value));
return (
<figure className="bui-stack" aria-labelledby={id}>
<figcaption id={id} className="bui-label">
{title}
</figcaption>
<table className="bui-chart">
<thead className="bui-sr-only">
<tr>
<th scope="col">Category</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{data.map((item) => (
<tr key={item.label}>
<th scope="row">{item.label}</th>
<td>
<span
className="bui-chart-bar"
aria-hidden="true"
style={{ width: (item.value / max) * 100 + "%" }}
/>
<span>{formatValue(item.value)}</span>
</td>
</tr>
))}
</tbody>
</table>
</figure>
);
}

View File

@@ -0,0 +1,12 @@
/** Joins optional class names without rewriting semantic size roles. */
export function cx(...values: Array<string | false | undefined>) {
return values.filter(Boolean).join(" ");
}
/** Preserves Base UI state-aware className callbacks when adding the Buzz skin. */
export function skin<State>(
base: string,
custom?: string | ((state: State) => string | undefined),
) {
return (state: State) =>
cx(base, typeof custom === "function" ? custom(state) : custom);
}

View File

@@ -0,0 +1,51 @@
import { useState } from "react";
import { Combobox } from "./primitives/combobox";
/** A command is an explicit user action, identified independently of its label. */
export interface CommandItem {
id: string;
label: string;
}
/** Searchable actions with arrow-key selection. Opening or filtering never invokes an action. */
export function Command({
label,
items,
onAction,
}: {
label: string;
items: readonly CommandItem[];
onAction: (id: string) => void;
}) {
const [query, setQuery] = useState("");
return (
<Combobox.Root<CommandItem>
items={items}
itemToStringLabel={(item) => item.label}
itemToStringValue={(item) => item.id}
inputValue={query}
onInputValueChange={setQuery}
value={null}
onValueChange={(item) => {
if (item) {
onAction(item.id);
setQuery("");
}
}}
>
<Combobox.Input aria-label={label} placeholder="Search commands…" />
<Combobox.Portal>
<Combobox.Positioner sideOffset={8}>
<Combobox.Popup>
<Combobox.Empty>No matching commands</Combobox.Empty>
<Combobox.List>
{(item: CommandItem) => (
<Combobox.Item key={item.id} value={item}>
{item.label}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Popup>
</Combobox.Positioner>
</Combobox.Portal>
</Combobox.Root>
);
}

47
packages/ui/src/index.ts Normal file
View File

@@ -0,0 +1,47 @@
import "./styles.css";
export { Accordion } from "./primitives/accordion";
export { AlertDialog } from "./primitives/alert-dialog";
export { Avatar } from "./primitives/avatar";
export { Checkbox } from "./primitives/checkbox";
export { CheckboxGroup } from "./primitives/checkbox-group";
export { Collapsible } from "./primitives/collapsible";
export { Combobox } from "./primitives/combobox";
export { Autocomplete } from "./primitives/autocomplete";
export { ContextMenu } from "./primitives/context-menu";
export { Dialog } from "./primitives/dialog";
export { Drawer } from "./primitives/drawer";
export { Field } from "./primitives/field";
export { Fieldset } from "./primitives/fieldset";
export { Form } from "./primitives/form";
export { Menu } from "./primitives/menu";
export { Menubar } from "./primitives/menubar";
export { NavigationMenu } from "./primitives/navigation-menu";
export { NumberField } from "./primitives/number-field";
export { OTPField } from "./primitives/otp-field";
export { Popover } from "./primitives/popover";
export { PreviewCard } from "./primitives/preview-card";
export { Progress } from "./primitives/progress";
export { Meter } from "./primitives/meter";
export { Radio } from "./primitives/radio";
export { RadioGroup } from "./primitives/radio-group";
export { ScrollArea } from "./primitives/scroll-area";
export { Select } from "./primitives/select";
export { Slider } from "./primitives/slider";
export { Switch } from "./primitives/switch";
export { Tabs } from "./primitives/tabs";
export { Toast } from "./primitives/toast";
export { Toggle } from "./primitives/toggle";
export { ToggleGroup } from "./primitives/toggle-group";
export { Toolbar } from "./primitives/toolbar";
export { Tooltip } from "./primitives/tooltip";
export { Button, IconButton, type ButtonProps } from "./button";
export { Input, Textarea } from "./input";
export * from "./surfaces";
export { Calendar } from "./calendar";
export { Resizable } from "./resizable";
export { Carousel } from "./carousel";
export { BarChart, type ChartDatum } from "./chart";
export { Command, type CommandItem } from "./command";
export { InputGroup } from "./input-group";
export { Sheet } from "./sheet";

View File

@@ -0,0 +1,6 @@
import type { ComponentProps } from "react";
import { cx } from "./classes";
/** Inline input accessories. Label the input itself; decorative accessories should be aria-hidden. */
export function InputGroup(props: ComponentProps<"div">) {
return <div {...props} className={cx("bui-input-group", props.className)} />;
}

24
packages/ui/src/input.tsx Normal file
View File

@@ -0,0 +1,24 @@
import type { ComponentProps } from "react";
import { Input as Primitive } from "@base-ui/react/input";
import { Field } from "@base-ui/react/field";
import { skin } from "./classes";
/** A text input that participates in Field labeling and validation. */
export function Input({
className,
...props
}: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-input", className)} />;
}
/** A multiline field using the same labeling and validation seam as Input. */
export function Textarea({
className,
...props
}: ComponentProps<typeof Field.Control>) {
return (
<Field.Control
{...props}
render={<textarea />}
className={skin("bui-input bui-textarea", className)}
/>
);
}

View File

@@ -0,0 +1,64 @@
import type { ComponentProps } from "react";
import { Accordion as Primitive } from "@base-ui/react/accordion";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-stack", className)} />;
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item
{...props}
className={skin("bui-accordion-item", className)}
/>
);
}
function StyledHeader({
className,
...props
}: ComponentProps<typeof Primitive.Header>) {
return (
<Primitive.Header {...props} className={skin("bui-reset", className)} />
);
}
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-accordion-trigger", className)}
/>
);
}
function StyledPanel({
className,
...props
}: ComponentProps<typeof Primitive.Panel>) {
return (
<Primitive.Panel
{...props}
className={skin("bui-accordion-panel", className)}
/>
);
}
/** Accordion behavior primitives with the Buzz visual roles. */
export const Accordion = {
Root: StyledRoot,
Item: StyledItem,
Header: StyledHeader,
Trigger: StyledTrigger,
Panel: StyledPanel,
};

View File

@@ -0,0 +1,57 @@
import type { ComponentProps } from "react";
import { AlertDialog as Primitive } from "@base-ui/react/alert-dialog";
import { skin } from "../classes";
function StyledBackdrop({
className,
...props
}: ComponentProps<typeof Primitive.Backdrop>) {
return (
<Primitive.Backdrop
{...props}
className={skin("bui-backdrop", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-dialog", className)} />
);
}
function StyledTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title {...props} className={skin("bui-title", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
/** AlertDialog behavior primitives with the Buzz visual roles. */
export const AlertDialog = {
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Backdrop: StyledBackdrop,
Popup: StyledPopup,
Title: StyledTitle,
Description: StyledDescription,
Close: Primitive.Close,
};

View File

@@ -0,0 +1,85 @@
import type { ComponentProps } from "react";
import { Autocomplete as Primitive } from "@base-ui/react/autocomplete";
import { skin } from "../classes";
function StyledInput({
className,
...props
}: ComponentProps<typeof Primitive.Input>) {
return (
<Primitive.Input {...props} className={skin("bui-input", className)} />
);
}
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledList({
className,
...props
}: ComponentProps<typeof Primitive.List>) {
return (
<Primitive.List {...props} className={skin("bui-option-list", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item {...props} className={skin("bui-option", className)} />
);
}
function StyledEmpty({
className,
...props
}: ComponentProps<typeof Primitive.Empty>) {
return (
<Primitive.Empty {...props} className={skin("bui-empty", className)} />
);
}
/** Autocomplete behavior primitives with the Buzz visual roles. */
export const Autocomplete = {
Root: Primitive.Root,
Input: StyledInput,
Trigger: StyledTrigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
List: StyledList,
Item: StyledItem,
Empty: StyledEmpty,
};

View File

@@ -0,0 +1,43 @@
import type { ComponentProps } from "react";
import { Avatar as Primitive } from "@base-ui/react/avatar";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-avatar", className)} />
);
}
function StyledImage({
className,
...props
}: ComponentProps<typeof Primitive.Image>) {
return (
<Primitive.Image
{...props}
className={skin("bui-avatar-image", className)}
/>
);
}
function StyledFallback({
className,
...props
}: ComponentProps<typeof Primitive.Fallback>) {
return (
<Primitive.Fallback
{...props}
className={skin("bui-avatar-fallback", className)}
/>
);
}
/** Avatar behavior primitives with the Buzz visual roles. */
export const Avatar = {
Root: StyledRoot,
Image: StyledImage,
Fallback: StyledFallback,
};

View File

@@ -0,0 +1,10 @@
import type { ComponentProps } from "react";
import { CheckboxGroup as Primitive } from "@base-ui/react/checkbox-group";
import { skin } from "../classes";
function StyledRoot({ className, ...props }: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-stack", className)} />;
}
/** CheckboxGroup behavior primitives with the Buzz visual roles. */
export const CheckboxGroup = { Root: StyledRoot };

View File

@@ -0,0 +1,27 @@
import type { ComponentProps } from "react";
import { Checkbox as Primitive } from "@base-ui/react/checkbox";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-checkbox", className)} />
);
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-check-indicator", className)}
/>
);
}
/** Checkbox behavior primitives with the Buzz visual roles. */
export const Checkbox = { Root: StyledRoot, Indicator: StyledIndicator };

View File

@@ -0,0 +1,41 @@
import type { ComponentProps } from "react";
import { Collapsible as Primitive } from "@base-ui/react/collapsible";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-stack", className)} />;
}
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-disclosure", className)}
/>
);
}
function StyledPanel({
className,
...props
}: ComponentProps<typeof Primitive.Panel>) {
return (
<Primitive.Panel
{...props}
className={skin("bui-accordion-panel", className)}
/>
);
}
/** Collapsible behavior primitives with the Buzz visual roles. */
export const Collapsible = {
Root: StyledRoot,
Trigger: StyledTrigger,
Panel: StyledPanel,
};

View File

@@ -0,0 +1,179 @@
import type { ComponentProps } from "react";
import { Combobox as Primitive } from "@base-ui/react/combobox";
import { skin } from "../classes";
function StyledInput({
className,
...props
}: ComponentProps<typeof Primitive.Input>) {
return (
<Primitive.Input {...props} className={skin("bui-input", className)} />
);
}
function StyledInputGroup({
className,
...props
}: ComponentProps<typeof Primitive.InputGroup>) {
return (
<Primitive.InputGroup
{...props}
className={skin("bui-input-group", className)}
/>
);
}
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledList({
className,
...props
}: ComponentProps<typeof Primitive.List>) {
return (
<Primitive.List {...props} className={skin("bui-option-list", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item {...props} className={skin("bui-option", className)} />
);
}
function StyledItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.ItemIndicator>) {
return (
<Primitive.ItemIndicator
{...props}
className={skin("bui-option-check", className)}
/>
);
}
function StyledEmpty({
className,
...props
}: ComponentProps<typeof Primitive.Empty>) {
return (
<Primitive.Empty {...props} className={skin("bui-empty", className)} />
);
}
function StyledGroup({
className,
...props
}: ComponentProps<typeof Primitive.Group>) {
return (
<Primitive.Group {...props} className={skin("bui-stack", className)} />
);
}
function StyledGroupLabel({
className,
...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
return (
<Primitive.GroupLabel
{...props}
className={skin("bui-group-label", className)}
/>
);
}
function StyledChips({
className,
...props
}: ComponentProps<typeof Primitive.Chips>) {
return (
<Primitive.Chips {...props} className={skin("bui-chips", className)} />
);
}
function StyledChip({
className,
...props
}: ComponentProps<typeof Primitive.Chip>) {
return <Primitive.Chip {...props} className={skin("bui-chip", className)} />;
}
function StyledChipRemove({
className,
...props
}: ComponentProps<typeof Primitive.ChipRemove>) {
return (
<Primitive.ChipRemove
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledClear({
className,
...props
}: ComponentProps<typeof Primitive.Clear>) {
return (
<Primitive.Clear
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
/** Combobox behavior primitives with the Buzz visual roles. */
export const Combobox = {
Root: Primitive.Root,
Input: StyledInput,
InputGroup: StyledInputGroup,
Trigger: StyledTrigger,
Value: Primitive.Value,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
List: StyledList,
Item: StyledItem,
ItemIndicator: StyledItemIndicator,
Empty: StyledEmpty,
Group: StyledGroup,
GroupLabel: StyledGroupLabel,
Chips: StyledChips,
Chip: StyledChip,
ChipRemove: StyledChipRemove,
Clear: StyledClear,
};

View File

@@ -0,0 +1,121 @@
import type { ComponentProps } from "react";
import { ContextMenu as Primitive } from "@base-ui/react/context-menu";
import { skin } from "../classes";
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-context-target", className)}
/>
);
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item {...props} className={skin("bui-option", className)} />
);
}
function StyledSeparator({
className,
...props
}: ComponentProps<typeof Primitive.Separator>) {
return (
<Primitive.Separator
{...props}
className={skin("bui-separator", className)}
/>
);
}
function StyledGroupLabel({
className,
...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
return (
<Primitive.GroupLabel
{...props}
className={skin("bui-group-label", className)}
/>
);
}
function StyledCheckboxItem({
className,
...props
}: ComponentProps<typeof Primitive.CheckboxItem>) {
return (
<Primitive.CheckboxItem
{...props}
className={skin("bui-option", className)}
/>
);
}
function StyledCheckboxItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.CheckboxItemIndicator>) {
return (
<Primitive.CheckboxItemIndicator
{...props}
className={skin("bui-option-check", className)}
/>
);
}
function StyledSubmenuTrigger({
className,
...props
}: ComponentProps<typeof Primitive.SubmenuTrigger>) {
return (
<Primitive.SubmenuTrigger
{...props}
className={skin("bui-option", className)}
/>
);
}
/** ContextMenu behavior primitives with the Buzz visual roles. */
export const ContextMenu = {
Root: Primitive.Root,
Trigger: StyledTrigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
Item: StyledItem,
Separator: StyledSeparator,
GroupLabel: StyledGroupLabel,
CheckboxItem: StyledCheckboxItem,
CheckboxItemIndicator: StyledCheckboxItemIndicator,
SubmenuRoot: Primitive.SubmenuRoot,
SubmenuTrigger: StyledSubmenuTrigger,
};

View File

@@ -0,0 +1,70 @@
import type { ComponentProps } from "react";
import { Dialog as Primitive } from "@base-ui/react/dialog";
import { skin } from "../classes";
function StyledBackdrop({
className,
...props
}: ComponentProps<typeof Primitive.Backdrop>) {
return (
<Primitive.Backdrop
{...props}
className={skin("bui-backdrop", className)}
/>
);
}
function StyledViewport({
className,
...props
}: ComponentProps<typeof Primitive.Viewport>) {
return (
<Primitive.Viewport
{...props}
className={skin("bui-dialog-viewport", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-dialog", className)} />
);
}
function StyledTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title {...props} className={skin("bui-title", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
/** Dialog behavior primitives with the Buzz visual roles. */
export const Dialog = {
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Backdrop: StyledBackdrop,
Viewport: StyledViewport,
Popup: StyledPopup,
Title: StyledTitle,
Description: StyledDescription,
Close: Primitive.Close,
};

View File

@@ -0,0 +1,80 @@
import type { ComponentProps } from "react";
import { Drawer as Primitive } from "@base-ui/react/drawer";
import { skin } from "../classes";
function StyledBackdrop({
className,
...props
}: ComponentProps<typeof Primitive.Backdrop>) {
return (
<Primitive.Backdrop
{...props}
className={skin("bui-backdrop", className)}
/>
);
}
function StyledViewport({
className,
...props
}: ComponentProps<typeof Primitive.Viewport>) {
return (
<Primitive.Viewport
{...props}
className={skin("bui-drawer-viewport", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-drawer", className)} />
);
}
function StyledContent({
className,
...props
}: ComponentProps<typeof Primitive.Content>) {
return (
<Primitive.Content {...props} className={skin("bui-stack", className)} />
);
}
function StyledTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title {...props} className={skin("bui-title", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
/** Drawer behavior primitives with the Buzz visual roles. */
export const Drawer = {
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Backdrop: StyledBackdrop,
Viewport: StyledViewport,
Popup: StyledPopup,
Content: StyledContent,
Title: StyledTitle,
Description: StyledDescription,
Close: Primitive.Close,
};

View File

@@ -0,0 +1,61 @@
import type { ComponentProps } from "react";
import { Field as Primitive } from "@base-ui/react/field";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-field", className)} />;
}
function StyledLabel({
className,
...props
}: ComponentProps<typeof Primitive.Label>) {
return (
<Primitive.Label {...props} className={skin("bui-label", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
function StyledError({
className,
...props
}: ComponentProps<typeof Primitive.Error>) {
return (
<Primitive.Error
{...props}
className={skin("bui-field-error", className)}
/>
);
}
function StyledControl({
className,
...props
}: ComponentProps<typeof Primitive.Control>) {
return (
<Primitive.Control {...props} className={skin("bui-input", className)} />
);
}
/** Field behavior primitives with the Buzz visual roles. */
export const Field = {
Root: StyledRoot,
Label: StyledLabel,
Description: StyledDescription,
Error: StyledError,
Control: StyledControl,
};

View File

@@ -0,0 +1,24 @@
import type { ComponentProps } from "react";
import { Fieldset as Primitive } from "@base-ui/react/fieldset";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-fieldset", className)} />
);
}
function StyledLegend({
className,
...props
}: ComponentProps<typeof Primitive.Legend>) {
return (
<Primitive.Legend {...props} className={skin("bui-label", className)} />
);
}
/** Fieldset behavior primitives with the Buzz visual roles. */
export const Fieldset = { Root: StyledRoot, Legend: StyledLegend };

View File

@@ -0,0 +1,10 @@
import type { ComponentProps } from "react";
import { Form as Primitive } from "@base-ui/react/form";
import { skin } from "../classes";
function StyledRoot({ className, ...props }: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-stack", className)} />;
}
/** Form behavior primitives with the Buzz visual roles. */
export const Form = { Root: StyledRoot };

View File

@@ -0,0 +1,152 @@
import type { ComponentProps } from "react";
import { Menu as Primitive } from "@base-ui/react/menu";
import { skin } from "../classes";
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item {...props} className={skin("bui-option", className)} />
);
}
function StyledSeparator({
className,
...props
}: ComponentProps<typeof Primitive.Separator>) {
return (
<Primitive.Separator
{...props}
className={skin("bui-separator", className)}
/>
);
}
function StyledGroup({
className,
...props
}: ComponentProps<typeof Primitive.Group>) {
return (
<Primitive.Group {...props} className={skin("bui-stack", className)} />
);
}
function StyledGroupLabel({
className,
...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
return (
<Primitive.GroupLabel
{...props}
className={skin("bui-group-label", className)}
/>
);
}
function StyledCheckboxItem({
className,
...props
}: ComponentProps<typeof Primitive.CheckboxItem>) {
return (
<Primitive.CheckboxItem
{...props}
className={skin("bui-option", className)}
/>
);
}
function StyledCheckboxItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.CheckboxItemIndicator>) {
return (
<Primitive.CheckboxItemIndicator
{...props}
className={skin("bui-option-check", className)}
/>
);
}
function StyledRadioGroup({
className,
...props
}: ComponentProps<typeof Primitive.RadioGroup>) {
return (
<Primitive.RadioGroup {...props} className={skin("bui-stack", className)} />
);
}
function StyledRadioItem({
className,
...props
}: ComponentProps<typeof Primitive.RadioItem>) {
return (
<Primitive.RadioItem {...props} className={skin("bui-option", className)} />
);
}
function StyledRadioItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.RadioItemIndicator>) {
return (
<Primitive.RadioItemIndicator
{...props}
className={skin("bui-option-check", className)}
/>
);
}
function StyledSubmenuTrigger({
className,
...props
}: ComponentProps<typeof Primitive.SubmenuTrigger>) {
return (
<Primitive.SubmenuTrigger
{...props}
className={skin("bui-option", className)}
/>
);
}
/** Menu behavior primitives with the Buzz visual roles. */
export const Menu = {
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
Item: StyledItem,
Separator: StyledSeparator,
Group: StyledGroup,
GroupLabel: StyledGroupLabel,
CheckboxItem: StyledCheckboxItem,
CheckboxItemIndicator: StyledCheckboxItemIndicator,
RadioGroup: StyledRadioGroup,
RadioItem: StyledRadioItem,
RadioItemIndicator: StyledRadioItemIndicator,
SubmenuRoot: Primitive.SubmenuRoot,
SubmenuTrigger: StyledSubmenuTrigger,
};

View File

@@ -0,0 +1,10 @@
import type { ComponentProps } from "react";
import { Menubar as Primitive } from "@base-ui/react/menubar";
import { skin } from "../classes";
function StyledRoot({ className, ...props }: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-toolbar", className)} />;
}
/** Menubar behavior primitives with the Buzz visual roles. */
export const Menubar = { Root: StyledRoot };

View File

@@ -0,0 +1,64 @@
import type { ComponentProps } from "react";
import { Meter as Primitive } from "@base-ui/react/meter";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-field", className)} />;
}
function StyledLabel({
className,
...props
}: ComponentProps<typeof Primitive.Label>) {
return (
<Primitive.Label {...props} className={skin("bui-label", className)} />
);
}
function StyledValue({
className,
...props
}: ComponentProps<typeof Primitive.Value>) {
return (
<Primitive.Value
{...props}
className={skin("bui-description", className)}
/>
);
}
function StyledTrack({
className,
...props
}: ComponentProps<typeof Primitive.Track>) {
return (
<Primitive.Track
{...props}
className={skin("bui-progress-track", className)}
/>
);
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-progress-indicator", className)}
/>
);
}
/** Meter behavior primitives with the Buzz visual roles. */
export const Meter = {
Root: StyledRoot,
Label: StyledLabel,
Value: StyledValue,
Track: StyledTrack,
Indicator: StyledIndicator,
};

View File

@@ -0,0 +1,105 @@
import type { ComponentProps } from "react";
import { NavigationMenu as Primitive } from "@base-ui/react/navigation-menu";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-navigation", className)} />
);
}
function StyledList({
className,
...props
}: ComponentProps<typeof Primitive.List>) {
return (
<Primitive.List {...props} className={skin("bui-toolbar", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return <Primitive.Item {...props} className={skin("bui-reset", className)} />;
}
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-disclosure", className)}
/>
);
}
function StyledContent({
className,
...props
}: ComponentProps<typeof Primitive.Content>) {
return (
<Primitive.Content {...props} className={skin("bui-stack", className)} />
);
}
function StyledLink({
className,
...props
}: ComponentProps<typeof Primitive.Link>) {
return (
<Primitive.Link
{...props}
className={skin("bui-navigation-link", className)}
/>
);
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledViewport({
className,
...props
}: ComponentProps<typeof Primitive.Viewport>) {
return (
<Primitive.Viewport {...props} className={skin("bui-stack", className)} />
);
}
/** NavigationMenu behavior primitives with the Buzz visual roles. */
export const NavigationMenu = {
Root: StyledRoot,
List: StyledList,
Item: StyledItem,
Trigger: StyledTrigger,
Content: StyledContent,
Link: StyledLink,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
Viewport: StyledViewport,
};

View File

@@ -0,0 +1,77 @@
import type { ComponentProps } from "react";
import { NumberField as Primitive } from "@base-ui/react/number-field";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-field", className)} />;
}
function StyledGroup({
className,
...props
}: ComponentProps<typeof Primitive.Group>) {
return (
<Primitive.Group
{...props}
className={skin("bui-number-group", className)}
/>
);
}
function StyledInput({
className,
...props
}: ComponentProps<typeof Primitive.Input>) {
return (
<Primitive.Input
{...props}
className={skin("bui-number-input", className)}
/>
);
}
function StyledIncrement({
className,
...props
}: ComponentProps<typeof Primitive.Increment>) {
return (
<Primitive.Increment
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledDecrement({
className,
...props
}: ComponentProps<typeof Primitive.Decrement>) {
return (
<Primitive.Decrement
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledScrubArea({
className,
...props
}: ComponentProps<typeof Primitive.ScrubArea>) {
return (
<Primitive.ScrubArea {...props} className={skin("bui-label", className)} />
);
}
/** NumberField behavior primitives with the Buzz visual roles. */
export const NumberField = {
Root: StyledRoot,
Group: StyledGroup,
Input: StyledInput,
Increment: StyledIncrement,
Decrement: StyledDecrement,
ScrubArea: StyledScrubArea,
};

View File

@@ -0,0 +1,22 @@
import type { ComponentProps } from "react";
import { OTPField as Primitive } from "@base-ui/react/otp-field";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-otp", className)} />;
}
function StyledInput({
className,
...props
}: ComponentProps<typeof Primitive.Input>) {
return (
<Primitive.Input {...props} className={skin("bui-otp-input", className)} />
);
}
/** OTPField behavior primitives with the Buzz visual roles. */
export const OTPField = { Root: StyledRoot, Input: StyledInput };

View File

@@ -0,0 +1,57 @@
import type { ComponentProps } from "react";
import { Popover as Primitive } from "@base-ui/react/popover";
import { skin } from "../classes";
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popover", className)} />
);
}
function StyledTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title {...props} className={skin("bui-label", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
/** Popover behavior primitives with the Buzz visual roles. */
export const Popover = {
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
Title: StyledTitle,
Description: StyledDescription,
Close: Primitive.Close,
};

View File

@@ -0,0 +1,45 @@
import type { ComponentProps } from "react";
import { PreviewCard as Primitive } from "@base-ui/react/preview-card";
import { skin } from "../classes";
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-navigation-link", className)}
/>
);
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popover", className)} />
);
}
/** PreviewCard behavior primitives with the Buzz visual roles. */
export const PreviewCard = {
Root: Primitive.Root,
Trigger: StyledTrigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
};

View File

@@ -0,0 +1,64 @@
import type { ComponentProps } from "react";
import { Progress as Primitive } from "@base-ui/react/progress";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-field", className)} />;
}
function StyledLabel({
className,
...props
}: ComponentProps<typeof Primitive.Label>) {
return (
<Primitive.Label {...props} className={skin("bui-label", className)} />
);
}
function StyledValue({
className,
...props
}: ComponentProps<typeof Primitive.Value>) {
return (
<Primitive.Value
{...props}
className={skin("bui-description", className)}
/>
);
}
function StyledTrack({
className,
...props
}: ComponentProps<typeof Primitive.Track>) {
return (
<Primitive.Track
{...props}
className={skin("bui-progress-track", className)}
/>
);
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-progress-indicator", className)}
/>
);
}
/** Progress behavior primitives with the Buzz visual roles. */
export const Progress = {
Root: StyledRoot,
Label: StyledLabel,
Value: StyledValue,
Track: StyledTrack,
Indicator: StyledIndicator,
};

View File

@@ -0,0 +1,10 @@
import type { ComponentProps } from "react";
import { RadioGroup as Primitive } from "@base-ui/react/radio-group";
import { skin } from "../classes";
function StyledRoot({ className, ...props }: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-stack", className)} />;
}
/** RadioGroup behavior primitives with the Buzz visual roles. */
export const RadioGroup = { Root: StyledRoot };

View File

@@ -0,0 +1,25 @@
import type { ComponentProps } from "react";
import { Radio as Primitive } from "@base-ui/react/radio";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-radio", className)} />;
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-radio-indicator", className)}
/>
);
}
/** Radio behavior primitives with the Buzz visual roles. */
export const Radio = { Root: StyledRoot, Indicator: StyledIndicator };

View File

@@ -0,0 +1,66 @@
import type { ComponentProps } from "react";
import { ScrollArea as Primitive } from "@base-ui/react/scroll-area";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-scroll-area", className)} />
);
}
function StyledViewport({
className,
...props
}: ComponentProps<typeof Primitive.Viewport>) {
return (
<Primitive.Viewport
{...props}
className={skin("bui-scroll-viewport", className)}
/>
);
}
function StyledScrollbar({
className,
...props
}: ComponentProps<typeof Primitive.Scrollbar>) {
return (
<Primitive.Scrollbar
{...props}
className={skin("bui-scrollbar", className)}
/>
);
}
function StyledThumb({
className,
...props
}: ComponentProps<typeof Primitive.Thumb>) {
return (
<Primitive.Thumb
{...props}
className={skin("bui-scroll-thumb", className)}
/>
);
}
function StyledCorner({
className,
...props
}: ComponentProps<typeof Primitive.Corner>) {
return (
<Primitive.Corner {...props} className={skin("bui-reset", className)} />
);
}
/** ScrollArea behavior primitives with the Buzz visual roles. */
export const ScrollArea = {
Root: StyledRoot,
Viewport: StyledViewport,
Scrollbar: StyledScrollbar,
Thumb: StyledThumb,
Corner: StyledCorner,
};

View File

@@ -0,0 +1,124 @@
import type { ComponentProps } from "react";
import { Select as Primitive } from "@base-ui/react/select";
import { skin } from "../classes";
function StyledTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={skin("bui-select-trigger", className)}
/>
);
}
function StyledIcon({
className,
...props
}: ComponentProps<typeof Primitive.Icon>) {
return <Primitive.Icon {...props} className={skin("bui-icon", className)} />;
}
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-popup", className)} />
);
}
function StyledList({
className,
...props
}: ComponentProps<typeof Primitive.List>) {
return (
<Primitive.List {...props} className={skin("bui-option-list", className)} />
);
}
function StyledItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item {...props} className={skin("bui-option", className)} />
);
}
function StyledItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.ItemIndicator>) {
return (
<Primitive.ItemIndicator
{...props}
className={skin("bui-option-check", className)}
/>
);
}
function StyledGroup({
className,
...props
}: ComponentProps<typeof Primitive.Group>) {
return (
<Primitive.Group {...props} className={skin("bui-stack", className)} />
);
}
function StyledGroupLabel({
className,
...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
return (
<Primitive.GroupLabel
{...props}
className={skin("bui-group-label", className)}
/>
);
}
function StyledSeparator({
className,
...props
}: ComponentProps<typeof Primitive.Separator>) {
return (
<Primitive.Separator
{...props}
className={skin("bui-separator", className)}
/>
);
}
/** Select behavior primitives with the Buzz visual roles. */
export const Select = {
Root: Primitive.Root,
Trigger: StyledTrigger,
Value: Primitive.Value,
Icon: StyledIcon,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
List: StyledList,
Item: StyledItem,
ItemText: Primitive.ItemText,
ItemIndicator: StyledItemIndicator,
Group: StyledGroup,
GroupLabel: StyledGroupLabel,
Separator: StyledSeparator,
};

View File

@@ -0,0 +1,90 @@
import type { ComponentProps } from "react";
import { Slider as Primitive } from "@base-ui/react/slider";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-field", className)} />;
}
function StyledLabel({
className,
...props
}: ComponentProps<typeof Primitive.Label>) {
return (
<Primitive.Label {...props} className={skin("bui-label", className)} />
);
}
function StyledValue({
className,
...props
}: ComponentProps<typeof Primitive.Value>) {
return (
<Primitive.Value
{...props}
className={skin("bui-description", className)}
/>
);
}
function StyledControl({
className,
...props
}: ComponentProps<typeof Primitive.Control>) {
return (
<Primitive.Control
{...props}
className={skin("bui-slider-control", className)}
/>
);
}
function StyledTrack({
className,
...props
}: ComponentProps<typeof Primitive.Track>) {
return (
<Primitive.Track
{...props}
className={skin("bui-slider-track", className)}
/>
);
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-slider-indicator", className)}
/>
);
}
function StyledThumb({
className,
...props
}: ComponentProps<typeof Primitive.Thumb>) {
return (
<Primitive.Thumb
{...props}
className={skin("bui-slider-thumb", className)}
/>
);
}
/** Slider behavior primitives with the Buzz visual roles. */
export const Slider = {
Root: StyledRoot,
Label: StyledLabel,
Value: StyledValue,
Control: StyledControl,
Track: StyledTrack,
Indicator: StyledIndicator,
Thumb: StyledThumb,
};

View File

@@ -0,0 +1,27 @@
import type { ComponentProps } from "react";
import { Switch as Primitive } from "@base-ui/react/switch";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-switch", className)} />
);
}
function StyledThumb({
className,
...props
}: ComponentProps<typeof Primitive.Thumb>) {
return (
<Primitive.Thumb
{...props}
className={skin("bui-switch-thumb", className)}
/>
);
}
/** Switch behavior primitives with the Buzz visual roles. */
export const Switch = { Root: StyledRoot, Thumb: StyledThumb };

View File

@@ -0,0 +1,56 @@
import type { ComponentProps } from "react";
import { Tabs as Primitive } from "@base-ui/react/tabs";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-stack", className)} />;
}
function StyledList({
className,
...props
}: ComponentProps<typeof Primitive.List>) {
return (
<Primitive.List {...props} className={skin("bui-tabs-list", className)} />
);
}
function StyledTab({
className,
...props
}: ComponentProps<typeof Primitive.Tab>) {
return <Primitive.Tab {...props} className={skin("bui-tab", className)} />;
}
function StyledPanel({
className,
...props
}: ComponentProps<typeof Primitive.Panel>) {
return (
<Primitive.Panel {...props} className={skin("bui-tab-panel", className)} />
);
}
function StyledIndicator({
className,
...props
}: ComponentProps<typeof Primitive.Indicator>) {
return (
<Primitive.Indicator
{...props}
className={skin("bui-tab-indicator", className)}
/>
);
}
/** Tabs behavior primitives with the Buzz visual roles. */
export const Tabs = {
Root: StyledRoot,
List: StyledList,
Tab: StyledTab,
Panel: StyledPanel,
Indicator: StyledIndicator,
};

View File

@@ -0,0 +1,91 @@
import type { ComponentProps } from "react";
import { Toast as Primitive } from "@base-ui/react/toast";
import { skin } from "../classes";
function StyledViewport({
className,
...props
}: ComponentProps<typeof Primitive.Viewport>) {
return (
<Primitive.Viewport
{...props}
className={skin("bui-toast-viewport", className)}
/>
);
}
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return <Primitive.Root {...props} className={skin("bui-toast", className)} />;
}
function StyledContent({
className,
...props
}: ComponentProps<typeof Primitive.Content>) {
return (
<Primitive.Content {...props} className={skin("bui-stack", className)} />
);
}
function StyledTitle({
className,
...props
}: ComponentProps<typeof Primitive.Title>) {
return (
<Primitive.Title {...props} className={skin("bui-label", className)} />
);
}
function StyledDescription({
className,
...props
}: ComponentProps<typeof Primitive.Description>) {
return (
<Primitive.Description
{...props}
className={skin("bui-description", className)}
/>
);
}
function StyledClose({
className,
...props
}: ComponentProps<typeof Primitive.Close>) {
return (
<Primitive.Close
{...props}
className={skin("bui-icon-control", className)}
/>
);
}
function StyledAction({
className,
...props
}: ComponentProps<typeof Primitive.Action>) {
return (
<Primitive.Action
{...props}
className={skin("bui-disclosure", className)}
/>
);
}
/** Toast behavior primitives with the Buzz visual roles. */
export const Toast = {
Provider: Primitive.Provider,
Portal: Primitive.Portal,
Viewport: StyledViewport,
Root: StyledRoot,
Content: StyledContent,
Title: StyledTitle,
Description: StyledDescription,
Close: StyledClose,
Action: StyledAction,
useToastManager: Primitive.useToastManager,
createToastManager: Primitive.createToastManager,
};

View File

@@ -0,0 +1,15 @@
import type { ComponentProps } from "react";
import { ToggleGroup as Primitive } from "@base-ui/react/toggle-group";
import { skin } from "../classes";
function StyledToggleGroup({
className,
...props
}: ComponentProps<typeof Primitive>) {
return (
<Primitive {...props} className={skin("bui-toggle-group", className)} />
);
}
/** ToggleGroup behavior primitives with the Buzz visual roles. */
export const ToggleGroup = { ToggleGroup: StyledToggleGroup };

View File

@@ -0,0 +1,13 @@
import type { ComponentProps } from "react";
import { Toggle as Primitive } from "@base-ui/react/toggle";
import { skin } from "../classes";
function StyledToggle({
className,
...props
}: ComponentProps<typeof Primitive>) {
return <Primitive {...props} className={skin("bui-toggle", className)} />;
}
/** Toggle behavior primitives with the Buzz visual roles. */
export const Toggle = { Toggle: StyledToggle };

View File

@@ -0,0 +1,63 @@
import type { ComponentProps } from "react";
import { Toolbar as Primitive } from "@base-ui/react/toolbar";
import { skin } from "../classes";
function StyledRoot({
className,
...props
}: ComponentProps<typeof Primitive.Root>) {
return (
<Primitive.Root {...props} className={skin("bui-toolbar", className)} />
);
}
function StyledGroup({
className,
...props
}: ComponentProps<typeof Primitive.Group>) {
return (
<Primitive.Group {...props} className={skin("bui-toolbar", className)} />
);
}
function StyledButton({
className,
...props
}: ComponentProps<typeof Primitive.Button>) {
return (
<Primitive.Button {...props} className={skin("bui-toggle", className)} />
);
}
function StyledLink({
className,
...props
}: ComponentProps<typeof Primitive.Link>) {
return (
<Primitive.Link
{...props}
className={skin("bui-navigation-link", className)}
/>
);
}
function StyledSeparator({
className,
...props
}: ComponentProps<typeof Primitive.Separator>) {
return (
<Primitive.Separator
{...props}
className={skin("bui-separator", className)}
/>
);
}
/** Toolbar behavior primitives with the Buzz visual roles. */
export const Toolbar = {
Root: StyledRoot,
Group: StyledGroup,
Button: StyledButton,
Link: StyledLink,
Separator: StyledSeparator,
};

View File

@@ -0,0 +1,34 @@
import type { ComponentProps } from "react";
import { Tooltip as Primitive } from "@base-ui/react/tooltip";
import { skin } from "../classes";
function StyledPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={skin("bui-positioner", className)}
/>
);
}
function StyledPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup {...props} className={skin("bui-tooltip", className)} />
);
}
/** Tooltip behavior primitives with the Buzz visual roles. */
export const Tooltip = {
Provider: Primitive.Provider,
Root: Primitive.Root,
Trigger: Primitive.Trigger,
Portal: Primitive.Portal,
Positioner: StyledPositioner,
Popup: StyledPopup,
};

View File

@@ -0,0 +1,13 @@
import { Group, Panel, Separator } from "react-resizable-panels";
import type { ComponentProps } from "react";
import { cx } from "./classes";
function Handle(props: ComponentProps<typeof Separator>) {
return (
<Separator
{...props}
className={cx("bui-resize-handle", props.className)}
/>
);
}
/** Resizable regions with pointer and keyboard support. Sizes use the public panel API. */
export const Resizable = { Group, Panel, Handle };

View File

@@ -0,0 +1,8 @@
import type { ComponentProps } from "react";
import { Dialog } from "./primitives/dialog";
import { skin } from "./classes";
function Popup({ className, ...props }: ComponentProps<typeof Dialog.Popup>) {
return <Dialog.Popup {...props} className={skin("bui-sheet", className)} />;
}
/** A side sheet uses the same focus, dismissal, title, and description contract as Dialog. */
export const Sheet = { ...Dialog, Popup };

160
packages/ui/src/styles.css Normal file
View File

@@ -0,0 +1,160 @@
@import "tailwindcss";
@import "@buzz/design-tokens/tokens.css";
@import "@buzz/design-tokens/typography.css";
@import "@buzz/design-tokens/foundations.css";
@import "./styles/actions.css";
@import "./styles/controls.css";
@import "./styles/overlays.css";
@import "./styles/surfaces.css";
@import "./styles/layout.css";
@keyframes bui-spin {
to {
transform: rotate(360deg);
}
}
@keyframes bui-pulse {
50% {
opacity: 0.5;
}
}
@media (prefers-reduced-motion: reduce) {
.bui-spin,
.bui-loading,
.bui-skeleton {
animation: none;
}
.bui-popup,
.bui-popover,
.bui-button,
.bui-switch-thumb {
transition: none;
}
}
@layer components {
.bui-sheet {
left: auto;
right: 0;
top: 0;
transform: none;
height: 100dvh;
max-height: 100dvh;
width: min(28rem, 100vw);
border-radius: var(--radius-sheet) 0 0 var(--radius-sheet);
align-content: start;
}
.bui-calendar {
--rdp-accent-color: var(--bg-inverse);
color: var(--text-primary);
width: fit-content;
max-width: 100%;
position: relative;
}
.bui-calendar table {
border-collapse: collapse;
}
.bui-calendar .rdp-month_caption {
@apply text-label;
min-height: var(--control-md);
display: flex;
align-items: center;
padding-inline: var(--space-control-gap);
}
.bui-calendar .rdp-nav {
position: absolute;
top: 0;
right: 0;
display: flex;
}
.bui-calendar button {
cursor: pointer;
border-radius: var(--radius-pill);
min-width: 2.25rem;
min-height: 2.25rem;
display: inline-flex;
justify-content: center;
align-items: center;
}
.bui-calendar button:hover {
background: var(--bg-hover);
}
.bui-calendar button:focus-visible {
outline: 2px solid var(--ring-focus);
outline-offset: 1px;
}
.bui-calendar svg {
width: 1rem;
height: 1rem;
}
.bui-calendar .rdp-weekday {
@apply text-meta text-tertiary;
height: var(--control-sm);
}
.bui-calendar .rdp-day {
@apply text-body;
padding: 0.125rem;
text-align: center;
}
.bui-calendar .rdp-selected button {
background: var(--bg-inverse);
color: var(--text-on-inverse);
}
.bui-calendar .rdp-today:not(.rdp-selected) button {
box-shadow: inset 0 0 0 1px var(--text-primary);
}
.bui-calendar .rdp-disabled {
color: var(--text-disabled);
}
.bui-calendar .rdp-outside {
color: var(--text-tertiary);
}
.bui-calendar .rdp-range_middle {
background: var(--bg-inset);
}
.bui-resize-handle {
background: var(--border-tertiary);
border-radius: var(--radius-pill);
width: 0.375rem;
margin-inline: var(--space-control-gap);
}
.bui-resize-handle:hover,
.bui-resize-handle:focus-visible {
background: var(--border-primary);
}
.bui-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.bui-chart {
width: 100%;
border-spacing: 0 0.5rem;
border-collapse: separate;
}
.bui-chart th {
@apply text-caption;
width: 25%;
text-align: start;
padding-inline-end: var(--space-control-gap);
}
.bui-chart td {
@apply text-caption;
position: relative;
padding: var(--space-control-gap);
isolation: isolate;
}
.bui-chart-bar {
position: absolute;
inset: 0;
z-index: -1;
background: var(--bg-inset);
border-radius: var(--radius-control);
}
}

View File

@@ -0,0 +1,148 @@
@layer components {
.bui-stack,
.bui-field {
display: flex;
flex-direction: column;
gap: var(--space-control-gap);
min-width: 0;
}
.bui-inline,
.bui-toolbar,
.bui-chips,
.bui-otp,
.bui-toggle-group {
display: flex;
align-items: center;
gap: var(--space-control-gap);
flex-wrap: wrap;
}
.bui-reset {
margin: 0;
padding: 0;
list-style: none;
}
.bui-label {
@apply text-label text-primary;
}
.bui-title {
@apply text-heading text-primary;
margin: 0;
}
.bui-description {
@apply text-caption text-secondary;
margin: 0;
}
.bui-field-error {
@apply text-caption text-danger;
}
.bui-button {
@apply text-label;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-control-gap);
min-height: var(--control-md);
padding: 0 var(--space-action-inset);
border: 1px solid transparent;
border-radius: var(--radius-pill);
background: var(--bg-inverse);
color: var(--text-on-inverse);
cursor: pointer;
transition:
background var(--duration-state),
transform var(--duration-state) var(--ease-standard);
text-decoration: none;
white-space: nowrap;
}
.bui-button-label {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-control-gap);
}
.bui-button svg,
.bui-icon,
.bui-inline > svg {
width: 1.125em;
height: 1.125em;
flex-shrink: 0;
}
.bui-button[data-size="sm"] {
min-height: var(--control-sm);
padding-inline: var(--space-control-inset);
}
.bui-button[data-size="lg"] {
min-height: var(--control-lg);
padding-inline: var(--space-panel-inset);
}
.bui-button[data-variant="secondary"] {
background: var(--bg-inset);
color: var(--text-primary);
}
.bui-button[data-variant="outline"] {
background: transparent;
color: var(--text-primary);
border-color: var(--border-primary);
}
.bui-button[data-variant="ghost"],
.bui-button[data-variant="link"] {
background: transparent;
color: var(--text-primary);
}
.bui-button[data-variant="link"] {
text-decoration: underline;
text-underline-offset: 0.2em;
}
.bui-button[data-variant="danger"] {
background: var(--bg-danger);
color: var(--text-on-danger);
}
.bui-button:hover:not(:disabled) {
background: var(--bg-inverse-hover);
}
.bui-button[data-variant="secondary"]:hover:not(:disabled),
.bui-button[data-variant="outline"]:hover:not(:disabled),
.bui-button[data-variant="ghost"]:hover:not(:disabled),
.bui-button[data-variant="link"]:hover:not(:disabled) {
background: var(--bg-hover);
}
.bui-button[data-variant="danger"]:hover:not(:disabled) {
background: var(--bg-danger);
}
.bui-button:active:not(:disabled) {
transform: scale(var(--press-scale));
}
.bui-button:disabled {
cursor: not-allowed;
background: var(--bg-inset);
color: var(--text-disabled);
}
.bui-button[data-loading] {
color: var(--text-on-inverse);
background: var(--bg-inverse);
}
.bui-button[data-loading] .bui-button-label {
opacity: 0;
}
.bui-loading {
position: absolute;
animation: bui-spin 1s linear infinite;
}
.bui-icon-button {
width: var(--control-md);
padding: 0;
}
.bui-icon-button[data-size="sm"] {
width: var(--control-sm);
padding: 0;
}
.bui-icon-button[data-size="lg"] {
width: var(--control-lg);
padding: 0;
}
:where([class*="bui-"]):focus-visible {
outline: 2px solid var(--ring-focus);
outline-offset: 3px;
}
}

View File

@@ -0,0 +1,176 @@
@layer components {
.bui-input,
.bui-select-trigger,
.bui-number-group,
.bui-otp-input {
@apply text-body;
width: 100%;
min-height: var(--control-md);
border: 1px solid var(--border-primary);
border-radius: var(--radius-control);
padding: var(--space-control-gap) var(--space-control-inset);
background: var(--bg-panel);
color: var(--text-primary);
}
.bui-input::placeholder {
color: var(--text-tertiary);
}
.bui-input[data-invalid],
.bui-input[aria-invalid="true"] {
border-color: var(--text-danger);
}
.bui-input:disabled {
background: var(--bg-inset);
color: var(--text-disabled);
cursor: not-allowed;
}
.bui-textarea {
min-height: 7rem;
resize: vertical;
}
.bui-input-group {
display: flex;
align-items: center;
gap: var(--space-control-gap);
}
.bui-fieldset {
border: 0;
display: grid;
gap: var(--space-control-inset);
padding: 0;
min-width: 0;
}
.bui-fieldset legend {
margin-bottom: var(--space-control-inset);
}
.bui-checkbox,
.bui-radio {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.25rem;
height: 1.25rem;
border: 1px solid var(--text-tertiary);
border-radius: 0.375rem;
background: var(--bg-panel);
flex-shrink: 0;
cursor: pointer;
}
.bui-checkbox[data-checked],
.bui-checkbox[data-indeterminate] {
background: var(--bg-inverse);
border-color: var(--bg-inverse);
color: var(--text-on-inverse);
}
.bui-check-indicator {
display: flex;
}
.bui-check-indicator svg {
width: 1rem;
height: 1rem;
}
.bui-radio {
border-radius: var(--radius-pill);
}
.bui-radio-indicator {
width: 0.625rem;
height: 0.625rem;
border-radius: var(--radius-pill);
background: var(--bg-inverse);
}
.bui-switch {
width: 2.75rem;
height: 1.625rem;
border-radius: var(--radius-pill);
padding: 0.1875rem;
background: var(--border-primary);
border: 0;
cursor: pointer;
}
.bui-switch[data-checked] {
background: var(--bg-inverse);
}
.bui-switch-thumb {
display: block;
width: 1.25rem;
height: 1.25rem;
border-radius: var(--radius-pill);
background: var(--bg-panel);
transition: transform var(--duration-state);
}
.bui-switch[data-checked] .bui-switch-thumb {
transform: translateX(1.125rem);
}
.bui-switch[data-disabled],
.bui-checkbox[data-disabled],
.bui-radio[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.bui-tabs-list {
display: flex;
gap: 0.25rem;
background: var(--bg-inset);
padding: 0.25rem;
border-radius: var(--radius-pill);
width: fit-content;
max-width: 100%;
overflow-x: auto;
}
.bui-tab,
.bui-toggle,
.bui-disclosure,
.bui-navigation-link {
@apply text-label;
border: 0;
padding: var(--space-control-gap) var(--space-control-inset);
border-radius: var(--radius-pill);
color: var(--text-secondary);
background: transparent;
cursor: pointer;
text-decoration: none;
}
.bui-tab[data-active],
.bui-toggle[data-pressed],
.bui-navigation-link[aria-current="page"] {
background: var(--bg-panel);
color: var(--text-primary);
}
.bui-tab:not([data-active]):hover,
.bui-toggle:hover,
.bui-disclosure:hover,
.bui-navigation-link:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.bui-tab-panel {
padding-block: var(--space-control-inset);
}
.bui-accordion-item {
border-bottom: 1px solid var(--border-tertiary);
}
.bui-accordion-trigger {
@apply text-label;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding-block: var(--space-control-inset);
background: transparent;
color: var(--text-primary);
cursor: pointer;
text-align: start;
}
.bui-accordion-trigger svg {
width: 1rem;
height: 1rem;
transition: transform var(--duration-state);
}
.bui-accordion-trigger[data-panel-open] svg {
transform: rotate(180deg);
}
.bui-accordion-panel {
@apply text-body text-secondary;
padding-block: var(--space-control-gap);
}
}

View File

@@ -0,0 +1,150 @@
@layer components {
.bui-number-group {
display: flex;
padding: 0;
overflow: hidden;
width: fit-content;
}
.bui-number-input {
width: 5rem;
min-width: 0;
text-align: center;
background: transparent;
border: 0;
}
.bui-icon-control {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space-control-gap);
border-radius: var(--radius-control);
cursor: pointer;
min-width: var(--control-sm);
min-height: var(--control-sm);
}
.bui-icon-control:hover {
background: var(--bg-hover);
}
.bui-icon-control svg {
width: 1rem;
height: 1rem;
}
.bui-otp-input {
width: 2.5rem;
text-align: center;
padding: var(--space-control-gap);
}
.bui-scroll-area {
position: relative;
height: 12rem;
}
.bui-scroll-viewport {
height: 100%;
border-radius: inherit;
overscroll-behavior: contain;
}
.bui-scrollbar {
display: flex;
width: 0.5rem;
margin: 0.25rem;
border-radius: var(--radius-pill);
background: var(--bg-inset);
}
.bui-scroll-thumb {
width: 100%;
background: var(--border-primary);
border-radius: inherit;
}
.bui-toast-viewport {
position: fixed;
bottom: var(--space-panel-inset);
right: var(--space-panel-inset);
width: min(22rem, calc(100vw - 3rem));
z-index: var(--layer-toast);
display: flex;
flex-direction: column;
gap: var(--space-control-gap);
}
.bui-toast {
background: var(--bg-float);
border: 1px solid var(--border-secondary);
border-radius: var(--radius-menu);
padding: var(--space-control-inset);
box-shadow: var(--shadow-sm);
display: flex;
align-items: start;
justify-content: space-between;
}
.bui-skeleton {
min-height: var(--control-md);
border-radius: var(--radius-control);
background: var(--bg-inset);
animation: bui-pulse 2s ease-in-out infinite;
}
.bui-spin {
animation: bui-spin 1s linear infinite;
}
.bui-empty {
display: grid;
justify-items: center;
text-align: center;
gap: var(--space-control-inset);
padding: var(--space-page-inset);
}
.bui-table-scroll {
overflow-x: auto;
}
.bui-table {
@apply text-body;
border-collapse: collapse;
width: 100%;
text-align: start;
}
.bui-table th {
@apply text-label text-secondary;
}
.bui-table th,
.bui-table td {
padding: var(--space-control-inset);
border-bottom: 1px solid var(--border-tertiary);
white-space: nowrap;
}
.bui-table caption {
text-align: start;
margin-bottom: var(--space-control-inset);
}
.bui-breadcrumb {
display: flex;
flex-wrap: wrap;
gap: var(--space-control-gap);
list-style: none;
padding: 0;
}
.bui-breadcrumb li + li::before {
content: "/";
color: var(--text-tertiary);
padding-inline-end: var(--space-control-gap);
}
.bui-sidebar {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: var(--space-control-inset);
border-radius: var(--radius-container);
background: var(--bg-inset);
}
.bui-sidebar a {
@apply text-label;
padding: var(--space-control-gap) var(--space-control-inset);
border-radius: var(--radius-control);
}
.bui-sidebar a[aria-current="page"] {
background: var(--bg-panel);
}
.bui-context-target {
border: 1px dashed var(--border-primary);
border-radius: var(--radius-menu);
padding: var(--space-page-inset);
text-align: center;
}
}

View File

@@ -0,0 +1,152 @@
@layer components {
.bui-positioner {
z-index: var(--layer-floating);
outline: none;
max-width: calc(100vw - 2rem);
}
.bui-popup,
.bui-popover {
background: var(--bg-float);
color: var(--text-primary);
border: 1px solid var(--border-tertiary);
border-radius: var(--radius-menu);
padding: var(--space-control-gap);
box-shadow: var(--shadow-sm);
min-width: 12rem;
max-width: min(26rem, calc(100vw - 2rem));
max-height: var(--available-height, 70vh);
overflow: auto;
transform-origin: var(--transform-origin);
transition:
opacity var(--duration-enter),
transform var(--duration-enter) var(--ease-standard);
}
.bui-popup[data-starting-style],
.bui-popup[data-ending-style],
.bui-popover[data-starting-style],
.bui-popover[data-ending-style] {
opacity: 0;
transform: translateY(-0.25rem);
}
.bui-popover {
padding: var(--space-panel-inset);
display: grid;
gap: var(--space-control-gap);
}
.bui-option {
@apply text-label;
display: flex;
align-items: center;
gap: var(--space-control-gap);
padding: var(--space-control-gap) var(--space-control-inset);
border-radius: var(--radius-control);
cursor: pointer;
outline: none;
}
.bui-option[data-highlighted],
.bui-option:focus-visible {
background: var(--bg-hover);
outline: 2px solid var(--ring-focus);
outline-offset: -2px;
}
.bui-option[data-disabled] {
color: var(--text-disabled);
cursor: not-allowed;
}
.bui-option-check {
display: flex;
margin-inline-start: auto;
}
.bui-option-check svg {
width: 1rem;
height: 1rem;
}
.bui-option-list {
margin: 0;
padding: 0;
list-style: none;
}
.bui-group-label {
@apply text-meta text-tertiary;
padding: var(--space-control-inset);
}
.bui-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-control-gap);
width: fit-content;
min-width: 12rem;
cursor: pointer;
}
.bui-backdrop {
position: fixed;
inset: 0;
background: var(--overlay-scrim);
z-index: var(--layer-dialog);
transition: opacity var(--duration-enter);
}
.bui-backdrop[data-starting-style],
.bui-backdrop[data-ending-style] {
opacity: 0;
}
.bui-dialog-viewport {
position: fixed;
inset: 0;
display: grid;
place-items: center;
overflow-y: auto;
padding: var(--space-panel-inset);
z-index: var(--layer-dialog);
}
.bui-dialog {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: grid;
gap: var(--space-section-gap);
width: min(28rem, calc(100vw - 2rem));
max-height: calc(100dvh - 2rem);
overflow-y: auto;
padding: var(--space-panel-inset);
border-radius: var(--radius-overlay);
background: var(--bg-float);
color: var(--text-primary);
border: 1px solid var(--border-tertiary);
box-shadow: var(--shadow-sm);
z-index: var(--layer-dialog);
outline: none;
}
.bui-dialog-viewport .bui-dialog {
position: relative;
left: auto;
top: auto;
transform: none;
}
.bui-drawer-viewport {
position: fixed;
inset: 0;
display: flex;
align-items: flex-end;
z-index: var(--layer-dialog);
}
.bui-drawer {
width: 100%;
max-height: 85dvh;
overflow-y: auto;
background: var(--bg-float);
color: var(--text-primary);
padding: var(--space-page-inset);
border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
outline: none;
}
.bui-tooltip {
@apply text-caption;
padding: var(--space-tooltip-block) var(--space-control-gap);
background: var(--bg-inverse);
color: var(--text-on-inverse);
border-radius: var(--radius-notice);
max-width: 18rem;
}
}

View File

@@ -0,0 +1,105 @@
@layer components {
.bui-card {
display: flex;
flex-direction: column;
gap: var(--space-section-gap);
border-radius: var(--radius-container);
background: var(--bg-panel);
border: 1px solid var(--border-tertiary);
padding: var(--space-panel-inset);
color: var(--text-primary);
}
.bui-badge,
.bui-chip {
@apply text-meta;
display: inline-flex;
align-items: center;
gap: var(--space-control-gap);
padding: 0.375rem 0.625rem;
border-radius: var(--radius-pill);
background: var(--bg-inset);
color: var(--text-secondary);
}
:is(.bui-badge, .bui-alert)[data-tone="accent"] {
background: var(--bg-accent-tint);
color: var(--text-accent);
}
:is(.bui-badge, .bui-alert)[data-tone="success"] {
background: var(--bg-success-tint);
color: var(--text-success);
}
:is(.bui-badge, .bui-alert)[data-tone="warning"] {
background: var(--bg-warning-tint);
color: var(--text-warning);
}
:is(.bui-badge, .bui-alert)[data-tone="danger"] {
background: var(--bg-danger-tint);
color: var(--text-danger);
}
:is(.bui-badge, .bui-alert)[data-tone="info"] {
background: var(--bg-info-tint);
color: var(--text-info);
}
.bui-alert {
@apply text-body;
padding: var(--space-control-inset);
border-radius: var(--radius-notice);
}
.bui-avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--control-md);
height: var(--control-md);
overflow: hidden;
border-radius: var(--radius-pill);
background: var(--bg-inset);
}
.bui-avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.bui-avatar-fallback {
@apply text-label;
}
.bui-separator {
border: 0;
background: var(--border-tertiary);
height: 1px;
margin-block: var(--space-control-gap);
}
.bui-slider-control {
display: flex;
align-items: center;
height: var(--control-sm);
min-width: 10rem;
touch-action: none;
user-select: none;
}
.bui-slider-track,
.bui-progress-track {
position: relative;
height: 0.375rem;
width: 100%;
background: var(--bg-inset);
border-radius: var(--radius-pill);
overflow: hidden;
}
.bui-slider-track {
overflow: visible;
}
.bui-slider-indicator,
.bui-progress-indicator {
background: var(--bg-inverse);
height: 100%;
border-radius: inherit;
}
.bui-slider-thumb {
width: 1.125rem;
height: 1.125rem;
border-radius: var(--radius-pill);
background: var(--bg-panel);
border: 2px solid var(--bg-inverse);
}
}

View File

@@ -0,0 +1,114 @@
import type { ComponentProps, ReactNode } from "react";
import { LoaderCircle } from "lucide-react";
import { cx } from "./classes";
/** A contained group; use rows instead when content forms a list. */
export function Card({ className, ...props }: ComponentProps<"section">) {
return <section {...props} className={cx("bui-card", className)} />;
}
/** A concise status label; its text carries meaning in addition to color. */
export function Badge({
tone = "neutral",
className,
...props
}: ComponentProps<"span"> & {
tone?: "neutral" | "accent" | "success" | "warning" | "danger" | "info";
}) {
return (
<span {...props} data-tone={tone} className={cx("bui-badge", className)} />
);
}
/** An inline notice. Set role=alert only for newly occurring urgent errors. */
export function Alert({
tone = "info",
className,
...props
}: ComponentProps<"div"> & {
tone?: "info" | "success" | "warning" | "danger";
}) {
return (
<div {...props} data-tone={tone} className={cx("bui-alert", className)} />
);
}
/** Decorative placeholder; announce loading once on its owning region. */
export function Skeleton({ className, ...props }: ComponentProps<"div">) {
return (
<div
{...props}
aria-hidden="true"
className={cx("bui-skeleton", className)}
/>
);
}
/** A named loading status with a decorative animated icon. */
export function Spinner({ label = "Loading" }: { label?: string }) {
return (
<span role="status" className="bui-inline">
<LoaderCircle aria-hidden="true" className="bui-spin" />
<span>{label}</span>
</span>
);
}
/** A stable media frame with a caller-selected ratio. */
export function AspectRatio({
ratio = 16 / 9,
style,
...props
}: ComponentProps<"div"> & { ratio?: number }) {
return <div {...props} style={{ ...style, aspectRatio: ratio }} />;
}
/** A semantic table wrapper that allows horizontal scrolling on narrow screens. */
export function Table({ children, ...props }: ComponentProps<"table">) {
return (
<div className="bui-table-scroll">
<table {...props} className={cx("bui-table", props.className)}>
{children}
</table>
</div>
);
}
/** A quiet division between related regions. */
export function Separator(props: ComponentProps<"hr">) {
return <hr {...props} className={cx("bui-separator", props.className)} />;
}
/** Empty states explain purpose and provide an explicit next action. */
export function EmptyState({
title,
description,
action,
}: {
title: string;
description: string;
action?: ReactNode;
}) {
return (
<div className="bui-empty">
<h3 className="bui-label">{title}</h3>
<p className="bui-description">{description}</p>
{action}
</div>
);
}
/** Related destinations with one named navigation landmark. */
export function Breadcrumb({ children, ...props }: ComponentProps<"nav">) {
return (
<nav aria-label="Breadcrumb" {...props}>
<ol className="bui-breadcrumb">{children}</ol>
</nav>
);
}
/** A side navigation region; selection is represented by aria-current on links. */
export function Sidebar(
props: ComponentProps<"nav"> & { "aria-label": string },
) {
return <nav {...props} className={cx("bui-sidebar", props.className)} />;
}
/** Page navigation; callers own links and mark the current one with aria-current=page. */
export function Pagination(props: ComponentProps<"nav">) {
return (
<nav
aria-label="Pagination"
{...props}
className={cx("bui-toolbar", props.className)}
/>
);
}

18
packages/ui/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"rootDir": "src",
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,21 @@
import { defineConfig } from "vite";
export default defineConfig({
build: {
lib: {
entry: "src/index.ts",
formats: ["es"],
fileName: "index",
cssFileName: "ui",
},
rollupOptions: {
external: [
/^react($|\/)/,
/^react-dom($|\/)/,
/^@base-ui\//,
/^lucide-react/,
/^@daypicker\//,
/^react-resizable-panels/,
],
},
},
});

114
pnpm-lock.yaml generated
View File

@@ -310,12 +310,12 @@ importers:
desktop-next:
dependencies:
'@base-ui-components/react':
specifier: 1.0.0-rc.0
version: 1.0.0-rc.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@buzz/design-tokens':
specifier: workspace:*
version: link:../packages/design-tokens
'@buzz/ui':
specifier: workspace:*
version: link:../packages/ui
'@fontsource-variable/inter':
specifier: ^5.2.8
version: 5.2.8
@@ -325,6 +325,9 @@ importers:
'@tanstack/react-router':
specifier: ^1.168.10
version: 1.170.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
lucide-react:
specifier: ^1.0.0
version: 1.16.0(react@19.2.8)
react:
specifier: ^19.1.0
version: 19.2.8
@@ -332,6 +335,9 @@ importers:
specifier: ^19.1.0
version: 19.2.8(react@19.2.8)
devDependencies:
'@playwright/test':
specifier: ^1.58.2
version: 1.60.0
'@tailwindcss/postcss':
specifier: ^4.3.0
version: 4.3.0
@@ -363,6 +369,49 @@ importers:
specifier: 4.3.0
version: 4.3.0
packages/ui:
dependencies:
'@base-ui/react':
specifier: 1.7.0
version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.2.18)(date-fns@4.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@buzz/design-tokens':
specifier: workspace:*
version: link:../design-tokens
'@daypicker/react':
specifier: ^10.0.1
version: 10.0.1(@types/react@19.2.18)(react@19.2.8)
lucide-react:
specifier: ^1.0.0
version: 1.16.0(react@19.2.8)
react-resizable-panels:
specifier: ^4.12.4
version: 4.12.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
devDependencies:
'@tailwindcss/postcss':
specifier: ^4.3.0
version: 4.3.0
'@types/react':
specifier: ^19.1.8
version: 19.2.18
'@types/react-dom':
specifier: ^19.1.6
version: 19.2.4(@types/react@19.2.18)
react:
specifier: ^19.1.0
version: 19.2.8
react-dom:
specifier: ^19.1.0
version: 19.2.8(react@19.2.8)
tailwindcss:
specifier: ^4.3.0
version: 4.3.0
typescript:
specifier: ~6.0.0
version: 6.0.3
vite:
specifier: ^8.0.0
version: 8.0.16(@types/node@25.6.0)(jiti@2.7.0)(yaml@2.9.0)
web:
dependencies:
'@fontsource-variable/inter':
@@ -568,21 +617,25 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
'@base-ui-components/react@1.0.0-rc.0':
resolution: {integrity: sha512-9lhUFbJcbXvc9KulLev1WTFxS/alJRBWDH/ibKSQaNvmDwMFS2gKp1sTeeldYSfKuS/KC1w2MZutc0wHu2hRHQ==}
'@base-ui/react@1.7.0':
resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==}
engines: {node: '>=14.0.0'}
deprecated: Package was renamed to @base-ui/react
peerDependencies:
'@date-fns/tz': ^1.2.0
'@types/react': ^17 || ^18 || ^19
date-fns: ^4.0.0
react: ^17 || ^18 || ^19
react-dom: ^17 || ^18 || ^19
peerDependenciesMeta:
'@date-fns/tz':
optional: true
'@types/react':
optional: true
date-fns:
optional: true
'@base-ui-components/utils@0.2.2':
resolution: {integrity: sha512-rNJCD6TFy3OSRDKVHJDzLpxO3esTV1/drRtWNUpe7rCpPN9HZVHUCuP+6rdDYDGWfXnQHbqi05xOyRP2iZAlkw==}
deprecated: Package was renamed to @base-ui/utils
'@base-ui/utils@0.3.2':
resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==}
peerDependencies:
'@types/react': ^17 || ^18 || ^19
react: ^17 || ^18 || ^19
@@ -687,6 +740,16 @@ packages:
'@date-fns/tz@1.5.0':
resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
'@daypicker/react@10.0.1':
resolution: {integrity: sha512-lH4YQz4iMBWP8hsI1bD9Eg0T7t503IkSUR/WDGGkV5mKZvwVv+ukCkJz7yN+uVFBv7vHTK+ww7a5EvlkeFwPYQ==}
engines: {node: '>=18'}
peerDependencies:
'@types/react': '>=16.8.0'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
'@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies:
@@ -3309,6 +3372,12 @@ packages:
'@types/react':
optional: true
react-resizable-panels@4.12.4:
resolution: {integrity: sha512-SOsSj4e9U7vxL15MbgTfl35IO4vxSW4Xb0b1gtJVbRPWv7FOezcgOA00Mi0jP8XqC3y7jOQxpsGs/9zLKHPitA==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -3483,9 +3552,6 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
tabbable@6.5.0:
resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==}
tailwind-merge@3.6.0:
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
@@ -3963,21 +4029,21 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.29.7
'@base-ui-components/react@1.0.0-rc.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
'@base-ui/react@1.7.0(@date-fns/tz@1.5.0)(@types/react@19.2.18)(date-fns@4.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@babel/runtime': 7.29.7
'@base-ui-components/utils': 0.2.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@base-ui/utils': 0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@floating-ui/utils': 0.2.12
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
reselect: 5.3.0
tabbable: 6.5.0
use-sync-external-store: 1.6.0(react@19.2.8)
optionalDependencies:
'@date-fns/tz': 1.5.0
'@types/react': 19.2.18
date-fns: 4.4.0
'@base-ui-components/utils@0.2.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
'@base-ui/utils@0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@babel/runtime': 7.29.7
'@floating-ui/utils': 0.2.12
@@ -4049,6 +4115,13 @@ snapshots:
'@date-fns/tz@1.5.0': {}
'@daypicker/react@10.0.1(@types/react@19.2.18)(react@19.2.8)':
dependencies:
react: 19.2.8
react-day-picker: 10.0.1(@types/react@19.2.18)(react@19.2.8)
optionalDependencies:
'@types/react': 19.2.18
'@dnd-kit/accessibility@3.1.1(react@19.2.8)':
dependencies:
react: 19.2.8
@@ -6758,6 +6831,11 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.18
react-resizable-panels@4.12.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8):
dependencies:
get-nonce: 1.0.1
@@ -6968,8 +7046,6 @@ snapshots:
symbol-tree@3.2.4: {}
tabbable@6.5.0: {}
tailwind-merge@3.6.0: {}
tailwindcss@4.3.0: {}