@oppulence/design-system 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/components.json +21 -0
- package/hooks/use-mobile.tsx +21 -0
- package/lib/utils.ts +6 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/components/atoms/aspect-ratio.tsx +21 -0
- package/src/components/atoms/avatar.tsx +91 -0
- package/src/components/atoms/badge.tsx +47 -0
- package/src/components/atoms/button.tsx +128 -0
- package/src/components/atoms/checkbox.tsx +24 -0
- package/src/components/atoms/container.tsx +42 -0
- package/src/components/atoms/heading.tsx +56 -0
- package/src/components/atoms/index.ts +21 -0
- package/src/components/atoms/input.tsx +18 -0
- package/src/components/atoms/kbd.tsx +23 -0
- package/src/components/atoms/label.tsx +15 -0
- package/src/components/atoms/logo.tsx +52 -0
- package/src/components/atoms/progress.tsx +79 -0
- package/src/components/atoms/separator.tsx +17 -0
- package/src/components/atoms/skeleton.tsx +13 -0
- package/src/components/atoms/slider.tsx +56 -0
- package/src/components/atoms/spinner.tsx +14 -0
- package/src/components/atoms/stack.tsx +126 -0
- package/src/components/atoms/switch.tsx +26 -0
- package/src/components/atoms/text.tsx +69 -0
- package/src/components/atoms/textarea.tsx +19 -0
- package/src/components/atoms/toggle.tsx +40 -0
- package/src/components/molecules/accordion.tsx +72 -0
- package/src/components/molecules/ai-chat.tsx +251 -0
- package/src/components/molecules/alert.tsx +131 -0
- package/src/components/molecules/breadcrumb.tsx +301 -0
- package/src/components/molecules/button-group.tsx +96 -0
- package/src/components/molecules/card.tsx +184 -0
- package/src/components/molecules/collapsible.tsx +21 -0
- package/src/components/molecules/command-search.tsx +148 -0
- package/src/components/molecules/empty.tsx +98 -0
- package/src/components/molecules/field.tsx +217 -0
- package/src/components/molecules/grid.tsx +141 -0
- package/src/components/molecules/hover-card.tsx +45 -0
- package/src/components/molecules/index.ts +29 -0
- package/src/components/molecules/input-group.tsx +151 -0
- package/src/components/molecules/input-otp.tsx +74 -0
- package/src/components/molecules/item.tsx +194 -0
- package/src/components/molecules/page-header.tsx +89 -0
- package/src/components/molecules/pagination.tsx +130 -0
- package/src/components/molecules/popover.tsx +96 -0
- package/src/components/molecules/radio-group.tsx +37 -0
- package/src/components/molecules/resizable.tsx +52 -0
- package/src/components/molecules/scroll-area.tsx +45 -0
- package/src/components/molecules/section.tsx +108 -0
- package/src/components/molecules/select.tsx +201 -0
- package/src/components/molecules/settings.tsx +197 -0
- package/src/components/molecules/table.tsx +111 -0
- package/src/components/molecules/tabs.tsx +74 -0
- package/src/components/molecules/theme-switcher.tsx +187 -0
- package/src/components/molecules/toggle-group.tsx +89 -0
- package/src/components/molecules/tooltip.tsx +66 -0
- package/src/components/organisms/alert-dialog.tsx +152 -0
- package/src/components/organisms/app-shell.tsx +939 -0
- package/src/components/organisms/calendar.tsx +212 -0
- package/src/components/organisms/carousel.tsx +230 -0
- package/src/components/organisms/chart.tsx +333 -0
- package/src/components/organisms/combobox.tsx +274 -0
- package/src/components/organisms/command.tsx +200 -0
- package/src/components/organisms/context-menu.tsx +229 -0
- package/src/components/organisms/dialog.tsx +134 -0
- package/src/components/organisms/drawer.tsx +123 -0
- package/src/components/organisms/dropdown-menu.tsx +256 -0
- package/src/components/organisms/index.ts +17 -0
- package/src/components/organisms/menubar.tsx +203 -0
- package/src/components/organisms/navigation-menu.tsx +143 -0
- package/src/components/organisms/page-layout.tsx +105 -0
- package/src/components/organisms/sheet.tsx +126 -0
- package/src/components/organisms/sidebar.tsx +723 -0
- package/src/components/organisms/sonner.tsx +41 -0
- package/src/components/ui/index.ts +3 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +297 -0
- package/tailwind.config.ts +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @oppulence/design-system
|
|
2
|
+
|
|
3
|
+
A shadcn-style design system with Tailwind CSS v4. Ships raw TypeScript/TSX source files that your app compiles directly.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @oppulence/design-system
|
|
9
|
+
# or
|
|
10
|
+
bun add @oppulence/design-system
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup in Next.js
|
|
14
|
+
|
|
15
|
+
### 1. Configure Tailwind CSS
|
|
16
|
+
|
|
17
|
+
Add the design system to your `tailwind.config.ts` content paths:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// tailwind.config.ts
|
|
21
|
+
import type { Config } from "tailwindcss";
|
|
22
|
+
|
|
23
|
+
const config: Config = {
|
|
24
|
+
content: [
|
|
25
|
+
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
|
26
|
+
// Include the design system components
|
|
27
|
+
"./node_modules/@oppulence/design-system/src/**/*.{js,ts,jsx,tsx}",
|
|
28
|
+
],
|
|
29
|
+
// ... rest of your config
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default config;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Import Global Styles
|
|
36
|
+
|
|
37
|
+
Import the design system's CSS in your root layout:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// app/layout.tsx
|
|
41
|
+
import "@oppulence/design-system/globals.css";
|
|
42
|
+
// or import alongside your own globals
|
|
43
|
+
import "./globals.css";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Configure Next.js to Transpile
|
|
47
|
+
|
|
48
|
+
Add the package to `transpilePackages` in your `next.config.ts`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// next.config.ts
|
|
52
|
+
import type { NextConfig } from "next";
|
|
53
|
+
|
|
54
|
+
const nextConfig: NextConfig = {
|
|
55
|
+
transpilePackages: ["@oppulence/design-system"],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default nextConfig;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
Import components directly:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import {
|
|
67
|
+
Button,
|
|
68
|
+
Card,
|
|
69
|
+
CardHeader,
|
|
70
|
+
CardContent,
|
|
71
|
+
} from "@oppulence/design-system";
|
|
72
|
+
|
|
73
|
+
export function MyComponent() {
|
|
74
|
+
return (
|
|
75
|
+
<Card>
|
|
76
|
+
<CardHeader>Hello</CardHeader>
|
|
77
|
+
<CardContent>
|
|
78
|
+
<Button variant="outline">Click me</Button>
|
|
79
|
+
</CardContent>
|
|
80
|
+
</Card>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Available Exports
|
|
86
|
+
|
|
87
|
+
| Export | Description |
|
|
88
|
+
| ------------------------------------------ | ---------------------------------- |
|
|
89
|
+
| `@oppulence/design-system` | All components |
|
|
90
|
+
| `@oppulence/design-system/cn` | `cn()` utility for merging classes |
|
|
91
|
+
| `@oppulence/design-system/globals.css` | Global CSS with theme variables |
|
|
92
|
+
| `@oppulence/design-system/tailwind.config` | Tailwind configuration |
|
|
93
|
+
|
|
94
|
+
## Components
|
|
95
|
+
|
|
96
|
+
The design system includes:
|
|
97
|
+
|
|
98
|
+
- **Layout**: `Container`, `Stack`, `Grid`, `PageLayout`, `Section`
|
|
99
|
+
- **Typography**: `Heading`, `Text`
|
|
100
|
+
- **Forms**: `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroup`, `Switch`, `Label`, `Field`
|
|
101
|
+
- **Data Display**: `Card`, `Badge`, `Avatar`, `Table`, `Progress`
|
|
102
|
+
- **Feedback**: `Alert`, `AlertDialog`, `Dialog`, `Sheet`, `Drawer`, `Tooltip`, `Popover`
|
|
103
|
+
- **Navigation**: `Tabs`, `Breadcrumb`, `Pagination`, `NavigationMenu`, `DropdownMenu`
|
|
104
|
+
- **Utility**: `Separator`, `Skeleton`, `Spinner`, `ScrollArea`, `Collapsible`
|
|
105
|
+
|
|
106
|
+
## Design Principles
|
|
107
|
+
|
|
108
|
+
- **No className prop**: Components use variants and props, not className overrides
|
|
109
|
+
- **Semantic tokens**: Uses CSS variables for consistent theming
|
|
110
|
+
- **Dark mode**: Full dark mode support via `next-themes`
|
|
111
|
+
- **Accessible**: Built on Radix UI primitives
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-vega",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768;
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
|
7
|
+
undefined,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
12
|
+
const onChange = () => {
|
|
13
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
14
|
+
};
|
|
15
|
+
mql.addEventListener("change", onChange);
|
|
16
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
17
|
+
return () => mql.removeEventListener("change", onChange);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return !!isMobile;
|
|
21
|
+
}
|
package/lib/utils.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oppulence/design-system",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Design system for Oppulence - shadcn-style components with Tailwind CSS v4",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"module": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"import": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./components/*": {
|
|
16
|
+
"types": "./src/components/ui/*.tsx",
|
|
17
|
+
"import": "./src/components/ui/*.tsx",
|
|
18
|
+
"default": "./src/components/ui/*.tsx"
|
|
19
|
+
},
|
|
20
|
+
"./cn": {
|
|
21
|
+
"types": "./lib/utils.ts",
|
|
22
|
+
"import": "./lib/utils.ts",
|
|
23
|
+
"default": "./lib/utils.ts"
|
|
24
|
+
},
|
|
25
|
+
"./globals.css": "./src/styles/globals.css",
|
|
26
|
+
"./tailwind.config": {
|
|
27
|
+
"types": "./tailwind.config.ts",
|
|
28
|
+
"import": "./tailwind.config.ts",
|
|
29
|
+
"default": "./tailwind.config.ts"
|
|
30
|
+
},
|
|
31
|
+
"./postcss.config": "./postcss.config.mjs"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"lib",
|
|
36
|
+
"hooks",
|
|
37
|
+
"tailwind.config.ts",
|
|
38
|
+
"postcss.config.mjs",
|
|
39
|
+
"components.json",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"sideEffects": [
|
|
43
|
+
"**/*.css"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/Oppulence-Engineering/design-system.git",
|
|
51
|
+
"directory": "packages/design-system"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"keywords": [
|
|
55
|
+
"design-system",
|
|
56
|
+
"components",
|
|
57
|
+
"react",
|
|
58
|
+
"tailwindcss",
|
|
59
|
+
"shadcn",
|
|
60
|
+
"ui"
|
|
61
|
+
],
|
|
62
|
+
"scripts": {
|
|
63
|
+
"clean": "rm -rf .turbo node_modules",
|
|
64
|
+
"format": "prettier --write .",
|
|
65
|
+
"lint": "prettier --check .",
|
|
66
|
+
"typecheck": "tsc --noEmit"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@base-ui/react": "^1.0.0",
|
|
70
|
+
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
|
|
71
|
+
"class-variance-authority": "^0.7.1",
|
|
72
|
+
"clsx": "^2.1.1",
|
|
73
|
+
"cmdk": "^1.1.1",
|
|
74
|
+
"date-fns": "^4.1.0",
|
|
75
|
+
"embla-carousel-react": "^8.6.0",
|
|
76
|
+
"input-otp": "^1.4.2",
|
|
77
|
+
"lucide-react": "^0.562.0",
|
|
78
|
+
"next-themes": "^0.4.6",
|
|
79
|
+
"react-day-picker": "^9.13.0",
|
|
80
|
+
"react-resizable-panels": "^4.2.0",
|
|
81
|
+
"recharts": "2.15.4",
|
|
82
|
+
"shadcn": "^3.6.2",
|
|
83
|
+
"sonner": "^2.0.7",
|
|
84
|
+
"tailwind-merge": "^3.4.0",
|
|
85
|
+
"tw-animate-css": "^1.4.0",
|
|
86
|
+
"vaul": "^1.1.2"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"react": "^19.0.0",
|
|
90
|
+
"react-dom": "^19.0.0",
|
|
91
|
+
"tailwindcss": "^4.0.0"
|
|
92
|
+
},
|
|
93
|
+
"devDependencies": {
|
|
94
|
+
"@tailwindcss/postcss": "^4.1.10",
|
|
95
|
+
"@types/node": "^22.18.0",
|
|
96
|
+
"@types/react": "^19.2.7",
|
|
97
|
+
"@types/react-dom": "^19.2.3",
|
|
98
|
+
"postcss": "^8.5.4",
|
|
99
|
+
"react": "^19.1.1",
|
|
100
|
+
"react-dom": "^19.1.0",
|
|
101
|
+
"tailwindcss": "^4.1.8",
|
|
102
|
+
"typescript": "^5.9.3"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
function AspectRatio({
|
|
4
|
+
ratio,
|
|
5
|
+
...props
|
|
6
|
+
}: Omit<React.ComponentProps<"div">, "className"> & { ratio: number }) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
data-slot="aspect-ratio"
|
|
10
|
+
style={
|
|
11
|
+
{
|
|
12
|
+
"--ratio": ratio,
|
|
13
|
+
} as React.CSSProperties
|
|
14
|
+
}
|
|
15
|
+
className="relative aspect-(--ratio)"
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { AspectRatio };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
type AvatarSize = "xs" | "sm" | "default" | "lg" | "xl";
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
size = "default",
|
|
10
|
+
...props
|
|
11
|
+
}: Omit<AvatarPrimitive.Root.Props, "className"> & {
|
|
12
|
+
size?: AvatarSize;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<AvatarPrimitive.Root
|
|
16
|
+
data-slot="avatar"
|
|
17
|
+
data-size={size}
|
|
18
|
+
className="size-8 rounded-full after:rounded-full data-[size=xs]:size-5 data-[size=sm]:size-6 data-[size=lg]:size-10 data-[size=xl]:size-14 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function AvatarImage({
|
|
25
|
+
...props
|
|
26
|
+
}: Omit<AvatarPrimitive.Image.Props, "className">) {
|
|
27
|
+
return (
|
|
28
|
+
<AvatarPrimitive.Image
|
|
29
|
+
data-slot="avatar-image"
|
|
30
|
+
className="rounded-full aspect-square size-full object-cover"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AvatarFallback({
|
|
37
|
+
...props
|
|
38
|
+
}: Omit<AvatarPrimitive.Fallback.Props, "className">) {
|
|
39
|
+
return (
|
|
40
|
+
<AvatarPrimitive.Fallback
|
|
41
|
+
data-slot="avatar-fallback"
|
|
42
|
+
className="bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-xs group-data-[size=xs]/avatar:text-[8px] group-data-[size=sm]/avatar:text-[10px] group-data-[size=lg]/avatar:text-sm group-data-[size=xl]/avatar:text-base"
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function AvatarBadge({
|
|
49
|
+
...props
|
|
50
|
+
}: Omit<React.ComponentProps<"span">, "className">) {
|
|
51
|
+
return (
|
|
52
|
+
<span
|
|
53
|
+
data-slot="avatar-badge"
|
|
54
|
+
className="bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none group-data-[size=xs]/avatar:size-1.5 group-data-[size=xs]/avatar:[&>svg]:hidden group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2 group-data-[size=xl]/avatar:size-3.5 group-data-[size=xl]/avatar:[&>svg]:size-2.5"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function AvatarGroup({
|
|
61
|
+
...props
|
|
62
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
data-slot="avatar-group"
|
|
66
|
+
className="*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2"
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function AvatarGroupCount({
|
|
73
|
+
...props
|
|
74
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="avatar-group-count"
|
|
78
|
+
className="bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=xs]/avatar-group:size-5 group-has-data-[size=xs]/avatar-group:text-[10px] group-has-data-[size=sm]/avatar-group:size-6 group-has-data-[size=sm]/avatar-group:text-xs group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=lg]/avatar-group:text-base group-has-data-[size=xl]/avatar-group:size-14 group-has-data-[size=xl]/avatar-group:text-lg ring-background relative flex shrink-0 items-center justify-center ring-2"
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Avatar,
|
|
86
|
+
AvatarBadge,
|
|
87
|
+
AvatarFallback,
|
|
88
|
+
AvatarGroup,
|
|
89
|
+
AvatarGroupCount,
|
|
90
|
+
AvatarImage,
|
|
91
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"gap-1 rounded-sm px-1.5 py-1 text-[10px] font-semibold uppercase tracking-wider leading-none transition-colors has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:ring-ring/50 focus-visible:ring-[3px] overflow-hidden antialiased select-none",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/90",
|
|
11
|
+
secondary: "bg-muted text-muted-foreground [a]:hover:bg-muted/80",
|
|
12
|
+
destructive:
|
|
13
|
+
"bg-destructive/10 text-destructive [a]:hover:bg-destructive/15 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/15",
|
|
14
|
+
outline:
|
|
15
|
+
"border border-border/50 bg-transparent text-foreground [a]:hover:bg-muted/30",
|
|
16
|
+
ghost:
|
|
17
|
+
"bg-transparent text-muted-foreground hover:bg-muted/50 hover:text-foreground",
|
|
18
|
+
link: "bg-transparent text-primary underline-offset-4 hover:underline",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
type BadgeProps = Omit<useRender.ComponentProps<"span">, "className"> &
|
|
28
|
+
VariantProps<typeof badgeVariants>;
|
|
29
|
+
|
|
30
|
+
function Badge({ variant = "default", render, ...props }: BadgeProps) {
|
|
31
|
+
return useRender({
|
|
32
|
+
defaultTagName: "span",
|
|
33
|
+
props: mergeProps<"span">(
|
|
34
|
+
{
|
|
35
|
+
className: badgeVariants({ variant }),
|
|
36
|
+
},
|
|
37
|
+
props,
|
|
38
|
+
),
|
|
39
|
+
render,
|
|
40
|
+
state: {
|
|
41
|
+
slot: "badge",
|
|
42
|
+
variant,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { Spinner } from "./spinner";
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all duration-150 ease-out active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none cursor-pointer",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
13
|
+
outline:
|
|
14
|
+
"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
|
|
15
|
+
secondary:
|
|
16
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
17
|
+
ghost:
|
|
18
|
+
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
width: {
|
|
24
|
+
auto: "",
|
|
25
|
+
full: "w-full",
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
default:
|
|
29
|
+
"h-8 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
30
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
32
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
33
|
+
icon: "size-8",
|
|
34
|
+
"icon-xs":
|
|
35
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
36
|
+
"icon-sm":
|
|
37
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
38
|
+
"icon-lg": "size-9",
|
|
39
|
+
// Round icon buttons - for avatar triggers and circular icons
|
|
40
|
+
"icon-round": "size-8 rounded-full",
|
|
41
|
+
"icon-round-xs":
|
|
42
|
+
"size-6 rounded-full [&_svg:not([class*='size-'])]:size-3",
|
|
43
|
+
"icon-round-sm": "size-7 rounded-full",
|
|
44
|
+
"icon-round-lg": "size-9 rounded-full",
|
|
45
|
+
// Calendar day button - special size for calendar day cells
|
|
46
|
+
"calendar-day": [
|
|
47
|
+
// Base sizing
|
|
48
|
+
"relative isolate z-10 aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal",
|
|
49
|
+
// Selection states
|
|
50
|
+
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground",
|
|
51
|
+
"data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-middle=true]:rounded-none",
|
|
52
|
+
"data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius)",
|
|
53
|
+
"data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius)",
|
|
54
|
+
// Focus states (from parent day cell)
|
|
55
|
+
"group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 group-data-[focused=true]/day:ring-[3px]",
|
|
56
|
+
// Dark mode hover
|
|
57
|
+
"dark:hover:text-foreground",
|
|
58
|
+
// Nested span styling for additional content
|
|
59
|
+
"[&>span]:text-xs [&>span]:opacity-70",
|
|
60
|
+
].join(" "),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultVariants: {
|
|
64
|
+
variant: "default",
|
|
65
|
+
width: "auto",
|
|
66
|
+
size: "default",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
type ButtonProps = Omit<ButtonPrimitive.Props, "className"> &
|
|
72
|
+
VariantProps<typeof buttonVariants> & {
|
|
73
|
+
/** Show loading spinner and disable button */
|
|
74
|
+
loading?: boolean;
|
|
75
|
+
/** Icon to show on the left side of the button */
|
|
76
|
+
iconLeft?: React.ReactNode;
|
|
77
|
+
/** Icon to show on the right side of the button */
|
|
78
|
+
iconRight?: React.ReactNode;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
82
|
+
(
|
|
83
|
+
{
|
|
84
|
+
variant = "default",
|
|
85
|
+
width = "auto",
|
|
86
|
+
size = "default",
|
|
87
|
+
loading = false,
|
|
88
|
+
iconLeft,
|
|
89
|
+
iconRight,
|
|
90
|
+
disabled,
|
|
91
|
+
children,
|
|
92
|
+
...props
|
|
93
|
+
},
|
|
94
|
+
ref,
|
|
95
|
+
) => {
|
|
96
|
+
const isDisabled = disabled || loading;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<ButtonPrimitive
|
|
100
|
+
ref={ref}
|
|
101
|
+
data-slot="button"
|
|
102
|
+
data-loading={loading || undefined}
|
|
103
|
+
disabled={isDisabled}
|
|
104
|
+
className={buttonVariants({ variant, width, size })}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
{loading ? (
|
|
108
|
+
<Spinner />
|
|
109
|
+
) : iconLeft ? (
|
|
110
|
+
<span data-icon="inline-start" className="shrink-0">
|
|
111
|
+
{iconLeft}
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
114
|
+
{children}
|
|
115
|
+
{!loading && iconRight ? (
|
|
116
|
+
<span data-icon="inline-end" className="shrink-0">
|
|
117
|
+
{iconRight}
|
|
118
|
+
</span>
|
|
119
|
+
) : null}
|
|
120
|
+
</ButtonPrimitive>
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
Button.displayName = "Button";
|
|
126
|
+
|
|
127
|
+
export { Button, buttonVariants };
|
|
128
|
+
export type { ButtonProps };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
|
|
2
|
+
|
|
3
|
+
import { CheckIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
function Checkbox({
|
|
6
|
+
...props
|
|
7
|
+
}: Omit<CheckboxPrimitive.Root.Props, "className">) {
|
|
8
|
+
return (
|
|
9
|
+
<CheckboxPrimitive.Root
|
|
10
|
+
data-slot="checkbox"
|
|
11
|
+
className="border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-[3px] aria-invalid:ring-[3px] peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
<CheckboxPrimitive.Indicator
|
|
15
|
+
data-slot="checkbox-indicator"
|
|
16
|
+
className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
|
17
|
+
>
|
|
18
|
+
<CheckIcon />
|
|
19
|
+
</CheckboxPrimitive.Indicator>
|
|
20
|
+
</CheckboxPrimitive.Root>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Checkbox };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
const containerVariants = cva("mx-auto w-full", {
|
|
5
|
+
variants: {
|
|
6
|
+
size: {
|
|
7
|
+
sm: "max-w-screen-sm", // 640px
|
|
8
|
+
md: "max-w-screen-md", // 768px
|
|
9
|
+
lg: "max-w-screen-lg", // 1024px
|
|
10
|
+
xl: "max-w-[1200px]",
|
|
11
|
+
"2xl": "max-w-[1400px]",
|
|
12
|
+
full: "max-w-full",
|
|
13
|
+
},
|
|
14
|
+
padding: {
|
|
15
|
+
none: "px-0",
|
|
16
|
+
sm: "px-4",
|
|
17
|
+
default: "px-4 sm:px-6 lg:px-8",
|
|
18
|
+
lg: "px-6 sm:px-8 lg:px-12",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "xl",
|
|
23
|
+
padding: "default",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
interface ContainerProps
|
|
28
|
+
extends
|
|
29
|
+
Omit<React.ComponentProps<"div">, "className">,
|
|
30
|
+
VariantProps<typeof containerVariants> {}
|
|
31
|
+
|
|
32
|
+
function Container({ size, padding, ...props }: ContainerProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
data-slot="container"
|
|
36
|
+
className={containerVariants({ size, padding })}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { Container, containerVariants };
|