@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.
Files changed (80) hide show
  1. package/README.md +115 -0
  2. package/components.json +21 -0
  3. package/hooks/use-mobile.tsx +21 -0
  4. package/lib/utils.ts +6 -0
  5. package/package.json +104 -0
  6. package/postcss.config.mjs +8 -0
  7. package/src/components/atoms/aspect-ratio.tsx +21 -0
  8. package/src/components/atoms/avatar.tsx +91 -0
  9. package/src/components/atoms/badge.tsx +47 -0
  10. package/src/components/atoms/button.tsx +128 -0
  11. package/src/components/atoms/checkbox.tsx +24 -0
  12. package/src/components/atoms/container.tsx +42 -0
  13. package/src/components/atoms/heading.tsx +56 -0
  14. package/src/components/atoms/index.ts +21 -0
  15. package/src/components/atoms/input.tsx +18 -0
  16. package/src/components/atoms/kbd.tsx +23 -0
  17. package/src/components/atoms/label.tsx +15 -0
  18. package/src/components/atoms/logo.tsx +52 -0
  19. package/src/components/atoms/progress.tsx +79 -0
  20. package/src/components/atoms/separator.tsx +17 -0
  21. package/src/components/atoms/skeleton.tsx +13 -0
  22. package/src/components/atoms/slider.tsx +56 -0
  23. package/src/components/atoms/spinner.tsx +14 -0
  24. package/src/components/atoms/stack.tsx +126 -0
  25. package/src/components/atoms/switch.tsx +26 -0
  26. package/src/components/atoms/text.tsx +69 -0
  27. package/src/components/atoms/textarea.tsx +19 -0
  28. package/src/components/atoms/toggle.tsx +40 -0
  29. package/src/components/molecules/accordion.tsx +72 -0
  30. package/src/components/molecules/ai-chat.tsx +251 -0
  31. package/src/components/molecules/alert.tsx +131 -0
  32. package/src/components/molecules/breadcrumb.tsx +301 -0
  33. package/src/components/molecules/button-group.tsx +96 -0
  34. package/src/components/molecules/card.tsx +184 -0
  35. package/src/components/molecules/collapsible.tsx +21 -0
  36. package/src/components/molecules/command-search.tsx +148 -0
  37. package/src/components/molecules/empty.tsx +98 -0
  38. package/src/components/molecules/field.tsx +217 -0
  39. package/src/components/molecules/grid.tsx +141 -0
  40. package/src/components/molecules/hover-card.tsx +45 -0
  41. package/src/components/molecules/index.ts +29 -0
  42. package/src/components/molecules/input-group.tsx +151 -0
  43. package/src/components/molecules/input-otp.tsx +74 -0
  44. package/src/components/molecules/item.tsx +194 -0
  45. package/src/components/molecules/page-header.tsx +89 -0
  46. package/src/components/molecules/pagination.tsx +130 -0
  47. package/src/components/molecules/popover.tsx +96 -0
  48. package/src/components/molecules/radio-group.tsx +37 -0
  49. package/src/components/molecules/resizable.tsx +52 -0
  50. package/src/components/molecules/scroll-area.tsx +45 -0
  51. package/src/components/molecules/section.tsx +108 -0
  52. package/src/components/molecules/select.tsx +201 -0
  53. package/src/components/molecules/settings.tsx +197 -0
  54. package/src/components/molecules/table.tsx +111 -0
  55. package/src/components/molecules/tabs.tsx +74 -0
  56. package/src/components/molecules/theme-switcher.tsx +187 -0
  57. package/src/components/molecules/toggle-group.tsx +89 -0
  58. package/src/components/molecules/tooltip.tsx +66 -0
  59. package/src/components/organisms/alert-dialog.tsx +152 -0
  60. package/src/components/organisms/app-shell.tsx +939 -0
  61. package/src/components/organisms/calendar.tsx +212 -0
  62. package/src/components/organisms/carousel.tsx +230 -0
  63. package/src/components/organisms/chart.tsx +333 -0
  64. package/src/components/organisms/combobox.tsx +274 -0
  65. package/src/components/organisms/command.tsx +200 -0
  66. package/src/components/organisms/context-menu.tsx +229 -0
  67. package/src/components/organisms/dialog.tsx +134 -0
  68. package/src/components/organisms/drawer.tsx +123 -0
  69. package/src/components/organisms/dropdown-menu.tsx +256 -0
  70. package/src/components/organisms/index.ts +17 -0
  71. package/src/components/organisms/menubar.tsx +203 -0
  72. package/src/components/organisms/navigation-menu.tsx +143 -0
  73. package/src/components/organisms/page-layout.tsx +105 -0
  74. package/src/components/organisms/sheet.tsx +126 -0
  75. package/src/components/organisms/sidebar.tsx +723 -0
  76. package/src/components/organisms/sonner.tsx +41 -0
  77. package/src/components/ui/index.ts +3 -0
  78. package/src/index.ts +3 -0
  79. package/src/styles/globals.css +297 -0
  80. 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
@@ -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
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
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,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ "@tailwindcss/postcss": {},
5
+ },
6
+ };
7
+
8
+ export default config;
@@ -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 };