├── .prototools
├── web
├── README.md
├── app
│ ├── favicon.ico
│ ├── api
│ │ ├── search
│ │ │ └── route.ts
│ │ └── examples
│ │ │ └── [name]
│ │ │ └── route.ts
│ ├── docs
│ │ ├── content
│ │ │ ├── components
│ │ │ │ ├── tabs.mdx
│ │ │ │ ├── menu.mdx
│ │ │ │ ├── meter.mdx
│ │ │ │ ├── switch.mdx
│ │ │ │ ├── table.mdx
│ │ │ │ ├── popover.mdx
│ │ │ │ ├── slider.mdx
│ │ │ │ ├── tooltip.mdx
│ │ │ │ ├── date-field.mdx
│ │ │ │ ├── tag-group.mdx
│ │ │ │ ├── radio-group.mdx
│ │ │ │ ├── time-field.mdx
│ │ │ │ ├── card.mdx
│ │ │ │ ├── toggle.mdx
│ │ │ │ ├── accordion.mdx
│ │ │ │ ├── checkbox.mdx
│ │ │ │ ├── modal.mdx
│ │ │ │ ├── calendar.mdx
│ │ │ │ ├── breadcrumbs.mdx
│ │ │ │ ├── date-picker.mdx
│ │ │ │ ├── badge.mdx
│ │ │ │ ├── select.mdx
│ │ │ │ ├── command.mdx
│ │ │ │ ├── button.mdx
│ │ │ │ ├── input.mdx
│ │ │ │ └── combobox.mdx
│ │ │ ├── styles.mdx
│ │ │ ├── intro.mdx
│ │ │ └── usage.mdx
│ │ ├── page.tsx
│ │ ├── layout.tsx
│ │ └── [...slug]
│ │ │ └── page.tsx
│ ├── layout.tsx
│ ├── r
│ │ └── [[...segments]]
│ │ │ └── route.ts
│ └── globals.css
├── postcss.config.mjs
├── lib
│ ├── source.ts
│ ├── cx.ts
│ ├── metadata.ts
│ ├── examples-map.ts
│ ├── component-data.ts
│ └── navigation.ts
├── source.config.ts
├── public
│ ├── bl.svg
│ └── r
│ │ ├── badge.json
│ │ ├── tooltip.json
│ │ ├── popover.json
│ │ ├── meter.json
│ │ ├── switch.json
│ │ ├── modal.json
│ │ ├── breadcrumbs.json
│ │ └── toggle.json
├── components
│ ├── rac-link.tsx
│ ├── code-block.tsx
│ ├── sidebar-link.tsx
│ ├── theme-toggle.tsx
│ ├── code-block-client.tsx
│ ├── search.tsx
│ ├── open-in-ai-menu.tsx
│ ├── sidebar.tsx
│ ├── component-metadata.tsx
│ ├── docs-page.tsx
│ ├── table-of-contents.tsx
│ ├── preview.tsx
│ ├── mobile-nav.tsx
│ └── mdx-components.tsx
├── next.config.ts
├── CHANGELOG.md
├── components.json
├── .gitignore
├── tsconfig.json
├── package.json
└── hooks
│ └── use-active-heading.ts
├── .cursorrules
├── packages
├── components
│ ├── src
│ │ ├── index.ts
│ │ ├── examples
│ │ │ ├── toggle-base.tsx
│ │ │ ├── meter-base.tsx
│ │ │ ├── range-calendar-base.tsx
│ │ │ ├── switch-base.tsx
│ │ │ ├── badge-base.tsx
│ │ │ ├── date-picker-base.tsx
│ │ │ ├── button-base.tsx
│ │ │ ├── date-field-base.tsx
│ │ │ ├── checkbox-base.tsx
│ │ │ ├── calendar-base.tsx
│ │ │ ├── input-base.tsx
│ │ │ ├── input-label.tsx
│ │ │ ├── input-disabled.tsx
│ │ │ ├── date-range-picker-base.tsx
│ │ │ ├── slider-base.tsx
│ │ │ ├── badge-variants.tsx
│ │ │ ├── checkbox-group.tsx
│ │ │ ├── radio-group-base.tsx
│ │ │ ├── select-base.tsx
│ │ │ ├── button-variants.tsx
│ │ │ ├── time-field-base.tsx
│ │ │ ├── button-sizes.tsx
│ │ │ ├── select-popover-classname.tsx
│ │ │ ├── combobox-disabled-option.tsx
│ │ │ ├── combobox-menu-trigger.tsx
│ │ │ ├── toggle-group-base.tsx
│ │ │ ├── accordion-base.tsx
│ │ │ ├── select-searchable.tsx
│ │ │ ├── tag-group-base.tsx
│ │ │ ├── button-helper-icons.tsx
│ │ │ ├── combobox-base.tsx
│ │ │ ├── breadcrumbs-seperators.tsx
│ │ │ ├── breadcrumbs-base.tsx
│ │ │ ├── tooltip-base.tsx
│ │ │ ├── card-variants.tsx
│ │ │ ├── modal-base.tsx
│ │ │ ├── modal-dismissable.tsx
│ │ │ ├── menu-base.tsx
│ │ │ ├── card-base.tsx
│ │ │ ├── command-base.tsx
│ │ │ ├── command-custom-trigger.tsx
│ │ │ ├── index.ts
│ │ │ ├── table-base.tsx
│ │ │ ├── accordion-group.tsx
│ │ │ └── popover-base.tsx
│ │ ├── core
│ │ │ ├── badge
│ │ │ │ ├── meta.json
│ │ │ │ └── badge.tsx
│ │ │ ├── modal
│ │ │ │ ├── meta.json
│ │ │ │ └── modal.tsx
│ │ │ ├── input
│ │ │ │ ├── meta.json
│ │ │ │ └── input.tsx
│ │ │ ├── card
│ │ │ │ ├── meta.json
│ │ │ │ └── card.tsx
│ │ │ ├── menu
│ │ │ │ ├── meta.json
│ │ │ │ └── menu.tsx
│ │ │ ├── switch
│ │ │ │ ├── meta.json
│ │ │ │ └── switch.tsx
│ │ │ ├── popover
│ │ │ │ ├── meta.json
│ │ │ │ └── popover.tsx
│ │ │ ├── breadcrumbs
│ │ │ │ ├── meta.json
│ │ │ │ └── breadcrumbs.tsx
│ │ │ ├── meter
│ │ │ │ ├── meta.json
│ │ │ │ └── meter.tsx
│ │ │ ├── slider
│ │ │ │ ├── meta.json
│ │ │ │ └── slider.tsx
│ │ │ ├── button
│ │ │ │ ├── meta.json
│ │ │ │ └── button.tsx
│ │ │ ├── tooltip
│ │ │ │ ├── meta.json
│ │ │ │ └── tooltip.tsx
│ │ │ ├── select
│ │ │ │ └── meta.json
│ │ │ ├── tabs
│ │ │ │ ├── meta.json
│ │ │ │ └── tabs.tsx
│ │ │ ├── checkbox
│ │ │ │ ├── meta.json
│ │ │ │ └── checkbox.tsx
│ │ │ ├── radio-group
│ │ │ │ ├── meta.json
│ │ │ │ └── radio-group.tsx
│ │ │ ├── toggle
│ │ │ │ ├── meta.json
│ │ │ │ └── toggle.tsx
│ │ │ ├── combobox
│ │ │ │ ├── meta.json
│ │ │ │ └── combobox.tsx
│ │ │ ├── command
│ │ │ │ └── meta.json
│ │ │ ├── calendar
│ │ │ │ └── meta.json
│ │ │ ├── date-picker
│ │ │ │ └── meta.json
│ │ │ ├── time-field
│ │ │ │ ├── meta.json
│ │ │ │ └── time-field.tsx
│ │ │ ├── table
│ │ │ │ ├── meta.json
│ │ │ │ └── table.tsx
│ │ │ ├── date-field
│ │ │ │ ├── meta.json
│ │ │ │ └── date-field.tsx
│ │ │ ├── accordion
│ │ │ │ ├── meta.json
│ │ │ │ └── accordion.tsx
│ │ │ ├── tag-group
│ │ │ │ ├── meta.json
│ │ │ │ └── tag-group.tsx
│ │ │ └── index.ts
│ │ └── tailwind.css
│ ├── CHANGELOG.md
│ ├── tsconfig.json
│ └── package.json
└── registry
│ ├── CHANGELOG.md
│ ├── src
│ ├── index.ts
│ └── schema.ts
│ ├── tsconfig.json
│ └── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── README.md
├── package.json
├── LICENSE.md
├── .gitignore
├── biome.json
└── .github
└── workflows
└── sync-components.yml
/.prototools:
--------------------------------------------------------------------------------
1 | pnpm = "10.12.2"
2 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # baselayer v2
2 |
--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------
1 | # Cursor Rules
2 |
3 | ## General
4 |
5 | - Always use pnpm
--------------------------------------------------------------------------------
/packages/components/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./core";
2 | export * from "./examples";
3 |
--------------------------------------------------------------------------------
/web/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zwgnr/BaseLayer/HEAD/web/app/favicon.ico
--------------------------------------------------------------------------------
/web/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/packages/registry/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @baselayer/registry
2 |
3 | ## 2.0.0
4 |
5 | ### Major Changes
6 |
7 | - 7204382: v2
8 |
--------------------------------------------------------------------------------
/packages/components/src/examples/toggle-base.tsx:
--------------------------------------------------------------------------------
1 | import { Toggle } from "../core/toggle/toggle";
2 |
3 | export const ToggleExample = () => Mode ;
--------------------------------------------------------------------------------
/packages/registry/src/index.ts:
--------------------------------------------------------------------------------
1 | // Registry package entry point
2 | // Exports all schemas and types for component registry
3 |
4 | export * from './schema.js';
--------------------------------------------------------------------------------
/packages/components/src/examples/meter-base.tsx:
--------------------------------------------------------------------------------
1 | import { Meter } from "../core/meter/meter";
2 |
3 | export const MeterExample = () => ;
--------------------------------------------------------------------------------
/packages/components/src/examples/range-calendar-base.tsx:
--------------------------------------------------------------------------------
1 | import { RangeCalendar } from "../core/calendar/calendar";
2 |
3 | export const RangeCalendarBase = () => ;
--------------------------------------------------------------------------------
/packages/components/src/examples/switch-base.tsx:
--------------------------------------------------------------------------------
1 | import { Switch } from "../core/switch/switch";
2 |
3 | export const SwitchBase = () => {
4 | return On/Off ;
5 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/badge-base.tsx:
--------------------------------------------------------------------------------
1 | import { Badge } from "../core/badge/badge";
2 |
3 | export const BadgeBase = () => {
4 | return (
5 | New
6 | );
7 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/date-picker-base.tsx:
--------------------------------------------------------------------------------
1 | import { DatePicker } from "../core/date-picker/date-picker";
2 |
3 | export const DatePickerBase = () => ;
--------------------------------------------------------------------------------
/web/app/api/search/route.ts:
--------------------------------------------------------------------------------
1 | import { createFromSource } from 'fumadocs-core/search/server';
2 |
3 | import { source } from '@/lib/source';
4 |
5 | export const { GET } = createFromSource(source);
--------------------------------------------------------------------------------
/packages/components/src/examples/button-base.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "../core/button/button";
2 |
3 | export const ButtonBase = () => {
4 | return (
5 | Button
6 | );
7 | };
--------------------------------------------------------------------------------
/web/app/docs/content/components/tabs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tabs
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/packages/components/src/examples/date-field-base.tsx:
--------------------------------------------------------------------------------
1 | import { DateField } from "../core/date-field/date-field";
2 |
3 | export const DateFieldBase = () => {
4 | return ;
5 | };
6 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/menu.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Menu
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
11 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/meter.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Meter
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/switch.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Switch
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/table.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Table
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/popover.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Popover
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/slider.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Slider
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/tooltip.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tooltip
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/lib/source.ts:
--------------------------------------------------------------------------------
1 | import { loader } from 'fumadocs-core/source';
2 |
3 | import { docs } from '@/.source';
4 |
5 | export const source = loader({
6 | baseUrl: '/docs',
7 | source: docs.toFumadocsSource(),
8 | });
--------------------------------------------------------------------------------
/web/app/docs/content/components/date-field.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: DateField
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/tag-group.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: TagGroup
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/radio-group.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: RadioGroup
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/time-field.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: TimeField
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Source
9 |
10 |
--------------------------------------------------------------------------------
/packages/components/src/examples/checkbox-base.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from "../core/checkbox/checkbox";
2 |
3 | export const CheckboxBase = () => {
4 | return (
5 | Accept terms and conditions
6 | );
7 | };
--------------------------------------------------------------------------------
/web/lib/cx.ts:
--------------------------------------------------------------------------------
1 | import { twJoin, twMerge } from "tailwind-merge";
2 |
3 | /**
4 | * re export tailwind-merge
5 | */
6 | export const cx = twMerge;
7 |
8 | /**
9 | * re export tailwind-join
10 | */
11 | export const cn = twJoin;
--------------------------------------------------------------------------------
/packages/components/src/examples/calendar-base.tsx:
--------------------------------------------------------------------------------
1 | import { Calendar } from "../core/calendar/calendar";
2 |
3 | export const CalendarExample = () => (
4 |
5 |
6 |
7 | );
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'web'
3 | - 'packages/*'
4 |
5 | # Define a catalog of version ranges.
6 | catalog:
7 | react-aria-components: 1.11.0
8 | tailwind-variants: 2.1.0
9 | typescript: ^5.8.3
10 | "@types/node": ^24.0.3
--------------------------------------------------------------------------------
/web/app/docs/content/components/card.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Card
3 | ---
4 |
5 |
6 |
7 | ## Variants
8 |
9 |
10 |
11 | ## Source
12 |
13 |
--------------------------------------------------------------------------------
/packages/components/src/core/badge/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "badge",
3 | "category": "display",
4 | "status": "stable",
5 | "description": "A badge is a small label that can be used to display information or status.",
6 | "tags": [
7 | "status"
8 | ]
9 | }
--------------------------------------------------------------------------------
/packages/components/src/core/modal/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "modal",
3 | "category": "overlays",
4 | "status": "stable",
5 | "description": "A modal is an overlay element which blocks interaction with elements outside it.",
6 | "tags": [
7 | "overlay"
8 | ]
9 | }
--------------------------------------------------------------------------------
/packages/components/src/core/input/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "input",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "Allows a user to enter a plain text value with a keyboard.",
6 | "tags": [
7 | "form",
8 | "interactive"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/components/src/core/card/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "card",
3 | "category": "layout",
4 | "status": "stable",
5 | "description": "Displays a card with header, content, and footer.",
6 | "tags": [
7 | "layout",
8 | "container",
9 | "display"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/input-base.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "../core/input/input";
2 |
3 | export const InputBase = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/input-label.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "../core/input/input";
2 |
3 | export const InputLabel = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
--------------------------------------------------------------------------------
/packages/components/src/core/menu/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "menu",
3 | "category": "pickers",
4 | "status": "stable",
5 | "description": "A menu displays a list of actions or options that a user can choose.",
6 | "tags": [
7 | "interactive",
8 | "navigation"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/components/src/core/switch/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "switch",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A switch allows a user to turn a setting on or off.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "toggle"
10 | ]
11 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/toggle.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Toggle
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Group
9 |
10 |
11 |
12 | ## Source
13 |
14 |
--------------------------------------------------------------------------------
/packages/components/src/core/popover/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "popover",
3 | "category": "overlays",
4 | "status": "stable",
5 | "description": "A popover is an overlay element positioned relative to a trigger.",
6 | "tags": [
7 | "overlay",
8 | "interactive"
9 | ]
10 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/accordion.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Accordion
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Group
9 |
10 |
11 |
12 | ## Source
13 |
14 |
--------------------------------------------------------------------------------
/packages/components/src/core/breadcrumbs/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "breadcrumbs",
3 | "category": "navigation",
4 | "status": "stable",
5 | "description": "Breadcrumbs display a heirarchy of links to the current page or resource in an application.",
6 | "tags": [
7 | "navigation"
8 | ]
9 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/input-disabled.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "../core/input/input";
2 |
3 | export const InputDisabled = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
--------------------------------------------------------------------------------
/web/app/docs/content/components/checkbox.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Checkbox
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Group
9 |
10 |
11 |
12 |
13 | ## Source
14 |
15 |
--------------------------------------------------------------------------------
/packages/components/src/core/meter/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meter",
3 | "category": "data-display",
4 | "status": "stable",
5 | "description": "A meter represents a quantity within a known range, or a fractional value.",
6 | "tags": [
7 | "data-display",
8 | "progress"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/components/src/core/slider/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slider",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A slider allows a user to select one or more values within a range.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "range"
10 | ]
11 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/modal.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Modal
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Dismissable
9 |
10 |
11 |
12 |
13 |
14 | ## Source
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/components/src/core/button/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "button",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A button allows a user to perform an action, with mouse, touch, and keyboard interactions.",
6 | "tags": [
7 | "form",
8 | "interactive"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/components/src/core/tooltip/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tooltip",
3 | "category": "overlays",
4 | "status": "stable",
5 | "description": "A tooltip displays a description of an element on hover or focus.",
6 | "tags": [
7 | "overlay",
8 | "interactive",
9 | "help"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/date-range-picker-base.tsx:
--------------------------------------------------------------------------------
1 | import { DateRangePicker } from "../core/date-picker/date-picker";
2 |
3 | export const DateRangePickerBase = () => (
4 |
5 |
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/calendar.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Calendar
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Range Calendar
9 |
10 |
11 |
12 | ## Source
13 |
14 |
15 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/breadcrumbs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Breadcrumbs
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Separator Icons
9 |
10 |
11 |
12 | ## Source
13 |
14 |
--------------------------------------------------------------------------------
/packages/components/src/core/select/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "select",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A select displays a collapsible list of options and allows a user to select one of them.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "picker"
10 | ]
11 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/date-picker.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: DatePicker
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Date Range Picker
9 |
10 |
11 |
12 | ## Source
13 |
14 |
--------------------------------------------------------------------------------
/packages/components/src/core/tabs/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tabs",
3 | "category": "navigation",
4 | "status": "stable",
5 | "description": "Tabs organize content into multiple sections and allow users to navigate between them.",
6 | "tags": [
7 | "navigation",
8 | "interactive",
9 | "layout"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/slider-base.tsx:
--------------------------------------------------------------------------------
1 | import { SliderLabel, SliderRoot, SliderThumb } from "../core/slider/slider";
2 |
3 | export const Slider = () => {
4 | return (
5 |
6 | Temperature
7 |
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/web/source.config.ts:
--------------------------------------------------------------------------------
1 | import { defineDocs, frontmatterSchema } from "fumadocs-mdx/config";
2 | import { z } from "zod";
3 |
4 | export const docs = defineDocs({
5 | dir: "app/docs/content",
6 | docs: {
7 | schema: frontmatterSchema.extend({
8 | isRAC: z.boolean().optional().default(false),
9 | }),
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/packages/components/src/core/checkbox/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "checkbox",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A checkbox allows a user to select multiple items from a list of individual items, or to mark one individual item as selected.",
6 | "tags": [
7 | "form",
8 | "interactive"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/components/src/core/radio-group/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "radio-group",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A radio group allows a user to select a single option from a list of mutually exclusive options.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "selection"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/core/toggle/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "toggle",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A toggle button allows a user to toggle a selection on or off, for example switching between two states or modes.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "toggle"
10 | ]
11 | }
--------------------------------------------------------------------------------
/web/lib/metadata.ts:
--------------------------------------------------------------------------------
1 | export const metadataImage = {
2 | src: '/bl.svg',
3 | width: 1200,
4 | height: 630,
5 | alt: 'BaseLayer Documentation'
6 | };
7 |
8 | export const siteConfig = {
9 | name: 'BaseLayer',
10 | description: 'Modern React components for building beautiful user interfaces',
11 | url: 'https://baselayer.dev',
12 | };
--------------------------------------------------------------------------------
/packages/components/src/core/combobox/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "combobox",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "picker"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/core/command/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "command",
3 | "category": "navigation",
4 | "status": "beta",
5 | "description": "A command palette that allows users to quickly search and execute commands using keyboard shortcuts.",
6 | "tags": [
7 | "search",
8 | "navigation",
9 | "keyboard",
10 | "palette"
11 | ]
12 | }
--------------------------------------------------------------------------------
/packages/components/src/core/calendar/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "calendar",
3 | "category": "data-entry",
4 | "status": "stable",
5 | "description": "A calendar displays one or more date grids and allows users to select either a single date or a contiguous range of dates.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "date-time"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/core/date-picker/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "date-picker",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A date picker combines one or more DateFields with a calendar popover, allowing users to enter or select a single date/time or a range.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "date-time"
10 | ]
11 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/badge.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Badge
3 | description: A badge is a small, visual indicator that can be used to label, categorize, or organize content.
4 | ---
5 |
6 |
7 |
8 | ## Variants
9 |
10 |
11 |
12 | ## Source
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/components/src/core/time-field/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "time-field",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A time field allows users to enter and edit time values using a keyboard. Each part of a time value is displayed in an individually editable segment.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "time"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/core/table/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "table",
3 | "category": "data-display",
4 | "status": "stable",
5 | "description": "A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys, and optionally supports row selection and sorting.",
6 | "tags": [
7 | "data-display",
8 | "interactive"
9 | ]
10 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/select.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Select
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Searchable Select
9 |
10 |
11 |
12 | ## Popover Classname
13 |
14 |
15 |
16 | ## Source
17 |
18 |
--------------------------------------------------------------------------------
/packages/components/src/core/date-field/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "date-field",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A date field allows users to enter and edit date and time values using a keyboard. Each part of a date value is displayed in an individually editable segment.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "date"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/badge-variants.tsx:
--------------------------------------------------------------------------------
1 | import { Badge } from "../core/badge/badge";
2 |
3 | export const BadgeVariants = () => {
4 | return (
5 |
6 | Attention
7 | Neutral
8 | Danger
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/command.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Command
3 | description: A command palette that allows users to quickly search and execute commands using keyboard shortcuts.
4 | ---
5 |
6 |
7 |
8 | ## Custom Trigger
9 |
10 |
11 |
12 | ## Source
13 |
14 |
--------------------------------------------------------------------------------
/packages/components/src/core/accordion/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "accordion",
3 | "category": "layout",
4 | "status": "stable",
5 | "description": "An accordion allows users to toggle the display of sections of content. Each accordion consists of a header with a title and content area.",
6 | "tags": [
7 | "disclosure",
8 | "collapsible",
9 | "expandable",
10 | "content"
11 | ]
12 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/checkbox-group.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox, CheckboxGroup } from "../core/checkbox/checkbox";
2 |
3 | export const CheckboxGroupExample = () => {
4 | return (
5 |
6 | Option 1
7 | Option 2
8 | Option 3
9 |
10 | );
11 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/radio-group-base.tsx:
--------------------------------------------------------------------------------
1 | import { Radio, RadioGroup } from "../core/radio-group/radio-group";
2 |
3 | export const RadioExample = () => (
4 |
5 | Email
6 | Phone Call
7 | Text Message
8 |
9 | );
--------------------------------------------------------------------------------
/packages/components/src/core/tag-group/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tag-group",
3 | "category": "forms",
4 | "status": "stable",
5 | "description": "A tag group is a focusable list of labels, categories, keywords, filters, or other items, with support for keyboard navigation, selection, and removal.",
6 | "tags": [
7 | "form",
8 | "interactive",
9 | "selection",
10 | "filtering"
11 | ]
12 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/button.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Button
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | ## Variants
9 |
10 |
11 |
12 | ## Sizes
13 |
14 |
15 |
16 | ## Helper Icons
17 |
18 |
19 |
20 | ## Source
21 |
22 |
--------------------------------------------------------------------------------
/packages/components/src/examples/select-base.tsx:
--------------------------------------------------------------------------------
1 | import { Select, SelectItem } from "../core/select/select";
2 |
3 | export const SelectExample = () => {
4 | return (
5 |
6 | Today
7 | This Week
8 | This Month
9 | This Year
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/components/src/examples/button-variants.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "../core/button/button";
2 |
3 | export const ButtonVariants = () => {
4 | return (
5 |
6 | Primary
7 | Secondary
8 | Danger
9 | Ghost
10 |
11 | );
12 | };
--------------------------------------------------------------------------------
/web/app/docs/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | export default function DocsPage() {
4 | return (
5 |
6 |
7 |
8 |
Documentation
9 |
10 |
11 | Get Started
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/packages/components/src/examples/time-field-base.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { TimeField } from "../core/time-field/time-field";
4 |
5 | export const TimeFieldExample = () => (
6 |
7 |
8 |
9 |
12 |
13 |
17 |
18 | );
--------------------------------------------------------------------------------
/packages/components/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @baselayer/components
2 |
3 | ## 2.0.3
4 |
5 | ### Patch Changes
6 |
7 | - f594c82: update RAC + tw
8 |
9 | ## 2.0.2
10 |
11 | ### Patch Changes
12 |
13 | - 6f88f68: mobile optimizations
14 |
15 | ## 2.0.1
16 |
17 | ### Patch Changes
18 |
19 | - 6f94762: fix secondary button border
20 |
21 | ## 2.0.0
22 |
23 | ### Major Changes
24 |
25 | - 7204382: v2
26 |
27 | ### Patch Changes
28 |
29 | - Updated dependencies [7204382]
30 | - @baselayer/registry@2.0.0
31 |
--------------------------------------------------------------------------------
/packages/components/src/examples/button-sizes.tsx:
--------------------------------------------------------------------------------
1 | import { PlusIcon } from "lucide-react";
2 |
3 | import { Button } from "../core/button/button";
4 |
5 | export const ButtonSizes = () => {
6 | return (
7 |
8 |
Small
9 |
Medium
10 |
Large
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": [".next/**", "!.next/cache/**", "dist/**"]
8 | },
9 | "dev": {
10 | "cache": false,
11 | "persistent": true
12 | },
13 | "lint": {
14 | "dependsOn": ["^build"]
15 | },
16 | "type-check": {
17 | "dependsOn": ["^build"]
18 | },
19 | "clean": {
20 | "cache": false
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/web/app/docs/content/components/input.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Input
3 | description: Allows a user to enter a plain text value with a keyboard.
4 | ---
5 |
6 | import { RACLink } from "@/components/rac-link";
7 |
8 |
9 |
10 |
11 |
12 | ## Label
13 |
14 |
15 |
16 | ## Disabled
17 |
18 |
19 |
20 | ## Source
21 |
22 |
23 |
--------------------------------------------------------------------------------
/web/public/bl.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/components/src/examples/select-popover-classname.tsx:
--------------------------------------------------------------------------------
1 | import { Select, SelectItem } from "../core/select/select";
2 |
3 | export const SelectPopoverClassname = () => {
4 | return (
5 |
11 | Today
12 | This Week
13 | This Month
14 | This Year
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/web/components/rac-link.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | import { ArrowUpRightIcon } from "lucide-react";
4 |
5 | export const RACLink = ({ componentName }: { componentName: string }) => (
6 |
12 | RAC Docs
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BaseLayer
2 |
3 | ## Overview
4 |
5 | BaseLayer is a comprehensive React component library built for modern development workflows and AI-powered tooling. It combines the accessibility and power of React Aria Components with the styling flexibility of Tailwind CSS, creating a robust foundation for building incredible user interfaces.
6 |
7 | ## Docs
8 |
9 | [https://baselayer.dev](https://baselayer.dev)
10 |
11 | ## Contributing
12 |
13 | Coming soon, but in the meantime, feel free to open an issue or [reach out!](https://x.com/zwagnr)
14 |
--------------------------------------------------------------------------------
/packages/components/src/examples/combobox-disabled-option.tsx:
--------------------------------------------------------------------------------
1 | import { ComboBox, ComboBoxItem } from "../core/combobox/combobox";
2 |
3 | export const ComboboxDisabledOption = () => {
4 | return (
5 |
6 |
7 | ChatGPT
8 | Gemini
9 | Claude
10 |
11 |
12 | );
13 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/combobox-menu-trigger.tsx:
--------------------------------------------------------------------------------
1 | import { ComboBox, ComboBoxItem } from "../core/combobox/combobox";
2 |
3 | export const ComboboxMenuTrigger = () => {
4 | return (
5 |
6 |
7 | ChatGPT
8 | Gemini
9 | Claude
10 |
11 |
12 | );
13 | };
--------------------------------------------------------------------------------
/web/components/code-block.tsx:
--------------------------------------------------------------------------------
1 | import { codeToHtml } from "shiki";
2 |
3 | import { CodeBlockWithCopy } from "./code-block-client";
4 |
5 | interface CodeBlockProps {
6 | code: string;
7 | lang?: string;
8 | }
9 |
10 | export const CodeBlock = async ({ code, lang = "tsx" }: CodeBlockProps) => {
11 | const html = await codeToHtml(code, {
12 | lang,
13 | themes: {
14 | light: "catppuccin-latte",
15 | dark: "catppuccin-frappe",
16 | },
17 | defaultColor: "light-dark()",
18 | });
19 |
20 | return ;
21 | };
22 |
--------------------------------------------------------------------------------
/web/app/docs/content/components/combobox.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: ComboBox
3 | isRAC: true
4 | ---
5 |
6 |
7 |
8 | If this component is not what exactly you're looking for UX wise, you may want to check out the [Searchable Select](/docs/components/select#searchable-select) component.
9 |
10 | ## Menu trigger behavior
11 |
12 |
13 |
14 | ## Disabled Options
15 |
16 |
17 |
18 | ## Source
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "baselayer",
3 | "private": true,
4 | "version": "2.1.1",
5 | "description": "Beautiful, accessible, and AI optimized React components for the web.",
6 | "scripts": {
7 | "build": "turbo build",
8 | "dev": "turbo dev",
9 | "lint": "turbo lint",
10 | "clean": "turbo clean",
11 | "type-check": "turbo type-check"
12 | },
13 | "devDependencies": {
14 | "@biomejs/biome": "2.2.5",
15 | "shadcn": "3.4.0",
16 | "turbo": "^2.5.8"
17 | },
18 | "packageManager": "pnpm@10.12.1",
19 | "engines": {
20 | "node": "22.17.0"
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/toggle-group-base.tsx:
--------------------------------------------------------------------------------
1 | import { LayoutGrid, List, SquareStack } from "lucide-react";
2 |
3 | import { Toggle, ToggleGroup } from "../core/toggle/toggle";
4 |
5 | export const ToggleGroupExample = () => (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
--------------------------------------------------------------------------------
/packages/components/src/examples/accordion-base.tsx:
--------------------------------------------------------------------------------
1 | import { Accordion } from "../core/accordion/accordion";
2 |
3 | export const AccordionBase = () => {
4 | return (
5 |
6 | You can cancel your subscription at any time by going to your account
7 | settings and clicking "Cancel Subscription". Your access will continue
8 | until the end of your current billing period, and you won't be charged
9 | again. All your data will be safely stored for 30 days in case you decide
10 | to reactivate.
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/packages/components/src/examples/select-searchable.tsx:
--------------------------------------------------------------------------------
1 | import { SearchableSelect, SelectItem } from "../core/select/select";
2 |
3 | export const SearchableSelectExample = () => {
4 | return (
5 |
6 |
11 | 4o
12 | o3
13 | Sonnet 4
14 | Gemini 2.0
15 |
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/packages/registry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["ES6"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "ESNext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": false,
16 | "declaration": true,
17 | "outDir": "./dist",
18 | "rootDir": "./src"
19 | },
20 | "include": ["src/**/*"],
21 | "exclude": ["node_modules", "dist"]
22 | }
--------------------------------------------------------------------------------
/web/next.config.ts:
--------------------------------------------------------------------------------
1 | import { createMDX } from "fumadocs-mdx/next";
2 | import type { NextConfig } from "next";
3 |
4 | import path from "node:path";
5 |
6 | const withMDX = createMDX();
7 |
8 | /** @type {import('next').NextConfig} */
9 | const nextConfig: NextConfig = {
10 | reactStrictMode: true,
11 | pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
12 | webpack: (config) => {
13 | config.resolve.alias = {
14 | ...config.resolve.alias,
15 | "@/components/ui": path.resolve(
16 | __dirname,
17 | "../packages/components/src/core",
18 | ),
19 | };
20 | return config;
21 | },
22 | };
23 |
24 | export default withMDX(nextConfig);
25 |
--------------------------------------------------------------------------------
/packages/components/src/examples/tag-group-base.tsx:
--------------------------------------------------------------------------------
1 | import { Tag, TagGroup } from "../core/tag-group/tag-group";
2 |
3 | export const TagGroupExample = () => (
4 |
5 |
6 | React
7 | TypeScript
8 | Node.js
9 | CSS
10 | Design Systems
11 | GraphQL
12 |
13 |
14 |
15 | Frontend
16 | Backend
17 | Design
18 | DevOps
19 |
20 |
21 | );
--------------------------------------------------------------------------------
/web/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @baselayer/web
2 |
3 | ## 2.0.3
4 |
5 | ### Patch Changes
6 |
7 | - f594c82: update RAC + tw
8 | - Updated dependencies [f594c82]
9 | - @baselayer/components@2.0.3
10 |
11 | ## 2.0.2
12 |
13 | ### Patch Changes
14 |
15 | - 6f88f68: mobile optimizations
16 | - Updated dependencies [6f88f68]
17 | - @baselayer/components@2.0.2
18 |
19 | ## 2.0.1
20 |
21 | ### Patch Changes
22 |
23 | - Updated dependencies [6f94762]
24 | - @baselayer/components@2.0.1
25 |
26 | ## 2.0.0
27 |
28 | ### Major Changes
29 |
30 | - 7204382: v2
31 |
32 | ### Patch Changes
33 |
34 | - Updated dependencies [7204382]
35 | - @baselayer/components@2.0.0
36 | - @baselayer/registry@2.0.0
37 |
--------------------------------------------------------------------------------
/packages/registry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@baselayer/registry",
3 | "version": "2.1.0",
4 | "private": true,
5 | "type": "module",
6 | "main": "./dist/index.js",
7 | "types": "./dist/index.d.ts",
8 | "files": [
9 | "dist",
10 | "src"
11 | ],
12 | "scripts": {
13 | "build": "tsc",
14 | "dev": "tsc --watch",
15 | "type-check": "tsc --noEmit"
16 | },
17 | "dependencies": {
18 | "zod": "^3.25.67"
19 | },
20 | "devDependencies": {
21 | "@types/node": "catalog:",
22 | "typescript": "catalog:"
23 | },
24 | "keywords": [
25 | "baselayer",
26 | "registry",
27 | "schema"
28 | ],
29 | "author": "zwgnr",
30 | "license": "MIT"
31 | }
--------------------------------------------------------------------------------
/packages/components/src/examples/button-helper-icons.tsx:
--------------------------------------------------------------------------------
1 | import { PlusIcon, TrashIcon } from "lucide-react";
2 |
3 | import { Button } from "../core/button/button";
4 |
5 | export const ButtonHelperIcons = () => {
6 | return (
7 |
8 |
12 |
13 | Add Item
14 |
15 |
16 |
17 | Delete
18 |
19 |
20 | );
21 | };
--------------------------------------------------------------------------------
/web/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry.json",
3 | "name": "@baselayer",
4 | "homepage": "https://baselayer.dev",
5 | "baseUrl": "https://baselayer.dev",
6 | "registryUrl": "https://baselayer.dev/r",
7 | "aliases": {
8 | "components": "@/components",
9 | "utils": "@/lib/utils",
10 | "ui": "@/components/ui"
11 | },
12 | "registries": {
13 | "@baselayer": "https://baselayer.dev/r/{name}.json"
14 | },
15 | "rsc": {
16 | "singleComponent": false
17 | },
18 | "tailwind": {
19 | "config": "tailwind.config.ts",
20 | "css": "app/globals.css",
21 | "baseColor": "slate",
22 | "cssVariables": true,
23 | "prefix": ""
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
43 | # fumadocs generated files
44 | .source/
45 |
--------------------------------------------------------------------------------
/packages/components/src/examples/combobox-base.tsx:
--------------------------------------------------------------------------------
1 | import { ComboBox, ComboBoxItem } from "../core/combobox/combobox";
2 |
3 | const countries = [
4 | "United States",
5 | "United Kingdom",
6 | "Canada",
7 | "Australia",
8 | "Germany",
9 | "France",
10 | "Japan",
11 | "Brazil",
12 | "India",
13 | "China",
14 | "Mexico",
15 | "Netherlands",
16 | "Sweden",
17 | "Norway",
18 | "Italy",
19 | ];
20 |
21 | export const ComboboxBase = () => {
22 | return (
23 |
24 |
25 | {countries.map((country) => (
26 |
27 | {country}
28 |
29 | ))}
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"],
23 | "@/components/ui/*": ["../packages/components/src/core/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/web/components/sidebar-link.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { usePathname } from "next/navigation";
5 |
6 | import { cx } from "@/lib/cx";
7 |
8 | interface SidebarLinkProps {
9 | href: string;
10 | children: React.ReactNode;
11 | onClick?: () => void;
12 | }
13 |
14 | export function SidebarLink({ href, children, onClick }: SidebarLinkProps) {
15 | const pathname = usePathname();
16 | const isActive = pathname === href;
17 |
18 | return (
19 | {
21 | onClick?.();
22 | }}
23 | href={href}
24 | className={cx(
25 | "block rounded-lg px-3 py-2 font-semibold text-sm transition-colors hover:bg-secondary",
26 | isActive && "bg-secondary text-primary hover:bg-secondary",
27 | )}
28 | >
29 | {children}
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/components/src/examples/breadcrumbs-seperators.tsx:
--------------------------------------------------------------------------------
1 | import { SlashIcon } from "lucide-react";
2 |
3 | import { Breadcrumbs, BreadcrumbsItem, BreadcrumbsLink } from "../core/breadcrumbs/breadcrumbs";
4 |
5 | export const BreadcrumbsSeperators = () => {
6 | return (
7 |
8 |
9 |
10 | Home
11 |
12 |
13 |
14 | Documents
15 |
16 |
17 |
18 | Recent
19 |
20 |
21 |
22 | );
23 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/breadcrumbs-base.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronRightIcon } from "lucide-react";
2 |
3 | import { Breadcrumbs, BreadcrumbsItem, BreadcrumbsLink } from "../core/breadcrumbs/breadcrumbs";
4 |
5 | export const BreadcrumbsBase = () => {
6 | return (
7 |
8 |
9 |
10 | Home
11 |
12 |
13 |
14 | Documents
15 |
16 |
17 |
18 | Recent
19 |
20 |
21 |
22 | );
23 | };
--------------------------------------------------------------------------------
/packages/components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["DOM", "DOM.Iterable", "ES6"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "ESNext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": false,
16 | "jsx": "react-jsx",
17 | "declaration": true,
18 | "outDir": "./dist",
19 | "rootDir": "./src",
20 | "baseUrl": ".",
21 | "paths": {
22 | "@/components/ui/*": ["src/core/*"]
23 | }
24 | },
25 | "include": [
26 | "src/**/*"
27 | ],
28 | "exclude": [
29 | "node_modules",
30 | "dist",
31 | "**/*.test.*"
32 | ]
33 | }
--------------------------------------------------------------------------------
/packages/components/src/core/badge/badge.tsx:
--------------------------------------------------------------------------------
1 | import type { HTMLAttributes } from "react";
2 |
3 | import { tv, type VariantProps } from "tailwind-variants";
4 |
5 | export const badge = tv({
6 | base: "flex items-center justify-center rounded-4xl px-3 py-2 font-semibold text-xs",
7 | variants: {
8 | variant: {
9 | attention: "bg-gradient-to-r from-pink-500 to-purple-500 text-white",
10 | neutral: "bg-secondary text-secondary-fg",
11 | danger: "bg-danger text-danger-fg",
12 | },
13 | },
14 | defaultVariants: {
15 | variant: "attention",
16 | },
17 | });
18 |
19 | type BadgeProps = VariantProps & HTMLAttributes;
20 |
21 | const Badge = ({ className, variant, ...props }: BadgeProps) => (
22 |
23 | );
24 |
25 | export { Badge };
26 | export type { BadgeProps };
27 |
--------------------------------------------------------------------------------
/web/app/api/examples/[name]/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | import { getExampleSource } from "@/lib/component-data";
4 |
5 | export async function GET(
6 | _request: Request,
7 | { params }: { params: Promise<{ name: string }> },
8 | ) {
9 | try {
10 | const { name } = await params;
11 | const exampleSource = await getExampleSource(name);
12 |
13 | if (exampleSource && !exampleSource.includes("not found")) {
14 | return new NextResponse(exampleSource, {
15 | headers: {
16 | "Content-Type": "text/plain",
17 | "Cache-Control": "public, max-age=3600",
18 | },
19 | });
20 | }
21 |
22 | return new NextResponse("Not found", { status: 404 });
23 | } catch (error) {
24 | console.error("Error in examples API route:", error);
25 | return new NextResponse("Internal Server Error", { status: 500 });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/components/src/examples/tooltip-base.tsx:
--------------------------------------------------------------------------------
1 | import { Settings, Share } from "lucide-react";
2 |
3 | import { Button } from "../core/button/button";
4 | import { Tooltip, TooltipTrigger } from "../core/tooltip/tooltip";
5 |
6 | export const TooltipExample = () => (
7 |
8 |
9 | Hover me
10 | This is a helpful tooltip message
11 |
12 |
13 |
14 |
15 |
16 |
17 | Settings
18 |
19 |
20 |
21 |
22 |
23 |
24 | Share this content with others
25 |
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/packages/components/src/examples/card-variants.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent } from "../core/card/card";
2 |
3 | export const CardVariants = () => {
4 | return (
5 |
6 |
12 |
13 | Content goes here with outlined border.
14 |
15 |
16 |
17 |
23 |
24 | Content goes here with surface 2 background.
25 |
26 |
27 |
28 | );
29 | };
--------------------------------------------------------------------------------
/packages/components/src/examples/modal-base.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Heading } from "react-aria-components";
4 |
5 | import { Button } from "../core/button/button";
6 | import { Input } from "../core/input/input";
7 | import { Dialog, Modal, ModalTrigger } from "../core/modal/modal";
8 |
9 | export const ModalExample = () => {
10 | return (
11 |
12 | Edit
13 |
14 |
15 | Edit Name
16 |
17 |
18 |
19 |
20 | Update
21 |
22 |
23 | Cancel
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/packages/components/src/examples/modal-dismissable.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Heading } from "react-aria-components";
4 |
5 | import { Button } from "../core/button/button";
6 | import { Input } from "../core/input/input";
7 | import { Dialog, Modal, ModalTrigger } from "../core/modal/modal";
8 |
9 | export const ModalDismissable = () => {
10 | return (
11 |
12 | Edit
13 |
14 |
15 | Edit Name
16 |
17 |
18 |
19 |
20 | Update
21 |
22 |
23 | Cancel
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/packages/components/src/core/tooltip/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | Tooltip as AriaTooltip,
7 | type TooltipProps as AriaTooltipProps,
8 | TooltipTrigger as AriaTooltipTrigger,
9 | } from "react-aria-components";
10 | import { tv } from "tailwind-variants";
11 |
12 | const tooltip = tv({
13 | base: "[&[data-entering]]:fade-in [&[data-exiting]]:fade-out m-1 max-w-sm rounded-full border border-border bg-surface px-4 py-2 text-fg shadow-xl outline-none [&[data-entering]]:animate-fade-in [&[data-exiting]]:animate-fade-out",
14 | });
15 |
16 | const TooltipTrigger = AriaTooltipTrigger;
17 |
18 | interface TooltipProps extends Omit {
19 | className?: string;
20 | children: ReactNode;
21 | }
22 |
23 | const Tooltip = ({ children, className, ...props }: TooltipProps) => (
24 |
25 | {children}
26 |
27 | );
28 |
29 | export { Tooltip, TooltipTrigger };
30 |
--------------------------------------------------------------------------------
/packages/components/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./accordion/accordion";
2 | export * from "./badge/badge";
3 | export * from "./breadcrumbs/breadcrumbs";
4 | export * from "./button/button";
5 | export * from "./calendar/calendar";
6 | export * from "./card/card";
7 | export * from "./checkbox/checkbox";
8 | export * from "./combobox/combobox";
9 | export * from "./command/command";
10 | export * from "./date-field/date-field";
11 | export * from "./date-picker/date-picker";
12 | export * from "./input/input";
13 | export * from "./menu/menu";
14 | export * from "./meter/meter";
15 | export * from "./modal/modal";
16 | export * from "./popover/popover";
17 | export * from "./radio-group/radio-group";
18 | export * from "./select/select";
19 | export * from "./slider/slider";
20 | export * from "./switch/switch";
21 | export * from "./table/table";
22 | export * from "./tabs/tabs";
23 | export * from "./tag-group/tag-group";
24 | export * from "./time-field/time-field";
25 | export * from "./toggle/toggle";
26 | export * from "./tooltip/tooltip";
--------------------------------------------------------------------------------
/packages/components/src/core/popover/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | DialogTrigger as AriaDialogTrigger,
7 | Popover as AriaPopover,
8 | type PopoverProps as AriaPopoverProps,
9 | Dialog,
10 | } from "react-aria-components";
11 | import { tv } from "tailwind-variants";
12 |
13 | export const popover = tv({
14 | base: "data-[entering]:fade-in data-[exiting]:fade-out m-1 max-w-lg rounded-2xl border border-border/25 bg-surface p-2 text-fg shadow-lg outline-none data-[entering]:animate-in data-[exiting]:animate-out",
15 | });
16 |
17 | interface DialogProps extends Omit {
18 | children: ReactNode;
19 | className?: string;
20 | }
21 |
22 | const PopoverTrigger = AriaDialogTrigger;
23 |
24 | const Popover = ({ children, className, ...props }: DialogProps) => (
25 |
26 | {children}
27 |
28 | );
29 |
30 | export { Popover, PopoverTrigger };
31 |
--------------------------------------------------------------------------------
/packages/components/src/examples/menu-base.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | HelpCircle,
3 | LogOut,
4 | Plus,
5 | Settings,
6 | } from "lucide-react";
7 |
8 | import { Button } from "../core/button/button";
9 | import {
10 | MenuContent,
11 | MenuHeader,
12 | MenuItem,
13 | MenuSection,
14 | MenuSeperator,
15 | MenuTrigger,
16 | } from "../core/menu/menu";
17 |
18 | export const Menu = () => {
19 | return (
20 |
21 |
22 | Z
23 |
24 |
25 |
26 | me@hello.com
27 |
28 |
29 |
30 | Account Settings
31 |
32 |
33 |
34 | Add Account
35 |
36 |
37 |
38 |
39 | Support
40 |
41 |
42 |
43 | Logout
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/packages/components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@baselayer/components",
3 | "version": "2.1.1",
4 | "private": true,
5 | "type": "module",
6 | "main": "./src/index.ts",
7 | "types": "./src/index.ts",
8 | "files": [
9 | "src"
10 | ],
11 | "scripts": {
12 | "build": "tsc && pnpm generate:registry && pnpm registry:build",
13 | "dev": "tsc --watch",
14 | "generate:registry": "tsx scripts/gen-registry-json.ts",
15 | "registry:build": "shadcn build --cwd . --output ../../web/public/r"
16 | },
17 | "dependencies": {
18 | "@baselayer/registry": "workspace:*",
19 | "lucide-react": "^0.525.0",
20 | "react": "^19.2.3",
21 | "react-aria-components": "catalog:",
22 | "tailwind-variants": "catalog:",
23 | "zod": "^3.25.67"
24 | },
25 | "devDependencies": {
26 | "@types/node": "catalog:",
27 | "@types/react": "^19.2.7",
28 | "fast-glob": "^3.3.2",
29 | "ts-morph": "^26.0.0",
30 | "tsx": "^4.20.3",
31 | "tw-animate-css": "^1.3.6",
32 | "typescript": "catalog:"
33 | },
34 | "peerDependencies": {
35 | "react": ">=19.0.0",
36 | "tailwindcss": ">=4.0.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/components/src/core/meter/meter.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Meter as AriaMeter,
5 | type MeterProps as AriaMeterProps,
6 | Label,
7 | } from "react-aria-components";
8 | import { tv } from "tailwind-variants";
9 |
10 | const bar = tv({
11 | base: "h-4 overflow-hidden rounded-2xl bg-surface-2",
12 | });
13 |
14 | interface MeterProps extends Omit {
15 | label?: string;
16 | className?: string;
17 | }
18 |
19 | const Meter = ({ label, className, ...props }: MeterProps) => (
20 |
21 | {({ percentage, valueText }) => (
22 | <>
23 |
24 | {label && {label} }
25 | {valueText}
26 |
27 |
33 | >
34 | )}
35 |
36 | );
37 |
38 | export { Meter };
39 | export type { MeterProps };
40 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Zach Wagner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/web/components/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 |
5 | import { Button } from "@baselayer/components";
6 | import { Moon, Sun } from "lucide-react";
7 | import { useTheme } from "next-themes";
8 |
9 | export const ThemeToggle = () => {
10 | const { theme, setTheme } = useTheme();
11 | const [mounted, setMounted] = useState(false);
12 |
13 | // Ensure component is mounted on client before rendering theme-dependent content
14 | useEffect(() => {
15 | setMounted(true);
16 | }, []);
17 |
18 | const toggleTheme = () => {
19 | setTheme(theme === "light" ? "dark" : "light");
20 | };
21 |
22 | const isDark = theme === "dark";
23 |
24 | if (!mounted) {
25 | return ;
26 | }
27 |
28 | return (
29 |
36 | {isDark ? : }
37 |
38 | );
39 | };
40 |
41 | export default ThemeToggle;
42 |
--------------------------------------------------------------------------------
/web/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { GeistMono } from 'geist/font/mono';
2 | import { GeistSans } from 'geist/font/sans';
3 | import type { Metadata } from "next";
4 |
5 | import "./globals.css";
6 |
7 | import { Analytics } from '@vercel/analytics/next';
8 | import { ThemeProvider } from "next-themes";
9 |
10 | export const metadata: Metadata = {
11 | title: "BaseLayer",
12 | description: "BaseLayer is a component system for building web applications.",
13 | icons: {
14 | icon: "/bl.svg",
15 | },
16 | };
17 |
18 | export default function RootLayout({
19 | children,
20 | }: Readonly<{
21 | children: React.ReactNode;
22 | }>) {
23 | return (
24 |
25 |
28 |
34 | {children}
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/packages/components/src/examples/card-base.tsx:
--------------------------------------------------------------------------------
1 | import { GithubIcon } from "lucide-react";
2 |
3 | import { Button } from "../core/button/button";
4 | import { Card, CardContent, CardFooter } from "../core/card/card";
5 | import { Input } from "../core/input/input";
6 |
7 | export const CardBase = () => {
8 | return (
9 |
14 |
15 |
29 |
30 |
31 | Sign in
32 |
33 |
34 | Continue with GitHub
35 |
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/web/components/code-block-client.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 |
5 | import { Check, Copy } from "lucide-react";
6 |
7 | interface CodeBlockWithCopyProps {
8 | code: string;
9 | html: string;
10 | }
11 |
12 | const CopyButton = ({ code }: { code: string }) => {
13 | const [copied, setCopied] = useState(false);
14 |
15 | const handleCopy = async () => {
16 | try {
17 | await navigator.clipboard.writeText(code);
18 | setCopied(true);
19 | setTimeout(() => setCopied(false), 2000);
20 | } catch (err) {
21 | console.error("Failed to copy text: ", err);
22 | }
23 | };
24 |
25 | return (
26 |
32 | {copied ? : }
33 |
34 | );
35 | };
36 |
37 | export const CodeBlockWithCopy = ({ code, html }: CodeBlockWithCopyProps) => {
38 | return (
39 |
40 | {/* biome-ignore lint/security/noDangerouslySetInnerHtml: fine */}
41 |
42 |
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/web/app/docs/content/styles.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Styles
3 | ---
4 |
5 | ## Tailwind CSS
6 |
7 | BaseLayer uses Tailwind CSS for styling. Tailwind CSS is a utility-first CSS framework that allows you to build custom designs without leaving your HTML.
8 |
9 | For help setting up Tailwind in your framework, see the [Tailwind docs](https://tailwindcss.com/docs/installation/framework-guides).
10 |
11 | **Please note: Only Tailwind 4 is supported**
12 |
13 | ## Tailwind Variants
14 |
15 | [Tailwind Variants](https://www.tailwind-variants.org/)
16 |
17 | >The power of Tailwind combined with a first-class variant API.
18 |
19 | By default, BaseLayer uses Tailwind Variants for styling. Tailwind Variants is a library that allows you to create variants for your Tailwind CSS classes. This library also allows us to provide className overrides for every component without needed to install a separate library.
20 |
21 | ## Base Theme
22 |
23 | Upon setup, global CSS variables are set for the theme. These variables are used to style the components.
24 |
25 | The CSS var set is intentionally kept minimal. You may need to break out of the default and add more, and that's fine! Feel free to customize the theme to your liking.
26 |
27 | When you install components via the shadcn CLI, the necessary Tailwind configuration will be automatically added to your project.
28 |
29 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@baselayer/web",
3 | "version": "2.1.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "type-check": "tsc --noEmit",
10 | "clean": "rm -rf .next dist",
11 | "postinstall": "fumadocs-mdx"
12 | },
13 | "dependencies": {
14 | "@baselayer/components": "workspace:*",
15 | "@baselayer/registry": "workspace:*",
16 | "@types/mdx": "^2.0.13",
17 | "@vercel/analytics": "^1.5.0",
18 | "fumadocs-core": "^15.5.5",
19 | "fumadocs-mdx": "^11.6.9",
20 | "geist": "^1.5.1",
21 | "lucide-react": "^0.525.0",
22 | "next": "15.5.9",
23 | "next-themes": "^0.4.6",
24 | "react": "^19.2.3",
25 | "react-aria-components": "catalog:",
26 | "react-dom": "^19.2.3",
27 | "react-element-to-jsx-string": "^17.0.1",
28 | "shiki": "3.7.0",
29 | "tailwind-variants": "catalog:",
30 | "zod": "^3.25.67"
31 | },
32 | "devDependencies": {
33 | "@tailwindcss/postcss": "^4.1.10",
34 | "@tailwindcss/typography": "^0.5.16",
35 | "@types/node": "catalog:",
36 | "@types/react": "^19.2.7",
37 | "@types/react-dom": "^19.2.3",
38 | "tailwind-merge": "^3.3.1",
39 | "tailwindcss": "^4.1.10",
40 | "tw-animate-css": "^1.3.6",
41 | "typescript": "catalog:"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/components/src/core/switch/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | Switch as AriaSwitch,
7 | type SwitchProps as AriaSwitchProps,
8 | Label,
9 | } from "react-aria-components";
10 | import { tv } from "tailwind-variants";
11 |
12 | const switchStyles = tv({
13 | slots: {
14 | root: "group flex items-center gap-2 transition-none duration-200",
15 | indicator:
16 | "h-6 w-10 cursor-pointer rounded-xl bg-secondary duration-200 before:mx-[3px] before:mt-[3px] before:block before:size-4.5 before:rounded-2xl before:bg-surface before:transition-all data-[selected]:bg-primary group-data-[selected]:bg-primary group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-focus group-data-[focus-visible]:ring-offset-2 group-data-[focus-visible]:ring-offset-surface group-data-[selected]:before:translate-x-4",
17 | label: "font-semibold text-fg text-sm",
18 | },
19 | });
20 |
21 | const styles = switchStyles();
22 |
23 | interface SwitchProps extends AriaSwitchProps {
24 | children?: ReactNode;
25 | className?: string;
26 | }
27 |
28 | const Switch = ({ className, children, ...restProps }: SwitchProps) => (
29 |
30 |
31 | {children}
32 |
33 | );
34 |
35 | export { Switch };
36 | export type { SwitchProps };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules/
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 | /dist
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 | pnpm-debug.log*
28 |
29 | # local env files
30 | .env.local
31 | .env
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
40 | # turbo
41 | .turbo
42 |
43 | # pnpm
44 | .pnpm-store/
45 | .pnpm-debug.log*
46 |
47 | # build outputs
48 | **/dist/
49 | **/build/
50 | **/.next/
51 | **/.turbo/
52 |
53 | # IDE and editor files
54 | .vscode/
55 | .idea/
56 | *.swp
57 | *.swo
58 | *~
59 | .project
60 | .classpath
61 | .settings/
62 |
63 | # OS generated files
64 | .DS_Store
65 | .DS_Store?
66 | ._*
67 | .Spotlight-V100
68 | .Trashes
69 | ehthumbs.db
70 | Thumbs.db
71 |
72 | # Logs
73 | logs
74 | *.log
75 | npm-debug.log*
76 | yarn-debug.log*
77 | yarn-error.log*
78 | pnpm-debug.log*
79 | lerna-debug.log*
80 |
81 | # Runtime data
82 | pids
83 | *.pid
84 | *.seed
85 | *.pid.lock
86 |
87 | # Optional npm cache directory
88 | .npm
89 |
90 | # Optional eslint cache
91 | .eslintcache
92 |
93 | # Optional stylelint cache
94 | .stylelintcache
95 |
96 | *.vsix
97 |
98 | # Go workspace file
99 | go.work
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "formatter": {
4 | "enabled": true
5 | },
6 | "linter": {
7 | "enabled": true,
8 | "domains": {
9 | "next": "recommended"
10 | },
11 | "rules": {
12 | "recommended": true,
13 | "nursery": {
14 | "useSortedClasses": {
15 | "level": "warn",
16 | "fix": "safe",
17 | "options": {
18 | "attributes": ["className"],
19 | "functions": ["cx", "tv"]
20 | }
21 | }
22 | }
23 | }
24 | },
25 | "assist": {
26 | "actions": {
27 | "source": {
28 | "organizeImports": {
29 | "level": "on",
30 | "options": {
31 | "groups": [
32 | ["react", "react/**"],
33 | ":BLANK_LINE:",
34 | "next/**",
35 | ":BLANK_LINE:",
36 | "react-aria-components",
37 | "tailwind-variants",
38 | ":BLANK_LINE:",
39 | ":PACKAGE:",
40 | ":BLANK_LINE:",
41 | "@/hooks/**",
42 | ":BLANK_LINE:",
43 | ["@/lib/**", "@/utils/**"],
44 | ":BLANK_LINE:",
45 | "@/components/**",
46 | ":BLANK_LINE:",
47 | "~/**",
48 | ":BLANK_LINE:",
49 | ":PATH:"
50 | ]
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/packages/components/src/core/modal/modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Dialog as AriaDialog,
5 | DialogTrigger as AriaDialogTrigger,
6 | Modal as AriaModal,
7 | type DialogProps,
8 | ModalOverlay,
9 | type ModalOverlayProps,
10 | } from "react-aria-components";
11 | import { tv } from "tailwind-variants";
12 |
13 | export const modal = tv({
14 | slots: {
15 | dialog: "flex w-full flex-col gap-6 outline-none",
16 | modalStyles:
17 | "fade-in w-full rounded-2xl bg-surface p-6 text-fg outline-none data-[entering]:animate-in md:w-md",
18 | },
19 | });
20 |
21 | const styles = modal();
22 |
23 | interface ModalProps extends Omit {
24 | className?: string;
25 | }
26 |
27 | const Modal = ({ children, className, ...props }: ModalProps) => (
28 |
32 |
33 | {children}
34 |
35 |
36 | );
37 |
38 | const Dialog = ({ children, className, ...props }: DialogProps) => (
39 |
40 | {children}
41 |
42 | );
43 |
44 | const ModalTrigger = AriaDialogTrigger;
45 |
46 | export { Modal, Dialog, ModalTrigger };
47 | export type { ModalProps, DialogProps };
--------------------------------------------------------------------------------
/web/hooks/use-active-heading.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useRef, useState } from "react";
4 |
5 | export function useActiveHeading() {
6 | const containerRef = useRef(null);
7 | const [activeId, setActiveId] = useState("");
8 |
9 | useEffect(() => {
10 | const observer = new IntersectionObserver(
11 | (entries) => {
12 | const intersectingEntries = entries.filter(
13 | (entry) => entry.isIntersecting,
14 | );
15 |
16 | if (intersectingEntries.length > 0) {
17 | const sorted = intersectingEntries.sort((a, b) => {
18 | const aRect = a.boundingClientRect;
19 | const bRect = b.boundingClientRect;
20 | const viewportCenter = window.innerHeight / 2;
21 |
22 | const aDistance = Math.abs(aRect.top - viewportCenter);
23 | const bDistance = Math.abs(bRect.top - viewportCenter);
24 |
25 | return aDistance - bDistance;
26 | });
27 |
28 | setActiveId(sorted[0].target.id);
29 | }
30 | },
31 | {
32 | rootMargin: "-10% 0% -60% 0%",
33 | threshold: 0,
34 | },
35 | );
36 |
37 | const headings = containerRef.current?.querySelectorAll(
38 | "h1, h2, h3, h4, h5, h6",
39 | );
40 |
41 | if (headings) {
42 | headings.forEach((heading) => {
43 | if (heading.id) {
44 | observer.observe(heading);
45 | }
46 | });
47 | }
48 |
49 | return () => {
50 | observer.disconnect();
51 | };
52 | }, []);
53 |
54 | return { containerRef, activeId };
55 | }
56 |
--------------------------------------------------------------------------------
/web/lib/examples-map.ts:
--------------------------------------------------------------------------------
1 | // Filesystem-based examples map using dynamic discovery
2 |
3 | import type { ComponentType } from "react";
4 |
5 | import { readFileSync } from "node:fs";
6 | import { join } from "node:path";
7 |
8 | const examplesDir = join(process.cwd(), "../packages/components/src/examples");
9 |
10 | export async function getExampleComponent(exampleId: string) {
11 | try {
12 | // Read the example file to extract the export name
13 | const exampleFilePath = join(examplesDir, `${exampleId}.tsx`);
14 | const fileContent = readFileSync(exampleFilePath, "utf8");
15 |
16 | // Extract the export name using regex
17 | // Matches: export const ComponentName = ...
18 | const exportMatch = fileContent.match(/export\s+const\s+(\w+)\s*=/);
19 |
20 | if (!exportMatch) {
21 | console.error(`Could not find export in ${exampleId}.tsx`);
22 | return null;
23 | }
24 |
25 | const componentName = exportMatch[1];
26 |
27 | // Dynamically import the component from the built package
28 | const module = await import("@baselayer/components");
29 | const Component = (module as unknown as Record)[
30 | componentName
31 | ];
32 |
33 | if (!Component) {
34 | console.error(
35 | `Component ${componentName} not found in @baselayer/components`,
36 | );
37 | return null;
38 | }
39 |
40 | return Component as React.ComponentType;
41 | } catch (error) {
42 | console.error(`Failed to load example component ${exampleId}:`, error);
43 | return null;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/components/src/examples/command-base.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Calendar,
5 | FileBarChart,
6 | Flag,
7 | FolderPlus,
8 | MessageSquare,
9 | Plus,
10 | RotateCcw,
11 | Target,
12 | UserPlus,
13 | Users,
14 | } from "lucide-react";
15 |
16 | import { Command, type CommandItem } from "../core/command/command";
17 |
18 | export const CommandBase = () => {
19 | const commands = [
20 | { id: "create-task", label: "Create new task", icon: Plus },
21 | { id: "create-project", label: "Create new project", icon: FolderPlus },
22 | { id: "assign-task", label: "Assign task to team member", icon: UserPlus },
23 | { id: "set-priority", label: "Set task priority", icon: Flag },
24 | { id: "change-status", label: "Change task status", icon: RotateCcw },
25 | { id: "add-comment", label: "Add comment to task", icon: MessageSquare },
26 | { id: "set-deadline", label: "Set task deadline", icon: Calendar },
27 | { id: "create-milestone", label: "Create project milestone", icon: Target },
28 | {
29 | id: "generate-report",
30 | label: "Generate project report",
31 | icon: FileBarChart,
32 | },
33 | { id: "invite-member", label: "Invite team member", icon: Users },
34 | ] satisfies CommandItem[];
35 |
36 | return (
37 |
38 | {
42 | console.log("Executed:", command.label);
43 | }}
44 | />
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/packages/components/src/core/toggle/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | ToggleButton,
7 | ToggleButtonGroup,
8 | type ToggleButtonGroupProps,
9 | type ToggleButtonProps,
10 | } from "react-aria-components";
11 | import { tv } from "tailwind-variants";
12 |
13 | const toggle = tv({
14 | base: "inline-flex appearance-none items-center justify-center rounded-full bg-surface-2 px-4 py-1 font-medium outline-none ring-focus ring-offset-2 ring-offset-surface transition-transform duration-100 disabled:pointer-events-none disabled:opacity-50 data-[selected]:bg-primary data-[selected]:text-primary-fg data-[focus-visible]:ring-2",
15 | });
16 |
17 | const toggleGroup = tv({
18 | base: "flex min-h-11 gap-1 rounded-full border border-border bg-surface-2 p-1",
19 | });
20 |
21 | interface ToggleProps extends Omit {
22 | className?: string;
23 | }
24 |
25 | const Toggle = ({ className, children, ...props }: ToggleProps) => (
26 |
27 | {children}
28 |
29 | );
30 |
31 | interface ToggleGroupProps extends ToggleButtonGroupProps {
32 | className?: string;
33 | children: ReactNode;
34 | }
35 |
36 | const ToggleGroup = ({ className, children, ...props }: ToggleGroupProps) => (
37 |
38 | {children}
39 |
40 | );
41 |
42 | export { Toggle, ToggleGroup };
43 | export type { ToggleProps, ToggleGroupProps };
44 |
--------------------------------------------------------------------------------
/packages/components/src/core/breadcrumbs/breadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Breadcrumbs as AriaBreadcrumbs,
5 | type BreadcrumbsProps as AriaBreadcrumbsProps,
6 | Breadcrumb,
7 | type BreadcrumbProps,
8 | Link,
9 | type LinkProps,
10 | } from "react-aria-components";
11 | import { tv } from "tailwind-variants";
12 |
13 | const breadcrumbs = tv({
14 | slots: {
15 | root: "m-0 flex list-none items-center gap-2 p-0 font-md",
16 | link: "relative cursor-pointer rounded-md text-fg outline-none ring-focus data-[hovered]:underline data-[focus-visible]:ring-2 [&[aria-current]]:font-extrabold [&[aria-current]]:text-fg",
17 | item: "flex items-center gap-2",
18 | },
19 | });
20 |
21 | const styles = breadcrumbs();
22 |
23 | const Breadcrumbs = ({
24 | children,
25 | className,
26 | ...props
27 | }: AriaBreadcrumbsProps & { className?: string }) => (
28 |
29 | {children}
30 |
31 | );
32 |
33 | const BreadcrumbsLink = ({
34 | children,
35 | className,
36 | ...props
37 | }: LinkProps & { className?: string }) => (
38 |
39 | {children}
40 |
41 | );
42 |
43 | const BreadcrumbsItem = ({
44 | children,
45 | className,
46 | ...props
47 | }: BreadcrumbProps & { className?: string }) => (
48 |
49 | {children}
50 |
51 | );
52 |
53 | export { BreadcrumbsItem, BreadcrumbsLink, Breadcrumbs };
--------------------------------------------------------------------------------
/web/lib/component-data.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from "node:fs";
2 | import { join } from "node:path";
3 |
4 | const examplesDir = join(process.cwd(), "../packages/components/src/examples");
5 |
6 | export async function getComponentSource(name: string): Promise {
7 | try {
8 | const componentPath = join(process.cwd(), `public/r/${name}.json`);
9 | const componentContent = readFileSync(componentPath, "utf8");
10 | const component = JSON.parse(componentContent);
11 |
12 | if (component?.files?.[0]?.content) {
13 | return component.files[0].content;
14 | }
15 |
16 | return `// Component '${name}' not found in registry`;
17 | } catch (error) {
18 | console.error(`Error loading component ${name}:`, error);
19 | return `// Error loading component: ${
20 | error instanceof Error ? error.message : "Unknown error"
21 | }`;
22 | }
23 | }
24 |
25 | export async function getExampleSource(name: string): Promise {
26 | try {
27 | // Read the example file directly from the filesystem
28 | const exampleFilePath = join(examplesDir, `${name}.tsx`);
29 | let content = readFileSync(exampleFilePath, "utf8");
30 |
31 | // Transform the import path for display
32 | content = content.replace(
33 | /from ["']\.\.\/core\/([^/]+)\/\1["']/g,
34 | 'from "@/components/ui/$1"',
35 | );
36 |
37 | return content;
38 | } catch (error) {
39 | console.error(`Error loading example ${name}:`, error);
40 | return `// Error loading example: ${
41 | error instanceof Error ? error.message : "Unknown error"
42 | }`;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/web/app/r/[[...segments]]/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | import { existsSync, readFileSync } from "node:fs";
4 | import { join } from "node:path";
5 |
6 | export async function GET(
7 | _request: Request,
8 | { params }: { params: Promise<{ segments?: string[] }> },
9 | ) {
10 | try {
11 | const { segments } = await params;
12 |
13 | // If no segments, return the registry index
14 | if (!segments || segments.length === 0) {
15 | const registryPath = join(process.cwd(), "public/r/registry.json");
16 | const registryContent = readFileSync(registryPath, "utf8");
17 | return new NextResponse(registryContent, {
18 | headers: {
19 | "Content-Type": "application/json",
20 | "Cache-Control": "public, max-age=3600",
21 | },
22 | });
23 | }
24 |
25 | // Handle specific registry item requests
26 | // Support both /r/button and /r/button.json
27 | const itemName = segments[0].replace(/\.json$/, "");
28 | const itemPath = join(process.cwd(), `public/r/${itemName}.json`);
29 |
30 | if (!existsSync(itemPath)) {
31 | return new NextResponse("Registry item not found", { status: 404 });
32 | }
33 |
34 | const itemContent = readFileSync(itemPath, "utf8");
35 | return new NextResponse(itemContent, {
36 | headers: {
37 | "Content-Type": "application/json",
38 | "Cache-Control": "public, max-age=3600",
39 | },
40 | });
41 | } catch (error) {
42 | console.error("Error in registry API route:", error);
43 | return new NextResponse("Internal Server Error", { status: 500 });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/web/components/search.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useMemo, useState } from "react";
4 |
5 | import { useRouter } from "next/navigation";
6 |
7 | import { Command, type CommandItem } from "@baselayer/components";
8 | import { useDocsSearch } from "fumadocs-core/search/client";
9 | import { BookOpenIcon, FileTextIcon } from "lucide-react";
10 |
11 | export function SearchDialog() {
12 | const router = useRouter();
13 | const [searchTerm, setSearchTerm] = useState("");
14 | const { setSearch, query } = useDocsSearch({
15 | type: "fetch",
16 | });
17 |
18 | // Update search when searchTerm changes
19 | useEffect(() => {
20 | setSearch(searchTerm);
21 | }, [searchTerm, setSearch]);
22 |
23 | const results = Array.isArray(query.data) ? query.data : [];
24 |
25 | // Convert search results to CommandItem format
26 | const commands: CommandItem[] = useMemo(() => {
27 | // Show default popular pages when no search term
28 | if (searchTerm.length === 0) {
29 | return [
30 | {
31 | id: "intro",
32 | label: "Introduction",
33 | icon: BookOpenIcon,
34 | onSelect: () => router.push("/docs/intro"),
35 | },
36 | ];
37 | }
38 |
39 | return results.slice(0, 10).map((result, index) => ({
40 | id: result.id || `result-${index}`,
41 | label: result.content || "Untitled",
42 | icon: FileTextIcon,
43 | onSelect: () => {
44 | router.push(result.url);
45 | },
46 | }));
47 | }, [results, router, searchTerm]);
48 |
49 | return (
50 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/packages/components/src/core/input/input.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type {
4 | TextFieldProps as AriaTextFieldProps,
5 | ValidationResult,
6 | } from "react-aria-components";
7 | import {
8 | Input as AriaInput,
9 | TextField as AriaTextField,
10 | FieldError,
11 | Label,
12 | Text,
13 | } from "react-aria-components";
14 | import { tv } from "tailwind-variants";
15 |
16 | const input = tv({
17 | base: "min-h-11 appearance-none rounded-lg border border-border bg-surface px-3 py-1 text-fg outline-none ring-primary transition-all data-[disabled]:cursor-not-allowed data-[focused]:border-transparent data-[disabled]:bg-primary/10 data-[disabled]:text-fg-disabled data-[focused]:ring-2 [&::placeholder]:text-fg-muted [&::placeholder]:text-sm",
18 | });
19 |
20 | interface InputProps extends Omit {
21 | className?: string;
22 | label?: string;
23 | description?: string;
24 | errorMessage?: string | ((validation: ValidationResult) => string);
25 | placeholder?: string;
26 | }
27 |
28 | const Input = ({
29 | label,
30 | description,
31 | errorMessage,
32 | placeholder,
33 | className,
34 | ...props
35 | }: InputProps) => (
36 |
37 | {label && {label} }
38 |
39 | {description && (
40 |
41 | {description}
42 |
43 | )}
44 | {errorMessage}
45 |
46 | );
47 |
48 | export { Input };
49 | export type { InputProps };
50 |
--------------------------------------------------------------------------------
/packages/components/src/examples/command-custom-trigger.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Calendar,
5 | FileBarChart,
6 | Flag,
7 | FolderPlus,
8 | MessageSquare,
9 | Plus,
10 | RotateCcw,
11 | Search,
12 | Target,
13 | UserPlus,
14 | Users,
15 | } from "lucide-react";
16 |
17 | import { Button } from "../core/button/button";
18 | import { Command, type CommandItem } from "../core/command/command";
19 |
20 | export const CommandCustomTrigger = () => {
21 | const commands = [
22 | { id: "create-task", label: "Create new task", icon: Plus },
23 | { id: "create-project", label: "Create new project", icon: FolderPlus },
24 | { id: "assign-task", label: "Assign task to team member", icon: UserPlus },
25 | { id: "set-priority", label: "Set task priority", icon: Flag },
26 | { id: "change-status", label: "Change task status", icon: RotateCcw },
27 | { id: "add-comment", label: "Add comment to task", icon: MessageSquare },
28 | { id: "set-deadline", label: "Set task deadline", icon: Calendar },
29 | { id: "create-milestone", label: "Create project milestone", icon: Target },
30 | {
31 | id: "generate-report",
32 | label: "Generate project report",
33 | icon: FileBarChart,
34 | },
35 | { id: "invite-member", label: "Invite team member", icon: Users },
36 | ] satisfies CommandItem[];
37 |
38 | return (
39 |
40 |
45 | Search commands
46 |
47 | }
48 | onCommandSelect={(command) => {
49 | console.log("Executed:", command.label);
50 | }}
51 | />
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/packages/components/src/examples/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./accordion-base";
2 | export * from "./accordion-group";
3 | export * from "./badge-base";
4 | export * from "./badge-variants";
5 | export * from "./breadcrumbs-base";
6 | export * from "./breadcrumbs-seperators";
7 | export * from "./button-base";
8 | export * from "./button-helper-icons";
9 | export * from "./button-sizes";
10 | export * from "./button-variants";
11 | export * from "./calendar-base";
12 | export * from "./card-base";
13 | export * from "./card-variants";
14 | export * from "./checkbox-base";
15 | export * from "./checkbox-group";
16 | export * from "./combobox-base";
17 | export * from "./combobox-disabled-option";
18 | export * from "./combobox-menu-trigger";
19 | export * from "./command-base";
20 | export * from "./command-custom-trigger";
21 | export * from "./date-field-base";
22 | export * from "./date-picker-base";
23 | export * from "./date-range-picker-base";
24 | export * from "./input-base";
25 | export * from "./input-disabled";
26 | export * from "./input-label";
27 | export * from "./menu-base";
28 | export * from "./meter-base";
29 | export * from "./modal-base";
30 | export * from "./modal-dismissable";
31 | export * from "./popover-base";
32 | export * from "./radio-group-base";
33 | export * from "./range-calendar-base";
34 | export * from "./select-base";
35 | export * from "./select-popover-classname";
36 | export * from "./select-searchable";
37 | export * from "./slider-base";
38 | export * from "./switch-base";
39 | export * from "./table-base";
40 | export * from "./tabs-base";
41 | export * from "./tag-group-base";
42 | export * from "./time-field-base";
43 | export * from "./toggle-base";
44 | export * from "./toggle-group-base";
45 | export * from "./tooltip-base";
46 |
--------------------------------------------------------------------------------
/packages/components/src/core/slider/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Slider as AriaSlider,
5 | SliderThumb as AriaSliderThumb,
6 | SliderTrack as AriaSliderTrack,
7 | Label,
8 | type LabelProps,
9 | SliderOutput,
10 | type SliderProps,
11 | type SliderThumbProps,
12 | } from "react-aria-components";
13 | import { tv } from "tailwind-variants";
14 |
15 | const slider = tv({
16 | slots: {
17 | root: "grid w-64 auto-cols-fr grid-cols-1 text-fg-muted",
18 | thumb:
19 | "h-5 w-5 rounded-full bg-primary ring-focus ring-offset-2 ring-offset-surface data-[dragging]:outline-2 data-[focus-visible]:ring-2",
20 | track:
21 | "before:-translate-y-1/2 relative col-span-2 col-start-1 w-full before:absolute before:top-1/2 before:h-0.5 before:w-full before:transform before:bg-secondary",
22 | },
23 | });
24 |
25 | const { root, thumb, track } = slider();
26 |
27 | const SliderRoot = ({
28 | children,
29 | className,
30 | ...props
31 | }: SliderProps & { className?: string }) => (
32 |
33 | {children}
34 |
35 | );
36 |
37 | const SliderThumb = ({
38 | children,
39 | className,
40 | ...props
41 | }: SliderThumbProps & { className?: string }) => (
42 |
43 |
44 |
45 | );
46 |
47 | const SliderLabel = ({
48 | children,
49 | className,
50 | ...props
51 | }: LabelProps & { className?: string }) => (
52 |
53 |
54 | {children}
55 |
56 |
57 |
58 | );
59 |
60 | export { SliderRoot, SliderThumb, SliderLabel };
61 |
--------------------------------------------------------------------------------
/packages/components/src/core/button/button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Button as AriaButton,
5 | type ButtonProps as AriaButtonProps,
6 | } from "react-aria-components";
7 | import { tv, type VariantProps } from "tailwind-variants";
8 |
9 | const button = tv({
10 | base: "inline-flex appearance-none items-center justify-center rounded-full font-semibold outline-none ring-focus ring-offset-3 ring-offset-surface transition-transform duration-100 disabled:pointer-events-none disabled:opacity-50 data-[focus-visible]:ring-2",
11 | variants: {
12 | variant: {
13 | primary:
14 | "bg-primary text-primary-fg data-[hovered]:bg-primary/80",
15 | secondary:
16 | "border border-border text-fg data-[hovered]:border-secondary data-[hovered]:bg-secondary",
17 | ghost:
18 | "bg-transparent text-fg data-[hovered]:bg-secondary data-[hovered]:text-secondary-fg",
19 | danger:
20 | "border border-transparent bg-danger text-danger-fg data-[hovered]:bg-danger/80",
21 | },
22 | size: {
23 | sm: "px-2 py-1.5 text-sm",
24 | md: "px-4 py-2.5 text-base",
25 | lg: "px-6 py-3.5 font-bold text-lg",
26 | icon: "size-9",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "primary",
31 | size: "md",
32 | },
33 | });
34 |
35 | type ButtonVariantProps = VariantProps;
36 |
37 | interface ButtonProps
38 | extends Omit,
39 | ButtonVariantProps {
40 | className?: string;
41 | }
42 |
43 | const Button = ({
44 | className,
45 | size,
46 | variant,
47 | children,
48 | ...props
49 | }: ButtonProps) => (
50 |
51 | {children}
52 |
53 | );
54 |
55 | Button.displayName = "Button";
56 |
57 | export { Button };
58 | export type { ButtonProps };
59 |
--------------------------------------------------------------------------------
/web/app/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 | @import "tw-animate-css";
3 | @import "../../packages/components/src/tailwind.css";
4 | @source '../../packages/components/src/**/*.{ts,tsx}';
5 | @plugin "@tailwindcss/typography";
6 |
7 | @theme {
8 | --font-sans: var(--font-geist-sans);
9 | --font-mono: var(--font-geist-mono);
10 | }
11 |
12 | @media (prefers-color-scheme: dark) {
13 | :root {
14 | --background: var(--surface);
15 | --foreground: var(--fg);
16 | }
17 | }
18 |
19 | html,
20 | body {
21 | overscroll-behavior: none;
22 | overscroll-behavior-y: none;
23 | overscroll-behavior-x: none;
24 | }
25 |
26 | html {
27 | scroll-behavior: auto;
28 | /* Prevent elastic scrolling on Safari */
29 | -webkit-overflow-scrolling: auto;
30 | }
31 |
32 | body {
33 | background: var(--surface);
34 | color: var(--fg);
35 | font-family: Arial, Helvetica, sans-serif;
36 | /* Additional Safari fixes */
37 | -webkit-overflow-scrolling: auto;
38 | }
39 |
40 | /* CSS variables for theme support */
41 | :root {
42 | --scrollbar-thumb: #888;
43 | --scrollbar-thumb-hover: #555;
44 | }
45 |
46 | [data-theme="dark"] {
47 | --scrollbar-thumb: #666;
48 | --scrollbar-thumb-hover: #999;
49 | }
50 |
51 | /* Auto-hide scrollbar until hover */
52 | * {
53 | scrollbar-width: thin;
54 | scrollbar-color: transparent transparent;
55 | }
56 |
57 | *:hover {
58 | scrollbar-color: var(--scrollbar-thumb) transparent;
59 | }
60 |
61 | /* Webkit browsers */
62 | *::-webkit-scrollbar {
63 | width: 8px;
64 | height: 8px;
65 | }
66 |
67 | *::-webkit-scrollbar-track {
68 | background: transparent;
69 | }
70 |
71 | *::-webkit-scrollbar-thumb {
72 | background-color: transparent;
73 | border-radius: 4px;
74 | }
75 |
76 | *:hover::-webkit-scrollbar-thumb {
77 | background-color: var(--scrollbar-thumb);
78 | }
79 |
80 | *::-webkit-scrollbar-thumb:hover {
81 | background-color: var(--scrollbar-thumb-hover);
82 | }
--------------------------------------------------------------------------------
/web/lib/navigation.ts:
--------------------------------------------------------------------------------
1 | import { source } from "./source";
2 |
3 | export interface NavigationPage {
4 | title: string;
5 | url: string;
6 | }
7 |
8 | export interface PageNavigation {
9 | previous?: NavigationPage;
10 | next?: NavigationPage;
11 | }
12 |
13 | export function getPageNavigation(currentUrl: string): PageNavigation {
14 | const pages = source.getPages();
15 |
16 | // Create ordered list following sidebar structure
17 | const introDocs = pages
18 | .filter((page) => !page.url.startsWith("/docs/components"))
19 | .sort((a, b) => {
20 | // Define specific order for intro docs
21 | const order = ["/docs/intro", "/docs/usage", "/docs/styles"];
22 | const aIndex = order.indexOf(a.url);
23 | const bIndex = order.indexOf(b.url);
24 |
25 | if (aIndex !== -1 && bIndex !== -1) {
26 | return aIndex - bIndex;
27 | }
28 | if (aIndex !== -1) return -1;
29 | if (bIndex !== -1) return 1;
30 | return a.url.localeCompare(b.url);
31 | });
32 |
33 | const componentDocs = pages
34 | .filter((page) => page.url.startsWith("/docs/components"))
35 | .sort((a, b) => a.data.title.localeCompare(b.data.title));
36 |
37 | // Combine in order: intro docs first, then components
38 | const orderedPages = [...introDocs, ...componentDocs];
39 |
40 | // Find current page index
41 | const currentIndex = orderedPages.findIndex(
42 | (page) => page.url === currentUrl,
43 | );
44 |
45 | if (currentIndex === -1) {
46 | return {};
47 | }
48 |
49 | const previous =
50 | currentIndex > 0
51 | ? {
52 | title: orderedPages[currentIndex - 1].data.title,
53 | url: orderedPages[currentIndex - 1].url,
54 | }
55 | : undefined;
56 |
57 | const next =
58 | currentIndex < orderedPages.length - 1
59 | ? {
60 | title: orderedPages[currentIndex + 1].data.title,
61 | url: orderedPages[currentIndex + 1].url,
62 | }
63 | : undefined;
64 |
65 | return { previous, next };
66 | }
67 |
--------------------------------------------------------------------------------
/packages/components/src/core/radio-group/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import type {
6 | RadioGroupProps as AriaRadioGroupProps,
7 | RadioProps,
8 | ValidationResult,
9 | } from "react-aria-components";
10 | import {
11 | Radio as AriaRadio,
12 | RadioGroup as AriaRadioGroup,
13 | FieldError,
14 | Text,
15 | } from "react-aria-components";
16 | import { tv } from "tailwind-variants";
17 |
18 | const radioGroup = tv({
19 | slots: {
20 | radio:
21 | "flex items-center gap-4 text-fg before:block before:h-5 before:w-5 before:rounded-full before:border before:border-border before:ring-focus before:ring-offset-2 before:ring-offset-surface before:transition-all data-[selected]:before:border-4 data-[selected]:before:border-primary data-[focus-visible]:before:ring-2",
22 | group: "flex flex-col gap-4 text-fg",
23 | },
24 | });
25 |
26 | const { group, radio } = radioGroup();
27 |
28 | interface RadioGroupProps extends Omit {
29 | className?: string;
30 | children?: ReactNode;
31 | label?: string;
32 | description?: string;
33 | errorMessage?: string | ((validation: ValidationResult) => string)
34 | }
35 |
36 | const RadioGroup = ({
37 | className,
38 | label,
39 | description,
40 | errorMessage,
41 | children,
42 | ...props
43 | }: RadioGroupProps) => (
44 |
45 | {label}
46 | {children}
47 | {description && {description} }
48 | {errorMessage}
49 |
50 | );
51 |
52 | const Radio = ({
53 | children,
54 | className,
55 | ...props
56 | }: RadioProps & {
57 | className?: string;
58 | }) => (
59 |
60 | {children}
61 |
62 | );
63 |
64 | export { RadioGroup, Radio };
--------------------------------------------------------------------------------
/packages/components/src/core/date-field/date-field.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type {
4 | DateFieldProps as AriaDateFieldProps,
5 | DateValue,
6 | } from "react-aria-components";
7 | import {
8 | DateField as AriaDateField,
9 | DateInput,
10 | DateSegment,
11 | FieldError,
12 | Label,
13 | Text,
14 | } from "react-aria-components";
15 | import { tv } from "tailwind-variants";
16 |
17 | const dateField = tv({
18 | slots: {
19 | input:
20 | "flex min-h-11 min-w-48 appearance-none items-center rounded-lg border border-border bg-surface px-3 py-0.5 outline-none ring-primary transition-all data-[disabled]:cursor-not-allowed data-[focus-within]:border-transparent data-[disabled]:border-none data-[disabled]:bg-primary/10 data-[focus-within]:bg-surface data-[disabled]:text-fg-disabled data-[focus-within]:ring-2 [&::placeholder]:text-sm",
21 | segmentStyles:
22 | "rounded-md p-1 text-end outline-none focus:text-primary-fg data-[focused]:bg-primary data-[placeholder]:text-fg-muted data-[type='literal']:text-fg-muted data-[placeholder]:focus:text-primary-fg",
23 | },
24 | });
25 |
26 | const styles = dateField();
27 |
28 | interface DateFieldProps extends AriaDateFieldProps {
29 | label?: string;
30 | description?: string;
31 | errorMessage?: string;
32 | }
33 |
34 | const DateField = ({
35 | label,
36 | description,
37 | errorMessage,
38 | ...props
39 | }: DateFieldProps) => (
40 |
41 | {label && {label} }
42 |
43 | {(segment) => (
44 |
45 | )}
46 |
47 | {description && {description} }
48 | {errorMessage}
49 |
50 | );
51 |
52 | export { DateField };
53 | export type { DateFieldProps };
54 |
--------------------------------------------------------------------------------
/packages/components/src/core/time-field/time-field.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type {
4 | TimeFieldProps as AriaTimeFieldProps,
5 | TimeValue,
6 | } from "react-aria-components";
7 | import {
8 | TimeField as AriaTimeField,
9 | DateInput,
10 | DateSegment,
11 | FieldError,
12 | Label,
13 | Text,
14 | } from "react-aria-components";
15 | import { tv } from "tailwind-variants";
16 |
17 | const timeField = tv({
18 | slots: {
19 | input:
20 | "appearance-none rounded-lg border border-border bg-surface px-3 py-1.75 outline-none ring-primary transition-all data-[disabled]:cursor-not-allowed data-[focus-within]:border-transparent data-[disabled]:bg-primary/10 data-[focus-within]:bg-surface data-[disabled]:text-fg-disabled data-[focus-within]:ring-2 [&::placeholder]:text-sm [&::placeholder]:focus:text-primary-fg",
21 | segmentStyles:
22 | "rounded-md p-1 text-end outline-none focus:bg-primary focus:text-primary-fg data-[placeholder]:text-fg-muted data-[type='literal']:text-fg-muted",
23 | },
24 | });
25 |
26 | const styles = timeField();
27 |
28 | interface TimeFieldProps extends AriaTimeFieldProps {
29 | label?: string;
30 | description?: string;
31 | errorMessage?: string;
32 | }
33 |
34 | const TimeField = ({
35 | label,
36 | description,
37 | errorMessage,
38 | ...props
39 | }: TimeFieldProps) => (
40 |
41 | {label && {label} }
42 |
43 | {(segment) => (
44 |
45 | )}
46 |
47 | {description && (
48 |
49 | {description}
50 |
51 | )}
52 | {errorMessage}
53 |
54 | );
55 |
56 | export { TimeField };
57 | export type { TimeFieldProps };
58 |
--------------------------------------------------------------------------------
/web/components/open-in-ai-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Button,
5 | MenuContent,
6 | MenuItem,
7 | MenuTrigger,
8 | } from "@baselayer/components";
9 | import { ArrowUpRight, Share } from "lucide-react";
10 |
11 | interface OpenInAiMenuProps {
12 | pageTitle: string;
13 | pageUrl: string;
14 | }
15 |
16 | const AI_SERVICES = [
17 | {
18 | name: "ChatGPT",
19 | baseUrl: "https://chatgpt.com/",
20 | urlPattern: "?hints=search&q=",
21 | messageTemplate: (url: string) =>
22 | `Read this BaseLayer documentation: ${url}`,
23 | },
24 | {
25 | name: "Claude",
26 | baseUrl: "https://claude.ai/new",
27 | urlPattern: "?q=",
28 | messageTemplate: (url: string) =>
29 | `Read this BaseLayer documentation: ${url}`,
30 | },
31 | {
32 | name: "T3 Chat",
33 | baseUrl: "https://t3.chat/new",
34 | urlPattern: "?q=",
35 | messageTemplate: (url: string) =>
36 | `Read this BaseLayer documentation: ${url}`,
37 | },
38 | ] as const;
39 |
40 | export function OpenInAiMenu({ pageTitle, pageUrl }: OpenInAiMenuProps) {
41 | const baseUrl = "https://baselayer.dev";
42 | const fullUrl = `${baseUrl}${pageUrl}`;
43 |
44 | const openInAI = (service: (typeof AI_SERVICES)[number]) => {
45 | const message = service.messageTemplate(fullUrl);
46 | const finalUrl = `${service.baseUrl}${service.urlPattern}${encodeURIComponent(message)}`;
47 |
48 | window.open(finalUrl, "_blank");
49 | };
50 |
51 | return (
52 |
53 |
57 | Open in AI
58 |
59 |
60 | {AI_SERVICES.map((service) => (
61 | openInAI(service)}
64 | className="flex items-center gap-2"
65 | >
66 | {service.name}
67 |
68 |
69 |
70 |
71 | ))}
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/web/app/docs/content/intro.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Intro
3 | description: BaseLayer is a collection of beautiful, accessible React components built with React Aria Components and TailwindCSS. Powered by the shadcn registry.
4 | ---
5 |
6 | ## What is BaseLayer?
7 |
8 | BaseLayer is a component collection built for the [shadcn](https://ui.shadcn.com) ecosystem. Like shadcn/ui, components are **copied into your project** and become part of your codebase - not installed as dependencies.
9 |
10 | BaseLayer uses [React Aria Components](https://react-spectrum.adobe.com/react-aria/) and all components have beautiful default styles.
11 |
12 | ## Key Features
13 |
14 | - **Own your components** - Components live in your codebase, not as a dependency
15 | - **shadcn CLI compatible** - Use the familiar `npx shadcn add` workflow to add components
16 | - **AI Native** - Works seamlessly with AI coding assistants via the built-in shadcn MCP server
17 | - **Variant-based design** - Powered by TailwindCSS and tailwind-variants for effortless customization
18 | - **Accessible by default** - Built on battle-tested React Aria Components
19 |
20 | ## Stack
21 |
22 | BaseLayer is built on top of the following technologies:
23 |
24 | - [shadcn Registry](https://ui.shadcn.com/docs/registry) - Component delivery system
25 | - [React Aria Components](https://react-spectrum.adobe.com/react-aria/getting-started.html) - Accessible component primitives
26 | - [TailwindCSS](https://tailwindcss.com/) - Utility-first styling
27 | - [tailwind-variants](https://www.tailwind-variants.org/) - Type-safe variant management
28 | - [TypeScript](https://www.typescriptlang.org/) - Type safety
29 |
30 | ## Design Philosophy
31 |
32 | BaseLayer components are designed with intelligent grouping in mind. Many components like Accordion, RadioGroup, and CheckboxGroup include both individual and group functionality within the same component export. This approach provides:
33 |
34 | - **Code reuse** - Shared styling and behavior between individual and group variants
35 | - **Consistency** - Unified design language across single and grouped instances
36 | - **Simplicity** - One component to learn instead of separate individual/group components
37 |
--------------------------------------------------------------------------------
/web/components/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { source } from "@/lib/source";
2 |
3 | import { SearchDialog } from "./search";
4 | import { SidebarLink } from "./sidebar-link";
5 |
6 | export function Sidebar() {
7 | const pages = source.getPages();
8 |
9 | // Separate docs into categories
10 | const introDocs = pages
11 | .filter((page) => !page.url.startsWith("/docs/components"))
12 | .sort((a, b) => {
13 | const order = ["/docs/intro", "/docs/usage", "/docs/styles"];
14 | const aIndex = order.indexOf(a.url);
15 | const bIndex = order.indexOf(b.url);
16 |
17 | if (aIndex !== -1 && bIndex !== -1) {
18 | return aIndex - bIndex;
19 | }
20 | if (aIndex !== -1) return -1;
21 | if (bIndex !== -1) return 1;
22 | return a.url.localeCompare(b.url);
23 | });
24 | const componentDocs = pages.filter((page) =>
25 | page.url.startsWith("/docs/components"),
26 | );
27 |
28 | return (
29 |
30 | {/* Fixed Search Section */}
31 |
32 |
33 |
34 |
35 | {/* Scrollable Navigation Section */}
36 |
37 |
38 |
39 |
40 | Getting Started
41 |
42 |
43 | {introDocs.map((page) => (
44 |
45 | {page.data.title}
46 |
47 | ))}
48 |
49 |
50 |
51 | {/* Components */}
52 | {componentDocs.length > 0 && (
53 |
54 |
55 | Components
56 |
57 |
58 | {componentDocs.map((page) => (
59 |
60 | {page.data.title}
61 |
62 | ))}
63 |
64 |
65 | )}
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/packages/components/src/examples/table-base.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Table,
5 | TableBody,
6 | TableCell,
7 | TableColumn,
8 | TableHeader,
9 | TableRow,
10 | } from "../core/table/table";
11 |
12 | interface User {
13 | id: number;
14 | name: string;
15 | email: string;
16 | role: string;
17 | status: "Active" | "Inactive" | "Pending";
18 | }
19 |
20 | const users: User[] = [
21 | {
22 | id: 1,
23 | name: "Alice Johnson",
24 | email: "alice@example.com",
25 | role: "Admin",
26 | status: "Active",
27 | },
28 | {
29 | id: 2,
30 | name: "Bob Smith",
31 | email: "bob@example.com",
32 | role: "Developer",
33 | status: "Active",
34 | },
35 | {
36 | id: 3,
37 | name: "Carol Davis",
38 | email: "carol@example.com",
39 | role: "Designer",
40 | status: "Pending",
41 | },
42 | {
43 | id: 4,
44 | name: "David Wilson",
45 | email: "david@example.com",
46 | role: "Developer",
47 | status: "Inactive",
48 | },
49 | {
50 | id: 5,
51 | name: "Eva Brown",
52 | email: "eva@example.com",
53 | role: "Manager",
54 | status: "Active",
55 | },
56 | ];
57 |
58 | export const TableExample = () => (
59 |
60 |
61 |
62 | Name
63 | Email
64 | Role
65 | Status
66 |
67 |
68 | {(user) => (
69 |
70 | {user.name}
71 | {user.email}
72 | {user.role}
73 |
74 |
83 | {user.status}
84 |
85 |
86 |
87 | )}
88 |
89 |
90 |
91 | );
92 |
--------------------------------------------------------------------------------
/packages/components/src/core/tag-group/tag-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Tag as AriaTag,
5 | TagGroup as AriaTagGroup,
6 | type TagGroupProps as AriaTagGroupProps,
7 | type TagProps as AriaTagProps,
8 | Label,
9 | TagList,
10 | type TagListProps,
11 | Text,
12 | } from "react-aria-components";
13 | import { tv } from "tailwind-variants";
14 |
15 | const tagGroup = tv({
16 | slots: {
17 | root: "flex flex-col gap-2 text-sm",
18 | list: "flex flex-wrap gap-2",
19 | tag: "flex cursor-default items-center rounded-full border border-border px-2 py-1 outline-none ring-focus ring-offset-2 ring-offset-surface aria-selected:border-primary aria-selected:bg-primary aria-selected:text-primary-fg data-[focus-visible]:ring-2",
20 | },
21 | });
22 |
23 | const styles = tagGroup();
24 |
25 | interface TagGroupProps
26 | extends Omit,
27 | Pick, "items" | "children" | "renderEmptyState"> {
28 | label?: string;
29 | description?: string;
30 | errorMessage?: string;
31 | }
32 |
33 | const TagGroup = ({
34 | label,
35 | className,
36 | description,
37 | errorMessage,
38 | items,
39 | children,
40 | renderEmptyState,
41 | ...props
42 | }: TagGroupProps) => (
43 |
44 | {label && {label} }
45 |
50 | {children}
51 |
52 | {description && (
53 |
54 | {description}
55 |
56 | )}
57 | {errorMessage && (
58 |
59 | {errorMessage}
60 |
61 | )}
62 |
63 | );
64 |
65 | const Tag = ({
66 | children,
67 | className,
68 | ...props
69 | }: AriaTagProps & { className?: string }) => {
70 | const textValue = typeof children === "string" ? children : undefined;
71 | return (
72 |
77 | {children}
78 |
79 | );
80 | };
81 |
82 | export { Tag, TagGroup };
83 |
--------------------------------------------------------------------------------
/packages/components/src/core/menu/menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { HTMLAttributes } from "react";
4 |
5 | import {
6 | Header as AriaHeader,
7 | Menu as AriaMenu,
8 | MenuItem as AriaMenuItem,
9 | MenuSection as AriaMenuSection,
10 | MenuTrigger as AriaMenuTrigger,
11 | type MenuItemProps,
12 | type MenuProps,
13 | Popover,
14 | Separator,
15 | } from "react-aria-components";
16 | import { tv } from "tailwind-variants";
17 |
18 | export const menu = tv({
19 | slots: {
20 | menuPopover:
21 | "data-[entering]:fade-in data-[exiting]:fade-out overflow-auto rounded-2xl border border-border/25 bg-surface shadow-xl data-[entering]:animate-in data-[exiting]:animate-out",
22 | header: "p-2 font-semibold",
23 | content: "flex h-fit min-w-56 flex-col gap-2 p-3 outline-none",
24 | item: "relative flex cursor-default justify-between rounded-lg p-3 font-semibold outline-none data-[disabled]:cursor-not-allowed data-[focused]:bg-secondary data-[disabled]:text-fg-disabled",
25 | separator: "h-[1px] bg-border",
26 | },
27 | });
28 |
29 | const { menuPopover, content, header, item, separator } = menu();
30 |
31 | const MenuTrigger = AriaMenuTrigger;
32 | const MenuSection = AriaMenuSection;
33 |
34 | const MenuContent = ({
35 | children,
36 | className,
37 | ...props
38 | }: MenuProps & { className?: string }) => (
39 |
40 |
41 | {children}
42 |
43 |
44 | );
45 |
46 | const MenuItem = ({
47 | children,
48 | className,
49 | ...props
50 | }: MenuItemProps & { className?: string }) => (
51 |
52 | {children}
53 |
54 | );
55 |
56 | const MenuHeader = ({
57 | children,
58 | className,
59 | ...props
60 | }: HTMLAttributes & { className?: string }) => (
61 |
62 | {children}
63 |
64 | );
65 |
66 | const MenuSeperator = ({
67 | className,
68 | ...props
69 | }: HTMLAttributes & { className?: string }) => (
70 |
71 | );
72 |
73 | export {
74 | MenuContent,
75 | MenuHeader,
76 | MenuItem,
77 | MenuSeperator,
78 | MenuTrigger,
79 | MenuSection,
80 | };
81 |
--------------------------------------------------------------------------------
/packages/registry/src/schema.ts:
--------------------------------------------------------------------------------
1 | // Registry Schema Definition
2 | // This defines the structure for machine-readable component metadata compatible with shadcn
3 |
4 | import { z } from "zod";
5 |
6 | // Zod schemas for shadcn registry compatibility
7 | export const RegistryItemFileSchema = z.object({
8 | path: z.string(),
9 | content: z.string().optional(),
10 | type: z.enum([
11 | "registry:block",
12 | "registry:component",
13 | "registry:lib",
14 | "registry:hook",
15 | "registry:ui",
16 | "registry:page",
17 | "registry:file",
18 | "registry:style",
19 | "registry:theme",
20 | "registry:item",
21 | ]),
22 | target: z.string().optional(),
23 | });
24 |
25 | export const RegistryItemSchema = z.object({
26 | name: z.string(),
27 | type: z.enum([
28 | "registry:block", // Complex components with multiple files
29 | "registry:component", // Simple components
30 | "registry:lib", // Lib and utils
31 | "registry:hook", // Hooks
32 | "registry:ui", // UI components and single-file primitives
33 | "registry:page", // Page or file-based routes
34 | "registry:file", // Miscellaneous files
35 | "registry:style", // Registry styles (e.g. new-york)
36 | "registry:theme", // Themes
37 | "registry:item", // Universal registry items
38 | ]),
39 | title: z.string().optional(),
40 | description: z.string().optional(),
41 | author: z.string().optional(),
42 | categories: z.array(z.string()).optional(),
43 | docs: z.string().optional(),
44 | registryDependencies: z.array(z.string()).optional(),
45 | dependencies: z.array(z.string()).optional(),
46 | devDependencies: z.array(z.string()).optional(),
47 | files: z.array(RegistryItemFileSchema),
48 | tailwind: z
49 | .object({
50 | config: z.record(z.any()).optional(),
51 | })
52 | .optional(),
53 | cssVars: z.record(z.any()).optional(),
54 | css: z.record(z.any()).optional(),
55 | meta: z.record(z.any()).optional(),
56 | });
57 |
58 | export const RegistrySchema = z.object({
59 | name: z.string(),
60 | $schema: z.string(),
61 | homepage: z.string().url().optional(),
62 | items: z.array(RegistryItemSchema),
63 | });
64 |
65 | // Derived TypeScript types
66 | export type RegistryItem = z.infer;
67 | export type RegistryItemFile = z.infer;
68 | export type Registry = z.infer;
69 |
--------------------------------------------------------------------------------
/packages/components/src/core/card/card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { HTMLAttributes } from "react";
4 |
5 | import { tv, type VariantProps } from "tailwind-variants";
6 |
7 | const card = tv({
8 | slots: {
9 | root: "rounded-2xl border-2 bg-surface text-fg",
10 | header: "flex flex-col space-y-1.5 p-6",
11 | title: "font-semibold text-2xl leading-none tracking-tight",
12 | description: "text-fg-muted text-sm",
13 | content: "p-6 pt-0",
14 | footer: "flex items-center p-6 pt-0",
15 | },
16 | variants: {
17 | variant: {
18 | outlined: {
19 | root: "border border-border/50",
20 | },
21 | filled: {
22 | root: "border-surface-2 bg-surface-2",
23 | },
24 | },
25 | },
26 | defaultVariants: {
27 | variant: "outlined",
28 | },
29 | });
30 |
31 | const styles = card();
32 |
33 | type CardVariantProps = VariantProps;
34 |
35 | interface CardProps extends HTMLAttributes, CardVariantProps {
36 | className?: string;
37 | title?: string;
38 | description?: string;
39 | }
40 |
41 | interface CardHeaderProps extends HTMLAttributes {
42 | className?: string;
43 | }
44 |
45 | interface CardContentProps extends HTMLAttributes {
46 | className?: string;
47 | }
48 |
49 | interface CardFooterProps extends HTMLAttributes {
50 | className?: string;
51 | }
52 |
53 | const Card = ({
54 | className,
55 | variant,
56 | title,
57 | description,
58 | children,
59 | ...props
60 | }: CardProps) => (
61 |
62 | {(title || description) && (
63 |
64 | {title &&
{title} }
65 | {description &&
{description}
}
66 |
67 | )}
68 | {children}
69 |
70 | );
71 |
72 | const CardHeader = ({ className, ...props }: CardHeaderProps) => (
73 |
74 | );
75 |
76 | const CardContent = ({ className, ...props }: CardContentProps) => (
77 |
78 | );
79 |
80 | const CardFooter = ({ className, ...props }: CardFooterProps) => (
81 |
82 | );
83 |
84 | export { Card, CardHeader, CardContent, CardFooter };
85 | export type { CardProps, CardHeaderProps, CardContentProps, CardFooterProps };
86 |
--------------------------------------------------------------------------------
/packages/components/src/examples/accordion-group.tsx:
--------------------------------------------------------------------------------
1 | import { Accordion, AccordionGroup } from "../core/accordion/accordion";
2 |
3 | export const AccordionGroupExample = () => {
4 | return (
5 |
6 |
10 |
11 |
12 |
13 | Quick Setup: Install our SDK with npm install
14 | @company/sdk
15 |
16 |
17 | API Key: Generate your API key from the dashboard
18 |
19 |
20 | First Request: Make your first API call in under
21 | 5 minutes
22 |
23 |
24 | Our comprehensive documentation includes code examples in Python,
25 | JavaScript, and cURL. Start building in minutes with our
26 | interactive tutorials.
27 |
28 |
29 |
30 |
31 |
32 |
33 | API Keys: Secure your requests with bearer token
34 | authentication
35 |
36 |
37 | Rate Limits: 1,000 requests per minute on free
38 | tier, unlimited on pro
39 |
40 |
41 | Webhooks: Real-time notifications with 256-bit
42 | SSL encryption
43 |
44 |
45 | Enterprise-grade security with SOC 2 compliance, end-to-end
46 | encryption, and 99.9% uptime SLA.
47 |
48 |
49 |
50 |
51 |
52 |
53 | Official SDKs: JavaScript, Python, Ruby, Go, PHP
54 |
55 |
56 | Frameworks: React, Vue, Angular, Next.js
57 | components
58 |
59 |
60 | Platforms: Slack, Discord, Teams, Notion
61 | integrations
62 |
63 |
64 | Join 50,000+ developers using our APIs. Browse community-built
65 | packages and contribute to our open-source ecosystem.
66 |
67 |
68 |
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/packages/components/src/core/tabs/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Tab as AriaTab,
5 | TabList as AriaTabList,
6 | type TabListProps as AriaTabListProps,
7 | TabPanel as AriaTabPanel,
8 | type TabPanelProps as AriaTabPanelProps,
9 | type TabProps as AriaTabProps,
10 | type TabsProps as AriaTabsProps,
11 | Tabs as AriaTabsRoot,
12 | } from "react-aria-components";
13 | import { tv } from "tailwind-variants";
14 |
15 | const tabs = tv({
16 | slots: {
17 | root: "flex w-full flex-col items-start",
18 | list: "relative inline-flex items-center justify-between gap-6 rounded-md p-1",
19 | tab: "relative flex w-fit cursor-pointer justify-center rounded-2xl border-primary pb-0.5 font-semibold text-fg-muted outline-none ring-focus ring-offset-2 ring-offset-surface transition-colors duration-200 aria-selected:cursor-default aria-selected:rounded-none aria-selected:border-b-2 aria-selected:pb-0 aria-selected:text-primary aria-selected:hover:bg-transparent data-[hovered]:text-primary data-[focus-visible]:ring-2",
20 | panel:
21 | "mt-4 w-96 rounded-xl p-4 outline-none ring-focus data-[focus-visible]:ring-2",
22 | },
23 | });
24 |
25 | const styles = tabs();
26 |
27 | interface TabsProps extends Omit {
28 | className?: string;
29 | }
30 |
31 | const TabsRoot = ({ children, className, ...props }: TabsProps) => (
32 |
33 | {children}
34 |
35 | );
36 |
37 | interface TabListProps extends Omit, "className"> {
38 | className?: string;
39 | }
40 |
41 | const TabList = ({
42 | children,
43 | className,
44 | ...props
45 | }: TabListProps) => (
46 |
47 | {children}
48 |
49 | );
50 |
51 | interface TabProps extends Omit {
52 | className?: string;
53 | }
54 |
55 | const Tab = ({ children, className, ...props }: TabProps) => (
56 |
57 | {children}
58 |
59 | );
60 |
61 | interface TabPanelProps extends Omit {
62 | className?: string;
63 | }
64 |
65 | const TabPanel = ({ children, className, ...props }: TabPanelProps) => (
66 |
67 | {children}
68 |
69 | );
70 |
71 | export { Tab, TabList, TabPanel, TabsRoot };
72 | export type { TabsProps, TabListProps, TabProps, TabPanelProps };
--------------------------------------------------------------------------------
/web/components/component-metadata.tsx:
--------------------------------------------------------------------------------
1 | import { Badge } from "@baselayer/components";
2 |
3 | import { readFileSync } from "node:fs";
4 | import { join } from "node:path";
5 |
6 | interface ComponentMetadataProps {
7 | componentId: string;
8 | showTitle?: boolean;
9 | showDescription?: boolean;
10 | showCategory?: boolean;
11 | showStatus?: boolean;
12 | showTags?: boolean;
13 | }
14 |
15 | interface RegistryItem {
16 | name: string;
17 | title?: string;
18 | description?: string;
19 | categories?: string[];
20 | meta?: {
21 | status?: string;
22 | tags?: string[];
23 | };
24 | }
25 |
26 | export async function ComponentMetadata({
27 | componentId,
28 | showTitle = false,
29 | showDescription = true,
30 | showCategory = false,
31 | showStatus = false,
32 | showTags = false,
33 | }: ComponentMetadataProps) {
34 | // Read from shadcn registry for metadata
35 | const shadcnRegistryPath = join(process.cwd(), "public/r/registry.json");
36 | const shadcnRegistryContent = readFileSync(shadcnRegistryPath, "utf8");
37 | const shadcnRegistry = JSON.parse(shadcnRegistryContent);
38 |
39 | const item = shadcnRegistry.items?.find(
40 | (i: RegistryItem) => i.name === componentId,
41 | );
42 | if (!item) {
43 | return null;
44 | }
45 |
46 | const { title, description, categories, meta } = item;
47 |
48 | return (
49 |
50 | {showTitle && title && (
51 |
52 | {title}
53 |
54 | )}
55 |
56 | {showDescription && description && (
57 |
{description}
58 | )}
59 |
60 |
61 | {showCategory && categories && categories.length > 0 && (
62 |
63 | {categories[0]}
64 |
65 | )}
66 |
67 | {showStatus && meta?.status && (
68 |
80 | {meta.status}
81 |
82 | )}
83 |
84 | {showTags &&
85 | meta?.tags &&
86 | meta.tags.length > 0 &&
87 | meta.tags.map((tag: string) => (
88 |
89 | {tag}
90 |
91 | ))}
92 |
93 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/web/components/docs-page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import Link from "next/link";
6 |
7 | import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
8 |
9 | import { useActiveHeading } from "../hooks/use-active-heading";
10 | import type { PageNavigation } from "../lib/navigation";
11 | import { TableOfContents } from "./table-of-contents";
12 |
13 | interface TocItem {
14 | depth: number;
15 | url: string;
16 | title: ReactNode;
17 | }
18 |
19 | interface DocsPageProps {
20 | toc: TocItem[];
21 | children: ReactNode;
22 | navigation?: PageNavigation;
23 | }
24 |
25 | export function DocsPage({ toc, children, navigation }: DocsPageProps) {
26 | const { containerRef, activeId } = useActiveHeading();
27 |
28 | return (
29 |
30 | {/* Main Content */}
31 |
32 |
33 | {children}
34 | {/* Page Navigation */}
35 | {(navigation?.previous || navigation?.next) && (
36 |
37 |
38 | {navigation.previous ? (
39 |
43 |
44 |
45 |
Previous
46 |
47 | {navigation.previous.title}
48 |
49 |
50 |
51 | ) : (
52 |
53 | )}
54 |
55 | {navigation.next ? (
56 |
60 |
61 |
Next
62 |
{navigation.next.title}
63 |
64 |
65 |
66 | ) : (
67 |
68 | )}
69 |
70 |
71 | )}
72 |
73 |
74 |
75 | {/* Right Sidebar - Table of Contents */}
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/web/app/docs/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 |
3 | import Image from "next/image";
4 | import Link from "next/link";
5 |
6 | import { GithubIcon } from "lucide-react";
7 |
8 | import { source } from "@/lib/source";
9 |
10 | import { MobileNav } from "@/components/mobile-nav";
11 | import { Sidebar } from "@/components/sidebar";
12 | import { ThemeToggle } from "@/components/theme-toggle";
13 |
14 | interface LayoutProps {
15 | children: ReactNode;
16 | }
17 |
18 | export default function Layout({ children }: LayoutProps) {
19 | const pages = source.getPages();
20 | const introDocs = pages
21 | .filter((page) => !page.url.startsWith("/docs/components"))
22 | .map((page) => ({
23 | url: page.url,
24 | data: {
25 | title: page.data.title,
26 | },
27 | }));
28 | const componentDocs = pages
29 | .filter((page) => page.url.startsWith("/docs/components"))
30 | .map((page) => ({
31 | url: page.url,
32 | data: {
33 | title: page.data.title,
34 | },
35 | }));
36 |
37 | return (
38 |
39 | {/* Top Navigation Bar - Full Width */}
40 |
74 |
75 | {/* below the top nav */}
76 |
77 | {/* Left Sidebar - hidden on mobile, visible on desktop */}
78 |
79 |
80 |
81 |
82 | {/* Main content area - center content + right TOC */}
83 |
{children}
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/packages/components/src/examples/popover-base.tsx:
--------------------------------------------------------------------------------
1 | import { Calendar, MapPin, MessageCircle, Settings, Star, User } from "lucide-react";
2 |
3 | import { Button } from "../core/button/button";
4 | import { Popover, PopoverTrigger } from "../core/popover/popover";
5 |
6 | export const PopoverExample = () => (
7 |
8 |
9 |
10 | Alex Morgan
11 |
12 |
13 |
14 |
15 |
16 |
Alex Morgan
17 |
Senior Product Designer
18 |
19 |
20 | San Francisco, CA
21 |
22 |
23 |
24 |
25 |
26 |
27 |
127
28 |
Projects
29 |
30 |
31 |
4.9
32 |
33 |
34 | Rating
35 |
36 |
37 |
38 |
2.1k
39 |
Followers
40 |
41 |
42 |
43 |
44 | Passionate about creating intuitive user experiences and building design systems that scale.
45 |
46 |
47 |
48 |
49 |
50 | Message
51 |
52 |
53 |
54 | Schedule
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
--------------------------------------------------------------------------------
/web/components/table-of-contents.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import { cx } from "@/lib/cx";
6 |
7 | interface TocItem {
8 | depth: number;
9 | url: string;
10 | title: ReactNode;
11 | }
12 |
13 | interface TableOfContentsProps {
14 | toc: TocItem[];
15 | activeId: string;
16 | }
17 |
18 | export function TableOfContents({ toc, activeId }: TableOfContentsProps) {
19 | const handleTocClick = (url: string, e: React.MouseEvent) => {
20 | e.preventDefault();
21 | const id = url.replace("#", "");
22 |
23 | const element = document.getElementById(id);
24 | if (element) {
25 | const headerOffset = 120; // Account for top nav height
26 | const elementPosition = element.getBoundingClientRect().top;
27 | const offsetPosition =
28 | elementPosition + window.pageYOffset - headerOffset;
29 |
30 | window.scrollTo({
31 | top: offsetPosition,
32 | behavior: "auto",
33 | });
34 | }
35 | };
36 |
37 | if (toc.length === 0) return null;
38 |
39 | return (
40 |
41 |
42 |
43 | On This Page
44 |
45 |
46 |
47 | {toc.map((item) => {
48 | const id = item.url.replace("#", "");
49 | const isActive = activeId === id;
50 |
51 | // Calculate indentation based on depth
52 | // h1=1, h2=2, h3=3, h4=4, etc.
53 | const getIndentClass = (depth: number) => {
54 | switch (depth) {
55 | case 1:
56 | return "pl-0"; // No indent for h1
57 | case 2:
58 | return "pl-0"; // No indent for h2
59 | case 3:
60 | return "pl-3"; // Small indent for h3
61 | case 4:
62 | return "pl-6"; // Larger indent for h4 (####)
63 | case 5:
64 | return "pl-9"; // Even larger for h5
65 | case 6:
66 | return "pl-12"; // Largest for h6
67 | default:
68 | return "pl-0";
69 | }
70 | };
71 |
72 | return (
73 | handleTocClick(item.url, e)}
77 | className={cx(
78 | "block px-3 py-1 text-fg-muted text-sm transition-all duration-200",
79 | getIndentClass(item.depth),
80 | isActive && "text-fg",
81 | !isActive && "text-fg-muted hover:text-fg",
82 | )}
83 | >
84 | {item.title}
85 |
86 | );
87 | })}
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/packages/components/src/core/accordion/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | Button,
7 | Disclosure,
8 | DisclosureGroup,
9 | type DisclosureGroupProps,
10 | DisclosurePanel,
11 | type DisclosureProps,
12 | Heading,
13 | } from "react-aria-components";
14 | import { tv, type VariantProps } from "tailwind-variants";
15 |
16 | import { Minus, Plus } from "lucide-react";
17 |
18 | const accordion = tv({
19 | slots: {
20 | root: "group w-full",
21 | group: "flex flex-col gap-3",
22 | button:
23 | "flex w-full items-center justify-between gap-6 rounded-2xl border border-border bg-surface-2 p-4 data-[focus-visible]:outline-none data-[focus-visible]:ring-2 data-[focus-visible]:ring-focus data-[focus-visible]:ring-offset-2 data-[focus-visible]:ring-offset-surface group-data-[expanded]:rounded-b-none group-data-[expanded]:border-b-0",
24 | icon: "size-4 shrink-0 fill-none transition-transform duration-200",
25 | panel:
26 | "rounded-b-2xl border-border bg-surface-2 px-4 text-muted-foreground text-sm group-data-[expanded]:border-x group-data-[expanded]:border-b group-data-[expanded]:pb-4",
27 | },
28 | });
29 |
30 | const styles = accordion();
31 |
32 | type AccordionVariantProps = VariantProps;
33 |
34 | interface AccordionProps
35 | extends AccordionVariantProps,
36 | Omit {
37 | className?: string;
38 | title?: string;
39 | children?: ReactNode;
40 | }
41 |
42 | const Accordion = ({
43 | className,
44 | title,
45 | children,
46 | ...props
47 | }: AccordionProps) => {
48 | return (
49 |
50 | {({ isExpanded }) => (
51 | <>
52 |
53 |
54 | {title}
55 | {isExpanded ? (
56 |
57 | ) : (
58 |
59 | )}
60 |
61 |
62 |
63 | {children}
64 |
65 | >
66 | )}
67 |
68 | );
69 | };
70 |
71 | interface AccordionGroupProps extends DisclosureGroupProps {
72 | className?: string;
73 | children: ReactNode;
74 | }
75 |
76 | const AccordionGroup = ({
77 | className,
78 | children,
79 | ...props
80 | }: AccordionGroupProps) => (
81 |
82 | {children}
83 |
84 | );
85 |
86 | export { Accordion, AccordionGroup };
87 | export type { AccordionProps, AccordionGroupProps };
88 |
--------------------------------------------------------------------------------
/.github/workflows/sync-components.yml:
--------------------------------------------------------------------------------
1 | name: Component Sync Check
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - 'packages/components/src/**'
7 | - 'packages/components/scripts/**'
8 | - 'packages/components/package.json'
9 | push:
10 | branches:
11 | - main
12 |
13 | jobs:
14 | pr-check:
15 | if: github.event_name == 'pull_request'
16 | runs-on: ubuntu-latest
17 | name: Check Registry Drift
18 |
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v4
22 |
23 | - name: Install pnpm
24 | uses: pnpm/action-setup@v4
25 | with:
26 | version: 10.12.1
27 |
28 | - name: Setup Node.js
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: '20'
32 | cache: 'pnpm'
33 |
34 | - name: Install dependencies
35 | run: pnpm install
36 |
37 | - name: Build registry dependency first
38 | run: pnpm --filter @baselayer/registry build
39 |
40 | - name: Build components and generate registry
41 | run: pnpm --filter @baselayer/components build
42 |
43 | - name: Check for registry drift
44 | run: |
45 | # Check registry.json and generated shadcn registry files
46 | if git diff --quiet packages/components/registry.json web/public/r/; then
47 | echo "✅ Registry is in sync"
48 | else
49 | echo "❌ Registry out of sync! Generated files differ from committed files."
50 | echo ""
51 | echo "Please run the following locally and commit the changes:"
52 | echo " pnpm --filter @baselayer/components build"
53 | echo ""
54 | echo "Changed files:"
55 | git diff --name-only packages/components/registry.json web/public/r/
56 | echo ""
57 | echo "Diff:"
58 | git diff packages/components/registry.json web/public/r/
59 | exit 1
60 | fi
61 |
62 | build-and-test:
63 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
64 | runs-on: ubuntu-latest
65 | name: Build and Test
66 |
67 | steps:
68 | - name: Checkout
69 | uses: actions/checkout@v4
70 |
71 | - name: Install pnpm
72 | uses: pnpm/action-setup@v4
73 | with:
74 | version: 10.12.1
75 |
76 | - name: Setup Node.js
77 | uses: actions/setup-node@v4
78 | with:
79 | node-version: '20'
80 | cache: 'pnpm'
81 |
82 | - name: Install dependencies
83 | run: pnpm install
84 |
85 | - name: Build all packages
86 | run: pnpm build
87 |
88 |
--------------------------------------------------------------------------------
/web/app/docs/[...slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { notFound } from "next/navigation";
2 |
3 | import type { Metadata } from "next";
4 |
5 | import { getPageNavigation } from "@/lib/navigation";
6 | import { source } from "@/lib/source";
7 |
8 | import { ComponentMetadata } from "@/components/component-metadata";
9 | import { DocsPage } from "@/components/docs-page";
10 | import { mdxComponents } from "@/components/mdx-components";
11 | import { OpenInAiMenu } from "@/components/open-in-ai-menu";
12 | import { RACLink } from "@/components/rac-link";
13 |
14 | // Ensure strict static-only build
15 | export const dynamic = "error";
16 |
17 | interface PageProps {
18 | params: Promise<{
19 | slug?: string[];
20 | }>;
21 | }
22 |
23 | // Convert PascalCase title to kebab-case component ID
24 | function titleToComponentId(title: string): string {
25 | // special cases
26 | if (title === "ComboBox") {
27 | return "combobox";
28 | }
29 |
30 | return (
31 | title
32 | // Insert a hyphen before uppercase letters that follow lowercase letters
33 | .replace(/([a-z])([A-Z])/g, "$1-$2")
34 | .toLowerCase()
35 | );
36 | }
37 |
38 | export default async function Page({ params }: PageProps) {
39 | const { slug } = await params;
40 | const page = source.getPage(slug);
41 |
42 | if (!page) {
43 | notFound();
44 | }
45 |
46 | const toc = page.data.toc || [];
47 | const MDXContent = page.data.body;
48 | const navigation = getPageNavigation(page.url);
49 | const componentId = titleToComponentId(page.data.title);
50 | const isComponentPage = page.url.includes("/docs/components/");
51 |
52 | return (
53 |
54 |
55 |
{page.data.title}
56 |
57 | {/* Component metadata for component pages only */}
58 | {isComponentPage &&
}
59 |
60 | {/* Links section - always show for all docs pages */}
61 |
62 | {page.data.isRAC && }
63 |
64 |
65 |
66 |
67 |
68 |
69 | );
70 | }
71 |
72 | export async function generateStaticParams() {
73 | return source.generateParams();
74 | }
75 |
76 | export async function generateMetadata({
77 | params,
78 | }: PageProps): Promise {
79 | const { slug } = await params;
80 | const page = source.getPage(slug);
81 |
82 | if (!page) notFound();
83 |
84 | return {
85 | title: `${page.data.title} - BaseLayer`,
86 | description: page.data.description,
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/packages/components/src/core/checkbox/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | type CheckboxProps as AriaCheckBoxProps,
7 | Checkbox as AriaCheckbox,
8 | CheckboxGroup as AriaCheckboxGroup,
9 | type CheckboxGroupProps as AriaCheckboxGroupProps,
10 | FieldError,
11 | Text,
12 | type ValidationResult,
13 | } from "react-aria-components";
14 | import { tv, type VariantProps } from "tailwind-variants";
15 |
16 | import { Check } from "lucide-react";
17 |
18 | const checkbox = tv({
19 | base: "group flex items-center justify-center gap-2 py-1 text-fg",
20 | });
21 |
22 | const checkboxGroup = tv({
23 | base: "flex flex-col gap-2",
24 | });
25 |
26 | type CheckboxVariantProps = VariantProps;
27 |
28 | interface CheckboxProps extends CheckboxVariantProps, AriaCheckBoxProps {
29 | className?: string;
30 | children: ReactNode;
31 | description?: string;
32 | errorMessage?: string | ((validation: ValidationResult) => string);
33 | }
34 |
35 | const Checkbox = ({
36 | className,
37 | errorMessage,
38 | children,
39 | description,
40 | ...props
41 | }: CheckboxProps) => {
42 | return (
43 |
44 | {({ isSelected }) => (
45 | <>
46 |
47 | {isSelected && }
48 |
49 | {children}
50 | {description && (
51 |
52 | {description}
53 |
54 | )}
55 |
56 | {errorMessage}
57 |
58 | >
59 | )}
60 |
61 | );
62 | };
63 |
64 | interface CheckboxGroupProps extends AriaCheckboxGroupProps {
65 | className?: string;
66 | label?: string;
67 | children: ReactNode;
68 | description?: string;
69 | errorMessage?: string | ((validation: ValidationResult) => string);
70 | }
71 |
72 | const CheckboxGroup = ({
73 | className,
74 | label,
75 | description,
76 | errorMessage,
77 | children,
78 | ...props
79 | }: CheckboxGroupProps) => (
80 |
81 | {label && (
82 |
83 | {label}
84 |
85 | )}
86 | {children}
87 | {description && (
88 |
89 | {description}
90 |
91 | )}
92 | {errorMessage}
93 |
94 | );
95 |
96 | export { Checkbox, CheckboxGroup };
97 | export type { CheckboxProps, CheckboxGroupProps };
98 |
--------------------------------------------------------------------------------
/packages/components/src/tailwind.css:
--------------------------------------------------------------------------------
1 | @variant dark ([data-theme="dark"] &);
2 |
3 | :root {
4 | /* SURFACES (neutrals & elevation) */
5 | --surface: oklch(98.5% 0 0); /* app/page background */
6 | --surface-2: oklch(97% 0 0); /* card / raised */
7 |
8 | /* CONTENT (foreground) */
9 | --fg: oklch(14.5% 0 0); /* primary reading text */
10 | --fg-muted: oklch(55.6% 0 0); /* secondary text, captions */
11 | --fg-inverse: oklch(98.5% 0 0); /* text on dark */
12 | --fg-disabled: oklch(70.8% 0 0); /* explicit disabled state text */
13 |
14 | /* OUTLINES & DIVIDERS */
15 | --border: oklch(87% 0 0); /* generic 1px rules, inputs */
16 | --focus: oklch(68.5% 0.169 237.323); /* focus ring / a11y outline */
17 |
18 | /* INTERACTIVE ROLES (paired bg / fg ) */
19 | --primary: oklch(14.5% 0 0);
20 | --primary-fg: oklch(98.5% 0 0);
21 |
22 | --secondary: oklch(92.2% 0 0);
23 | --secondary-fg: oklch(14.5% 0 0);
24 |
25 | /* STATUS / FUNCTIONAL */
26 |
27 | --danger: oklch(63.7% 0.237 25.331);
28 | --danger-fg: #ffffff;
29 | }
30 |
31 | [data-theme='dark'] {
32 | /* SURFACES (neutrals & elevation) */
33 | --surface: oklch(14.5% 0 0); /* app/page background */
34 | --surface-2: oklch(20.5% 0 0); /* card / raised */
35 |
36 | /* CONTENT (foreground) */
37 | --fg: oklch(98.5% 0.001 106.423); /* primary reading text */
38 | --fg-muted: oklch(55.6% 0 0); /* secondary text, captions */
39 | --fg-inverse: oklch(98.5% 0 0); /* text on dark */
40 | --fg-disabled: oklch(37.1% 0 0); /* explicit disabled state text */
41 |
42 | /* OUTLINES & DIVIDERS */
43 | --border: oklch(37.1% 0 0); /* generic 1px rules, inputs */
44 | --focus: oklch(68.5% 0.169 237.323); /* focus ring / a11y outline */
45 |
46 | /* INTERACTIVE ROLES (paired bg / fg ) */
47 | --primary: oklch(98.5% 0 0);
48 | --primary-fg: oklch(14.5% 0 0);
49 |
50 | --secondary: oklch(26.9% 0 0);
51 | --secondary-fg: oklch(98.5% 0 0);
52 |
53 | /* STATUS / FUNCTIONAL */
54 |
55 | --danger: oklch(63.7% 0.237 25.331);
56 | --danger-fg: oklch(98.5% 0 0);
57 | }
58 |
59 | @theme {
60 | --color-surface: var(--surface);
61 | --color-surface-2: var(--surface-2);
62 | --color-fg: var(--fg);
63 | --color-fg-muted: var(--fg-muted);
64 | --color-fg-disabled: var(--fg-disabled);
65 | --color-fg-inverse: var(--fg-inverse);
66 | --color-border: var(--border);
67 | --color-focus: var(--focus);
68 | --color-primary: var(--primary);
69 | --color-primary-fg: var(--primary-fg);
70 | --color-secondary: var(--secondary);
71 | --color-secondary-fg: var(--secondary-fg);
72 | --color-danger: var(--danger);
73 | --color-danger-fg: var(--danger-fg);
74 | }
75 |
76 | /* clears the ‘X’ from Internet Explorer */
77 | input[type="search"]::-ms-clear {
78 | display: none;
79 | width: 0;
80 | height: 0;
81 | }
82 | input[type="search"]::-ms-reveal {
83 | display: none;
84 | width: 0;
85 | height: 0;
86 | }
87 | /* clears the ‘X’ from Chrome */
88 | input[type="search"]::-webkit-search-decoration,
89 | input[type="search"]::-webkit-search-cancel-button,
90 | input[type="search"]::-webkit-search-results-button,
91 | input[type="search"]::-webkit-search-results-decoration {
92 | display: none;
93 | }
94 |
--------------------------------------------------------------------------------
/web/components/preview.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 |
5 | import { CodeIcon, ComponentIcon, type LucideIcon } from "lucide-react";
6 |
7 | interface TabButtonProps {
8 | tab: "preview" | "source";
9 | activeTab: "preview" | "source";
10 | onClick: () => void;
11 | icon: LucideIcon;
12 | }
13 |
14 | const TabButton = ({ tab, activeTab, onClick, icon: Icon }: TabButtonProps) => {
15 | const isActive = activeTab === tab;
16 |
17 | return (
18 |
26 |
27 |
28 | );
29 | };
30 |
31 | export const Preview = ({
32 | children,
33 | sourceCodeElement,
34 | }: {
35 | children?: React.ReactNode;
36 | sourceCodeElement?: React.ReactElement;
37 | }) => {
38 | const [activeTab, setActiveTab] = useState<"preview" | "source">("preview");
39 |
40 | // If no children and only sourceCodeElement, render just the source code
41 | if (!children && sourceCodeElement) {
42 | return (
43 |
44 |
45 | {sourceCodeElement}
46 |
47 |
48 | );
49 | }
50 |
51 | return (
52 |
53 | {sourceCodeElement && (
54 |
55 |
56 | {/* Active indicator background */}
57 |
64 |
65 |
setActiveTab("preview")}
69 | icon={ComponentIcon}
70 | />
71 | setActiveTab("source")}
75 | icon={CodeIcon}
76 | />
77 |
78 |
79 | )}
80 |
81 | {activeTab === "preview" ? (
82 |
83 | {children}
84 |
85 | ) : (
86 |
87 | {sourceCodeElement || (
88 |
89 | {"// Source code not available"}
90 |
91 | )}
92 |
93 | )}
94 |
95 |
96 | );
97 | };
98 |
--------------------------------------------------------------------------------
/web/components/mobile-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 |
5 | import Image from "next/image";
6 |
7 | import { Button, Dialog, Modal, ModalOverlay } from "react-aria-components";
8 |
9 | import { MenuIcon } from "lucide-react";
10 |
11 | import { SidebarLink } from "./sidebar-link";
12 |
13 | interface Page {
14 | url: string;
15 | data: {
16 | title: string;
17 | };
18 | }
19 |
20 | interface MobileNavProps {
21 | introDocs: Page[];
22 | componentDocs: Page[];
23 | }
24 |
25 | export function MobileNav({ introDocs, componentDocs }: MobileNavProps) {
26 | const [isOpen, setIsOpen] = useState(false);
27 | return (
28 | <>
29 | setIsOpen(true)}
31 | className="ml-3 rounded-md p-1 transition-colors hover:bg-surface-variant md:hidden"
32 | aria-label="Open navigation menu"
33 | >
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
53 |
54 | BaseLayer
55 |
56 |
57 |
58 |
62 |
63 | {/* Getting Started */}
64 |
65 |
66 | Getting Started
67 |
68 |
69 | {introDocs.map((page) => (
70 |
71 | setIsOpen(false)}
74 | >
75 | {page.data.title}
76 |
77 |
78 | ))}
79 |
80 |
81 |
82 | {/* Components */}
83 | {componentDocs.length > 0 && (
84 |
85 |
86 | Components
87 |
88 |
89 | {componentDocs.map((page) => (
90 |
91 | setIsOpen(false)}
94 | >
95 | {page.data.title}
96 |
97 |
98 | ))}
99 |
100 |
101 | )}
102 |
103 |
104 |
105 |
106 |
107 | >
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/packages/components/src/core/combobox/combobox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 |
5 | import {
6 | ComboBox as AriaComboBox,
7 | type ComboBoxProps as AriaComboBoxProps,
8 | Button,
9 | FieldError,
10 | Input,
11 | Label,
12 | ListBox,
13 | ListBoxItem,
14 | type ListBoxItemProps,
15 | Popover,
16 | Text,
17 | type ValidationResult,
18 | } from "react-aria-components";
19 | import { tv } from "tailwind-variants";
20 |
21 | import { CheckIcon, ChevronsUpDown } from "lucide-react";
22 |
23 | const combobox = tv({
24 | slots: {
25 | root: "group max-h-inherit w-full overflow-auto p-1 outline-none",
26 | input:
27 | "w-full rounded-lg border border-border bg-surface px-4 py-1.5 align-middle font-semibold text-fg outline-none ring-fg transition-all group-data-[focused]:border-transparent group-data-[focused]:bg-surface group-data-[focused]:ring-2",
28 | button:
29 | "absolute right-2 flex appearance-none items-center justify-center rounded-full border-0 outline-none ring-focus ring-offset-2 ring-offset-surface data-[focus-visible]:ring-2",
30 | popover:
31 | "w-[var(--trigger-width)] rounded-xl border border-border/25 bg-surface p-1 text-fg shadow-lg outline-none",
32 | item: "relative m-1 flex cursor-default flex-col rounded-lg p-2 font-semibold outline-none data-[disabled]:cursor-not-allowed data-[focused]:bg-secondary data-[disabled]:text-fg-disabled",
33 | },
34 | });
35 |
36 | const styles = combobox();
37 |
38 | interface ComboBoxProps
39 | extends Omit, "className"> {
40 | className?: string;
41 | label?: string;
42 | description?: string;
43 | errorMessage?: string | ((validation: ValidationResult) => string);
44 | }
45 |
46 | const ComboBox = ({
47 | label,
48 | className,
49 | description,
50 | errorMessage,
51 | children,
52 | ...props
53 | }: ComboBoxProps) => (
54 |
58 | {label && {label} }
59 |
60 |
61 |
62 |
63 |
64 |
65 | {description && (
66 |
67 | {description}
68 |
69 | )}
70 | {errorMessage}
71 |
72 | {children}
73 |
74 |
75 | );
76 |
77 | interface ComboBoxItemProps
78 | extends Omit {
79 | children: ReactNode;
80 | className?: string;
81 | }
82 |
83 | const ComboBoxItem = ({ className, ...props }: ComboBoxItemProps) => (
84 |
85 | {({ isSelected }) => (
86 |
87 | {props.children}
88 | {isSelected && }
89 |
90 | )}
91 |
92 | );
93 |
94 | export { ComboBox, ComboBoxItem };
95 | export type { ComboBoxProps, ListBoxItemProps as ComboBoxItemProps };
--------------------------------------------------------------------------------
/packages/components/src/core/table/table.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Column as AriaColumn,
5 | Table as AriaTable,
6 | TableBody as AriaTableBody,
7 | TableHeader as AriaTableheader,
8 | Cell,
9 | type CellProps,
10 | Collection,
11 | type ColumnProps,
12 | Row,
13 | type RowProps,
14 | type TableHeaderProps,
15 | type TableProps,
16 | useTableOptions,
17 | } from "react-aria-components";
18 | import { tv } from "tailwind-variants";
19 |
20 | import { Menu } from "lucide-react";
21 |
22 | import { Button } from "@/components/ui/button/button";
23 | import { Checkbox } from "@/components/ui/checkbox/checkbox";
24 |
25 | const table = tv({
26 | slots: {
27 | root: "table min-h-[100px] border-separate border-spacing-0 rounded-xl border border-border p-4 outline-none",
28 | column: "border-border border-b-2 px-4 py-1 text-left outline-none",
29 | header: "text-fg after:table-row after:h-[2px]",
30 | label: "text-fg-3",
31 | row: "relative cursor-default rounded-xl text-fg outline-none ring-focus data-[focus-visible]:ring-2",
32 | cell: "px-4 py-2 outline-none ring-focus data-[focus-visible]:ring-2",
33 | },
34 | });
35 |
36 | const styles = table();
37 |
38 | const TableBody = AriaTableBody;
39 |
40 | const Table = ({
41 | children,
42 | className,
43 | ...props
44 | }: TableProps & { className?: string }) => (
45 |
46 | {children}
47 |
48 | );
49 |
50 | const TableCell = ({
51 | children,
52 | className,
53 | ...props
54 | }: CellProps & { className?: string }) => (
55 |
56 | {children}
57 | |
58 | );
59 |
60 | const TableColumn = ({
61 | children,
62 | className,
63 | ...props
64 | }: ColumnProps & { className?: string }) => (
65 |
66 | {children}
67 |
68 | );
69 |
70 | const TableHeader = ({
71 | children,
72 | className,
73 | columns,
74 | ...props
75 | }: TableHeaderProps & { className?: string }) => {
76 | const { selectionBehavior, selectionMode, allowsDragging } =
77 | useTableOptions();
78 | return (
79 |
80 | {/* Add extra columns for drag and drop and selection. */}
81 | {allowsDragging && }
82 | {selectionBehavior === "toggle" && (
83 |
84 | {selectionMode === "multiple" && (
85 |
86 | )}
87 |
88 | )}
89 | {children}
90 |
91 | );
92 | };
93 |
94 | const TableRow = ({
95 | children,
96 | className,
97 | columns,
98 | id,
99 | ...props
100 | }: RowProps & { className?: string }) => {
101 | const { selectionBehavior, allowsDragging } = useTableOptions();
102 | return (
103 |
104 | {allowsDragging && (
105 |
106 |
107 |
108 |
109 | |
110 | )}
111 | {selectionBehavior === "toggle" && (
112 |
113 |
114 | |
115 | )}
116 | {children}
117 |
118 | );
119 | };
120 |
121 | export { TableColumn, Table, TableBody, TableCell, TableHeader, TableRow };
122 |
--------------------------------------------------------------------------------
/web/components/mdx-components.tsx:
--------------------------------------------------------------------------------
1 | import { getComponentSource, getExampleSource } from "../lib/component-data";
2 | import { getExampleComponent } from "../lib/examples-map";
3 | import { CodeBlock } from "./code-block";
4 | import { ComponentMetadata } from "./component-metadata";
5 | import { Preview } from "./preview";
6 |
7 | export const ComponentPreview = async ({
8 | children,
9 | name,
10 | example,
11 | }: {
12 | children?: React.ReactNode;
13 | name?: string;
14 | example?: string;
15 | }) => {
16 | let sourceCode: string | undefined;
17 | let exampleComponent: React.ReactNode | undefined;
18 |
19 | if (example) {
20 | try {
21 | sourceCode = await getExampleSource(example);
22 | const ExampleComponent = await getExampleComponent(example);
23 | if (ExampleComponent) {
24 | exampleComponent = ;
25 | }
26 | } catch (error) {
27 | console.error(`Failed to load example ${example}:`, error);
28 | }
29 | } else if (name) {
30 | try {
31 | sourceCode = await getComponentSource(name);
32 | } catch (error) {
33 | console.error(`Failed to load source for ${name}:`, error);
34 | sourceCode = undefined;
35 | }
36 | }
37 |
38 | return (
39 |
40 |
: undefined
43 | }
44 | >
45 | {exampleComponent || children}
46 |
47 |
48 | );
49 | };
50 |
51 | // Layout components
52 | export const CodeTabs = ({ children }: { children: React.ReactNode }) => (
53 |
54 | {children}
55 |
56 | );
57 |
58 | export const TabsList = ({ children }: { children: React.ReactNode }) => (
59 | {children}
60 | );
61 |
62 | export const TabsTrigger = ({ children }: { children: React.ReactNode }) => (
63 |
67 | {children}
68 |
69 | );
70 |
71 | export const TabsContent = ({ children }: { children: React.ReactNode }) => (
72 | {children}
73 | );
74 |
75 | export const Steps = ({ children }: { children: React.ReactNode }) => (
76 | {children}
77 | );
78 |
79 | export const Step = ({ children }: { children: React.ReactNode }) => (
80 | {children}
81 | );
82 |
83 | export const Pre = ({ children }: { children: React.ReactNode }) => (
84 |
85 | {children}
86 |
87 | );
88 |
89 | export const Code = ({ children }: { children: React.ReactNode }) => (
90 |
91 | {children}
92 |
93 | );
94 |
95 | export const Ul = ({ children }: { children: React.ReactNode }) => (
96 |
97 | );
98 |
99 | export const Li = ({ children }: { children: React.ReactNode }) => (
100 | {children}
101 | );
102 |
103 | // MDX Components export
104 | export const mdxComponents = {
105 | ComponentPreview,
106 | ComponentMetadata,
107 | CodeTabs,
108 | TabsList,
109 | TabsTrigger,
110 | TabsContent,
111 | Steps,
112 | Step,
113 | pre: Pre,
114 | code: Code,
115 | ul: Ul,
116 | li: Li,
117 | };
118 |
--------------------------------------------------------------------------------
/web/app/docs/content/usage.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Usage
3 | description: How to setup and use BaseLayer components in your project
4 | ---
5 |
6 | ## Prerequisites
7 |
8 | - React with TypeScript
9 | - TailwindCSS >= 4
10 | - Familiarity with [shadcn](https://ui.shadcn.com) (see their [docs](https://ui.shadcn.com/docs) if you're new to shadcn)
11 |
12 | ## Quick Start
13 |
14 | ### 1. Initialize (first time only)
15 |
16 | ```bash
17 | npx shadcn@latest init
18 | ```
19 |
20 | Skip this if you already have a `components.json` file - the CLI will auto-configure on first use.
21 |
22 | ### 2. Add components
23 |
24 | ```bash
25 | npx shadcn@latest add https://baselayer.dev/r/button
26 | ```
27 |
28 | The CLI handles installation, dependencies, and file placement automatically.
29 |
30 | ### 3. Use it
31 |
32 | ```tsx
33 | import { Button } from '@/components/ui/button'
34 |
35 | export default function Page() {
36 | return Click me
37 | }
38 | ```
39 |
40 | ## Optional: Shorter Commands
41 |
42 | Add to your `components.json` for shorter commands:
43 |
44 | ```json
45 | {
46 | "registries": {
47 | "@baselayer": "https://baselayer.dev/r/{name}.json"
48 | }
49 | }
50 | ```
51 |
52 | Then use:
53 |
54 | ```bash
55 | npx shadcn@latest add @baselayer/button
56 | ```
57 |
58 | ## AI-Powered Workflow
59 |
60 | ### Cursor (MCP)
61 |
62 | The shadcn MCP server is built into Cursor. Simply ask:
63 |
64 | > "Add BaseLayer button component"
65 |
66 | > "Create a sign up page with BaseLayer components"
67 |
68 | **Pro Tip:** Add to `.cursorrules`:
69 | ```
70 | Use BaseLayer components (baselayer.dev) instead of shadcn/ui
71 | ```
72 |
73 | ### ChatGPT, Claude, Other Chatbots
74 |
75 | Use the "Open in AI" button on component pages, or reference in prompts:
76 |
77 | > Use BaseLayer components. Docs: https://www.baselayer.dev/llms.txt
78 |
79 | ## Manual Copy-Paste Setup
80 |
81 | If you prefer not to use the CLI and want complete manual control:
82 |
83 | 1. **Install dependencies:**
84 |
85 | ```bash
86 | pnpm add react-aria-components tailwind-variants tw-css-animate
87 | ```
88 |
89 | 2. **Add global styles** - see [styles](/docs/styles) for details
90 |
91 | 3. **Configure TypeScript paths:**
92 |
93 |
94 | Next.js example
95 |
96 | ```json
97 | // tsconfig.json
98 | {
99 | "compilerOptions": {
100 | "baseUrl": ".",
101 | "paths": {
102 | "@/*": ["./*"]
103 | }
104 | }
105 | }
106 | ```
107 |
108 |
109 |
110 |
111 | Vite example
112 |
113 | ```json
114 | // tsconfig.json
115 | {
116 | "compilerOptions": {
117 | "baseUrl": ".",
118 | "paths": {
119 | "@/*": ["./src/*"]
120 | }
121 | }
122 | }
123 | ```
124 |
125 | ```json
126 | // tsconfig.app.json
127 | {
128 | "compilerOptions": {
129 | "baseUrl": ".",
130 | "paths": {
131 | "@/*": ["./src/*"]
132 | }
133 | }
134 | }
135 | ```
136 |
137 | ```ts
138 | // vite.config.ts
139 | import { defineConfig } from 'vite';
140 | import react from '@vitejs/plugin-react';
141 | import tailwindcss from '@tailwindcss/vite';
142 | import path from 'path';
143 |
144 | export default defineConfig({
145 | plugins: [react(), tailwindcss()],
146 | resolve: {
147 | alias: {
148 | '@': path.resolve(__dirname, './src'),
149 | },
150 | },
151 | });
152 | ```
153 |
154 |
155 |
156 | 4. **Copy components** - Each component page has a source section showing the code to copy
157 |
158 | ## Additional Resources
159 |
160 | - [React Aria Components docs](https://react-spectrum.adobe.com/react-aria/getting-started.html) - Detailed documentation for all component props and behavior
--------------------------------------------------------------------------------
/web/public/r/badge.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "badge",
4 | "type": "registry:ui",
5 | "title": "badge",
6 | "author": "BaseLayer",
7 | "description": "A badge is a small label that can be used to display information or status.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/badge/badge.tsx",
17 | "content": "import type { HTMLAttributes } from \"react\";\n\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const badge = tv({\n\tbase: \"flex items-center justify-center rounded-4xl px-3 py-2 font-semibold text-xs\",\n\tvariants: {\n\t\tvariant: {\n\t\t\tattention: \"bg-gradient-to-r from-pink-500 to-purple-500 text-white\",\n\t\t\tneutral: \"bg-secondary text-secondary-fg\",\n\t\t\tdanger: \"bg-danger text-danger-fg\",\n\t\t},\n\t},\n\tdefaultVariants: {\n\t\tvariant: \"attention\",\n\t},\n});\n\ntype BadgeProps = VariantProps & HTMLAttributes;\n\nconst Badge = ({ className, variant, ...props }: BadgeProps) => (\n\t \n);\n\nexport { Badge };\nexport type { BadgeProps };\n",
18 | "type": "registry:component",
19 | "target": "components/ui/badge.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "status"
89 | ]
90 | },
91 | "categories": [
92 | "display"
93 | ]
94 | }
--------------------------------------------------------------------------------
/web/public/r/tooltip.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "tooltip",
4 | "type": "registry:ui",
5 | "title": "tooltip",
6 | "author": "BaseLayer",
7 | "description": "A tooltip displays a description of an element on hover or focus.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/tooltip/tooltip.tsx",
17 | "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport {\n\tTooltip as AriaTooltip,\n\ttype TooltipProps as AriaTooltipProps,\n\tTooltipTrigger as AriaTooltipTrigger,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nconst tooltip = tv({\n\tbase: \"[&[data-entering]]:fade-in [&[data-exiting]]:fade-out m-1 max-w-sm rounded-full border border-border bg-surface px-4 py-2 text-fg shadow-xl outline-none [&[data-entering]]:animate-fade-in [&[data-exiting]]:animate-fade-out\",\n});\n\nconst TooltipTrigger = AriaTooltipTrigger;\n\ninterface TooltipProps extends Omit {\n\tclassName?: string;\n\tchildren: ReactNode;\n}\n\nconst Tooltip = ({ children, className, ...props }: TooltipProps) => (\n\t\n\t\t{children}\n\t \n);\n\nexport { Tooltip, TooltipTrigger };\n",
18 | "type": "registry:component",
19 | "target": "components/ui/tooltip.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "overlay",
89 | "interactive",
90 | "help"
91 | ]
92 | },
93 | "categories": [
94 | "overlays"
95 | ]
96 | }
--------------------------------------------------------------------------------
/web/public/r/popover.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "popover",
4 | "type": "registry:ui",
5 | "title": "popover",
6 | "author": "BaseLayer",
7 | "description": "A popover is an overlay element positioned relative to a trigger.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/popover/popover.tsx",
17 | "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport {\n\tDialogTrigger as AriaDialogTrigger,\n\tPopover as AriaPopover,\n\ttype PopoverProps as AriaPopoverProps,\n\tDialog,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nexport const popover = tv({\n\tbase: \"data-[entering]:fade-in data-[exiting]:fade-out m-1 max-w-lg rounded-2xl border border-border/25 bg-surface p-2 text-fg shadow-lg outline-none data-[entering]:animate-in data-[exiting]:animate-out\",\n});\n\ninterface DialogProps extends Omit {\n\tchildren: ReactNode;\n\tclassName?: string;\n}\n\nconst PopoverTrigger = AriaDialogTrigger;\n\nconst Popover = ({ children, className, ...props }: DialogProps) => (\n\t\n\t\t{children} \n\t \n);\n\nexport { Popover, PopoverTrigger };\n",
18 | "type": "registry:component",
19 | "target": "components/ui/popover.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "overlay",
89 | "interactive"
90 | ]
91 | },
92 | "categories": [
93 | "overlays"
94 | ]
95 | }
--------------------------------------------------------------------------------
/web/public/r/meter.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "meter",
4 | "type": "registry:ui",
5 | "title": "meter",
6 | "author": "BaseLayer",
7 | "description": "A meter represents a quantity within a known range, or a fractional value.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/meter/meter.tsx",
17 | "content": "\"use client\";\n\nimport {\n\tMeter as AriaMeter,\n\ttype MeterProps as AriaMeterProps,\n\tLabel,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nconst bar = tv({\n\tbase: \"h-4 overflow-hidden rounded-2xl bg-surface-2\",\n});\n\ninterface MeterProps extends Omit {\n\tlabel?: string;\n\tclassName?: string;\n}\n\nconst Meter = ({ label, className, ...props }: MeterProps) => (\n\t\n\t\t{({ percentage, valueText }) => (\n\t\t\t<>\n\t\t\t\t\n\t\t\t\t\t{label && {label} }\n\t\t\t\t\t{valueText} \n\t\t\t\t
\n\t\t\t\t\n\t\t\t>\n\t\t)}\n\t \n);\n\nexport { Meter };\nexport type { MeterProps };\n",
18 | "type": "registry:component",
19 | "target": "components/ui/meter.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "data-display",
89 | "progress"
90 | ]
91 | },
92 | "categories": [
93 | "data-display"
94 | ]
95 | }
--------------------------------------------------------------------------------
/web/public/r/switch.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "switch",
4 | "type": "registry:ui",
5 | "title": "switch",
6 | "author": "BaseLayer",
7 | "description": "A switch allows a user to turn a setting on or off.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/switch/switch.tsx",
17 | "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport {\n\tSwitch as AriaSwitch,\n\ttype SwitchProps as AriaSwitchProps,\n\tLabel,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nconst switchStyles = tv({\n\tslots: {\n\t\troot: \"group flex items-center gap-2 transition-none duration-200\",\n\t\tindicator:\n\t\t\t\"h-6 w-10 cursor-pointer rounded-xl bg-secondary duration-200 before:mx-[3px] before:mt-[3px] before:block before:size-4.5 before:rounded-2xl before:bg-surface before:transition-all data-[selected]:bg-primary group-data-[selected]:bg-primary group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-focus group-data-[focus-visible]:ring-offset-2 group-data-[focus-visible]:ring-offset-surface group-data-[selected]:before:translate-x-4\",\n\t\tlabel: \"font-semibold text-fg text-sm\",\n\t},\t\n});\n\nconst styles = switchStyles();\n\ninterface SwitchProps extends AriaSwitchProps {\n\tchildren?: ReactNode;\n\tclassName?: string;\n}\n\n const Switch = ({ className, children, ...restProps }: SwitchProps) => (\n\t\n\t\t
\n\t\t {children} \n\t \n);\n\nexport { Switch };\nexport type { SwitchProps };",
18 | "type": "registry:component",
19 | "target": "components/ui/switch.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "form",
89 | "interactive",
90 | "toggle"
91 | ]
92 | },
93 | "categories": [
94 | "forms"
95 | ]
96 | }
--------------------------------------------------------------------------------
/web/public/r/modal.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "modal",
4 | "type": "registry:ui",
5 | "title": "modal",
6 | "author": "BaseLayer",
7 | "description": "A modal is an overlay element which blocks interaction with elements outside it.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/modal/modal.tsx",
17 | "content": "\"use client\";\n\nimport {\n\tDialog as AriaDialog,\n\tDialogTrigger as AriaDialogTrigger,\n\tModal as AriaModal,\n\ttype DialogProps,\n\tModalOverlay,\n\ttype ModalOverlayProps,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nexport const modal = tv({\n\tslots: {\n\t\tdialog: \"flex w-full flex-col gap-6 outline-none\",\n\t\tmodalStyles:\n\t\t\t\"fade-in w-full rounded-2xl bg-surface p-6 text-fg outline-none data-[entering]:animate-in md:w-md\",\n\t},\n});\n\nconst styles = modal();\n\ninterface ModalProps extends Omit {\n\tclassName?: string;\n}\n\nconst Modal = ({ children, className, ...props }: ModalProps) => (\n\t\n\t\t\n\t\t\t{children}\n\t\t \n\t \n);\n\nconst Dialog = ({ children, className, ...props }: DialogProps) => (\n\t\n\t\t{children}\n\t \n);\n\nconst ModalTrigger = AriaDialogTrigger;\n\nexport { Modal, Dialog, ModalTrigger };\nexport type { ModalProps, DialogProps };",
18 | "type": "registry:component",
19 | "target": "components/ui/modal.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "overlay"
89 | ]
90 | },
91 | "categories": [
92 | "overlays"
93 | ]
94 | }
--------------------------------------------------------------------------------
/web/public/r/breadcrumbs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "breadcrumbs",
4 | "type": "registry:ui",
5 | "title": "breadcrumbs",
6 | "author": "BaseLayer",
7 | "description": "Breadcrumbs display a heirarchy of links to the current page or resource in an application.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/breadcrumbs/breadcrumbs.tsx",
17 | "content": "\"use client\";\n\nimport {\n\tBreadcrumbs as AriaBreadcrumbs,\n\ttype BreadcrumbsProps as AriaBreadcrumbsProps,\n\tBreadcrumb,\n\ttype BreadcrumbProps,\n\tLink,\n\ttype LinkProps,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nconst breadcrumbs = tv({\n\tslots: {\n\t\troot: \"m-0 flex list-none items-center gap-2 p-0 font-md\",\n\t\tlink: \"relative cursor-pointer rounded-md text-fg outline-none ring-focus data-[hovered]:underline data-[focus-visible]:ring-2 [&[aria-current]]:font-extrabold [&[aria-current]]:text-fg\",\n\t\titem: \"flex items-center gap-2\",\n\t},\n});\n\nconst styles = breadcrumbs();\n\nconst Breadcrumbs = ({\n\tchildren,\n\tclassName,\n\t...props\n}: AriaBreadcrumbsProps & { className?: string }) => (\n\t\n\t\t{children}\n\t \n);\n\nconst BreadcrumbsLink = ({\n\tchildren,\n\tclassName,\n\t...props\n}: LinkProps & { className?: string }) => (\n\t \n\t\t{children}\n\t\n);\n\nconst BreadcrumbsItem = ({\n\tchildren,\n\tclassName,\n\t...props\n}: BreadcrumbProps & { className?: string }) => (\n\t\n\t\t{children}\n\t \n);\n\nexport { BreadcrumbsItem, BreadcrumbsLink, Breadcrumbs };",
18 | "type": "registry:component",
19 | "target": "components/ui/breadcrumbs.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "navigation"
89 | ]
90 | },
91 | "categories": [
92 | "navigation"
93 | ]
94 | }
--------------------------------------------------------------------------------
/web/public/r/toggle.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3 | "name": "toggle",
4 | "type": "registry:ui",
5 | "title": "toggle",
6 | "author": "BaseLayer",
7 | "description": "A toggle button allows a user to toggle a selection on or off, for example switching between two states or modes.",
8 | "dependencies": [
9 | "react-aria-components",
10 | "tailwind-variants",
11 | "lucide-react",
12 | "tw-animate-css"
13 | ],
14 | "files": [
15 | {
16 | "path": "src/core/toggle/toggle.tsx",
17 | "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport {\n\tToggleButton,\n\tToggleButtonGroup,\n\ttype ToggleButtonGroupProps,\n\ttype ToggleButtonProps,\n} from \"react-aria-components\";\nimport { tv } from \"tailwind-variants\";\n\nconst toggle = tv({\n\tbase: \"inline-flex appearance-none items-center justify-center rounded-full bg-surface-2 px-4 py-1 font-medium outline-none ring-focus ring-offset-2 ring-offset-surface transition-transform duration-100 disabled:pointer-events-none disabled:opacity-50 data-[selected]:bg-primary data-[selected]:text-primary-fg data-[focus-visible]:ring-2\",\n});\n\nconst toggleGroup = tv({\n\tbase: \"flex min-h-11 gap-1 rounded-full border border-border bg-surface-2 p-1\",\n});\n\ninterface ToggleProps extends Omit {\n\tclassName?: string;\n}\n\nconst Toggle = ({ className, children, ...props }: ToggleProps) => (\n\t\n\t\t{children}\n\t \n);\n\ninterface ToggleGroupProps extends ToggleButtonGroupProps {\n\tclassName?: string;\n\tchildren: ReactNode;\n}\n\nconst ToggleGroup = ({ className, children, ...props }: ToggleGroupProps) => (\n\t\n\t\t{children}\n\t \n);\n\nexport { Toggle, ToggleGroup };\nexport type { ToggleProps, ToggleGroupProps };\n",
18 | "type": "registry:component",
19 | "target": "components/ui/toggle.tsx"
20 | }
21 | ],
22 | "tailwind": {
23 | "config": {
24 | "content": [
25 | "./components/**/*.{ts,tsx}"
26 | ],
27 | "theme": {
28 | "extend": {}
29 | }
30 | }
31 | },
32 | "cssVars": {
33 | "theme": {
34 | "--color-surface": "var(--surface)",
35 | "--color-surface-2": "var(--surface-2)",
36 | "--color-fg": "var(--fg)",
37 | "--color-fg-muted": "var(--fg-muted)",
38 | "--color-fg-disabled": "var(--fg-disabled)",
39 | "--color-fg-inverse": "var(--fg-inverse)",
40 | "--color-border": "var(--border)",
41 | "--color-focus": "var(--focus)",
42 | "--color-primary": "var(--primary)",
43 | "--color-primary-fg": "var(--primary-fg)",
44 | "--color-secondary": "var(--secondary)",
45 | "--color-secondary-fg": "var(--secondary-fg)",
46 | "--color-danger": "var(--danger)",
47 | "--color-danger-fg": "var(--danger-fg)"
48 | },
49 | "light": {
50 | "--surface": "oklch(98.5% 0 0)",
51 | "--surface-2": "oklch(97% 0 0)",
52 | "--fg": "oklch(14.5% 0 0)",
53 | "--fg-muted": "oklch(55.6% 0 0)",
54 | "--fg-inverse": "oklch(98.5% 0 0)",
55 | "--fg-disabled": "oklch(70.8% 0 0)",
56 | "--border": "oklch(87% 0 0)",
57 | "--focus": "oklch(68.5% 0.169 237.323)",
58 | "--primary": "oklch(14.5% 0 0)",
59 | "--primary-fg": "oklch(98.5% 0 0)",
60 | "--secondary": "oklch(92.2% 0 0)",
61 | "--secondary-fg": "oklch(14.5% 0 0)",
62 | "--danger": "oklch(63.7% 0.237 25.331)",
63 | "--danger-fg": "#ffffff"
64 | },
65 | "dark": {
66 | "--surface": "oklch(14.5% 0 0)",
67 | "--surface-2": "oklch(20.5% 0 0)",
68 | "--fg": "oklch(98.5% 0.001 106.423)",
69 | "--fg-muted": "oklch(55.6% 0 0)",
70 | "--fg-inverse": "oklch(98.5% 0 0)",
71 | "--fg-disabled": "oklch(37.1% 0 0)",
72 | "--border": "oklch(37.1% 0 0)",
73 | "--focus": "oklch(68.5% 0.169 237.323)",
74 | "--primary": "oklch(98.5% 0 0)",
75 | "--primary-fg": "oklch(14.5% 0 0)",
76 | "--secondary": "oklch(26.9% 0 0)",
77 | "--secondary-fg": "oklch(98.5% 0 0)",
78 | "--danger": "oklch(63.7% 0.237 25.331)",
79 | "--danger-fg": "oklch(98.5% 0 0)"
80 | }
81 | },
82 | "css": {
83 | "@import \"tw-animate-css\"": {}
84 | },
85 | "meta": {
86 | "status": "stable",
87 | "tags": [
88 | "form",
89 | "interactive",
90 | "toggle"
91 | ]
92 | },
93 | "categories": [
94 | "forms"
95 | ]
96 | }
--------------------------------------------------------------------------------