`),带底部边框 |
+| `TableHead` | 头部单元格(``),粗体文字 |
+| `TableCell` | 数据单元格(` `) |
+| `TableCaption` | 表格标题(``) |
+
+## 属性
+
+所有子组件均接受:
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `class` | `string` | — |
diff --git a/apps/docs/components/tabs.md b/apps/docs/components/tabs.md
new file mode 100644
index 0000000..7f4d2ad
--- /dev/null
+++ b/apps/docs/components/tabs.md
@@ -0,0 +1,89 @@
+---
+title: Tabs 标签页
+description: 选项卡组件,提供按压切换动画和极高辨识度的激活状态边框。
+---
+
+# Tabs 标签页
+
+基于 reka-ui 的 Tabs 原语构建的新粗野主义风格标签页导航组件。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+ Account
+ Password
+
+
+ Manage your account settings.
+
+
+ Change your password here.
+
+
+
+```
+
+## 子组件
+
+| 组件 | 说明 |
+|------|------|
+| `Tabs` | 根组件(从 reka-ui 重新导出为 `TabsRoot`) |
+| `TabsList` | 标签触发器容器 |
+| `TabsTrigger` | 可点击的标签按钮 |
+| `TabsContent` | 每个标签的内容面板 |
+
+## 属性
+
+### Tabs
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `defaultValue` | `string` | — |
+| `modelValue` | `string` | — |
+
+### TabsTrigger
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `value` | `string` | —(必填) |
+| `disabled` | `boolean` | — |
+| `class` | `string` | — |
+
+### TabsContent
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `value` | `string` | —(必填) |
+| `class` | `string` | — |
+
+### TabsList
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `class` | `string` | — |
+
+## 无障碍
+
+- 方向键在标签触发器之间导航
+- 标签内容通过 ARIA 属性与其触发器关联
+- 激活标签具有 `aria-selected="true"`
diff --git a/apps/docs/components/tags-input.md b/apps/docs/components/tags-input.md
new file mode 100644
index 0000000..d2ad1d0
--- /dev/null
+++ b/apps/docs/components/tags-input.md
@@ -0,0 +1,86 @@
+---
+title: TagsInput 标签输入
+description: 标签输入框组件,用于输入或粘贴以添加标签、分类,支持键盘快捷键和退格键删除。
+---
+
+# TagsInput 标签输入
+
+新粗野主义风格的标签录入组件,常用于文章标签、邮件收件人、关键词筛选等表单场景。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+ {{ tag }}
+
+
+
+
+
+```
+
+## 变体
+
+可以使用 `TagsInputItem` 的 `variant` 属性定制单个标签的配色方案:
+
+| 变体 | 颜色说明 |
+|------|----------|
+| `primary` | 默认珊瑚红背景,配黑色粗边框 |
+| `secondary` | 薄荷青背景 |
+| `accent` | 粗野黄色背景 |
+| `success` | 经典绿色背景 |
+| `danger` | 经典红色背景 |
+| `default` | 纯白背景 |
+
+```vue
+
+
+ CSS
+
+
+
+```
+
+## Props
+
+### TagsInput Props
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `modelValue` | `string[]` | `[]` | 标签数据列表 |
+| `disabled` | `boolean` | `false` | 是否禁用输入 |
+| `max` | `number` | — | 最大允许标签数 |
+| `addOnPaste` | `boolean` | `false` | 是否在粘贴时根据分词自动添加标签 |
+
+### TagsInputItem Props
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `value` | `string` | — | 标签值 (必填) |
+| `variant` | `'default' \| 'primary' \| 'secondary' \| 'accent' \| 'danger' \| 'success'` | `'primary'` | 标签的视觉配色变体 |
+| `disabled` | `boolean` | `false` | 是否禁用此标签 |
diff --git a/apps/docs/components/textarea.md b/apps/docs/components/textarea.md
new file mode 100644
index 0000000..aa57402
--- /dev/null
+++ b/apps/docs/components/textarea.md
@@ -0,0 +1,111 @@
+---
+title: Textarea 文本域
+description: 多行文本输入域,支持自适应高度或固定行高,硬边框外观。
+---
+
+# Textarea 文本域
+
+新粗野主义风格多行文本输入框,支持变体、尺寸和 v-model。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+```
+
+## 变体
+
+| 变体 | 说明 |
+|------|------|
+| `default` | 标准边框 |
+| `error` | 危险色边框,主色聚焦阴影 |
+| `success` | 成功色边框,辅助色聚焦阴影 |
+
+## 尺寸
+
+| 尺寸 | 内边距 | 字体大小 |
+|------|--------|----------|
+| `sm` | `px-3 py-2` | `text-sm` |
+| `default` | `px-4 py-3` | `text-base` |
+| `lg` | `px-5 py-4` | `text-lg` |
+
+## 带标签
+
+```vue
+
+
+
+
+ Bio
+
+
+
+```
+
+## 禁用状态
+
+```vue
+
+
+
+
+
+```
+
+## 属性
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `modelValue` | `string` | — |
+| `variant` | `'default' \| 'error' \| 'success'` | `'default'` |
+| `textareaSize` | `'sm' \| 'default' \| 'lg'` | `'default'` |
+| `disabled` | `boolean` | `false` |
+| `placeholder` | `string` | — |
+| `class` | `string` | — |
+
+## 事件
+
+| 事件 | 载荷 |
+|------|------|
+| `update:modelValue` | `string` |
+
+## 样式
+
+Textarea 默认设置了 `resize-none`。如需允许调整大小,可通过自定义 class 覆盖:
+
+```vue
+
+
+
+
+
+```
diff --git a/apps/docs/components/theme-provider.tsx b/apps/docs/components/theme-provider.tsx
deleted file mode 100644
index 0e6efbb..0000000
--- a/apps/docs/components/theme-provider.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { ThemeProvider as NextThemesProvider } from 'next-themes';
-import type { ThemeProviderProps } from 'next-themes/dist/types';
-
-export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
- return {children} ;
-}
diff --git a/apps/docs/components/theme-toggle.tsx b/apps/docs/components/theme-toggle.tsx
deleted file mode 100644
index 888b379..0000000
--- a/apps/docs/components/theme-toggle.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { useTheme } from 'next-themes';
-import { Moon, Sun } from 'lucide-react';
-import { Button } from '@/components/ui';
-
-export function ThemeToggle() {
- const { theme, setTheme } = useTheme();
- const [mounted, setMounted] = React.useState(false);
-
- React.useEffect(() => {
- setMounted(true);
- }, []);
-
- if (!mounted) {
- return (
-
-
-
- );
- }
-
- return (
- setTheme(theme === 'dark' ? 'light' : 'dark')}
- >
- {theme === 'dark' ? : }
- Toggle theme
-
- );
-}
diff --git a/apps/docs/components/timeline.md b/apps/docs/components/timeline.md
new file mode 100644
index 0000000..a5b03d1
--- /dev/null
+++ b/apps/docs/components/timeline.md
@@ -0,0 +1,89 @@
+---
+title: Timeline 时间线
+description: 时间线组件,用于垂直或水平展示一系列时间事件、里程碑或活动日志。
+---
+
+# Timeline 时间线
+
+以一条主线串联的流式信息展示组件,适用于构建企业发展里程碑、版本发布日志、以及任务审批进度流程。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+
+
+ 1
+
+
+
+ 第一阶段
+ 这里是第一阶段的信息说明。
+
+
+
+
+
+
+ 2
+
+
+ 第二阶段
+ 这里是第二阶段的信息说明。
+
+
+
+
+```
+
+## 节点属性配置
+
+`TimelineDot` 支持多样化的新粗野主义几何设计与颜色主题,通过 `shape` 和 `variant` 进行自定义:
+
+### 形状选择 (`shape`)
+- `circle` (默认):经典的圆形小徽章。
+- `square`:方正的硬朗线条卡片。
+- `diamond`:斜向 45 度旋转的菱形(文字插槽内容会被内部自动反向微调,以防排版发生倾斜)。
+
+### 配色变体 (`variant`)
+- `'primary' | 'secondary' | 'accent' | 'success' | 'danger' | 'default'`
+
+## Props
+
+### Timeline Props
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | 时间线排版布局朝向 |
+| `class` | `string` | `""` | 整体包裹容器自定义样式类 |
+
+### TimelineDot Props
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `variant` | `'default' \| 'primary' \| 'secondary' \| 'accent' \| 'success' \| 'danger'` | `'accent'` | 配色变体 |
+| `shape` | `'circle' \| 'square' \| 'diamond'` | `'circle'` | 几何形态变体 |
+| `class` | `string` | `""` | 节点微标的自定义样式类 |
diff --git a/apps/docs/components/toast.md b/apps/docs/components/toast.md
new file mode 100644
index 0000000..a762356
--- /dev/null
+++ b/apps/docs/components/toast.md
@@ -0,0 +1,138 @@
+---
+title: Toast 轻提示
+description: 全局通知气泡组件,支持成功、失败、信息等多种类型的通知分发。
+---
+
+# Toast 轻提示
+
+新粗野主义风格通知提示系统,提供 `useToast` 组合式函数、5 种变体和 `ToastContainer` 用于渲染。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+### 1. 在应用中添加 ToastContainer
+
+将 `ToastContainer` 放置在根 `App.vue` 中:
+
+```vue
+
+
+
+
+
+
+```
+
+### 2. 使用 useToast 组合式函数
+
+```vue
+
+
+
+ Save
+ Trigger Error
+
+```
+
+## 变体
+
+| 变体 | 背景 | 文字 |
+|------|------|------|
+| `default` | `bg-brutal-bg` | `text-brutal-fg` |
+| `success` | `bg-brutal-success` | `text-black` |
+| `error` | `bg-brutal-destructive` | `text-white` |
+| `warning` | `bg-brutal-accent` | `text-black` |
+| `info` | `bg-brutal-secondary` | `text-black` |
+
+## 尺寸
+
+| 尺寸 | 最大宽度 |
+|------|----------|
+| `sm` | `max-w-xs` |
+| `default` | `max-w-sm` |
+| `lg` | `max-w-md` |
+
+## useToast API
+
+```ts
+const {
+ toasts, // Ref - 响应式提示列表
+ addToast, // (toast: Omit) => string
+ removeToast, // (id: string) => void
+ clearToasts, // () => void
+ success, // (title: string, description?: string) => string
+ error, // (title: string, description?: string) => string
+ warning, // (title: string, description?: string) => string
+ info, // (title: string, description?: string) => string
+} = useToast()
+```
+
+## 自定义提示
+
+```vue
+
+```
+
+## ToastItem 类型
+
+```ts
+interface ToastItem {
+ id: string
+ variant?: 'default' | 'success' | 'error' | 'warning' | 'info'
+ title?: string
+ description?: string
+ duration?: number
+}
+```
+
+## 属性
+
+### Toast
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `variant` | `'default' \| 'success' \| 'error' \| 'warning' \| 'info'` | `'default'` |
+| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` |
+| `class` | `string` | — |
+
+### ToastContainer
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `class` | `string` | — |
diff --git a/apps/docs/components/toggle-group.md b/apps/docs/components/toggle-group.md
new file mode 100644
index 0000000..78eebae
--- /dev/null
+++ b/apps/docs/components/toggle-group.md
@@ -0,0 +1,103 @@
+---
+title: Toggle Group 切换组
+description: 切换按钮组,支持单选或多选组合,适合控制视图或排版。
+---
+
+# Toggle Group 切换组
+
+基于 reka-ui 的 ToggleGroup 原语构建的新粗野主义风格切换按钮组,支持单选或多选。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+### 单选
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 多选
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 属性
+
+### ToggleGroup
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `type` | `'single' \| 'multiple'` | —(必填) |
+| `modelValue` | `string \| string[]` | — |
+| `variant` | `'default' \| 'outline'` | `'default'` |
+| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` |
+| `disabled` | `boolean` | — |
+| `class` | `string` | — |
+
+### ToggleGroupItem
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `value` | `string` | —(必填) |
+| `disabled` | `boolean` | — |
+| `class` | `string` | — |
+
+## 事件
+
+### ToggleGroup
+
+| 事件 | 载荷 |
+|------|------|
+| `update:modelValue` | `string \| string[]` |
diff --git a/apps/docs/components/toggle.md b/apps/docs/components/toggle.md
new file mode 100644
index 0000000..7265539
--- /dev/null
+++ b/apps/docs/components/toggle.md
@@ -0,0 +1,84 @@
+---
+title: Toggle 切换
+description: 独立切换按钮,用于表示启用/关闭或选中/未选中状态。
+---
+
+# Toggle 切换
+
+基于 reka-ui 的 Toggle 原语构建的新粗野主义风格切换按钮,支持按下状态。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 变体
+
+| 变体 | 说明 |
+|------|------|
+| `default` | 带背景和小阴影,按下时为主色背景 |
+| `outline` | 透明带边框,按下时为辅助色背景 |
+
+## 尺寸
+
+| 尺寸 | 高度 | 最小宽度 | 字体大小 |
+|------|------|----------|----------|
+| `sm` | `h-8` | `min-w-8` | `text-xs` |
+| `default` | `h-10` | `min-w-10` | `text-sm` |
+| `lg` | `h-12` | `min-w-12` | `text-sm` |
+
+## 属性
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `variant` | `'default' \| 'outline'` | `'default'` |
+| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` |
+| `pressed` | `boolean` | — |
+| `defaultValue` | `boolean` | — |
+| `disabled` | `boolean` | — |
+| `class` | `string` | — |
+
+## 事件
+
+| 事件 | 载荷 |
+|------|------|
+| `update:pressed` | `boolean` |
+
+## 样式
+
+- **未按下**:带背景和小阴影,悬停时上浮并增大阴影
+- **按下**:主色/辅助色背景,无阴影,向下偏移(按下偏移量)
+- **禁用**:降低不透明度,显示禁止光标
diff --git a/apps/docs/components/tooltip.md b/apps/docs/components/tooltip.md
new file mode 100644
index 0000000..1413e36
--- /dev/null
+++ b/apps/docs/components/tooltip.md
@@ -0,0 +1,126 @@
+---
+title: Tooltip 工具提示
+description: 工具提示浮层组件,悬停或聚焦时快速展示辅助文本。
+---
+
+# Tooltip 工具提示
+
+基于 reka-ui 的 Tooltip 原语构建的新粗野主义风格工具提示,悬停时显示信息文字。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+
+ Hover me
+
+
+ This is a tooltip
+
+
+
+
+```
+
+## 配合 Provider 使用
+
+用 `TooltipProvider` 包裹你的应用(或某个区域)以启用工具提示:
+
+```vue
+
+
+
+
+
+
+
+```
+
+## 触发延迟
+
+通过 `TooltipProvider` 的 `delayDuration` 属性控制从指针进入触发元素到工具提示打开的延迟时间(毫秒)。也可以在单个 `Tooltip` 上通过 `delayDuration` 覆盖全局设置。
+
+```vue
+
+
+
+ 立即显示
+
+
+ 无延迟
+
+
+
+
+
+
+
+ 长延迟
+
+
+ 覆盖为 1500ms
+
+
+
+```
+
+## 子组件
+
+| 组件 | 说明 |
+|------|------|
+| `TooltipProvider` | 上下文提供者(必需的祖先组件) |
+| `Tooltip` | 根组件(从 reka-ui 重新导出为 `TooltipRoot`) |
+| `TooltipTrigger` | 悬停时触发工具提示的元素 |
+| `TooltipContent` | 工具提示内容面板 |
+
+## 属性
+
+### TooltipProvider
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `delayDuration` | `number` | `700` | 指针进入触发元素后到工具提示打开的延迟时间(毫秒) |
+| `skipDelayDuration` | `number` | `300` | 从一个工具提示移到另一个时跳过延迟的时间窗口(毫秒) |
+| `disableHoverableContent` | `boolean` | `false` | 为 `true` 时,指针移入内容区域会关闭工具提示 |
+
+### Tooltip
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `delayDuration` | `number` | `700` | 覆盖 Provider 的延迟时间,针对单个工具提示自定义 |
+| `disableHoverableContent` | `boolean` | `false` | 继承自 Provider,可单独覆盖 |
+| `disableClosingTrigger` | `boolean` | `false` | 为 `true` 时,点击触发元素不会关闭工具提示 |
+| `disabled` | `boolean` | `false` | 为 `true` 时,禁用工具提示 |
+
+### TooltipContent
+
+| 属性 | 类型 | 默认值 |
+|------|------|--------|
+| `class` | `string` | — |
+| `sideOffset` | `number` | — |
+
+## 无障碍
+
+- 工具提示在悬停和聚焦时出现
+- 按 `Escape` 键关闭工具提示
+- 工具提示内容可被屏幕阅读器朗读
diff --git a/apps/docs/components/tree-view.md b/apps/docs/components/tree-view.md
new file mode 100644
index 0000000..3d480ff
--- /dev/null
+++ b/apps/docs/components/tree-view.md
@@ -0,0 +1,79 @@
+---
+title: TreeView 树形目录
+description: 递归树形组件,支持节点展开折叠、单选、图标、自定义缩进,适合文件系统、分类层级等场景。
+---
+
+# TreeView 树形目录
+
+可递归展开的层级数据可视化组件,内置文件夹 / 文件图标,支持键盘导航与受控单选状态。
+
+## 预览
+
+
+
+
+
+## 安装
+
+
+
+## 用法
+
+```vue
+
+
+
+
+
+```
+
+## TreeNode 类型
+
+```ts
+interface TreeNode {
+ id: string // 唯一标识
+ label: string // 显示文本
+ children?: TreeNode[] // 子节点(省略则为叶节点)
+ data?: unknown // 自定义附加数据
+}
+```
+
+## Props
+
+### TreeView Props
+
+| 属性 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `nodes` | `TreeNode[]` | — | 树形数据源 |
+| `modelValue` | `string \| null` | `null` | 当前选中节点的 id(v-model) |
+| `defaultExpanded` | `string[]` | `[]` | 初始展开的节点 id 列表 |
+| `class` | `string` | — | 根节点自定义样式类 |
+
+## 事件
+
+| 事件 | 参数 | 说明 |
+|------|------|------|
+| `update:modelValue` | `string \| null` | 选中节点 id 变更 |
+| `select` | `TreeNode` | 点击任意节点时触发 |
+| `expand` | `(id: string, expanded: boolean)` | 展开 / 折叠节点时触发 |
diff --git a/apps/docs/components/ui/alert-dialog.tsx b/apps/docs/components/ui/alert-dialog.tsx
deleted file mode 100644
index 8fec28b..0000000
--- a/apps/docs/components/ui/alert-dialog.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
-
-import { cn } from '@/lib/utils';
-import { buttonVariants } from '@/components/ui/button';
-
-const AlertDialog = AlertDialogPrimitive.Root;
-
-const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
-
-const AlertDialogPortal = AlertDialogPrimitive.Portal;
-
-const AlertDialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
-
-const AlertDialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-));
-AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
-
-const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-AlertDialogHeader.displayName = 'AlertDialogHeader';
-
-const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-AlertDialogFooter.displayName = 'AlertDialogFooter';
-
-const AlertDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
-
-const AlertDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
-
-const AlertDialogAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- variant?: 'primary' | 'secondary' | 'accent' | 'danger' | 'outline';
- }
->(({ className, variant = 'primary', ...props }, ref) => (
-
-));
-AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
-
-const AlertDialogCancel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
-
-export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
-};
diff --git a/apps/docs/components/ui/alert.tsx b/apps/docs/components/ui/alert.tsx
deleted file mode 100644
index 2c3114b..0000000
--- a/apps/docs/components/ui/alert.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const alertVariants = cva(
- [
- 'relative w-full p-4',
- 'border-3 border-black dark:border-gray-600',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#64748b]',
- '[&>svg~*]:pl-8 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4',
- '[&>svg]:h-5 [&>svg]:w-5 [&>svg]:stroke-[2.5]',
- ],
- {
- variants: {
- variant: {
- default: 'bg-white text-black dark:bg-gray-900 dark:text-gray-100',
- primary: 'bg-[#FF6B6B] text-black',
- secondary: 'bg-[#4ECDC4] text-black',
- success: 'bg-[#7FB069] text-black',
- warning: 'bg-[#FFE66D] text-black',
- danger: 'bg-[#EF476F] text-white',
- info: 'bg-[#4A90D9] text-white',
- },
- },
- defaultVariants: {
- variant: 'default',
- },
- }
-);
-
-export interface AlertProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-const Alert = React.forwardRef(
- ({ className, variant, ...props }, ref) => (
-
- )
-);
-Alert.displayName = 'Alert';
-
-const AlertTitle = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-AlertTitle.displayName = 'AlertTitle';
-
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-AlertDescription.displayName = 'AlertDescription';
-
-export { Alert, AlertTitle, AlertDescription, alertVariants };
diff --git a/apps/docs/components/ui/auth-card.tsx b/apps/docs/components/ui/auth-card.tsx
deleted file mode 100644
index 46948ca..0000000
--- a/apps/docs/components/ui/auth-card.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import * as React from 'react';
-import { Mail, Lock, Github, Chrome } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-
-export interface AuthCardProps extends React.HTMLAttributes {
- title?: string;
- description?: string;
- onLoginSubmit?: (e: React.FormEvent) => void;
- onForgotPasswordClick?: () => void;
- onGoogleClick?: () => void;
- onGithubClick?: () => void;
-}
-
-export function AuthCard({
- title = 'Welcome back',
- description = 'Enter your credential tags to access your custom developer workspace console.',
- onLoginSubmit,
- onForgotPasswordClick,
- onGoogleClick,
- onGithubClick,
- className,
- ...props
-}: AuthCardProps) {
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- if (onLoginSubmit) onLoginSubmit(e);
- };
-
- return (
-
-
-
-
- {title}
-
-
- {description}
-
-
-
-
-
-
-
- Google
-
-
-
- GitHub
-
-
-
-
-
-
-
-
- or email login
-
-
-
-
-
-
-
-
- Don't have an account?{' '}
-
- Register workspace
-
-
-
-
-
- );
-}
diff --git a/apps/docs/components/ui/avatar.tsx b/apps/docs/components/ui/avatar.tsx
deleted file mode 100644
index c861b0b..0000000
--- a/apps/docs/components/ui/avatar.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const avatarVariants = cva(
- ['relative flex shrink-0 overflow-hidden', 'border-3 border-black', 'bg-gray-100'],
- {
- variants: {
- size: {
- sm: 'h-8 w-8',
- default: 'h-10 w-10',
- lg: 'h-14 w-14',
- xl: 'h-20 w-20',
- },
- shape: {
- square: '',
- rounded: 'rounded-lg',
- },
- },
- defaultVariants: {
- size: 'default',
- shape: 'square',
- },
- }
-);
-
-export interface AvatarProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-const Avatar = React.forwardRef(
- ({ className, size, shape, ...props }, ref) => (
-
- )
-);
-Avatar.displayName = 'Avatar';
-
-const AvatarImage = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-AvatarImage.displayName = 'AvatarImage';
-
-const AvatarFallback = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-AvatarFallback.displayName = 'AvatarFallback';
-
-export { Avatar, AvatarImage, AvatarFallback, avatarVariants };
diff --git a/apps/docs/components/ui/badge.tsx b/apps/docs/components/ui/badge.tsx
deleted file mode 100644
index 487656d..0000000
--- a/apps/docs/components/ui/badge.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const badgeVariants = cva(
- [
- 'inline-flex items-center',
- 'border-2 border-black',
- 'font-bold tracking-wide',
- 'transition-colors',
- ],
- {
- variants: {
- variant: {
- default: 'bg-white text-black shadow-[2px_2px_0px_0px_#000000]',
- primary: 'bg-[#FF6B6B] text-black shadow-[2px_2px_0px_0px_#000000]',
- secondary: 'bg-[#4ECDC4] text-black shadow-[2px_2px_0px_0px_#000000]',
- accent: 'bg-[#FFE66D] text-black shadow-[2px_2px_0px_0px_#000000]',
- danger: 'bg-[#EF476F] text-white shadow-[2px_2px_0px_0px_#000000]',
- success: 'bg-[#7FB069] text-black shadow-[2px_2px_0px_0px_#000000]',
- outline: 'bg-transparent text-black dark:text-white',
- },
- size: {
- sm: 'px-2 py-0.5 text-xs',
- default: 'px-3 py-1 text-sm',
- lg: 'px-4 py-1.5 text-base',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- }
-);
-
-export interface BadgeProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-const Badge = React.forwardRef(
- ({ className, variant, size, ...props }, ref) => (
-
- )
-);
-Badge.displayName = 'Badge';
-
-export { Badge, badgeVariants };
diff --git a/apps/docs/components/ui/brutalist-hero.tsx b/apps/docs/components/ui/brutalist-hero.tsx
deleted file mode 100644
index 02b05e1..0000000
--- a/apps/docs/components/ui/brutalist-hero.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import * as React from 'react';
-import { ArrowRight, Sparkles } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent } from '@/components/ui/card';
-
-export interface BrutalistHeroProps extends React.HTMLAttributes {
- title?: string;
- subtitle?: string;
- primaryCtaText?: string;
- secondaryCtaText?: string;
- onPrimaryCtaClick?: () => void;
- onSecondaryCtaClick?: () => void;
-}
-
-export function BrutalistHero({
- title = 'Build Bold Interfaces Faster with BrutxUI',
- subtitle = 'A hyper-responsive, neo-brutalist component registry built with Radix UI and Tailwind CSS. Designed for devs who dare to be different.',
- primaryCtaText = 'Get Started Now',
- secondaryCtaText = 'View Component Registry',
- onPrimaryCtaClick,
- onSecondaryCtaClick,
- className,
- ...props
-}: BrutalistHeroProps) {
- return (
-
-
-
-
-
-
- V0.2.1 Release
-
-
-
- {title}
-
-
-
- {subtitle}
-
-
-
-
- {primaryCtaText}
-
-
-
- {secondaryCtaText}
-
-
-
-
-
-
-
-
-
- ~/projects/my-bold-saas
-
- $
- npx brutx init
-
- Custom Tailwind design tokens configured
- Global CSS token layer synchronized
-
- $
- npx brutx add hero pricing auth-card
-
- Successfully added 3 blocks into /components/ui!
-
-
-
-
-
-
- );
-}
diff --git a/apps/docs/components/ui/button.tsx b/apps/docs/components/ui/button.tsx
deleted file mode 100644
index 60d2431..0000000
--- a/apps/docs/components/ui/button.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-import { Loader2 } from 'lucide-react';
-
-const buttonVariants = cva(
- [
- 'inline-flex items-center justify-center gap-2',
- 'border-3 border-black dark:border-white',
- 'font-black tracking-wide',
- 'transition-all duration-150',
- 'focus:outline-none focus:ring-2 focus:ring-black dark:focus:ring-white focus:ring-offset-2',
- 'disabled:opacity-50 disabled:pointer-events-none',
- 'active:translate-y-0.5 active:shadow-none',
- ],
- {
- variants: {
- variant: {
- default: [
- 'bg-white dark:bg-gray-900 text-black dark:text-white',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- primary: [
- 'bg-[#FF6B6B] text-black',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- secondary: [
- 'bg-[#4ECDC4] text-black',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- accent: [
- 'bg-[#FFE66D] text-black',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- danger: [
- 'bg-[#EF476F] text-white',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- success: [
- 'bg-[#7FB069] text-black',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- outline: [
- 'bg-transparent text-black dark:text-white',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:bg-black dark:hover:bg-white hover:text-white dark:hover:text-black',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- ],
- ghost: [
- 'bg-transparent text-black dark:text-white border-transparent',
- 'shadow-none',
- 'hover:bg-gray-100 dark:hover:bg-gray-800 hover:border-black dark:hover:border-white',
- ],
- link: [
- 'bg-transparent text-black dark:text-white border-transparent',
- 'shadow-none underline-offset-4',
- 'hover:underline',
- ],
- },
- size: {
- sm: 'h-9 px-3 py-1 text-sm',
- default: 'h-11 px-5 py-2 text-base',
- lg: 'h-13 px-8 py-3 text-lg',
- xl: 'h-16 px-10 py-4 text-xl',
- icon: 'h-11 w-11 p-0',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- }
-);
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean;
- loading?: boolean;
-}
-
-const Button = React.forwardRef(
- (
- {
- className,
- variant,
- size,
- asChild = false,
- loading = false,
- disabled,
- children,
- ...props
- },
- ref
- ) => {
- const Comp = asChild ? Slot : 'button';
- const isDisabled = disabled || loading;
-
- return (
-
- {loading ? (
- <>
-
- {children}
- >
- ) : (
- children
- )}
-
- );
- }
-);
-Button.displayName = 'Button';
-
-export { Button, buttonVariants };
diff --git a/apps/docs/components/ui/calendar.tsx b/apps/docs/components/ui/calendar.tsx
deleted file mode 100644
index 2e1712c..0000000
--- a/apps/docs/components/ui/calendar.tsx
+++ /dev/null
@@ -1,251 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from 'lucide-react';
-import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
-
-import { cn } from '@/lib/utils';
-import { Button, buttonVariants } from './button';
-
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- captionLayout = 'label',
- buttonVariant = 'outline',
- formatters,
- components,
- ...props
-}: React.ComponentProps & {
- buttonVariant?: React.ComponentProps['variant'];
-}) {
- const defaultClassNames = getDefaultClassNames();
-
- return (
- date.toLocaleString('default', { month: 'short' }),
- ...formatters,
- }}
- classNames={{
- root: cn('w-fit select-none', defaultClassNames.root),
- months: cn('flex gap-2 sm:gap-4 flex-col', defaultClassNames.months),
- month: cn('flex flex-col gap-1 sm:gap-2', defaultClassNames.month),
- nav: cn(
- 'flex items-center gap-1 absolute top-0 inset-x-0 justify-between z-10',
- defaultClassNames.nav
- ),
- button_previous: cn(
- buttonVariants({ variant: buttonVariant, size: 'icon' }),
- 'h-6 w-6 sm:h-7 sm:w-7 p-0 select-none',
- 'border-2 border-black dark:border-white',
- 'shadow-[2px_2px_0px_0px_#000000] dark:shadow-[2px_2px_0px_0px_#FFFFFF]',
- 'hover:translate-x-[1px] hover:translate-y-[1px] hover:shadow-[1px_1px_0px_0px_#000000]',
- 'transition-all duration-100',
- 'aria-disabled:opacity-50',
- defaultClassNames.button_previous
- ),
- button_next: cn(
- buttonVariants({ variant: buttonVariant, size: 'icon' }),
- 'h-6 w-6 sm:h-7 sm:w-7 p-0 select-none',
- 'border-2 border-black dark:border-white',
- 'shadow-[2px_2px_0px_0px_#000000] dark:shadow-[2px_2px_0px_0px_#FFFFFF]',
- 'hover:translate-x-[1px] hover:translate-y-[1px] hover:shadow-[1px_1px_0px_0px_#000000]',
- 'transition-all duration-100',
- 'aria-disabled:opacity-50',
- defaultClassNames.button_next
- ),
- month_caption: cn(
- 'flex items-center justify-center h-6 sm:h-7 relative',
- 'font-black text-xs sm:text-sm tracking-tight uppercase text-black dark:text-gray-100',
- defaultClassNames.month_caption
- ),
- dropdowns: cn(
- 'flex items-center text-[10px] sm:text-xs font-bold justify-center gap-1',
- defaultClassNames.dropdowns
- ),
- dropdown_root: cn(
- 'relative border-2 border-black dark:border-white bg-white text-black dark:bg-gray-800 dark:text-gray-100',
- 'shadow-[2px_2px_0px_0px_#000000] dark:shadow-[2px_2px_0px_0px_#FFFFFF]',
- 'focus-within:ring-2 focus-within:ring-offset-1 focus-within:ring-black',
- defaultClassNames.dropdown_root
- ),
- dropdown: cn(
- 'absolute inset-0 opacity-0 cursor-pointer',
- defaultClassNames.dropdown
- ),
- caption_label: cn(
- 'select-none font-black tracking-tight text-black dark:text-gray-100',
- captionLayout === 'label'
- ? 'text-xs sm:text-sm'
- : 'pl-1 sm:pl-1.5 pr-0.5 flex items-center gap-0.5 sm:gap-1 text-[10px] sm:text-xs h-6 sm:h-7 [&>svg]:size-2.5 sm:[&>svg]:size-3',
- defaultClassNames.caption_label
- ),
- table: 'w-full border-collapse border-spacing-0',
- weekdays: cn('', defaultClassNames.weekdays),
- weekday: cn(
- 'text-black dark:text-white font-black text-[8px] sm:text-[10px] select-none uppercase tracking-wide',
- 'h-6 w-6 sm:h-8 sm:w-8 text-center',
- 'bg-[#FFE66D] dark:bg-[#FFE66D] dark:text-black',
- 'border sm:border-2 border-black dark:border-black',
- defaultClassNames.weekday
- ),
- week: cn('', defaultClassNames.week),
- week_number_header: cn(
- 'select-none h-6 w-6 sm:h-8 sm:w-8 text-center font-black text-[8px] sm:text-[10px]',
- 'bg-[#A8E6CF] border sm:border-2 border-black',
- defaultClassNames.week_number_header
- ),
- week_number: cn(
- 'text-[8px] sm:text-[10px] select-none font-black text-black',
- 'h-6 w-6 sm:h-8 sm:w-8 text-center align-middle',
- 'bg-[#A8E6CF] border sm:border-2 border-black',
- defaultClassNames.week_number
- ),
- day: cn(
- 'relative h-6 w-6 sm:h-8 sm:w-8 p-0 text-center select-none',
- 'text-black dark:text-gray-100 border border-black/10 dark:border-white/10',
- defaultClassNames.day
- ),
- range_start: cn(
- '[&>button]:bg-[#FF6B6B] [&>button]:border-2 [&>button]:border-black [&>button]:font-black',
- defaultClassNames.range_start
- ),
- range_middle: cn(
- '[&>button]:bg-[#FFE66D] [&>button]:text-black',
- defaultClassNames.range_middle
- ),
- range_end: cn(
- '[&>button]:bg-[#FF6B6B] [&>button]:border-2 [&>button]:border-black [&>button]:font-black',
- defaultClassNames.range_end
- ),
- today: cn(
- '[&>button]:bg-[#4ECDC4] [&>button]:text-black [&>button]:font-black',
- '[&>button]:border-2 [&>button]:border-black',
- defaultClassNames.today
- ),
- outside: cn(
- 'text-gray-400 dark:text-gray-600 opacity-40',
- defaultClassNames.outside
- ),
- disabled: cn(
- 'text-gray-400 dark:text-gray-600 opacity-40 cursor-not-allowed',
- 'bg-gray-100 dark:bg-gray-800',
- defaultClassNames.disabled
- ),
- hidden: cn('invisible', defaultClassNames.hidden),
- ...classNames,
- }}
- components={{
- Root: ({ className, rootRef, ...props }) => {
- return (
-
- );
- },
- Chevron: ({ className, orientation, ...props }) => {
- if (orientation === 'left') {
- return (
-
- );
- }
-
- if (orientation === 'right') {
- return (
-
- );
- }
-
- return (
-
- );
- },
- DayButton: CalendarDayButton,
- WeekNumber: ({ children, ...props }) => {
- return (
-
-
- {children}
-
-
- );
- },
- ...components,
- }}
- {...props}
- />
- );
-}
-
-function CalendarDayButton({
- className,
- day,
- modifiers,
- ...props
-}: React.ComponentProps) {
- const defaultClassNames = getDefaultClassNames();
-
- const ref = React.useRef(null);
- React.useEffect(() => {
- if (modifiers.focused) ref.current?.focus();
- }, [modifiers.focused]);
-
- return (
-
- );
-}
-
-export { Calendar, CalendarDayButton };
diff --git a/apps/docs/components/ui/card.tsx b/apps/docs/components/ui/card.tsx
deleted file mode 100644
index 838167e..0000000
--- a/apps/docs/components/ui/card.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const cardVariants = cva(
- [
- 'border-3 border-black dark:border-white',
- 'bg-white dark:bg-gray-900 dark:text-white',
- 'transition-all duration-150',
- ],
- {
- variants: {
- variant: {
- default: 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- elevated: 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- flat: 'shadow-none',
- interactive: [
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:shadow-[6px_6px_0px_0px_#000000] dark:hover:shadow-[6px_6px_0px_0px_#FFFFFF] hover:-translate-x-0.5 hover:-translate-y-0.5',
- 'cursor-pointer',
- ],
- primary: 'shadow-[4px_4px_0px_0px_#FF6B6B] border-[#FF6B6B]',
- secondary: 'shadow-[4px_4px_0px_0px_#4ECDC4] border-[#4ECDC4]',
- },
- padding: {
- none: 'p-0',
- sm: 'p-3',
- default: 'p-5',
- lg: 'p-8',
- },
- },
- defaultVariants: {
- variant: 'default',
- padding: 'default',
- },
- }
-);
-
-export interface CardProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-const Card = React.forwardRef(
- ({ className, variant, padding, ...props }, ref) => (
-
- )
-);
-Card.displayName = 'Card';
-
-const CardHeader = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardHeader.displayName = 'CardHeader';
-
-const CardTitle = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardTitle.displayName = 'CardTitle';
-
-const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardDescription.displayName = 'CardDescription';
-
-const CardContent = React.forwardRef>(
- ({ className, ...props }, ref) =>
-);
-CardContent.displayName = 'CardContent';
-
-const CardFooter = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-CardFooter.displayName = 'CardFooter';
-
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent, cardVariants };
diff --git a/apps/docs/components/ui/checkbox.tsx b/apps/docs/components/ui/checkbox.tsx
deleted file mode 100644
index 3d94065..0000000
--- a/apps/docs/components/ui/checkbox.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
-import { Check } from 'lucide-react';
-import { cn } from '@/lib/utils';
-
-const Checkbox = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-
-));
-Checkbox.displayName = CheckboxPrimitive.Root.displayName;
-
-export { Checkbox };
diff --git a/apps/docs/components/ui/combobox.tsx b/apps/docs/components/ui/combobox.tsx
deleted file mode 100644
index 18d28a4..0000000
--- a/apps/docs/components/ui/combobox.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react';
-
-import { cn } from '@/lib/utils';
-import { Button } from './button';
-import { Popover, PopoverContent, PopoverTrigger } from './popover';
-import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
-} from './command';
-
-export interface ComboboxOption {
- value: string;
- label: string;
- disabled?: boolean;
-}
-
-export interface ComboboxProps {
- options: ComboboxOption[];
- value?: string;
- onValueChange?: (value: string) => void;
- placeholder?: string;
- searchPlaceholder?: string;
- emptyText?: string;
- className?: string;
- disabled?: boolean;
-}
-
-function Combobox({
- options,
- value,
- onValueChange,
- placeholder = 'Select option...',
- searchPlaceholder = 'Search...',
- emptyText = 'No results found.',
- className,
- disabled = false,
-}: ComboboxProps) {
- const [open, setOpen] = React.useState(false);
-
- const selectedOption = options.find((option) => option.value === value);
-
- return (
-
-
-
- {selectedOption ? selectedOption.label : placeholder}
-
-
-
-
-
-
-
- {emptyText}
-
- {options.map((option) => (
- {
- onValueChange?.(currentValue === value ? '' : currentValue);
- setOpen(false);
- }}
- >
-
- {option.label}
-
- ))}
-
-
-
-
-
- );
-}
-
-export interface ComboboxMultiProps {
- options: ComboboxOption[];
- value?: string[];
- onValueChange?: (value: string[]) => void;
- placeholder?: string;
- searchPlaceholder?: string;
- emptyText?: string;
- className?: string;
- disabled?: boolean;
- maxDisplay?: number;
-}
-
-function ComboboxMulti({
- options,
- value = [],
- onValueChange,
- placeholder = 'Select options...',
- searchPlaceholder = 'Search...',
- emptyText = 'No results found.',
- className,
- disabled = false,
- maxDisplay = 3,
-}: ComboboxMultiProps) {
- const [open, setOpen] = React.useState(false);
-
- const selectedOptions = options.filter((option) => value.includes(option.value));
-
- const displayText = React.useMemo(() => {
- if (selectedOptions.length === 0) return placeholder;
- if (selectedOptions.length <= maxDisplay) {
- return selectedOptions.map((o) => o.label).join(', ');
- }
- return `${selectedOptions.length} selected`;
- }, [selectedOptions, placeholder, maxDisplay]);
-
- const handleSelect = (optionValue: string) => {
- const newValue = value.includes(optionValue)
- ? value.filter((v) => v !== optionValue)
- : [...value, optionValue];
- onValueChange?.(newValue);
- };
-
- return (
-
-
-
- {displayText}
-
-
-
-
-
-
-
- {emptyText}
-
- {options.map((option) => (
- handleSelect(option.value)}
- >
-
- {value.includes(option.value) && (
-
- )}
-
- {option.label}
-
- ))}
-
-
-
-
-
- );
-}
-
-export { Combobox, ComboboxMulti };
diff --git a/apps/docs/components/ui/command.tsx b/apps/docs/components/ui/command.tsx
deleted file mode 100644
index 413debc..0000000
--- a/apps/docs/components/ui/command.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { Command as CommandPrimitive } from 'cmdk';
-import { SearchIcon } from 'lucide-react';
-
-import { cn } from '@/lib/utils';
-import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog';
-
-function Command({ className, ...props }: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandDialog({
- title = 'Command Palette',
- description = 'Search for a command to run...',
- children,
- className,
- showCloseButton = true,
- ...props
-}: React.ComponentProps & {
- title?: string;
- description?: string;
- className?: string;
- showCloseButton?: boolean;
-}) {
- return (
-
-
- {title}
- {description}
-
-
-
- {children}
-
-
-
- );
-}
-
-function CommandInput({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
- );
-}
-
-function CommandList({ className, ...props }: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandEmpty({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandGroup({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandSeparator({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandItem({ className, ...props }: React.ComponentProps) {
- return (
-
- );
-}
-
-function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
- return (
-
- );
-}
-
-export {
- Command,
- CommandDialog,
- CommandInput,
- CommandList,
- CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
-};
diff --git a/apps/docs/components/ui/dashboard-shell.tsx b/apps/docs/components/ui/dashboard-shell.tsx
deleted file mode 100644
index 4e52e33..0000000
--- a/apps/docs/components/ui/dashboard-shell.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import * as React from 'react';
-import {
- LayoutDashboard,
- Settings,
- Users,
- Layers,
- Bell,
- Search,
- Menu,
- LogOut,
- TrendingUp,
- ShieldAlert,
- CheckCircle2
-} from 'lucide-react';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
-import { Badge } from '@/components/ui/badge';
-import { Button } from '@/components/ui/button';
-
-export interface DashboardShellProps extends React.HTMLAttributes {
- userEmail?: string;
- onSignOutClick?: () => void;
-}
-
-const mockTransactions = [
- { id: 'TX-901', name: 'Alpha Agency', status: 'completed', amount: '$1,299.00', date: 'May 24, 2026' },
- { id: 'TX-902', name: 'Beta Labs', status: 'pending', amount: '$499.00', date: 'May 23, 2026' },
- { id: 'TX-903', name: 'Delta Studio', status: 'completed', amount: '$2,800.00', date: 'May 22, 2026' },
- { id: 'TX-904', name: 'Gamma Corp', status: 'failed', amount: '$99.00', date: 'May 20, 2026' },
-];
-
-export function DashboardShell({
- userEmail = 'creator@brutxui.site',
- onSignOutClick,
- className,
- ...props
-}: DashboardShellProps) {
- const [sidebarOpen, setSidebarOpen] = React.useState(true);
-
- return (
-
-
-
-
-
-
-
- Overview
-
-
-
-
- Deployments
-
-
-
-
- Team Access
-
-
-
-
- System Settings
-
-
-
-
-
-
- {userEmail}
- Owner Account
-
-
-
-
- Sign Out
-
-
-
-
-
-
-
-
setSidebarOpen(!sidebarOpen)}
- className="p-1.5 border-3 border-brutal bg-white text-black shadow-brutal-sm active:translate-y-[2px] active:shadow-none transition-all"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Monthly Revenue
-
-
-
- $12,492.00
-
- +18.2% from previous billing cycle
-
-
-
-
-
-
- Active Licenses
-
-
-
- +428
- +4.1% monthly net active users
-
-
-
-
-
- System Warnings
-
-
-
- 2 Warnings
- SSL domain renewals pending config
-
-
-
-
-
-
- Recent Transaction Ledgers
-
-
-
-
-
- Receipt ID
- Account
- Status
- date
- Amount
-
-
-
- {mockTransactions.map((tx) => (
-
- {tx.id}
- {tx.name}
-
-
- {tx.status}
-
-
- {tx.date}
- {tx.amount}
-
- ))}
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/docs/components/ui/dashboard-stats.tsx b/apps/docs/components/ui/dashboard-stats.tsx
deleted file mode 100644
index a65b6d2..0000000
--- a/apps/docs/components/ui/dashboard-stats.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { Card, CardHeader, CardContent, CardTitle, CardDescription } from './card';
-import { Badge } from './badge';
-import { ArrowUpRight, ArrowDownRight, TrendingUp, Users, Zap, Wallet, MoreHorizontal } from 'lucide-react';
-
-export interface StatItem {
- title: string;
- value: string;
- description: string;
- change: string;
- trend: 'up' | 'down' | 'neutral';
- icon: React.ComponentType<{ className?: string }>;
- accentColor: string;
- progress?: number;
-}
-
-const defaultStats: StatItem[] = [
- {
- title: 'Monthly Recurring Revenue',
- value: '$24,890',
- description: 'Total subscription earnings',
- change: '+14.2%',
- trend: 'up',
- icon: Wallet,
- accentColor: '#FF6B6B',
- progress: 78,
- },
- {
- title: 'Active Subscribers',
- value: '1,429',
- description: 'Subscribed active users',
- change: '+8.3%',
- trend: 'up',
- icon: Users,
- accentColor: '#4ECDC4',
- progress: 62,
- },
- {
- title: 'API Request Load',
- value: '4.8M',
- description: 'Requests in the last 24h',
- change: '-2.1%',
- trend: 'down',
- icon: Zap,
- accentColor: '#FFE66D',
- progress: 45,
- },
-];
-
-export interface DashboardStatsProps extends React.HTMLAttributes {
- stats?: StatItem[];
- title?: string;
- subtitle?: string;
-}
-
-export function DashboardStats({
- stats = defaultStats,
- title = 'Overview Performance',
- subtitle = 'Real-time telemetry and operational growth metrics.',
- className,
- ...props
-}: DashboardStatsProps) {
- return (
-
-
-
-
-
-
- {title}
-
-
- {subtitle}
-
-
-
-
-
- Last 30 Days
-
-
- Export PDF
-
-
-
-
-
- {stats.map((stat, idx) => {
- const Icon = stat.icon;
-
- return (
-
-
-
-
- {stat.title}
-
-
- {stat.description}
-
-
-
-
-
-
-
-
-
-
{stat.value}
-
- {stat.trend === 'up' ? (
-
- ) : (
-
- )}
- {stat.change}
-
-
-
- {stat.progress !== undefined && (
-
-
- TARGET LIMIT
- {stat.progress}%
-
-
-
- )}
-
-
- );
- })}
-
-
-
-
-
- INSIGHT
-
-
- Operational status is outstanding. Request volume is running 12% below cluster threshold limit.
-
-
-
- Details
-
-
-
-
- );
-}
-DashboardStats.displayName = 'DashboardStats';
diff --git a/apps/docs/components/ui/dialog.tsx b/apps/docs/components/ui/dialog.tsx
deleted file mode 100644
index 5c1d398..0000000
--- a/apps/docs/components/ui/dialog.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as React from 'react';
-import * as DialogPrimitive from '@radix-ui/react-dialog';
-import { X } from 'lucide-react';
-import { cn } from '@/lib/utils';
-
-const Dialog = DialogPrimitive.Root;
-const DialogTrigger = DialogPrimitive.Trigger;
-const DialogPortal = DialogPrimitive.Portal;
-const DialogClose = DialogPrimitive.Close;
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
-
-const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- showCloseButton?: boolean;
- }
->(({ className, children, showCloseButton = true, ...props }, ref) => (
-
-
-
- {children}
- {showCloseButton && (
-
-
- Close
-
- )}
-
-
-));
-DialogContent.displayName = DialogPrimitive.Content.displayName;
-
-const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-DialogHeader.displayName = 'DialogHeader';
-
-const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-DialogFooter.displayName = 'DialogFooter';
-
-const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogTitle.displayName = DialogPrimitive.Title.displayName;
-
-const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogDescription.displayName = DialogPrimitive.Description.displayName;
-
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogTrigger,
- DialogClose,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-};
diff --git a/apps/docs/components/ui/dropdown-menu.tsx b/apps/docs/components/ui/dropdown-menu.tsx
deleted file mode 100644
index f555a86..0000000
--- a/apps/docs/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import * as React from 'react';
-import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
-import { Check, ChevronRight, Circle } from 'lucide-react';
-import { cn } from '@/lib/utils';
-
-const DropdownMenu = DropdownMenuPrimitive.Root;
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, children, ...props }, ref) => (
-
- {children}
-
-
-));
-DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 6, ...props }, ref) => (
-
-
-
-));
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, checked, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
-
-const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return (
-
- );
-};
-DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
-
-export {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuGroup,
- DropdownMenuPortal,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuRadioGroup,
-};
diff --git a/apps/docs/components/ui/empty-state.tsx b/apps/docs/components/ui/empty-state.tsx
deleted file mode 100644
index 9b6450e..0000000
--- a/apps/docs/components/ui/empty-state.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import * as React from 'react';
-import { Plus, FolderOpen } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-
-export interface EmptyStateProps extends React.HTMLAttributes {
- title?: string;
- description?: string;
- actionText?: string;
- onActionClick?: () => void;
- icon?: React.ReactNode;
-}
-
-export function EmptyState({
- title = 'No active deployments found',
- description = 'You haven\'t launched any brutalist web projects on this cluster node. Deploy your first static directory in seconds.',
- actionText = 'Deploy New App',
- onActionClick,
- icon,
- className,
- ...props
-}: EmptyStateProps) {
- return (
-
-
-
-
- {title}
-
-
-
- {description}
-
-
-
-
- {actionText}
-
-
- );
-}
diff --git a/apps/docs/components/ui/form.tsx b/apps/docs/components/ui/form.tsx
deleted file mode 100644
index 81f3c75..0000000
--- a/apps/docs/components/ui/form.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as LabelPrimitive from '@radix-ui/react-label';
-import { Slot } from '@radix-ui/react-slot';
-import {
- Controller,
- ControllerProps,
- FieldPath,
- FieldValues,
- FormProvider,
- useFormContext,
-} from 'react-hook-form';
-
-import { cn } from '@/lib/utils';
-import { Label } from '@/components/ui/label';
-
-const Form = FormProvider;
-
-type FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
-> = {
- name: TName;
-};
-
-const FormFieldContext = React.createContext({} as FormFieldContextValue);
-
-const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
->({
- ...props
-}: ControllerProps) => {
- return (
-
-
-
- );
-};
-
-const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext);
- const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
-
- const fieldState = getFieldState(fieldContext.name, formState);
-
- if (!fieldContext) {
- throw new Error('useFormField should be used within ');
- }
-
- const { id } = itemContext;
-
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- };
-};
-
-type FormItemContextValue = {
- id: string;
-};
-
-const FormItemContext = React.createContext({} as FormItemContextValue);
-
-const FormItem = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const id = React.useId();
-
- return (
-
-
-
- );
- }
-);
-FormItem.displayName = 'FormItem';
-
-const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField();
-
- return (
-
- );
-});
-FormLabel.displayName = 'FormLabel';
-
-const FormControl = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
-
- return (
-
- );
-});
-FormControl.displayName = 'FormControl';
-
-const FormDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
-
- return (
-
- );
-});
-FormDescription.displayName = 'FormDescription';
-
-const FormMessage = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField();
- const body = error ? String(error?.message) : children;
-
- if (!body) {
- return null;
- }
-
- return (
-
- {body}
-
- );
-});
-FormMessage.displayName = 'FormMessage';
-
-export {
- useFormField,
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-};
diff --git a/apps/docs/components/ui/index.ts b/apps/docs/components/ui/index.ts
deleted file mode 100644
index 15d0204..0000000
--- a/apps/docs/components/ui/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-export * from './alert';
-export * from './avatar';
-export * from './badge';
-export * from './button';
-export * from './calendar';
-export * from './card';
-export * from './checkbox';
-export * from './combobox';
-export * from './command';
-export * from './dialog';
-export * from './dropdown-menu';
-export * from './input';
-export * from './label';
-export * from './pagination';
-export * from './popover';
-export * from './scroll-area';
-export * from './select';
-export * from './separator';
-export * from './skeleton';
-export * from './spinner';
-export * from './submit-button';
-export * from './switch';
-export * from './table';
-export * from './tabs';
-export * from './textarea';
-export * from './toast';
-export * from './tooltip';
-export * from './saas-pricing';
-export * from './dashboard-stats';
-export * from './form';
-export * from './alert-dialog';
-export * from './sheet';
-export * from './radio-group';
-export * from './slider';
-export * from './progress';
-export * from './toggle';
-export * from './toggle-group';
-
-export { cn } from '@/lib/utils';
diff --git a/apps/docs/components/ui/input.tsx b/apps/docs/components/ui/input.tsx
deleted file mode 100644
index 22b2ff2..0000000
--- a/apps/docs/components/ui/input.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const inputVariants = cva(
- [
- 'flex w-full',
- 'border-3 border-black dark:border-white',
- 'bg-white dark:bg-gray-900 dark:text-white',
- 'font-medium',
- 'placeholder:text-gray-400 dark:placeholder:text-gray-500 placeholder:font-normal',
- 'transition-all duration-150',
- 'focus:outline-none focus:shadow-[4px_4px_0px_0px_#000000] dark:focus:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:ring-offset-2',
- 'disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-100 dark:disabled:bg-gray-800',
- ],
- {
- variants: {
- variant: {
- default: '',
- error: 'border-[#EF476F] focus:shadow-[4px_4px_0px_0px_#EF476F]',
- success: 'border-[#7FB069] focus:shadow-[4px_4px_0px_0px_#7FB069]',
- },
- inputSize: {
- sm: 'h-9 px-3 py-1 text-sm',
- default: 'h-11 px-4 py-2 text-base',
- lg: 'h-14 px-5 py-3 text-lg',
- },
- },
- defaultVariants: {
- variant: 'default',
- inputSize: 'default',
- },
- }
-);
-
-export interface InputProps
- extends Omit, 'size'>,
- VariantProps {}
-
-const Input = React.forwardRef(
- ({ className, variant, inputSize, type, ...props }, ref) => {
- return (
-
- );
- }
-);
-Input.displayName = 'Input';
-
-export { Input, inputVariants };
diff --git a/apps/docs/components/ui/label.tsx b/apps/docs/components/ui/label.tsx
deleted file mode 100644
index db10ea5..0000000
--- a/apps/docs/components/ui/label.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const labelVariants = cva(
- [
- 'text-sm font-bold tracking-wide leading-none',
- 'peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
- ],
- {
- variants: {
- variant: {
- default: 'text-black dark:text-white',
- error: 'text-[#EF476F]',
- success: 'text-[#7FB069]',
- muted: 'text-gray-500 dark:text-gray-400',
- },
- },
- defaultVariants: {
- variant: 'default',
- },
- }
-);
-
-export interface LabelProps
- extends React.LabelHTMLAttributes,
- VariantProps {}
-
-const Label = React.forwardRef(
- ({ className, variant, ...props }, ref) => (
-
- )
-);
-Label.displayName = 'Label';
-
-export { Label, labelVariants };
diff --git a/apps/docs/components/ui/pagination.tsx b/apps/docs/components/ui/pagination.tsx
deleted file mode 100644
index 49fcf09..0000000
--- a/apps/docs/components/ui/pagination.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const paginationVariants = cva('flex items-center justify-center', {
- variants: {
- variant: {
- default: '',
- rounded: '[&_button]:rounded-none',
- minimal: '[&_button]:border-0 [&_button]:shadow-none',
- },
- size: {
- sm: 'gap-1',
- default: 'gap-2',
- lg: 'gap-3',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
-});
-
-const paginationButtonVariants = cva(
- [
- 'inline-flex items-center justify-center font-black',
- 'border-3 border-black dark:border-white',
- 'transition-all duration-150',
- 'focus:outline-none focus:ring-2 focus:ring-black dark:focus:ring-white focus:ring-offset-2',
- 'disabled:opacity-40 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none',
- ],
- {
- variants: {
- size: {
- sm: 'h-8 min-w-8 text-sm px-2',
- default: 'h-10 min-w-10 text-base px-3',
- lg: 'h-12 min-w-12 text-lg px-4',
- },
- isActive: {
- true: [
- 'bg-black text-white dark:bg-white dark:text-black',
- 'shadow-[4px_4px_0px_0px_#FF6B6B]',
- 'hover:shadow-[2px_2px_0px_0px_#FF6B6B]',
- 'active:shadow-none active:translate-x-1 active:translate-y-1',
- ],
- false: [
- 'bg-white dark:bg-gray-900 text-black dark:text-white',
- 'shadow-[4px_4px_0px_0px_#000000] dark:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'hover:bg-gray-100 dark:hover:bg-gray-800',
- 'hover:shadow-[2px_2px_0px_0px_#000000] dark:hover:shadow-[2px_2px_0px_0px_#FFFFFF]',
- 'active:shadow-none active:translate-x-1 active:translate-y-1',
- ],
- },
- },
- defaultVariants: {
- size: 'default',
- isActive: false,
- },
- }
-);
-
-interface PaginationProps
- extends React.HTMLAttributes,
- VariantProps {
- currentPage: number;
- totalPages: number;
- onPageChange: (page: number) => void;
- siblingCount?: number;
- showFirstLast?: boolean;
- showPageNumbers?: boolean;
-}
-
-const Pagination = React.forwardRef(
- (
- {
- className,
- currentPage,
- totalPages,
- onPageChange,
- siblingCount = 1,
- showFirstLast = true,
- showPageNumbers = true,
- variant,
- size,
- ...props
- },
- ref
- ) => {
- const range = (start: number, end: number) => {
- const length = end - start + 1;
- return Array.from({ length }, (_, idx) => idx + start);
- };
-
- const paginationRange = React.useMemo(() => {
- const totalPageNumbers = siblingCount + 5;
-
- if (totalPageNumbers >= totalPages) {
- return range(1, totalPages);
- }
-
- const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
- const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
-
- const shouldShowLeftDots = leftSiblingIndex > 2;
- const shouldShowRightDots = rightSiblingIndex < totalPages - 2;
-
- const firstPageIndex = 1;
- const lastPageIndex = totalPages;
-
- if (!shouldShowLeftDots && shouldShowRightDots) {
- const leftItemCount = 3 + 2 * siblingCount;
- const leftRange = range(1, leftItemCount);
- return [...leftRange, 'dots', totalPages];
- }
-
- if (shouldShowLeftDots && !shouldShowRightDots) {
- const rightItemCount = 3 + 2 * siblingCount;
- const rightRange = range(totalPages - rightItemCount + 1, totalPages);
- return [firstPageIndex, 'dots', ...rightRange];
- }
-
- if (shouldShowLeftDots && shouldShowRightDots) {
- const middleRange = range(leftSiblingIndex, rightSiblingIndex);
- return [firstPageIndex, 'dots', ...middleRange, 'dots', lastPageIndex];
- }
-
- return range(1, totalPages);
- }, [totalPages, siblingCount, currentPage]);
-
- const buttonSize = size || 'default';
-
- return (
-
- {showFirstLast && (
- onPageChange(1)}
- disabled={currentPage === 1}
- aria-label="Go to first page"
- >
-
-
- )}
-
- onPageChange(currentPage - 1)}
- disabled={currentPage === 1}
- aria-label="Go to previous page"
- >
-
-
-
- {showPageNumbers &&
- paginationRange.map((pageNumber, index) => {
- if (pageNumber === 'dots') {
- return (
-
- •••
-
- );
- }
-
- return (
- onPageChange(pageNumber as number)}
- aria-label={`Go to page ${pageNumber}`}
- aria-current={currentPage === pageNumber ? 'page' : undefined}
- >
- {pageNumber}
-
- );
- })}
-
- {!showPageNumbers && (
-
- {currentPage} / {totalPages}
-
- )}
-
- onPageChange(currentPage + 1)}
- disabled={currentPage === totalPages}
- aria-label="Go to next page"
- >
-
-
-
- {showFirstLast && (
- onPageChange(totalPages)}
- disabled={currentPage === totalPages}
- aria-label="Go to last page"
- >
-
-
- )}
-
- );
- }
-);
-Pagination.displayName = 'Pagination';
-
-export { Pagination, paginationVariants, paginationButtonVariants };
-export type { PaginationProps };
diff --git a/apps/docs/components/ui/popover.tsx b/apps/docs/components/ui/popover.tsx
deleted file mode 100644
index 73938a2..0000000
--- a/apps/docs/components/ui/popover.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as React from 'react';
-import * as PopoverPrimitive from '@radix-ui/react-popover';
-import { cn } from '@/lib/utils';
-
-const Popover = PopoverPrimitive.Root;
-const PopoverTrigger = PopoverPrimitive.Trigger;
-const PopoverAnchor = PopoverPrimitive.Anchor;
-
-const PopoverContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = 'center', sideOffset = 8, ...props }, ref) => (
-
-
-
-));
-PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/apps/docs/components/ui/pricing-section.tsx b/apps/docs/components/ui/pricing-section.tsx
deleted file mode 100644
index 06c1238..0000000
--- a/apps/docs/components/ui/pricing-section.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import * as React from 'react';
-import { Check } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
-
-export interface BrutalistPricingPlan {
- name: string;
- price: string;
- description: string;
- features: string[];
- ctaText: string;
- popular?: boolean;
- variant: 'primary' | 'secondary' | 'accent' | 'default';
-}
-
-export interface PricingSectionProps extends React.HTMLAttributes {
- title?: string;
- subtitle?: string;
- plans?: BrutalistPricingPlan[];
-}
-
-const defaultPlans: BrutalistPricingPlan[] = [
- {
- name: 'Indie Creator',
- price: '$9',
- description: 'For solo devs and side-project hackers building clean products.',
- features: [
- 'Access to 24+ atomic components',
- 'Full registry copying workflow',
- 'Figma design assets',
- 'Community Support Channel',
- ],
- ctaText: 'Start Building',
- variant: 'default',
- },
- {
- name: 'Pro Developer',
- price: '$29',
- description: 'For growing creators, SaaS platforms, and professional agencies.',
- features: [
- 'Access to all 36+ components',
- 'All copy-paste premium blocks',
- 'Advanced customizable variables',
- '1 year of regular registry updates',
- 'Priority GitHub issue support',
- ],
- ctaText: 'Unlock Pro Access',
- popular: true,
- variant: 'primary',
- },
- {
- name: 'Team Studio',
- price: '$99',
- description: 'For collaborative development groups and startups building scaled apps.',
- features: [
- 'Unlimited user accounts',
- 'Custom internal branding theme presets',
- 'AI-ready prompts and generation guidelines',
- 'Direct Slack developer console support',
- 'SLA uptime assurance',
- ],
- ctaText: 'Contact Workspace Sales',
- variant: 'secondary',
- },
-];
-
-export function PricingSection({
- title = 'Simple, Transparent Brutalist Plans',
- subtitle = 'Choose the subscription tier that matches your current development workflow. No hidden fees, instant cancel.',
- plans = defaultPlans,
- className,
- ...props
-}: PricingSectionProps) {
- return (
-
-
-
-
- Flexible Pricing Plans
-
-
- {title}
-
-
- {subtitle}
-
-
-
-
- {plans.map((plan, idx) => {
- return (
-
- {plan.popular && (
-
-
- Most Popular Tier
-
-
- )}
-
-
-
- {plan.name}
-
- {plan.price}
- / lifetime
-
-
- {plan.description}
-
-
-
-
- What's included:
-
- {plan.features.map((feature, fIdx) => (
-
-
-
-
- {feature}
-
- ))}
-
-
-
-
-
- {plan.ctaText}
-
-
-
-
- );
- })}
-
-
-
- );
-}
diff --git a/apps/docs/components/ui/progress.tsx b/apps/docs/components/ui/progress.tsx
deleted file mode 100644
index 7fd9168..0000000
--- a/apps/docs/components/ui/progress.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as ProgressPrimitive from '@radix-ui/react-progress';
-
-import { cn } from '@/lib/utils';
-
-const Progress = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, value, ...props }, ref) => (
-
-
-
-));
-Progress.displayName = ProgressPrimitive.Root.displayName;
-
-export { Progress };
diff --git a/apps/docs/components/ui/radio-group.tsx b/apps/docs/components/ui/radio-group.tsx
deleted file mode 100644
index edfcc3f..0000000
--- a/apps/docs/components/ui/radio-group.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
-import { Circle } from 'lucide-react';
-
-import { cn } from '@/lib/utils';
-
-const RadioGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
-
-const RadioGroupItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- return (
-
-
-
-
-
- );
-});
-RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
-
-export { RadioGroup, RadioGroupItem };
diff --git a/apps/docs/components/ui/saas-pricing.tsx b/apps/docs/components/ui/saas-pricing.tsx
deleted file mode 100644
index 8ac6645..0000000
--- a/apps/docs/components/ui/saas-pricing.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { Button } from './button';
-import { Card, CardHeader, CardContent, CardTitle, CardDescription, CardFooter } from './card';
-import { Badge } from './badge';
-import { Check, HelpCircle } from 'lucide-react';
-
-export interface PricingFeature {
- text: string;
- included: boolean;
-}
-
-export interface PricingPlan {
- name: string;
- description: string;
- priceMonthly: number;
- priceAnnually: number;
- features: PricingFeature[];
- popular?: boolean;
- buttonText: string;
- buttonVariant?: 'default' | 'primary' | 'secondary' | 'accent' | 'outline';
-}
-
-const defaultPlans: PricingPlan[] = [
- {
- name: 'Starter',
- description: 'For builders, side-projects, and early prototypes.',
- priceMonthly: 19,
- priceAnnually: 15,
- buttonText: 'Start Free Trial',
- buttonVariant: 'outline',
- features: [
- { text: '3 active projects', included: true },
- { text: 'Basic analytics dashboard', included: true },
- { text: '10,000 monthly active users', included: true },
- { text: 'Community slack support', included: true },
- { text: 'API endpoints access', included: false },
- { text: 'Custom brand domains', included: false },
- ],
- },
- {
- name: 'Pro',
- description: 'The sweet spot for growing companies and SaaS products.',
- priceMonthly: 49,
- priceAnnually: 39,
- popular: true,
- buttonText: 'Upgrade to Pro',
- buttonVariant: 'primary',
- features: [
- { text: 'Unlimited active projects', included: true },
- { text: 'Advanced real-time analytics', included: true },
- { text: '100,000 monthly active users', included: true },
- { text: 'Priority 24/7 support email', included: true },
- { text: 'Full API access', included: true },
- { text: 'Custom brand domains', included: true },
- ],
- },
- {
- name: 'Enterprise',
- description: 'Bespoke security, unlimited scaling, and dedicated resources.',
- priceMonthly: 149,
- priceAnnually: 119,
- buttonText: 'Contact Sales',
- buttonVariant: 'secondary',
- features: [
- { text: 'Dedicated isolated server cluster', included: true },
- { text: 'Custom SLAs and data policies', included: true },
- { text: 'Unlimited active users', included: true },
- { text: 'Dedicated account success manager', included: true },
- { text: 'Full API access + custom webhooks', included: true },
- { text: 'White-label custom portal', included: true },
- ],
- },
-];
-
-export interface SaaSPricingProps extends React.HTMLAttributes {
- plans?: PricingPlan[];
- title?: string;
- subtitle?: string;
-}
-
-export function SaaSPricing({
- plans = defaultPlans,
- title = 'Simple, Unapologetic Pricing',
- subtitle = 'Choose the tier that fuels your app. No hidden fees, cancel anytime you want.',
- className,
- ...props
-}: SaaSPricingProps) {
- const [billingPeriod, setBillingPeriod] = React.useState<'monthly' | 'annually'>('monthly');
-
- return (
-
-
-
-
- PRICING PLANS
-
-
- {title}
-
-
- {subtitle}
-
-
-
-
- setBillingPeriod('monthly')}
- className={`px-4 py-2 font-black transition-all ${
- billingPeriod === 'monthly'
- ? 'bg-[#FFE66D] text-black border-2 border-black shadow-brutal-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-black dark:hover:text-white'
- }`}
- >
- Monthly Billing
-
- setBillingPeriod('annually')}
- className={`px-4 py-2 font-black transition-all flex items-center gap-2 ${
- billingPeriod === 'annually'
- ? 'bg-[#FFE66D] text-black border-2 border-black shadow-brutal-sm'
- : 'text-gray-600 dark:text-gray-400 hover:text-black dark:hover:text-white'
- }`}
- >
- Annually (Save 20%)
-
- PROMO
-
-
-
-
-
- {plans.map((plan) => {
- const price = billingPeriod === 'monthly' ? plan.priceMonthly : plan.priceAnnually;
-
- return (
-
- {plan.popular && (
-
-
- MOST POPULAR
-
-
- )}
-
-
-
- {plan.name}
-
-
- {plan.description}
-
-
-
-
-
- ${price}
-
- / month
-
-
-
-
- {plan.features.map((feature, idx) => (
-
-
-
-
-
- {feature.text}
-
-
- ))}
-
-
-
-
-
- {plan.buttonText}
-
-
-
- );
- })}
-
-
-
-
-
-
- No risk guarantee:
-
- Try any tier completely free for 14 days. If you are not satisfied, we will issue a full refund within 30 seconds.
-
-
-
-
- );
-}
-SaaSPricing.displayName = 'SaaSPricing';
diff --git a/apps/docs/components/ui/scroll-area.tsx b/apps/docs/components/ui/scroll-area.tsx
deleted file mode 100644
index cc29260..0000000
--- a/apps/docs/components/ui/scroll-area.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
-import { cn } from '@/lib/utils';
-
-const ScrollArea = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
- {children}
-
-
-
-
-));
-ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
-
-const ScrollBar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, orientation = 'vertical', ...props }, ref) => (
-
-
-
-));
-ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
-
-export { ScrollArea, ScrollBar };
diff --git a/apps/docs/components/ui/select.tsx b/apps/docs/components/ui/select.tsx
deleted file mode 100644
index 3d283da..0000000
--- a/apps/docs/components/ui/select.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import * as React from 'react';
-import * as SelectPrimitive from '@radix-ui/react-select';
-import { Check, ChevronDown, ChevronUp } from 'lucide-react';
-import { cn } from '@/lib/utils';
-
-const Select = SelectPrimitive.Root;
-const SelectGroup = SelectPrimitive.Group;
-const SelectValue = SelectPrimitive.Value;
-
-const SelectTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
- span]:line-clamp-1',
- className
- )}
- {...props}
- >
- {children}
-
-
-
-
-));
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
-
-const SelectScrollUpButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
-
-const SelectScrollDownButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
-
-const SelectContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, position = 'popper', ...props }, ref) => (
-
-
-
-
- {children}
-
-
-
-
-));
-SelectContent.displayName = SelectPrimitive.Content.displayName;
-
-const SelectLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SelectLabel.displayName = SelectPrimitive.Label.displayName;
-
-const SelectItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-SelectItem.displayName = SelectPrimitive.Item.displayName;
-
-const SelectSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
-
-export {
- Select,
- SelectGroup,
- SelectValue,
- SelectTrigger,
- SelectContent,
- SelectLabel,
- SelectItem,
- SelectSeparator,
- SelectScrollUpButton,
- SelectScrollDownButton,
-};
diff --git a/apps/docs/components/ui/separator.tsx b/apps/docs/components/ui/separator.tsx
deleted file mode 100644
index 670868d..0000000
--- a/apps/docs/components/ui/separator.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const separatorVariants = cva('shrink-0 bg-black', {
- variants: {
- orientation: {
- horizontal: 'h-[3px] w-full',
- vertical: 'h-full w-[3px]',
- },
- },
- defaultVariants: {
- orientation: 'horizontal',
- },
-});
-
-export interface SeparatorProps
- extends React.HTMLAttributes,
- VariantProps {
- decorative?: boolean;
-}
-
-const Separator = React.forwardRef(
- ({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
-
- )
-);
-Separator.displayName = 'Separator';
-
-export { Separator, separatorVariants };
diff --git a/apps/docs/components/ui/sheet.tsx b/apps/docs/components/ui/sheet.tsx
deleted file mode 100644
index 5693741..0000000
--- a/apps/docs/components/ui/sheet.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as SheetPrimitive from '@radix-ui/react-dialog';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { X } from 'lucide-react';
-
-import { cn } from '@/lib/utils';
-
-const Sheet = SheetPrimitive.Root;
-const SheetTrigger = SheetPrimitive.Trigger;
-const SheetClose = SheetPrimitive.Close;
-const SheetPortal = SheetPrimitive.Portal;
-
-const SheetOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
-
-const sheetVariants = cva(
- 'fixed z-50 gap-4 bg-brutal-bg p-6 border-brutal border-3 text-brutal-fg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
- {
- variants: {
- side: {
- top: 'inset-x-0 top-0 border-b-3 data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
- bottom: 'inset-x-0 bottom-0 border-t-3 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
- left: 'inset-y-0 left-0 h-full w-3/4 border-r-3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
- right: 'inset-y-0 right-0 h-full w-3/4 border-l-3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
- },
- },
- defaultVariants: {
- side: 'right',
- },
- }
-);
-
-interface SheetContentProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {}
-
-const SheetContent = React.forwardRef<
- React.ElementRef,
- SheetContentProps
->(({ side = 'right', className, children, ...props }, ref) => (
-
-
-
- {children}
-
-
- Close
-
-
-
-));
-SheetContent.displayName = SheetPrimitive.Content.displayName;
-
-const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-SheetHeader.displayName = 'SheetHeader';
-
-const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (
-
-);
-SheetFooter.displayName = 'SheetFooter';
-
-const SheetTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetTitle.displayName = SheetPrimitive.Title.displayName;
-
-const SheetDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetDescription.displayName = SheetPrimitive.Description.displayName;
-
-export {
- Sheet,
- SheetPortal,
- SheetOverlay,
- SheetTrigger,
- SheetClose,
- SheetContent,
- SheetHeader,
- SheetFooter,
- SheetTitle,
- SheetDescription,
-};
diff --git a/apps/docs/components/ui/skeleton.tsx b/apps/docs/components/ui/skeleton.tsx
deleted file mode 100644
index ff8ad9f..0000000
--- a/apps/docs/components/ui/skeleton.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const skeletonVariants = cva(['animate-pulse', 'border-3 border-black dark:border-white'], {
- variants: {
- variant: {
- default: 'bg-gray-200 dark:bg-gray-800',
- primary: 'bg-[#FF6B6B]/30',
- secondary: 'bg-[#4ECDC4]/30',
- accent: 'bg-[#FFE66D]/30',
- },
- },
- defaultVariants: {
- variant: 'default',
- },
-});
-
-export interface SkeletonProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-const Skeleton = React.forwardRef(
- ({ className, variant, ...props }, ref) => {
- return (
-
- );
- }
-);
-Skeleton.displayName = 'Skeleton';
-
-const SkeletonText = React.forwardRef<
- HTMLDivElement,
- SkeletonProps & { lines?: number; lastLineWidth?: string }
->(({ className, lines = 3, lastLineWidth = '60%', variant, ...props }, ref) => {
- return (
-
- {Array.from({ length: lines }).map((_, index) => (
-
- ))}
-
- );
-});
-SkeletonText.displayName = 'SkeletonText';
-
-const SkeletonAvatar = React.forwardRef<
- HTMLDivElement,
- SkeletonProps & { size?: 'sm' | 'default' | 'lg' | 'xl' }
->(({ className, size = 'default', variant, ...props }, ref) => {
- const sizeClasses = {
- sm: 'h-8 w-8',
- default: 'h-10 w-10',
- lg: 'h-12 w-12',
- xl: 'h-16 w-16',
- };
-
- return (
-
- );
-});
-SkeletonAvatar.displayName = 'SkeletonAvatar';
-
-const SkeletonCard = React.forwardRef(
- ({ className, variant, ...props }, ref) => {
- return (
-
- );
- }
-);
-SkeletonCard.displayName = 'SkeletonCard';
-
-const SkeletonTable = React.forwardRef<
- HTMLDivElement,
- SkeletonProps & { rows?: number; columns?: number }
->(({ className, rows = 5, columns = 4, variant, ...props }, ref) => {
- return (
-
-
- {Array.from({ length: columns }).map((_, index) => (
-
-
-
- ))}
-
- {Array.from({ length: rows }).map((_, rowIndex) => (
-
- {Array.from({ length: columns }).map((_, colIndex) => (
-
-
-
- ))}
-
- ))}
-
- );
-});
-SkeletonTable.displayName = 'SkeletonTable';
-
-export { Skeleton, SkeletonText, SkeletonAvatar, SkeletonCard, SkeletonTable, skeletonVariants };
diff --git a/apps/docs/components/ui/slider.tsx b/apps/docs/components/ui/slider.tsx
deleted file mode 100644
index 616479f..0000000
--- a/apps/docs/components/ui/slider.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as SliderPrimitive from '@radix-ui/react-slider';
-
-import { cn } from '@/lib/utils';
-
-const Slider = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-
-
-));
-Slider.displayName = SliderPrimitive.Root.displayName;
-
-export { Slider };
diff --git a/apps/docs/components/ui/spinner.tsx b/apps/docs/components/ui/spinner.tsx
deleted file mode 100644
index ad404c3..0000000
--- a/apps/docs/components/ui/spinner.tsx
+++ /dev/null
@@ -1,237 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const spinnerVariants = cva(
- ['inline-block rounded-full', 'border-4 border-black dark:border-white', 'animate-spin'],
- {
- variants: {
- size: {
- sm: 'h-5 w-5 border-[3px]',
- default: 'h-8 w-8 border-4',
- lg: 'h-12 w-12 border-[5px]',
- xl: 'h-16 w-16 border-[6px]',
- },
- variant: {
- default: 'border-t-transparent border-r-transparent',
- primary: 'border-[#FF6B6B] border-t-transparent border-r-transparent',
- secondary: 'border-[#4ECDC4] border-t-transparent border-r-transparent',
- accent: 'border-[#FFE66D] border-t-[#000] border-r-[#000] dark:border-t-white dark:border-r-white',
- },
- },
- defaultVariants: {
- size: 'default',
- variant: 'default',
- },
- }
-);
-
-export interface SpinnerProps
- extends React.HTMLAttributes,
- VariantProps {
- label?: string;
-}
-
-const Spinner = React.forwardRef(
- ({ className, size, variant, label = 'Loading...', ...props }, ref) => {
- return (
-
- {label}
-
- );
- }
-);
-Spinner.displayName = 'Spinner';
-
-const blockSpinnerVariants = cva('grid grid-cols-2 gap-1', {
- variants: {
- size: {
- sm: 'h-5 w-5 gap-0.5',
- default: 'h-8 w-8 gap-1',
- lg: 'h-12 w-12 gap-1.5',
- xl: 'h-16 w-16 gap-2',
- },
- },
- defaultVariants: {
- size: 'default',
- },
-});
-
-const BlockSpinner = React.forwardRef<
- HTMLDivElement,
- Omit & {
- color?: 'default' | 'primary' | 'secondary' | 'accent' | 'mixed';
- }
->(({ className, size = 'default', color = 'default', label = 'Loading...', ...props }, ref) => {
- const colorMap = {
- default: [
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- ],
- primary: ['bg-[#FF6B6B]', 'bg-[#FF6B6B]', 'bg-[#FF6B6B]', 'bg-[#FF6B6B]'],
- secondary: ['bg-[#4ECDC4]', 'bg-[#4ECDC4]', 'bg-[#4ECDC4]', 'bg-[#4ECDC4]'],
- accent: ['bg-[#FFE66D]', 'bg-[#FFE66D]', 'bg-[#FFE66D]', 'bg-[#FFE66D]'],
- mixed: ['bg-[#FF6B6B]', 'bg-[#4ECDC4]', 'bg-[#FFE66D]', 'bg-[#A855F7]'],
- };
-
- const colors = colorMap[color];
-
- return (
-
- {[0, 1, 2, 3].map((i) => (
-
- ))}
-
{label}
-
- );
-});
-BlockSpinner.displayName = 'BlockSpinner';
-
-const DotsSpinner = React.forwardRef<
- HTMLDivElement,
- Omit & { color?: 'default' | 'primary' | 'secondary' | 'accent' }
->(({ className, size = 'default', color = 'default', label = 'Loading...', ...props }, ref) => {
- const sizeMap = {
- sm: 'h-1.5 w-1.5',
- default: 'h-2.5 w-2.5',
- lg: 'h-3.5 w-3.5',
- xl: 'h-5 w-5',
- };
-
- const gapMap = {
- sm: 'gap-1',
- default: 'gap-2',
- lg: 'gap-3',
- xl: 'gap-4',
- };
-
- const colorMap = {
- default: 'bg-black dark:bg-white',
- primary: 'bg-[#FF6B6B]',
- secondary: 'bg-[#4ECDC4]',
- accent: 'bg-[#FFE66D]',
- };
-
- return (
-
- {[0, 1, 2].map((i) => (
-
- ))}
-
{label}
-
- );
-});
-DotsSpinner.displayName = 'DotsSpinner';
-
-const BarsSpinner = React.forwardRef<
- HTMLDivElement,
- Omit & {
- color?: 'default' | 'primary' | 'secondary' | 'accent' | 'mixed';
- }
->(({ className, size = 'default', color = 'default', label = 'Loading...', ...props }, ref) => {
- const heightMap = {
- sm: 'h-4',
- default: 'h-6',
- lg: 'h-8',
- xl: 'h-12',
- };
-
- const barWidthMap = {
- sm: 'w-1',
- default: 'w-1.5',
- lg: 'w-2',
- xl: 'w-3',
- };
-
- const colorMap = {
- default: [
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- 'bg-black dark:bg-white',
- ],
- primary: ['bg-[#FF6B6B]', 'bg-[#FF6B6B]', 'bg-[#FF6B6B]', 'bg-[#FF6B6B]', 'bg-[#FF6B6B]'],
- secondary: ['bg-[#4ECDC4]', 'bg-[#4ECDC4]', 'bg-[#4ECDC4]', 'bg-[#4ECDC4]', 'bg-[#4ECDC4]'],
- accent: ['bg-[#FFE66D]', 'bg-[#FFE66D]', 'bg-[#FFE66D]', 'bg-[#FFE66D]', 'bg-[#FFE66D]'],
- mixed: ['bg-[#FF6B6B]', 'bg-[#4ECDC4]', 'bg-[#FFE66D]', 'bg-[#A855F7]', 'bg-[#FF6B6B]'],
- };
-
- const colors = colorMap[color];
-
- return (
-
- {[0, 1, 2, 3, 4].map((i) => (
-
- ))}
-
{label}
-
- );
-});
-BarsSpinner.displayName = 'BarsSpinner';
-
-export { Spinner, BlockSpinner, DotsSpinner, BarsSpinner, spinnerVariants, blockSpinnerVariants };
diff --git a/apps/docs/components/ui/submit-button.tsx b/apps/docs/components/ui/submit-button.tsx
deleted file mode 100644
index ba2fdf5..0000000
--- a/apps/docs/components/ui/submit-button.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-'use client';
-
-import { useFormStatus } from 'react-dom';
-import { Button, type ButtonProps } from './button';
-
-export interface SubmitButtonProps extends ButtonProps {
- pendingText?: string;
-}
-
-/**
- * Renders a submit button that follows React form pending state.
- *
- * @param props - Submit button props.
- * @returns Submit button with pending state.
- */
-function SubmitButton({ children, pendingText, disabled, ...props }: SubmitButtonProps) {
- const { pending } = useFormStatus();
-
- return (
-
- {pending && pendingText ? pendingText : children}
-
- );
-}
-
-SubmitButton.displayName = 'SubmitButton';
-
-export { SubmitButton };
diff --git a/apps/docs/components/ui/switch.tsx b/apps/docs/components/ui/switch.tsx
deleted file mode 100644
index 3a220cb..0000000
--- a/apps/docs/components/ui/switch.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as SwitchPrimitive from '@radix-ui/react-switch';
-import { cn } from '@/lib/utils';
-
-const Switch = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-Switch.displayName = SwitchPrimitive.Root.displayName;
-
-export { Switch };
diff --git a/apps/docs/components/ui/table.tsx b/apps/docs/components/ui/table.tsx
deleted file mode 100644
index 2047946..0000000
--- a/apps/docs/components/ui/table.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import * as React from 'react';
-import { cn } from '@/lib/utils';
-
-const Table = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-Table.displayName = 'Table';
-
-const TableHeader = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-TableHeader.displayName = 'TableHeader';
-
-const TableBody = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-TableBody.displayName = 'TableBody';
-
-const TableFooter = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
- tr]:last:border-b-0',
- className
- )}
- {...props}
- />
-));
-TableFooter.displayName = 'TableFooter';
-
-const TableRow = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-TableRow.displayName = 'TableRow';
-
-const TableHead = React.forwardRef<
- HTMLTableCellElement,
- React.ThHTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-TableHead.displayName = 'TableHead';
-
-const TableCell = React.forwardRef<
- HTMLTableCellElement,
- React.TdHTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-TableCell.displayName = 'TableCell';
-
-const TableCaption = React.forwardRef<
- HTMLTableCaptionElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-TableCaption.displayName = 'TableCaption';
-
-export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
diff --git a/apps/docs/components/ui/tabs.tsx b/apps/docs/components/ui/tabs.tsx
deleted file mode 100644
index aceafcd..0000000
--- a/apps/docs/components/ui/tabs.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as React from 'react';
-import * as TabsPrimitive from '@radix-ui/react-tabs';
-import { cn } from '@/lib/utils';
-
-const Tabs = TabsPrimitive.Root;
-
-const TabsList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsList.displayName = TabsPrimitive.List.displayName;
-
-const TabsTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
-
-const TabsContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsContent.displayName = TabsPrimitive.Content.displayName;
-
-export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/apps/docs/components/ui/textarea.tsx b/apps/docs/components/ui/textarea.tsx
deleted file mode 100644
index 83143df..0000000
--- a/apps/docs/components/ui/textarea.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const textareaVariants = cva(
- [
- 'flex min-h-[100px] w-full',
- 'border-3 border-black dark:border-white',
- 'bg-white dark:bg-gray-900 dark:text-white',
- 'font-medium',
- 'placeholder:text-gray-400 dark:placeholder:text-gray-500 placeholder:font-normal',
- 'transition-all duration-150',
- 'focus:outline-none focus:shadow-[4px_4px_0px_0px_#000000] dark:focus:shadow-[4px_4px_0px_0px_#FFFFFF]',
- 'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:ring-offset-2',
- 'disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-150 dark:disabled:bg-gray-800',
- 'resize-none',
- ],
- {
- variants: {
- variant: {
- default: '',
- error: 'border-[#EF476F] focus:shadow-[4px_4px_0px_0px_#EF476F]',
- success: 'border-[#7FB069] focus:shadow-[4px_4px_0px_0px_#7FB069]',
- },
- textareaSize: {
- sm: 'px-3 py-2 text-sm',
- default: 'px-4 py-3 text-base',
- lg: 'px-5 py-4 text-lg',
- },
- },
- defaultVariants: {
- variant: 'default',
- textareaSize: 'default',
- },
- }
-);
-
-export interface TextareaProps
- extends React.TextareaHTMLAttributes,
- VariantProps {}
-
-const Textarea = React.forwardRef(
- ({ className, variant, textareaSize, ...props }, ref) => {
- return (
-
- );
- }
-);
-Textarea.displayName = 'Textarea';
-
-export { Textarea, textareaVariants };
diff --git a/apps/docs/components/ui/toast.tsx b/apps/docs/components/ui/toast.tsx
deleted file mode 100644
index dd40955..0000000
--- a/apps/docs/components/ui/toast.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { X, CheckCircle, AlertCircle, AlertTriangle, Info, Zap } from 'lucide-react';
-import { cn } from '@/lib/utils';
-
-const toastVariants = cva(
- [
- 'pointer-events-auto relative w-full overflow-hidden',
- 'border-3 border-black dark:border-white',
- 'transition-all duration-300 ease-out',
- 'animate-in slide-in-from-right-full fade-in-0',
- ],
- {
- variants: {
- variant: {
- default: [
- 'bg-white dark:bg-gray-900 text-black dark:text-white',
- 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- ],
- success: [
- 'bg-[#7FB069] text-black dark:bg-[#2d4a28] dark:text-white',
- 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- ],
- error: [
- 'bg-[#FF6B6B] text-black dark:bg-[#7a2d2d] dark:text-white',
- 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- ],
- warning: [
- 'bg-[#FFE66D] text-black dark:bg-[#6b5a1f] dark:text-white',
- 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- ],
- info: [
- 'bg-[#4ECDC4] text-black dark:bg-[#1f5450] dark:text-white',
- 'shadow-[6px_6px_0px_0px_#000000] dark:shadow-[6px_6px_0px_0px_#FFFFFF]',
- ],
- },
- size: {
- sm: 'max-w-xs',
- default: 'max-w-sm',
- lg: 'max-w-md',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- }
-);
-
-export interface ToastProps
- extends React.HTMLAttributes,
- VariantProps {
- title?: string;
- description?: string;
- onClose?: () => void;
- icon?: React.ReactNode;
- action?: React.ReactNode;
- duration?: number;
-}
-
-const Toast = React.forwardRef(
- (
- {
- className,
- variant,
- size,
- title,
- description,
- onClose,
- icon,
- action,
- duration = 5000,
- children,
- ...props
- },
- ref
- ) => {
- const [isLeaving, setIsLeaving] = React.useState(false);
-
- React.useEffect(() => {
- if (duration && onClose) {
- const timer = setTimeout(() => {
- setIsLeaving(true);
- setTimeout(() => onClose(), 200);
- }, duration);
- return () => clearTimeout(timer);
- }
- return undefined;
- }, [duration, onClose]);
-
- const getDefaultIcon = (): React.ReactNode | null => {
- const iconClass = 'h-6 w-6 stroke-[2.5] flex-shrink-0';
- switch (variant) {
- case 'success':
- return ;
- case 'error':
- return ;
- case 'warning':
- return ;
- case 'info':
- return ;
- default:
- return ;
- }
- };
-
- const defaultIcon = getDefaultIcon();
-
- return (
-
- {duration && onClose && (
-
- )}
-
-
- {(icon || defaultIcon) && (
-
{icon || defaultIcon}
- )}
-
-
- {title &&
{title}
}
- {description && (
-
- {description}
-
- )}
- {children}
-
- {action &&
{action}
}
-
-
- {onClose && (
-
{
- setIsLeaving(true);
- setTimeout(() => onClose(), 200);
- }}
- className={cn(
- 'flex-shrink-0 h-8 w-8 flex items-center justify-center',
- 'border-2 border-black bg-white',
- 'shadow-[2px_2px_0px_0px_#000000]',
- 'transition-all duration-150',
- 'hover:shadow-[1px_1px_0px_0px_#000000] hover:translate-x-0.5 hover:translate-y-0.5',
- 'active:shadow-none active:translate-x-1 active:translate-y-1',
- 'focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2'
- )}
- aria-label="Close"
- >
-
-
- )}
-
-
- );
- }
-);
-Toast.displayName = 'Toast';
-
-interface ToastContainerProps extends React.HTMLAttributes {
- position?:
- | 'top-left'
- | 'top-center'
- | 'top-right'
- | 'bottom-left'
- | 'bottom-center'
- | 'bottom-right';
-}
-
-const ToastContainer = React.forwardRef(
- ({ className, position = 'bottom-right', children, ...props }, ref) => {
- const positionClasses = {
- 'top-left': 'top-4 left-4 items-start',
- 'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center',
- 'top-right': 'top-4 right-4 items-end',
- 'bottom-left': 'bottom-4 left-4 items-start',
- 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 items-center',
- 'bottom-right': 'bottom-4 right-4 items-end',
- };
-
- return (
- <>
-
-
- {children}
-
- >
- );
- }
-);
-ToastContainer.displayName = 'ToastContainer';
-
-interface ToastItem {
- id: string;
- variant?: ToastProps['variant'];
- title?: string;
- description?: string;
- duration?: number;
- action?: React.ReactNode;
-}
-
-function useToast() {
- const [toasts, setToasts] = React.useState([]);
-
- const addToast = React.useCallback((toast: Omit) => {
- const id = Math.random().toString(36).substr(2, 9);
- setToasts((prev) => [...prev, { ...toast, id }]);
- return id;
- }, []);
-
- const removeToast = React.useCallback((id: string) => {
- setToasts((prev) => prev.filter((toast) => toast.id !== id));
- }, []);
-
- const clearToasts = React.useCallback(() => {
- setToasts([]);
- }, []);
-
- const success = React.useCallback(
- (title: string, description?: string) => {
- return addToast({ variant: 'success', title, description });
- },
- [addToast]
- );
-
- const error = React.useCallback(
- (title: string, description?: string) => {
- return addToast({ variant: 'error', title, description });
- },
- [addToast]
- );
-
- const warning = React.useCallback(
- (title: string, description?: string) => {
- return addToast({ variant: 'warning', title, description });
- },
- [addToast]
- );
-
- const info = React.useCallback(
- (title: string, description?: string) => {
- return addToast({ variant: 'info', title, description });
- },
- [addToast]
- );
-
- return {
- toasts,
- addToast,
- removeToast,
- clearToasts,
- success,
- error,
- warning,
- info,
- };
-}
-
-export { Toast, ToastContainer, useToast, toastVariants };
-export type { ToastContainerProps, ToastItem };
diff --git a/apps/docs/components/ui/toggle-group.tsx b/apps/docs/components/ui/toggle-group.tsx
deleted file mode 100644
index ecd680f..0000000
--- a/apps/docs/components/ui/toggle-group.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
-import { VariantProps } from 'class-variance-authority';
-
-import { cn } from '@/lib/utils';
-import { toggleVariants } from '@/components/ui/toggle';
-
-const ToggleGroupContext = React.createContext<
- VariantProps
->({
- size: 'default',
- variant: 'default',
-});
-
-const ToggleGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, variant, size, children, ...props }, ref) => (
-
-
- {children}
-
-
-));
-
-ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
-
-const ToggleGroupItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, children, variant, size, ...props }, ref) => {
- const context = React.useContext(ToggleGroupContext);
-
- return (
-
- {children}
-
- );
-});
-
-ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
-
-export { ToggleGroup, ToggleGroupItem };
diff --git a/apps/docs/components/ui/tooltip.tsx b/apps/docs/components/ui/tooltip.tsx
deleted file mode 100644
index 7f7c579..0000000
--- a/apps/docs/components/ui/tooltip.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as React from 'react';
-import * as TooltipPrimitive from '@radix-ui/react-tooltip';
-import { cn } from '@/lib/utils';
-
-const TooltipProvider = TooltipPrimitive.Provider;
-const Tooltip = TooltipPrimitive.Root;
-const TooltipTrigger = TooltipPrimitive.Trigger;
-
-const TooltipContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 6, ...props }, ref) => (
-
-));
-TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/apps/docs/components/ui/waitlist-page.tsx b/apps/docs/components/ui/waitlist-page.tsx
deleted file mode 100644
index cfbced4..0000000
--- a/apps/docs/components/ui/waitlist-page.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from 'react';
-import { Sparkles, Users, Star } from 'lucide-react';
-import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-
-export interface WaitlistPageProps extends React.HTMLAttributes {
- title?: string;
- description?: string;
- ctaText?: string;
- onWaitlistSubmit?: (e: React.FormEvent, email: string) => void;
-}
-
-export function WaitlistPage({
- title = 'Join the BrutxUI Waitlist Club',
- description = 'Get early developer keys to copy premium blocks, customized theme presets, and unlock deep registry CLI integrations before everyone else.',
- ctaText = 'Secure Priority Access',
- onWaitlistSubmit,
- className,
- ...props
-}: WaitlistPageProps) {
- const [email, setEmail] = React.useState('');
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- if (onWaitlistSubmit) onWaitlistSubmit(e, email);
- };
-
- return (
-
-
-
-
-
- Private Beta Access Open
-
-
-
-
- {title}
-
-
- {description}
-
-
-
-
-
-
-
-
-
- 1,840+ Developers on waitlist
-
-
- {[...Array(5)].map((_, i) => (
-
- ))}
- 4.9/5 stars rating
-
-
-
- Beta Invites sent daily
-
-
-
-
-
-
- );
-}
diff --git a/apps/docs/config/component-installation.ts b/apps/docs/config/component-installation.ts
deleted file mode 100644
index c44b9f0..0000000
--- a/apps/docs/config/component-installation.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-export const componentDependencies: Record = {
- button: ['@radix-ui/react-slot', 'class-variance-authority'],
- card: ['class-variance-authority'],
- input: ['class-variance-authority'],
- textarea: [],
- label: ['class-variance-authority'],
- badge: ['class-variance-authority'],
- dialog: ['@radix-ui/react-dialog'],
- popover: ['@radix-ui/react-popover'],
- tooltip: ['@radix-ui/react-tooltip'],
- 'dropdown-menu': ['@radix-ui/react-dropdown-menu'],
- select: ['@radix-ui/react-select'],
- tabs: ['@radix-ui/react-tabs'],
- table: [],
- alert: ['class-variance-authority'],
- avatar: ['class-variance-authority'],
- separator: ['@radix-ui/react-separator'],
- switch: ['@radix-ui/react-switch'],
- checkbox: ['@radix-ui/react-checkbox'],
- pagination: [],
- spinner: [],
- toast: [],
- skeleton: [],
- calendar: ['react-day-picker'],
- 'context-menu': ['@radix-ui/react-context-menu'],
- command: ['cmdk'],
- combobox: ['cmdk', '@radix-ui/react-popover'],
- 'scroll-area': ['@radix-ui/react-scroll-area'],
-};
-
-export const componentImports: Record = {
- button: ['Button', 'buttonVariants'],
- card: ['Card', 'CardHeader', 'CardTitle', 'CardDescription', 'CardContent', 'CardFooter'],
- input: ['Input'],
- textarea: ['Textarea'],
- label: ['Label'],
- badge: ['Badge', 'badgeVariants'],
- dialog: [
- 'Dialog',
- 'DialogTrigger',
- 'DialogContent',
- 'DialogHeader',
- 'DialogFooter',
- 'DialogTitle',
- 'DialogDescription',
- 'DialogClose',
- ],
- popover: ['Popover', 'PopoverTrigger', 'PopoverContent'],
- tooltip: ['Tooltip', 'TooltipTrigger', 'TooltipContent', 'TooltipProvider'],
- 'dropdown-menu': [
- 'DropdownMenu',
- 'DropdownMenuTrigger',
- 'DropdownMenuContent',
- 'DropdownMenuItem',
- 'DropdownMenuCheckboxItem',
- 'DropdownMenuRadioItem',
- 'DropdownMenuLabel',
- 'DropdownMenuSeparator',
- 'DropdownMenuShortcut',
- 'DropdownMenuGroup',
- 'DropdownMenuSub',
- 'DropdownMenuSubContent',
- 'DropdownMenuSubTrigger',
- 'DropdownMenuRadioGroup',
- ],
- select: [
- 'Select',
- 'SelectGroup',
- 'SelectValue',
- 'SelectTrigger',
- 'SelectContent',
- 'SelectLabel',
- 'SelectItem',
- 'SelectSeparator',
- ],
- tabs: ['Tabs', 'TabsList', 'TabsTrigger', 'TabsContent'],
- table: [
- 'Table',
- 'TableHeader',
- 'TableBody',
- 'TableFooter',
- 'TableHead',
- 'TableRow',
- 'TableCell',
- 'TableCaption',
- ],
- alert: ['Alert', 'AlertTitle', 'AlertDescription'],
- avatar: ['Avatar', 'AvatarImage', 'AvatarFallback'],
- separator: ['Separator'],
- switch: ['Switch'],
- checkbox: ['Checkbox'],
- pagination: [
- 'Pagination',
- 'PaginationContent',
- 'PaginationEllipsis',
- 'PaginationItem',
- 'PaginationLink',
- 'PaginationNext',
- 'PaginationPrevious',
- ],
- spinner: ['Spinner'],
- toast: [
- 'Toast',
- 'ToastAction',
- 'ToastClose',
- 'ToastDescription',
- 'ToastTitle',
- 'ToastProvider',
- 'ToastViewport',
- ],
- skeleton: ['Skeleton'],
- calendar: ['Calendar'],
- 'context-menu': [
- 'ContextMenu',
- 'ContextMenuTrigger',
- 'ContextMenuContent',
- 'ContextMenuItem',
- 'ContextMenuCheckboxItem',
- 'ContextMenuRadioItem',
- 'ContextMenuLabel',
- 'ContextMenuSeparator',
- 'ContextMenuShortcut',
- 'ContextMenuGroup',
- 'ContextMenuSub',
- 'ContextMenuSubContent',
- 'ContextMenuSubTrigger',
- 'ContextMenuRadioGroup',
- ],
- command: [
- 'Command',
- 'CommandDialog',
- 'CommandInput',
- 'CommandList',
- 'CommandEmpty',
- 'CommandGroup',
- 'CommandItem',
- 'CommandShortcut',
- 'CommandSeparator',
- ],
- combobox: ['Combobox', 'ComboboxMulti'],
- 'scroll-area': ['ScrollArea', 'ScrollBar'],
-};
diff --git a/apps/docs/config/constants.ts b/apps/docs/config/constants.ts
deleted file mode 100644
index 011d4c4..0000000
--- a/apps/docs/config/constants.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import uiPackage from '../../../packages/ui/package.json';
-
-export const SITE_CONFIG = {
- version: uiPackage.version,
- name: 'Brutx',
- title: 'Brutx - Neo Brutalism React Component Library',
- description: 'Brutx UI library for React. 27+ Neo Brutalism UI components with bold borders, offset shadows, vibrant colors. Free & open-source. Built with Radix UI, Tailwind CSS. Use CLI: npx brutx@latest init. Best shadcn alternative for brutalist design.',
- url: 'https://brutxui.site',
- github: {
- owner: 'dev-snake',
- repo: 'brutxui',
- url: 'https://github.com/dev-snake/brutxui',
- raw: 'https://raw.githubusercontent.com/dev-snake/brutxui/main',
- componentsUrl: 'https://github.com/dev-snake/brutxui/tree/main/packages/ui/src/components',
- componentBlobUrl: 'https://github.com/dev-snake/brutxui/blob/main/packages/ui/src/components'
- },
- npm: {
- cli: 'https://www.npmjs.com/package/brutx',
- ui: 'https://www.npmjs.com/package/brutx-ui'
- },
- social: {
- twitter: 'https://twitter.com/intent/tweet?text=Check%20out%20Brutx%20-%20A%20Neo-Brutalism%20styled%20React%20component%20library!%20https://github.com/dev-snake/brutxui',
- creator: '@devsnake'
- }
-} as const;
diff --git a/apps/docs/config/seo.ts b/apps/docs/config/seo.ts
deleted file mode 100644
index 870df92..0000000
--- a/apps/docs/config/seo.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { Metadata } from 'next';
-
-interface ComponentSEOProps {
- name: string;
- description: string;
- slug: string;
-}
-
-export function generateComponentMetadata({
- name,
- description,
- slug,
-}: ComponentSEOProps): Metadata {
- const title = `${name} - Brutx Component`;
- const fullDescription = `${description} Free Neo-Brutalism styled React ${name} component with TypeScript support.`;
-
- return {
- title,
- description: fullDescription,
- keywords: [
- `react ${name.toLowerCase()}`,
- `${name.toLowerCase()} component`,
- `brutalist ${name.toLowerCase()}`,
- `neo brutalism ${name.toLowerCase()}`,
- `tailwind ${name.toLowerCase()}`,
- 'react component',
- 'brutx',
- ],
- openGraph: {
- title,
- description: fullDescription,
- url: `https://brutxui.site/docs/components/${slug}`,
- type: 'article',
- },
- twitter: {
- title,
- description: fullDescription,
- },
- alternates: {
- canonical: `https://brutxui.site/docs/components/${slug}`,
- },
- };
-}
-
-export const componentsSEO: Record = {
- alert: {
- name: 'Alert',
- description: 'Display important messages and notifications with Neo-Brutalism styling.',
- slug: 'alert',
- },
- avatar: {
- name: 'Avatar',
- description: 'User profile image with fallback support and multiple sizes.',
- slug: 'avatar',
- },
- badge: {
- name: 'Badge',
- description: 'Status indicators and labels with bold brutalist design.',
- slug: 'badge',
- },
- button: {
- name: 'Button',
- description: 'Interactive button with multiple variants, sizes, and states.',
- slug: 'button',
- },
- card: {
- name: 'Card',
- description: 'Container component with header, content, and footer sections.',
- slug: 'card',
- },
- checkbox: {
- name: 'Checkbox',
- description: 'Accessible checkbox input with Neo-Brutalism checkmark.',
- slug: 'checkbox',
- },
- dialog: {
- name: 'Dialog',
- description: 'Modal dialog windows built on Radix UI primitives.',
- slug: 'dialog',
- },
- 'dropdown-menu': {
- name: 'Dropdown Menu',
- description: 'Context and dropdown menus with keyboard navigation.',
- slug: 'dropdown-menu',
- },
- input: {
- name: 'Input',
- description: 'Text input field with brutalist borders and focus states.',
- slug: 'input',
- },
- label: {
- name: 'Label',
- description: 'Accessible form labels for input fields.',
- slug: 'label',
- },
- pagination: {
- name: 'Pagination',
- description: 'Page navigation with first, last, previous, and next buttons.',
- slug: 'pagination',
- },
- popover: {
- name: 'Popover',
- description: 'Floating content panels triggered by user interaction.',
- slug: 'popover',
- },
- select: {
- name: 'Select',
- description: 'Dropdown select menu with search and keyboard navigation.',
- slug: 'select',
- },
- separator: {
- name: 'Separator',
- description: 'Visual divider for separating content sections.',
- slug: 'separator',
- },
- skeleton: {
- name: 'Skeleton',
- description: 'Loading placeholder animations for content.',
- slug: 'skeleton',
- },
- spinner: {
- name: 'Spinner',
- description: 'Loading spinners with brutalist, dots, pulse, and bars variants.',
- slug: 'spinner',
- },
- 'submit-button': {
- name: 'Submit Button',
- description: 'Server component-ready form submission handler with brutalist styling.',
- slug: 'submit-button',
- },
- switch: {
- name: 'Switch',
- description: 'Toggle switch control for boolean settings.',
- slug: 'switch',
- },
- table: {
- name: 'Table',
- description: 'Data tables with brutalist styling and responsive design.',
- slug: 'table',
- },
- tabs: {
- name: 'Tabs',
- description: 'Tabbed content navigation built on Radix UI.',
- slug: 'tabs',
- },
- textarea: {
- name: 'Textarea',
- description: 'Multi-line text input with size variants.',
- slug: 'textarea',
- },
- toast: {
- name: 'Toast',
- description: 'Toast notification system for user feedback.',
- slug: 'toast',
- },
- tooltip: {
- name: 'Tooltip',
- description: 'Hover tooltips for additional information.',
- slug: 'tooltip',
- },
- calendar: {
- name: 'Calendar',
- description: 'Date picker calendar with neo-brutalist styling using react-day-picker.',
- slug: 'calendar',
- },
- command: {
- name: 'Command',
- description: 'Command palette and search interface built on cmdk.',
- slug: 'command',
- },
- combobox: {
- name: 'Combobox',
- description: 'Autocomplete and multi-select picker with search functionality.',
- slug: 'combobox',
- },
- 'scroll-area': {
- name: 'Scroll Area',
- description: 'Custom scrollbar component with neo-brutalist rails.',
- slug: 'scroll-area',
- },
- 'saas-pricing': {
- name: 'SaaS Pricing',
- description: 'Premium SaaS pricing plans, features matrix, and billing frequency toggles with stark brutalist design.',
- slug: 'saas-pricing',
- },
- 'dashboard-stats': {
- name: 'Dashboard Stats',
- description: 'Premium dashboard stat metrics, trends indicators, and interactive sparkline summaries with hard offset shadows.',
- slug: 'dashboard-stats',
- },
-};
diff --git a/apps/docs/global.d.ts b/apps/docs/global.d.ts
deleted file mode 100644
index 13c5a66..0000000
--- a/apps/docs/global.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-declare module '*.css' {
- const content: Record;
- export default content;
-}
-
-declare module 'brutx-ui/styles.css';
diff --git a/apps/docs/guide/ai.md b/apps/docs/guide/ai.md
new file mode 100644
index 0000000..dbc1ee9
--- /dev/null
+++ b/apps/docs/guide/ai.md
@@ -0,0 +1,135 @@
+# AI 集成
+
+BrutxUI 旨在与 AI 编码助手无缝协作。组件库提供结构化的、类型安全的 API,AI 工具可以理解并据此生成代码。
+
+## AGENTS.md
+
+项目根目录的 `AGENTS.md` 是 AI 编码助手的统一配置文件,为所有 AI 工具(Cursor、Copilot、Windsurf、Claude Code 等)提供项目特定的指令,包括:
+
+- Monorepo 结构与命令
+- Vue 3 `
+
+
+
+ Submit
+
+
+```
+
+### 变体用法
+
+所有变体值都是字符串字面量,AI 可以自动补全:
+
+- **Button 变体**:`default`、`primary`、`secondary`、`accent`、`danger`、`success`、`outline`、`ghost`、`link`
+- **Button 尺寸**:`sm`、`default`、`lg`、`xl`、`icon`
+- **Alert 变体**:`default`、`primary`、`secondary`、`success`、`warning`、`danger`、`info`
+- **Badge 变体**:`default`、`primary`、`secondary`、`accent`、`danger`、`success`、`outline`
+
+### 表单模式
+
+AI 可以使用 Form 组件配合 vee-validate 生成完整的表单实现:
+
+```vue
+
+
+
+
+
+```
+
+### 组合式函数用法
+
+AI 可以正确使用 BrutxUI 的组合式函数,如 `useToast`:
+
+```vue
+
+```
+
+## AI 辅助开发的最佳实践
+
+1. 始终使用 `v-model` 而非 `onChange`/`onInput` 进行双向绑定
+2. 使用 `@click` 而非 `onClick` 作为事件处理器
+3. 使用 `
+
+
+
+
+ Hello BrutxUI
+
+
+
+ Get Started
+
+
+
+
+```
+
+## 配置语言(可选)
+
+BrutxUI 默认显示中文文本。如需切换为英文或其他语言,在 `main.ts` 中配置 `BrutxUIPlugin`:
+
+```ts
+import { createApp } from 'vue'
+import App from './App.vue'
+import { BrutxUIPlugin, en } from 'brutx-ui-vue'
+import './style.css'
+
+const app = createApp(App)
+app.use(BrutxUIPlugin, { locale: en })
+app.mount('#app')
+```
+
+更多语言配置选项请参考[国际化](/guide/locale)指南。
diff --git a/apps/docs/guide/locale.md b/apps/docs/guide/locale.md
new file mode 100644
index 0000000..1cb27bd
--- /dev/null
+++ b/apps/docs/guide/locale.md
@@ -0,0 +1,261 @@
+# 国际化(i18n)
+
+BrutxUI 内置轻量多语言支持,**默认语言为中文(zh-CN)**,同时提供英文(en)语言包。无需安装 `vue-i18n`,开箱即用。
+
+## 设计原则
+
+- **不强制依赖 vue-i18n** — 自带轻量 locale 系统(provide/inject),可与 vue-i18n 共存
+- **默认中文** — 所有组件的默认文本为中文
+- **完全向后兼容** — props 优先级最高,现有用法不受影响
+- **零配置开箱即用** — 不传任何配置时显示中文
+- **响应式切换** — 运行时切换语言,组件自动更新
+
+## 优先级链
+
+组件文本的解析优先级从高到低:
+
+```text
+组件 props > 全局 locale 配置 > 默认中文(zh-CN)
+```
+
+## 默认中文(零配置)
+
+不进行任何配置时,组件自动显示中文文本:
+
+```vue
+
+
+
+
+```
+
+## 全局切换为英文
+
+通过 `BrutxUIPlugin` 的 `locale` 选项切换语言:
+
+```ts
+import { createApp } from 'vue'
+import App from './App.vue'
+import { BrutxUIPlugin, en } from 'brutx-ui-vue'
+
+const app = createApp(App)
+app.use(BrutxUIPlugin, { locale: en })
+app.mount('#app')
+```
+
+## 局部覆盖
+
+### 通过 props 覆盖单个组件
+
+props 优先级最高,可以覆盖任何 locale 文本:
+
+```vue
+
+
+
+
+```
+
+### 通过 texts prop 批量覆盖
+
+对于包含大量文本的组件(如 AuthCard),提供 `texts` prop 进行批量覆盖:
+
+```vue
+
+
+
+```
+
+### 局部子树覆盖语言
+
+使用 `provideLocale` 在某个组件子树内使用不同语言,不影响全局:
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+## 响应式切换
+
+`BrutxUIPlugin` 和 `provideLocale` 都接受响应式的 locale 值,切换时组件自动更新:
+
+```vue
+
+```
+
+## 自定义语言包
+
+### 部分覆盖
+
+使用 `mergeLocale` 深合并,只覆盖需要修改的字段:
+
+```ts
+import { zhCN, mergeLocale } from 'brutx-ui-vue/locales'
+
+const customLocale = mergeLocale(zhCN, {
+ command: { placeholder: '请输入...' },
+})
+app.use(BrutxUIPlugin, { locale: customLocale })
+```
+
+### 创建全新语言包
+
+导入 `Locale` 类型,创建完整的语言包:
+
+```ts
+import type { Locale } from 'brutx-ui-vue'
+import { zhCN } from 'brutx-ui-vue/locales'
+
+const jaJP: Locale = {
+ command: {
+ placeholder: 'コマンドを入力...',
+ emptyText: '結果が見つかりません。',
+ dialogTitle: 'コマンドパレット',
+ dialogDescription: '実行するコマンドを検索...',
+ },
+ // ... 其他组件的翻译
+}
+
+app.use(BrutxUIPlugin, { locale: jaJP })
+```
+
+## 与 vue-i18n 共存
+
+BrutxUI 的 locale 系统独立于 vue-i18n,两者互不干扰。推荐在 vue-i18n 的 locale watch 中同步更新 BrutxUI 的 locale:
+
+```ts
+import { watch, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { provideLocale, zhCN, en } from 'brutx-ui-vue'
+
+const LOCALE_MAP = { 'zh-CN': zhCN, en }
+
+const { locale } = useI18n()
+provideLocale(computed(() => LOCALE_MAP[locale.value] ?? zhCN))
+```
+
+## t() 翻译函数
+
+`useLocale()` 返回的 `t()` 函数支持点号路径访问和插值参数:
+
+```ts
+import { useLocale } from 'brutx-ui-vue'
+
+const { t } = useLocale()
+
+t('command.placeholder')
+// → '输入命令或搜索...'
+
+t('combobox.selectedCount', { count: 3 })
+// → '已选 3 项'
+
+t('pagination.page', { number: 5 })
+// → '第 5 页'
+```
+
+### 回退链
+
+当 `t(path, params?)` 被调用时,按以下顺序查找:
+
+1. 当前 locale 中查找 `path` 对应的值
+2. 不存在 → 回退到 zh-CN 语言包中 `path` 对应的值
+3. 仍不存在 → 返回路径字符串 `path` 本身
+
+## 可用语言包
+
+| 语言包 | 导入路径 | 说明 |
+|--------|---------|------|
+| `zhCN` | `brutx-ui-vue` 或 `brutx-ui-vue/locales` | 简体中文(默认) |
+| `en` | `brutx-ui-vue` 或 `brutx-ui-vue/locales` | 英文 |
+
+## API 参考
+
+### BrutxUIPlugin
+
+Vue 插件,用于全局配置 locale。
+
+```ts
+interface BrutxUIPluginOptions {
+ locale?: MaybeRef
+}
+
+app.use(BrutxUIPlugin, { locale: en })
+```
+
+### provideLocale
+
+在组件子树内注入 locale 配置。
+
+```ts
+function provideLocale(locale: MaybeRef): void
+```
+
+### useLocale
+
+获取当前 locale 和翻译函数。
+
+```ts
+function useLocale(): {
+ locale: ComputedRef
+ t: TranslateFunction
+}
+
+type TranslateFunction = (
+ path: string,
+ params?: Record
+) => string
+```
+
+### mergeLocale
+
+深合并语言包,用于部分覆盖。
+
+```ts
+function mergeLocale(base: Locale, override: DeepPartial): Locale
+```
+
+## 支持的组件文本键
+
+| 组件 | locale 键 | 含插值参数 |
+|------|----------|-----------|
+| Command | `command.placeholder`、`command.emptyText`、`command.dialogTitle`、`command.dialogDescription` | — |
+| Combobox | `combobox.placeholder`、`combobox.multiPlaceholder`、`combobox.searchPlaceholder`、`combobox.emptyText`、`combobox.selectedCount` | `selectedCount`: `{count}` |
+| Pagination | `pagination.firstPage`、`pagination.previousPage`、`pagination.nextPage`、`pagination.lastPage`、`pagination.page`、`pagination.label` | `page`: `{number}` |
+| Carousel | `carousel.previousSlide`、`carousel.nextSlide`、`carousel.goToSlide` | `goToSlide`: `{index}` |
+| Spinner | `spinner.loading` | — |
+| SubmitButton | `submitButton.submitting` | — |
+| CopyToClipboard | `copyToClipboard.copy`、`copyToClipboard.copied` | — |
+| BeforeAfter | `beforeAfter.before`、`beforeAfter.after` | — |
+| AuthCard | `authCard.welcomeBack`、`authCard.signInToContinue`、`authCard.google`、`authCard.github`、`authCard.orEmailLogin`、`authCard.email`、`authCard.password`、`authCard.forgotPassword`、`authCard.signIn`、`authCard.noAccount`、`authCard.register` | — |
+| WaitlistPage | `waitlistPage.title`、`waitlistPage.ctaText`、`waitlistPage.earlyAccess`、`waitlistPage.onWaitlist`、`waitlistPage.live` | `onWaitlist`: `{count}` |
+| DashboardShell | `dashboardShell.sidebarNavigation`、`dashboardShell.signOut` | — |
+| BrutalistHero | `brutalistHero.title`、`brutalistHero.primaryCtaText`、`brutalistHero.secondaryCtaText`、`brutalistHero.neoBrutalismUI` | — |
+| SaaSPricing | `saasPricing.title`、`saasPricing.monthly`、`saasPricing.annually`、`saasPricing.mostPopular`、`saasPricing.perMonth`、`saasPricing.perMonthBilledAnnually`、`saasPricing.billingPeriod` | — |
+| Toast | `toast.close` | — |
+| Dialog | `dialog.close` | — |
+| Sheet | `sheet.close` | — |
+| Breadcrumb | `breadcrumb.label`、`breadcrumb.more` | — |
+| TreeView | `treeView.fileTree` | — |
+| Stepper | `stepper.progressSteps`、`stepper.step` | `step`: `{index}`、`{title}` |
+| EmptyState | `emptyState.defaultTitle`、`emptyState.defaultActionText` | — |
diff --git a/apps/docs/guide/theme.md b/apps/docs/guide/theme.md
new file mode 100644
index 0000000..395709c
--- /dev/null
+++ b/apps/docs/guide/theme.md
@@ -0,0 +1,174 @@
+# 主题与令牌
+
+BrutxUI 使用 CSS 自定义属性(设计令牌)来控制新粗野主义系统的每个视觉方面。这让定制变得像覆盖一个变量一样简单。
+
+## CSS 变量
+
+所有令牌都以 `--brutal-` 为前缀,并在 `:root` 级别定义:
+
+| 令牌 | 亮色模式 | 暗色模式 | 用途 |
+|-------|-------|------|---------|
+| `--brutal-border-width` | `3px` | `3px` | 所有组件的边框粗细 |
+| `--brutal-border-color` | `#000000` | `#ffffff` | 边框颜色 |
+| `--brutal-shadow-offset-x` | `4px` | `4px` | 阴影水平偏移 |
+| `--brutal-shadow-offset-y` | `4px` | `4px` | 阴影垂直偏移 |
+| `--brutal-shadow-color` | `#000000` | `#ffffff` | 阴影颜色 |
+| `--brutal-radius` | `0px` | `0px` | 圆角元素的边框圆角 |
+| `--brutal-bg` | `#ffffff` | `#141414` | 背景颜色 |
+| `--brutal-fg` | `#000000` | `#ffffff` | 前景(文本)颜色 |
+| `--brutal-primary` | `#FF6B6B` | `#FF6B6B` | 主色(珊瑚色) |
+| `--brutal-secondary` | `#4ECDC4` | `#4ECDC4` | 辅助色(薄荷青) |
+| `--brutal-accent` | `#FFE66D` | `#FFE66D` | 强调色(黄色) |
+| `--brutal-destructive` | `#EF476F` | `#EF476F` | 危险/错误颜色 |
+| `--brutal-success` | `#7FB069` | `#7FB069` | 成功颜色 |
+| `--brutal-muted` | `#f3f4f6` | `#1e1e1e` | 柔和背景 |
+| `--brutal-muted-foreground` | `#4B5563` | `#9CA3AF` | 柔和文本颜色 |
+| `--brutal-ring` | `#000000` | `#ffffff` | 焦点环颜色 |
+| `--brutal-pressed-offset` | `2px` | `2px` | 按下状态的 Y 轴偏移 |
+| `--brutal-info` | `#4A90D9` | `#3B82F6` | 信息颜色 |
+| `--brutal-overlay` | `rgba(0,0,0,0.5)` | `rgba(0,0,0,0.7)` | 遮罩背景 |
+| `--brutal-placeholder` | `#9CA3AF` | `#6B7280` | 占位符文本颜色 |
+
+## 主题预设
+
+### Classic(默认)
+
+标志性的 BrutxUI 风格。粗边框、硬阴影、零圆角、鲜艳色彩。
+
+```css
+.theme-classic {
+ --brutal-border-width: 3px;
+ --brutal-border-color: #000000;
+ --brutal-shadow-offset-x: 4px;
+ --brutal-shadow-offset-y: 4px;
+ --brutal-shadow-color: #000000;
+ --brutal-radius: 0px;
+ --brutal-primary: #FF6B6B;
+ --brutal-secondary: #4ECDC4;
+ --brutal-accent: #FFE66D;
+}
+```
+
+### Pastel
+
+更柔和、更友好的风格。更细的边框、更小的阴影、圆角、柔和色调。
+
+```css
+.theme-pastel {
+ --brutal-border-width: 2px;
+ --brutal-border-color: #1e1e24;
+ --brutal-shadow-offset-x: 3px;
+ --brutal-shadow-offset-y: 3px;
+ --brutal-shadow-color: #1e1e24;
+ --brutal-radius: 8px;
+ --brutal-primary: #d6c6e1;
+ --brutal-secondary: #c5ded9;
+ --brutal-accent: #fbe3b5;
+}
+```
+
+### Mono
+
+极致对比。超粗边框、更大阴影、灰度调色板。
+
+```css
+.theme-mono {
+ --brutal-border-width: 4px;
+ --brutal-border-color: #000000;
+ --brutal-shadow-offset-x: 5px;
+ --brutal-shadow-offset-y: 5px;
+ --brutal-shadow-color: #000000;
+ --brutal-radius: 0px;
+ --brutal-primary: #000000;
+ --brutal-secondary: #ffffff;
+ --brutal-accent: #7a7a7a;
+}
+```
+
+## 自定义令牌
+
+在 `:root` 级别覆盖令牌以进行全局修改:
+
+```css
+:root {
+ --brutal-primary: #8B5CF6;
+ --brutal-secondary: #06B6D4;
+ --brutal-radius: 4px;
+}
+```
+
+或将覆盖范围限定到特定区域:
+
+```css
+.sidebar {
+ --brutal-primary: #8B5CF6;
+ --brutal-border-width: 2px;
+}
+```
+
+## Tailwind 工具类
+
+BrutxUI 注册了以下引用 CSS 变量的 Tailwind 工具类:
+
+### 边框
+
+| 工具类 | 映射到 |
+|---------|---------|
+| `border-3` | `border-width: var(--brutal-border-width)` |
+| `border-brutal` | `border-color: var(--brutal-border-color)` |
+| `rounded-brutal` | `border-radius: var(--brutal-radius)` |
+
+### 阴影
+
+| 工具类 | 映射到 |
+|---------|---------|
+| `shadow-brutal` | 完整偏移阴影 |
+| `shadow-brutal-sm` | 半偏移阴影 |
+| `shadow-brutal-lg` | 1.5 倍偏移阴影 |
+| `shadow-brutal-xl` | 2 倍偏移阴影 |
+
+### 颜色
+
+| 工具类 | 映射到 |
+|---------|---------|
+| `bg-brutal-bg` | `var(--brutal-bg)` |
+| `text-brutal-fg` | `var(--brutal-fg)` |
+| `bg-brutal-primary` | `var(--brutal-primary)` |
+| `bg-brutal-secondary` | `var(--brutal-secondary)` |
+| `bg-brutal-accent` | `var(--brutal-accent)` |
+| `bg-brutal-destructive` | `var(--brutal-destructive)` |
+| `bg-brutal-success` | `var(--brutal-success)` |
+| `bg-brutal-muted` | `var(--brutal-muted)` |
+| `text-brutal-muted-foreground` | `var(--brutal-muted-foreground)` |
+| `ring-brutal-ring` | `var(--brutal-ring)` |
+
+## 暗色模式
+
+BrutxUI 通过 `.dark` 类支持暗色模式。当 `dark` 类被应用到 `` 或 `` 元素时,所有 CSS 变量会自动切换为暗色值。
+
+```html
+
+
+
+```
+
+通过编程方式切换暗色模式:
+
+```vue
+
+
+
+ Toggle Dark Mode
+
+```
+
+主题预设也通过 `.dark .theme-pastel` 或 `.theme-pastel.dark` 选择器支持暗色模式。
diff --git a/apps/docs/hooks/use-copy-to-clipboard.ts b/apps/docs/hooks/use-copy-to-clipboard.ts
deleted file mode 100644
index 4dd1924..0000000
--- a/apps/docs/hooks/use-copy-to-clipboard.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client';
-
-import * as React from 'react';
-
-export function useCopyToClipboard(resetDelay = 2000) {
- const [copied, setCopied] = React.useState(false);
-
- const copy = React.useCallback(
- async (text: string) => {
- try {
- await navigator.clipboard.writeText(text);
- setCopied(true);
- window.setTimeout(() => setCopied(false), resetDelay);
- return true;
- } catch (error) {
- console.error('Failed to copy:', error);
- return false;
- }
- },
- [resetDelay]
- );
-
- return { copied, copy };
-}
diff --git a/apps/docs/index.md b/apps/docs/index.md
new file mode 100644
index 0000000..9ea798a
--- /dev/null
+++ b/apps/docs/index.md
@@ -0,0 +1,32 @@
+---
+layout: home
+
+hero:
+ name: BrutxUI
+ text: Neo-Brutalism Vue 3 组件库
+ tagline: 粗边框。硬阴影。零妥协。为 Vue 3 + Tailwind CSS 打造的复制粘贴优先组件库,每一个像素都遵循新粗野主义设计哲学。
+ actions:
+ - theme: brand
+ text: 快速开始 →
+ link: /guide/getting-started
+ - theme: alt
+ text: 浏览组件
+ link: /components/alert
+ - theme: alt
+ text: GitHub
+ link: https://github.com/lidaixingchen/brutxui-vue3
+
+features:
+ - title: 粗野主义设计系统
+ details: 3px 粗边框、4px 硬阴影、零圆角、鲜艳色彩。每个组件都基于 CSS 自定义属性令牌构建,支持 Classic / Pastel / Mono 三套主题预设。
+ - title: 无障碍优先
+ details: 基于 reka-ui 无头原语构建。正确的 ARIA 属性、键盘导航、焦点管理和屏幕阅读器兼容性,符合 WCAG 标准。
+ - title: TypeScript 严格模式
+ details: 全程严格类型安全。通过 defineProps、CVA 变体和每个 prop 的自动补全,零 any 类型。
+ - title: 复制粘贴优先
+ details: 零依赖锁定。直接将组件源码复制到你的项目中,完全拥有代码,随心定制每一个细节。
+ - title: CLI 工具
+ details: 一条命令添加组件。npx brutx-vue init 初始化项目,npx brutx-vue add button card 按需安装,AI 友好的项目结构。
+ - title: 现代技术栈
+ details: Vue 3.5+ script setup、Tailwind CSS 3.4+、Vite 6+、Vitest 3+、pnpm。为现代 Vue 生态而生。
+---
diff --git a/apps/docs/lib/utils.ts b/apps/docs/lib/utils.ts
deleted file mode 100644
index 8c7f872..0000000
--- a/apps/docs/lib/utils.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { type ClassValue, clsx } from 'clsx';
-import { twMerge } from 'tailwind-merge';
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
-}
-
-export function getVariantClass(
- variants: Record,
- variant: string,
- fallback: string = ''
-): string {
- return variants[variant] || fallback;
-}
-
-export const brutalismClasses = {
- base: 'nb-border nb-shadow nb-font nb-no-radius nb-transition',
- interactive: 'nb-hover nb-active nb-focus nb-disabled',
- button: 'nb-btn',
- card: 'nb-card',
- input: 'nb-input',
-} as const;
diff --git a/apps/docs/next-env.d.ts b/apps/docs/next-env.d.ts
deleted file mode 100644
index 830fb59..0000000
--- a/apps/docs/next-env.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-///
-///
-///
-
-// NOTE: This file should not be edited
-// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs
deleted file mode 100644
index 3cadd5d..0000000
--- a/apps/docs/next.config.mjs
+++ /dev/null
@@ -1,61 +0,0 @@
-import createMDX from '@next/mdx';
-
-/**
- * @type {import('next').NextConfig}
- */
-const nextConfig = {
- pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
- transpilePackages: ['brutx-ui'],
-
- experimental: {
- reactCompiler: true,
- optimizePackageImports: [
- 'lucide-react',
- '@radix-ui/react-dialog',
- '@radix-ui/react-dropdown-menu',
- '@radix-ui/react-popover',
- '@radix-ui/react-select',
- '@radix-ui/react-tabs',
- '@radix-ui/react-tooltip',
- '@radix-ui/react-checkbox',
- '@radix-ui/react-switch',
- '@radix-ui/react-avatar',
- '@radix-ui/react-scroll-area',
- '@radix-ui/react-separator',
- '@radix-ui/react-slot',
- 'cmdk',
- ],
- },
-
- images: {
- remotePatterns: [
- {
- protocol: 'https',
- hostname: 'i.pravatar.cc',
- },
- {
- protocol: 'https',
- hostname: 'avatars.githubusercontent.com',
- },
- ],
- },
-
- turbopack: {
- rules: {
- '*.svg': {
- loaders: ['@svgr/webpack'],
- as: '*.js',
- },
- },
- },
-};
-
-const withMDX = createMDX({
- extension: /\.mdx?$/,
- options: {
- remarkPlugins: [],
- rehypePlugins: [],
- },
-});
-
-export default withMDX(nextConfig);
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 5bee4e1..f284c0a 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -2,45 +2,27 @@
"name": "docs",
"version": "0.1.0",
"private": true,
+ "type": "module",
"scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint",
- "typecheck": "tsc --noEmit"
+ "dev": "vitepress dev",
+ "build": "vitepress build",
+ "preview": "vitepress preview"
},
"dependencies": {
- "@hookform/resolvers": "^5.4.0",
- "@mdx-js/loader": "^3.0.0",
- "@mdx-js/react": "^3.0.0",
- "@next/mdx": "^15.5.18",
- "@radix-ui/react-alert-dialog": "^1.1.15",
- "@radix-ui/react-label": "^2.1.8",
- "@radix-ui/react-progress": "^1.1.8",
- "@radix-ui/react-radio-group": "^1.3.8",
- "@radix-ui/react-slider": "^1.3.6",
- "@radix-ui/react-toggle": "^1.1.10",
- "@radix-ui/react-toggle-group": "^1.1.11",
- "brutx-ui": "workspace:*",
- "next": "15.5.18",
- "next-themes": "^0.2.1",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "react-hook-form": "^7.76.1",
- "zod": "^4.4.3"
+ "brutx-ui-vue": "workspace:*",
+ "clsx": "^2.1.0",
+ "lucide-vue-next": "^0.510.0",
+ "tailwind-merge": "^2.2.0",
+ "vue": "^3.5.0"
},
"devDependencies": {
- "@types/mdx": "^2.0.10",
- "@types/node": "^20.17.15",
- "@types/react": "^19.0.8",
- "@types/react-dom": "^19.0.3",
+ "@vitejs/plugin-vue": "^5.2.0",
"autoprefixer": "^10.4.16",
- "babel-plugin-react-compiler": "^1.0.0",
- "eslint": "^8.56.0",
- "eslint-config-next": "15.5.18",
- "lucide-react": "^0.555.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.0",
- "typescript": "^5.7.2"
+ "tailwindcss-animate": "^1.0.7",
+ "typescript": "^5.7.2",
+ "vitepress": "^1.6.0",
+ "vue-tsc": "^2.2.0"
}
}
diff --git a/apps/docs/postcss.config.js b/apps/docs/postcss.config.cjs
similarity index 96%
rename from apps/docs/postcss.config.js
rename to apps/docs/postcss.config.cjs
index 67cdf1a..fef1b22 100644
--- a/apps/docs/postcss.config.js
+++ b/apps/docs/postcss.config.cjs
@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
-};
+}
diff --git a/apps/docs/public/--output b/apps/docs/public/--output
deleted file mode 100644
index b440a5d..0000000
Binary files a/apps/docs/public/--output and /dev/null differ
diff --git a/apps/docs/public/manifest.json b/apps/docs/public/manifest.json
index 7ccfb05..a842563 100644
--- a/apps/docs/public/manifest.json
+++ b/apps/docs/public/manifest.json
@@ -1,14 +1,14 @@
{
- "name": "Brutx",
- "short_name": "Brutx",
- "description": "Brutx - Neo-Brutalism React Component Library - 27+ accessible components with bold borders, offset shadows, and vibrant colors.",
- "start_url": "/",
+ "name": "BrutxUI",
+ "short_name": "BrutxUI",
+ "description": "BrutxUI - Neo-Brutalism 风格 Vue 3 组件库 - 35+ 无障碍组件,粗边框、硬阴影、鲜艳色彩。",
+ "start_url": "/brutxui-vue3/",
"display": "standalone",
- "background_color": "#FFFBF5",
+ "background_color": "#ffffff",
"theme_color": "#FF6B6B",
"icons": [
{
- "src": "/favicon.svg",
+ "src": "/brutxui-vue3/favicon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
diff --git a/apps/docs/public/og-image.svg b/apps/docs/public/og-image.svg
index ba68b6b..a4b1ba2 100644
--- a/apps/docs/public/og-image.svg
+++ b/apps/docs/public/og-image.svg
@@ -31,13 +31,13 @@
- Neo-Brutalism React Component Library
+ Neo-Brutalism Vue 3 Component Library
-
+
- 27 Components
+ 43 Components
@@ -51,9 +51,9 @@
Dark Mode
-
+
- brutxui.site
+ lidaixingchen.github.io/brutxui-vue3
diff --git a/apps/docs/public/robots.txt b/apps/docs/public/robots.txt
new file mode 100644
index 0000000..ed5b2ec
--- /dev/null
+++ b/apps/docs/public/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: https://lidaixingchen.github.io/brutxui-vue3/sitemap.xml
diff --git a/apps/docs/tailwind.config.cjs b/apps/docs/tailwind.config.cjs
new file mode 100644
index 0000000..d04bb0c
--- /dev/null
+++ b/apps/docs/tailwind.config.cjs
@@ -0,0 +1,49 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: 'class',
+ content: [
+ './*.md',
+ './guide/*.md',
+ './components/*.md',
+ './blocks/*.md',
+ './.vitepress/**/*.{ts,vue}',
+ '../../packages/ui/src/**/*.{js,ts,vue}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ brutal: {
+ bg: 'var(--brutal-bg)',
+ fg: 'var(--brutal-fg)',
+ primary: 'var(--brutal-primary)',
+ secondary: 'var(--brutal-secondary)',
+ accent: 'var(--brutal-accent)',
+ destructive: 'var(--brutal-destructive)',
+ success: 'var(--brutal-success)',
+ muted: 'var(--brutal-muted)',
+ 'muted-foreground': 'var(--brutal-muted-foreground)',
+ ring: 'var(--brutal-ring)',
+ info: 'var(--brutal-info)',
+ overlay: 'var(--brutal-overlay)',
+ placeholder: 'var(--brutal-placeholder)',
+ },
+ },
+ fontFamily: {
+ brutal: ['"Space Grotesk"', 'system-ui', 'sans-serif'],
+ },
+ boxShadow: {
+ brutal: 'var(--brutal-shadow-offset-x, 4px) var(--brutal-shadow-offset-y, 4px) 0px 0px var(--brutal-shadow-color, #000000)',
+ 'brutal-sm': 'calc(var(--brutal-shadow-offset-x, 4px) / 2) calc(var(--brutal-shadow-offset-y, 4px) / 2) 0px 0px var(--brutal-shadow-color, #000000)',
+ 'brutal-lg': 'calc(var(--brutal-shadow-offset-x, 4px) * 1.5) calc(var(--brutal-shadow-offset-y, 4px) * 1.5) 0px 0px var(--brutal-shadow-color, #000000)',
+ 'brutal-xl': 'calc(var(--brutal-shadow-offset-x, 4px) * 2) calc(var(--brutal-shadow-offset-y, 4px) * 2) 0px 0px var(--brutal-shadow-color, #000000)',
+ },
+ borderWidth: {
+ 3: '3px',
+ },
+ },
+ },
+ plugins: [
+ require('../../packages/ui/src/lib/brutalism-plugin'),
+ require('tailwindcss-animate'),
+ ],
+}
diff --git a/apps/docs/tailwind.config.ts b/apps/docs/tailwind.config.ts
deleted file mode 100644
index 2818655..0000000
--- a/apps/docs/tailwind.config.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import type { Config } from 'tailwindcss';
-
-const config: Config = {
- darkMode: 'class',
- content: [
- './pages/**/*.{js,ts,jsx,tsx,mdx}',
- './components/**/*.{js,ts,jsx,tsx,mdx}',
- './app/**/*.{js,ts,jsx,tsx,mdx}',
- '../../packages/ui/src/**/*.{js,ts,jsx,tsx}',
- './node_modules/brutx-ui/**/*.{js,mjs}',
- ],
- theme: {
- extend: {
- colors: {
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))',
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))',
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))',
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))',
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))',
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))',
- },
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))',
- },
- brutalism: {
- bg: '#FFFFFF',
- text: '#000000',
- primary: '#FF6B6B',
- secondary: '#4ECDC4',
- accent: '#FFE66D',
- success: '#7FB069',
- warning: '#F9A825',
- danger: '#EF476F',
- info: '#4A90D9',
- },
- },
- fontFamily: {
- brutal: ['"Space Grotesk"', 'system-ui', 'sans-serif'],
- },
- boxShadow: {
- xs: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
- brutal: '4px 4px 0px 0px #000000',
- 'brutal-sm': '2px 2px 0px 0px #000000',
- 'brutal-lg': '6px 6px 0px 0px #000000',
- 'brutal-xl': '8px 8px 0px 0px #000000',
- 'brutal-dark': '4px 4px 0px 0px #FFFFFF',
- 'brutal-dark-sm': '2px 2px 0px 0px #FFFFFF',
- 'brutal-dark-lg': '6px 6px 0px 0px #FFFFFF',
- },
- borderWidth: {
- 3: '3px',
- },
- },
- },
- plugins: [require('../../packages/ui/src/lib/brutalism-plugin')],
-};
-
-export default config;
diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json
index dd517d9..dcc8e9e 100644
--- a/apps/docs/tsconfig.json
+++ b/apps/docs/tsconfig.json
@@ -1,29 +1,19 @@
{
"compilerOptions": {
- "target": "ES2017",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
+ "target": "ES2020",
+ "module": "ESNext",
"moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
+ "strict": true,
"jsx": "preserve",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+ "noEmit": true,
"paths": {
- "@/*": ["./*"],
- "@ui/*": ["../../packages/ui/src/*"],
- "@components/ui/*": ["../../packages/ui/src/components/ui/*"]
+ "@/*": ["./.vitepress/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "**/*.mdx"],
+ "include": [".vitepress/**/*.ts", ".vitepress/**/*.vue"],
"exclude": ["node_modules"]
}
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 60225ce..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Internal Architecture Documentation
-
-> **Note**: This directory contains **internal developer documentation** for the Brutx project.
-> For the public-facing documentation site, see [`apps/docs/`](../apps/docs/).
-
-## Contents
-
-- **[cli.md](./cli.md)** — CLI command reference and `components.json` schema
-- **[registry.md](./registry.md)** — Registry architecture, JSON schema, and how to add new components
-- **[tailwind.md](./tailwind.md)** — Tailwind CSS v3/v4 integration guide and dark mode support
diff --git a/docs/cli.md b/docs/cli.md
deleted file mode 100644
index af40cb1..0000000
--- a/docs/cli.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# Brutx CLI Reference
-
-The Brutx CLI (`brutx`) is an on-demand tool for project initialization, styles configuration, and component installation.
-
----
-
-## Usage
-
-Execute commands via `npx`:
-
-```bash
-npx brutx@latest [command] [options]
-```
-
----
-
-## Commands
-
-### 1. `init`
-
-Initialize Brutx configuration and style system in your local project.
-
-```bash
-npx brutx@latest init [options]
-```
-
-#### What it does
-- Detects project structure (Next.js, Vite, Create React App, Remix, etc.) and package managers (npm, pnpm, yarn, bun).
-- Creates `components.json` in the root folder.
-- Generates the utility file `lib/utils.ts` containing the standard class merger helper (`cn`).
-- Creates a `components/ui/` directory.
-- Appends Neo-Brutalist styles and utility classes (such as `.shadow-brutal`, `.border-3`) to your globals CSS.
-- Installs base dependencies (`clsx`, `tailwind-merge`, `class-variance-authority`, `lucide-react`).
-
-#### Options
-- `-y, --yes`: Skip all confirmation prompts and use detected configurations.
-- `-d, --defaults`: Initialize with default tailwind settings.
-- `-f, --force`: Force overwrite existing configuration file.
-- `-c, --cwd `: Override target working directory (default is `process.cwd()`).
-- `-s, --silent`: Suppress all outputs and spinner logging.
-
----
-
-### 2. `add`
-
-Add components and all of their dependencies to your project.
-
-```bash
-npx brutx@latest add [components...] [options]
-```
-
-#### What it does
-- Resolves and downloads all sub-components (`registryDependencies`) recursively.
-- Replaces import paths matching registry defaults with your workspace custom aliases (e.g. `@/lib/utils` is replaced with `~/utils/cn`).
-- Writes component files and recursively ensures target folders are created safely.
-- Detects and installs required npm dependencies.
-
-#### Options
-- `-y, --yes`: Skip confirmation picker if no components arguments are passed.
-- `-a, --all`: Add every available Brutx component.
-- `-o, --overwrite`: Overwrite existing local components files if they exist.
-- `-p, --path `: Override the path to install components to.
-- `-c, --cwd `: Override working directory.
-- `-s, --silent`: Mute all logs.
-- `--dry-run`: Simulate files generation and dependency installation without writing files to disk (useful for dry runs and testing configurations).
-- `-r, --registry `: Specify a custom registry URL or local file-based path for offline setups and testing.
-
----
-
-## Configuration File (`components.json`)
-
-The CLI reads and writes project settings inside `components.json`:
-
-```json
-{
- "$schema": "https://brutxui.site/schema.json",
- "style": "brutalism",
- "tailwind": {
- "config": "tailwind.config.js",
- "css": "src/app/globals.css"
- },
- "aliases": {
- "components": "@/components",
- "utils": "@/lib/utils"
- }
-}
-```
-
-### Fields
-- **`style`**: Design style token (default is `"brutalism"`).
-- **`tailwind.config`**: Path to the Tailwind configuration file.
-- **`tailwind.css`**: Path to the global CSS file of your project.
-- **`aliases.components`**: Import alias pointing to the components directory.
-- **`aliases.utils`**: Import alias pointing to the utilities directory.
diff --git a/docs/registry.md b/docs/registry.md
deleted file mode 100644
index 68495e2..0000000
--- a/docs/registry.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Component Registry System
-
-Brutx distributes React components through a **registry-based architecture**. Instead of installing one large npm package with every style and layout, users fetch the components they need directly into their local codebase.
-
----
-
-## How it Works
-
-Every component is defined as a static JSON file stored in `packages/registry/registry/[name].json`.
-
-When a user runs `npx brutx add [component]`, the CLI:
-1. Resolves all dependencies and sub-components (`registryDependencies`) recursively using a DFS topological sort.
-2. Fetches the JSON file from the GitHub remote registry (or reads it locally in offline mode).
-3. Resolves import aliases (e.g., `@/lib/utils` and `@/components`) to match the user's custom workspace configuration.
-4. Writes files to the local disk and triggers the project's package manager to install npm dependencies.
-
----
-
-## Registry JSON Schema
-
-Each registry item follows this TypeScript schema structure:
-
-```json
-{
- "name": "combobox",
- "type": "registry:ui",
- "dependencies": [
- "cmdk"
- ],
- "registryDependencies": [
- "button",
- "popover",
- "command"
- ],
- "files": [
- {
- "path": "components/ui/combobox.tsx",
- "content": "'use client';\n\nimport * as React from 'react';\n..."
- }
- ]
-}
-```
-
-### Fields
-- **`name`** *(string)*: Unique identifier of the component.
-- **`type`** *(string)*: Type of registry item (usually `"registry:ui"`).
-- **`dependencies`** *(string[])*: Third-party npm packages required by this component (e.g. `lucide-react`, `@radix-ui/react-slot`).
-- **`registryDependencies`** *(string[])*: Other Brutx components required by this component.
-- **`files`** *(Array)*: List of files to be written to the user's project, containing paths relative to the component alias and the raw file string content.
-
----
-
-## Adding a New Component to the Registry
-
-To add a new component dynamically:
-
-1. Create your React component in `packages/ui/src/components/[name].tsx` or `packages/ui/src/components/ui/[name].tsx`.
-2. Declare it inside the CLI `COMPONENTS` metadata constant within `packages/cli/src/lib/constants.ts`:
- ```typescript
- export const COMPONENTS = {
- // ...
- "new-component": {
- name: "new-component",
- dependencies: ["some-npm-package"]
- }
- }
- ```
-3. Run the automated registry bundler script:
- ```bash
- pnpm --filter brutx-registry build
- ```
- This script will:
- - Read the template code.
- - Use regex to detect other Brutx components imported in the file and add them as `registryDependencies`.
- - Package everything into a static `.json` schema file inside `packages/registry/registry/`.
-4. Commit your changes and push to GitHub. Once the registry is published, the component is available from the remote registry.
diff --git a/docs/tailwind.md b/docs/tailwind.md
deleted file mode 100644
index 6fe7d1d..0000000
--- a/docs/tailwind.md
+++ /dev/null
@@ -1,110 +0,0 @@
-# Tailwind CSS Integration Guide
-
-Brutx is built on React and uses **Tailwind CSS** for styling. It supports both **Tailwind CSS v3** and the CSS-first **Tailwind CSS v4** workflow.
-
----
-
-## Tailwind CSS v4 Configuration (CSS-First)
-
-Tailwind CSS v4 introduces a CSS-first configuration pipeline. In most v4 projects, you no longer need a `tailwind.config.js` file.
-
-### 1. Style Integration on `init`
-When you run `npx brutx@latest init` in a Tailwind v4 project, the CLI automatically detects the version and configures your `components.json` with an empty config value (`"config": ""`).
-
-It appends the Neo-Brutalist design tokens directly to your main CSS file (`globals.css`):
-
-```css
-@import "tailwindcss";
-
-/* Brutalist UI Styles */
-.border-3 {
- border-width: 3px;
-}
-
-.shadow-brutal {
- box-shadow: 4px 4px 0px 0px #000;
-}
-
-.shadow-brutal-sm {
- box-shadow: 2px 2px 0px 0px #000;
-}
-
-.shadow-brutal-lg {
- box-shadow: 6px 6px 0px 0px #000;
-}
-
-.dark .shadow-brutal {
- box-shadow: 4px 4px 0px 0px #fff;
-}
-
-.dark .shadow-brutal-sm {
- box-shadow: 2px 2px 0px 0px #fff;
-}
-
-.dark .shadow-brutal-lg {
- box-shadow: 6px 6px 0px 0px #fff;
-}
-```
-
-Because these utility classes are written in native CSS, Tailwind v4 can parse them directly.
-
-### 2. Customizing Theme Colors in Tailwind v4
-To customize theme colors in Tailwind v4, declare them inside the `@theme` directive in your `globals.css`:
-
-```css
-@import "tailwindcss";
-
-@theme {
- /* Override default tailwind colors or add custom ones */
- --color-coral: #FF6B6B;
- --color-teal: #4ECDC4;
- --color-yellow: #FFE66D;
-
- /* Configure custom font */
- --font-sans: 'Outfit', sans-serif;
-}
-```
-
-You can then use `bg-coral`, `text-teal`, and `border-yellow` inside your project.
-
----
-
-## Tailwind CSS v3 Configuration (JS-Based)
-
-Tailwind CSS v3 relies on JavaScript config files.
-
-### 1. Configuration on `init`
-Running `npx brutx@latest init` creates/updates `tailwind.config.js` and appends the Neo-Brutalist CSS utilities inside your globals CSS file.
-
-### 2. Customizing Theme Colors in Tailwind v3
-To customize colors in Tailwind v3, add them inside `tailwind.config.js`:
-
-```javascript
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- theme: {
- extend: {
- colors: {
- coral: '#FF6B6B',
- teal: '#4ECDC4',
- yellow: '#FFE66D',
- },
- },
- },
-}
-```
-
----
-
-## Dark Mode
-
-Brutx fully supports dark mode on both Tailwind v3 and v4:
-
-- **Tailwind v4:** Dark mode variants are automatically handled. Standard class selectors like `.dark` work out of the box.
-- **Tailwind v3:** If you are using class-based dark mode, ensure you have configured it in `tailwind.config.js`:
- ```javascript
- module.exports = {
- darkMode: 'class',
- // ...
- }
- ```
diff --git a/llms-full.txt b/llms-full.txt
deleted file mode 100644
index 59eabf5..0000000
--- a/llms-full.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-# BrutxUI (Full Context)
-
-BrutxUI is a high-fidelity Neo-Brutalist React and Tailwind CSS component registry. It maps bold, black comic-book borders, offset shadows, and saturated highlights directly into accessible React components.
-
----
-
-## 🎨 Complete Design Tokens & Utility Classes
-
-BrutxUI exposes CSS Custom Properties that can be dynamically customized. The entire system is built around these core tokens:
-
-```css
-:root {
- /* Sizing Outlines */
- --brutal-border-width: 3px;
- --brutal-border-color: #000000;
-
- /* Shadows */
- --brutal-shadow-offset-x: 4px;
- --brutal-shadow-offset-y: 4px;
- --brutal-shadow-color: #000000;
-
- /* Radii */
- --brutal-radius: 0px;
-
- /* pressed translation depths */
- --brutal-pressed-offset: 2px;
-
- /* Colors */
- --brutal-primary: #FF6B6B; /* Coral Red */
- --brutal-secondary: #4ECDC4; /* Mint Teal */
- --brutal-accent: #FFE66D; /* Neon Yellow */
- --brutal-destructive: #EF476F; /* Dangerous Red */
- --brutal-success: #7FB069; /* Saturated Green */
- --brutal-ring: #000000; /* Focus indicator */
-}
-```
-
----
-
-## 🧩 Architectural Blueprint: CVA Variant Setup
-
-Every BrutxUI component leverages `class-variance-authority` (cva) to declare variants cleanly, merging custom user classes via `cn`.
-
-### Button Component Blueprint:
-```tsx
-import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
-import { cva, type VariantProps } from 'class-variance-authority';
-import { cn } from '@/lib/utils';
-
-const buttonVariants = cva(
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-black ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:translate-x-[2px] active:translate-y-[2px] active:shadow-[2px_2px_0px_0px_#000]',
- {
- variants: {
- variant: {
- primary: 'bg-[#FF6B6B] text-white border-3 border-black shadow-brutal hover:bg-[#FF6B6B]/90',
- secondary: 'bg-[#4ECDC4] text-black border-3 border-black shadow-brutal hover:bg-[#4ECDC4]/90',
- accent: 'bg-[#FFE66D] text-black border-3 border-black shadow-brutal hover:bg-[#FFE66D]/90',
- outline: 'bg-white text-black border-3 border-black shadow-brutal hover:bg-gray-100',
- ghost: 'hover:bg-gray-100 text-black border-3 border-transparent'
- },
- size: {
- default: 'h-10 px-4 py-2',
- sm: 'h-9 px-3',
- lg: 'h-11 px-8'
- }
- },
- defaultVariants: {
- variant: 'primary',
- size: 'default'
- }
- }
-);
-```
-
----
-
-## 🛠️ Monorepo Directories & Code Locations
-
-- **Documentation Website:** `/apps/docs/`
-- **Core Package Registry:** `/packages/ui/`
- - Component files reside in: `/packages/ui/src/components/ui/`
- - Workspace exports mapping: `/packages/ui/package.json`
-- **Registry Builders:** `/packages/registry/`
- - Components schema configurations: `/packages/registry/registry.json`
-- **CLI Utilities:** `/packages/cli/`
- - CLI operations: `/packages/cli/src/commands/`
- - Tests suite coverage: `/packages/cli/tests/`
-
----
-
-## 🚫 Avoid These Bad Anti-Patterns
-
-When writing code for BrutxUI:
-1. **NO Soft Shadows:** Do not use Tailwind default `shadow-md` or `shadow-lg`. Use `shadow-brutal` or `shadow-brutal-lg`.
-2. **NO Rounded Borders:** Do not use `rounded-md` or `rounded-lg` as default variants unless softened explicitly via theme custom styling classes. Use `rounded-none` or `rounded-brutal`.
-3. **NO Dull Outlines:** Do not use light borders like `border-slate-100` or `border-slate-200` for primary borders. Outlines must remain heavy (`border-3 border-black`).
-4. **NO Missing Press Feedback:** Interactive buttons must translate on click. Do not let active states feel inert or lifeless.
-5. **NO Path Traversal:** When compiling or editing CLI dependencies, never allow relative traverses (`..`) to escape safe workspaces. Normalise and check paths.
diff --git a/llms.txt b/llms.txt
deleted file mode 100644
index a339816..0000000
--- a/llms.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-# BrutxUI
-
-The ultimate copy-paste Neo-Brutalist React & Tailwind CSS component registry. Bold, raw, accessible, and 100% compatible with shadcn/ui.
-
-## Philosophy
-BrutxUI distributes components on-demand via a JSON registry CLI rather than a monolithic node dependency. You copy, paste, and own the code.
-
-## Visual Styling Rules
-- **Borders:** Thick black outlines using `border-3 border-black` (or `dark:border-white` in dark mode).
-- **Shadows:** Hard offset shadows using `shadow-brutal` (4px 4px 0px 0px #000) or `shadow-brutal-lg` (6px 6px). Avoid standard blurred shadows.
-- **Corners:** Sharp 0px corners via `rounded-none`, or global token parameterized classes like `rounded-brutal`.
-- **Transitions / Press:** Physical press effect translation on active state: `active:translate-x-[2px] active:translate-y-[2px] active:shadow-[2px_2px_0px_0px_#000] transition-all`.
-- **Contrast / Accents:** Neon saturated colors: Coral (`#FF6B6B`), Mint Teal (`#4ECDC4`), Saturated Yellow (`#FFE66D`).
-
-## Directory Structure
-- `apps/docs/`: Next.js 15 documentation website
-- `packages/ui/`: Core React source code modules (`brutx-ui`)
- - `src/components/ui/`: Direct component files (e.g. `button.tsx`, `card.tsx`)
-- `packages/cli/`: CLI package commands (`brutx`) for `init` and `add`
-- `packages/registry/`: Dynamic JSON component registry builder
-
-## Core Commands
-```bash
-# Initialize BrutxUI
-npx brutx@latest init
-
-# Add atomic components
-npx brutx@latest add button card dialog
-
-# Add all components
-npx brutx@latest add --all
-
-# shadcn compatibility workflow addition
-npx shadcn@latest add https://brutxui.site/registry/button.json
-```
-
-## Creating Custom Components
-1. Save React markup in `packages/ui/src/components/ui/my-component.tsx`
-2. Extend class variance authorities (CVA) inside components.
-3. Import the shared class merger utility `cn` from `@/lib/utils` or `../lib/utils` depending on nesting depths.
-4. Keep props predictable, well-typed, and accessible.
diff --git a/package.json b/package.json
index 22b2ce8..c936c7b 100644
--- a/package.json
+++ b/package.json
@@ -2,18 +2,18 @@
"name": "brutx-monorepo",
"version": "1.0.0",
"private": true,
- "description": "Neo-Brutalism UI Library Monorepo",
+ "description": "Neo-Brutalism UI Library Monorepo (Vue 3)",
"scripts": {
"dev": "pnpm --filter docs dev",
- "build": "pnpm --filter brutx-ui build && pnpm --filter docs build",
- "build:ui": "pnpm --filter brutx-ui build",
+ "build": "pnpm --filter brutx-ui-vue build",
+ "build:ui": "pnpm --filter brutx-ui-vue build",
"build:docs": "pnpm --filter docs build",
"lint": "pnpm -r lint",
"clean": "pnpm -r clean",
"typecheck": "pnpm -r typecheck",
- "test": "pnpm --filter brutx-ui test && pnpm --filter brutx test",
- "test:watch": "pnpm --filter brutx-ui test:watch",
- "test:coverage": "pnpm --filter brutx-ui test:coverage"
+ "test": "pnpm --filter brutx-ui-vue test",
+ "test:watch": "pnpm --filter brutx-ui-vue test:watch",
+ "test:coverage": "pnpm --filter brutx-ui-vue test:coverage"
},
"devDependencies": {
"typescript": "^5.7.2"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 07d1131..63c5e81 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,9 +1,9 @@
{
- "name": "brutx",
- "version": "0.3.0",
- "description": "CLI for adding Brutalist UI components to your project",
+ "name": "brutx-vue",
+ "version": "0.3.1",
+ "description": "CLI for adding Neo-Brutalist Vue 3 UI components to your project",
"bin": {
- "brutx": "dist/index.js"
+ "brutx-vue": "dist/index.js"
},
"main": "./dist/index.js",
"type": "module",
@@ -17,7 +17,7 @@
"test": "vitest run"
},
"dependencies": {
- "brutx-shared": "workspace:*",
+ "brutx-shared-vue": "workspace:*",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"fs-extra": "^11.2.0",
@@ -32,17 +32,19 @@
"vitest": "^1.3.1"
},
"keywords": [
- "brutx",
+ "brutx-vue",
"cli",
"neo-brutalism",
- "react",
- "components"
+ "vue",
+ "vue3",
+ "components",
+ "tailwindcss"
],
- "author": "dev-snake",
+ "author": "lidaixingchen",
"license": "MIT",
"repository": {
"type": "git",
- "url": "https://github.com/dev-snake/brutxui"
+ "url": "https://github.com/lidaixingchen/brutxui-vue3"
},
- "homepage": "https://brutxui.site"
+ "homepage": "https://lidaixingchen.github.io/brutxui-vue3/"
}
diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts
index 8f032d2..9c92375 100644
--- a/packages/cli/src/commands/add.ts
+++ b/packages/cli/src/commands/add.ts
@@ -25,12 +25,18 @@ async function ensureInitialized(cwd: string): Promise {
const configPath = path.join(cwd, 'components.json');
if (!(await fs.pathExists(configPath))) {
- logger.error('Error: Brutx is not initialized.');
- logger.warn('Run: npx brutx@latest init');
+ logger.error('Error: Brutx-Vue is not initialized.');
+ logger.warn('Run: npx brutx-vue@latest init');
process.exit(1);
}
- return fs.readJson(configPath);
+ const config = await fs.readJson(configPath);
+ if (!config?.aliases?.components || !config?.aliases?.utils) {
+ logger.error('Error: Invalid components.json. Missing required "aliases.components" or "aliases.utils".');
+ logger.warn('Run: npx brutx-vue@latest init --force to regenerate.');
+ process.exit(1);
+ }
+ return config;
}
async function validateComponents(components: string[]): Promise {
@@ -40,7 +46,7 @@ async function validateComponents(components: string[]): Promise