├── .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 | 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 | 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 | 7 | 8 | 9 | 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 | 9 | 10 | 11 | 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 | 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 | 15 | 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 | 10 | This is a helpful tooltip message 11 | 12 | 13 | 14 | 17 | Settings 18 | 19 | 20 | 21 | 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 | 13 | 14 | 15 | Edit Name 16 |
17 | 18 |
19 | 22 | 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 | 13 | 14 | 15 | Edit Name 16 |
17 | 18 |
19 | 22 | 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 | 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 && } 25 | {valueText} 26 |
27 |
28 |
32 |
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 | 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 |
16 |
17 | 22 | 27 |
28 |
29 |
30 | 31 | 32 | 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 | 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 | 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 && } 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 | 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 && } 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 && } 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 | 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 | 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 && } 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 |
41 |
42 |
43 | BaseLayer 50 | 51 | 52 | 53 | {/* Desktop title - hidden on mobile, visible on desktop */} 54 | 58 | BaseLayer 59 | 60 |
61 | 62 |
63 | 68 | 69 | 70 | 71 |
72 |
73 |
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 | 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 | 52 | 56 | 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 | 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 | 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 | 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 | 36 | 42 | 43 | 44 |
45 |
46 | BaseLayer 53 | 54 | BaseLayer 55 | 56 |
57 |
58 | 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 && } 59 |
60 | 61 | 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 | 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 | 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 |
    {children}
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 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 && }\n\t\t\t\t\t{valueText}\n\t\t\t\t
    \n\t\t\t\t
    \n\t\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\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 | } --------------------------------------------------------------------------------