Skip to content

Commit db4d281

Browse files
feat(web): init shadcn base ui (base-vega) with core components
- set components.json style to base-vega to use Base UI primitives - add @base-ui/react and core ui components: button, input, label, card, dialog, dropdown-menu, badge, sonner - wire sonner toaster to the jotai theme atom and drop next-themes Co-Authored-By: First Fluke <our.first.fluke@gmail.com>
1 parent 703b147 commit db4d281

11 files changed

Lines changed: 707 additions & 9 deletions

File tree

apps/web/bun.lock

Lines changed: 25 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/web/components.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://ui.shadcn.com/schema.json",
3-
"style": "new-york",
3+
"style": "base-vega",
44
"rsc": true,
55
"tsx": true,
66
"tailwind": {
@@ -10,14 +10,17 @@
1010
"cssVariables": true,
1111
"prefix": ""
1212
},
13+
"iconLibrary": "lucide",
14+
"rtl": false,
1315
"aliases": {
1416
"components": "@/components",
1517
"utils": "@/lib/utils",
1618
"ui": "@/components/ui",
1719
"lib": "@/lib",
1820
"hooks": "@/hooks"
1921
},
20-
"iconLibrary": "lucide",
22+
"menuColor": "default",
23+
"menuAccent": "subtle",
2124
"registries": {
2225
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
2326
"@react-bits": "https://reactbits.dev/r/{name}.json"

apps/web/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"gen:api": "orval"
1717
},
1818
"dependencies": {
19+
"@base-ui/react": "^1.6.0",
1920
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
2021
"@opentelemetry/instrumentation-fetch": "^0.214.0",
2122
"@opentelemetry/instrumentation-http": "^0.214.0",
@@ -35,14 +36,15 @@
3536
"class-variance-authority": "^0.7.1",
3637
"clsx": "^2.1.1",
3738
"jotai": "^2.19.0",
38-
"lucide-react": "^1.7.0",
39+
"lucide-react": "^1.21.0",
3940
"next": "^16.2.2",
4041
"next-intl": "^4.9.0",
4142
"nuqs": "^2.8.9",
4243
"react": "^19.2.4",
4344
"react-dom": "^19.2.4",
4445
"resend": "^6.10.0",
45-
"tailwind-merge": "^3.5.0",
46+
"sonner": "^2.0.7",
47+
"tailwind-merge": "^3.6.0",
4648
"zod": "^4.3.6"
4749
},
4850
"devDependencies": {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
import { cn } from "@/lib/utils";
6+
7+
const badgeVariants = cva(
8+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
9+
{
10+
variants: {
11+
variant: {
12+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13+
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
14+
destructive:
15+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
16+
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
17+
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
18+
link: "text-primary underline-offset-4 hover:underline",
19+
},
20+
},
21+
defaultVariants: {
22+
variant: "default",
23+
},
24+
}
25+
);
26+
27+
function Badge({
28+
className,
29+
variant = "default",
30+
render,
31+
...props
32+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
33+
return useRender({
34+
defaultTagName: "span",
35+
props: mergeProps<"span">(
36+
{
37+
className: cn(badgeVariants({ variant }), className),
38+
},
39+
props
40+
),
41+
render,
42+
state: {
43+
slot: "badge",
44+
variant,
45+
},
46+
});
47+
}
48+
49+
export { Badge, badgeVariants };
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
2+
import { cva, type VariantProps } from "class-variance-authority";
3+
4+
import { cn } from "@/lib/utils";
5+
6+
const buttonVariants = cva(
7+
"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8+
{
9+
variants: {
10+
variant: {
11+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
12+
outline:
13+
"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
14+
secondary:
15+
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16+
ghost:
17+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
18+
destructive:
19+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
20+
link: "text-primary underline-offset-4 hover:underline",
21+
},
22+
size: {
23+
default:
24+
"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
25+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
26+
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
27+
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
28+
icon: "size-9",
29+
"icon-xs":
30+
"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
31+
"icon-sm":
32+
"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
33+
"icon-lg": "size-10",
34+
},
35+
},
36+
defaultVariants: {
37+
variant: "default",
38+
size: "default",
39+
},
40+
}
41+
);
42+
43+
function Button({
44+
className,
45+
variant = "default",
46+
size = "default",
47+
...props
48+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
49+
return (
50+
<ButtonPrimitive
51+
data-slot="button"
52+
className={cn(buttonVariants({ variant, size, className }))}
53+
{...props}
54+
/>
55+
);
56+
}
57+
58+
export { Button, buttonVariants };
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type * as React from "react";
2+
3+
import { cn } from "@/lib/utils";
4+
5+
function Card({
6+
className,
7+
size = "default",
8+
...props
9+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
10+
return (
11+
<div
12+
data-slot="card"
13+
data-size={size}
14+
className={cn(
15+
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
16+
className
17+
)}
18+
{...props}
19+
/>
20+
);
21+
}
22+
23+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
24+
return (
25+
<div
26+
data-slot="card-header"
27+
className={cn(
28+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
29+
className
30+
)}
31+
{...props}
32+
/>
33+
);
34+
}
35+
36+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
37+
return (
38+
<div
39+
data-slot="card-title"
40+
className={cn(
41+
"text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",
42+
className
43+
)}
44+
{...props}
45+
/>
46+
);
47+
}
48+
49+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
50+
return (
51+
<div
52+
data-slot="card-description"
53+
className={cn("text-sm text-muted-foreground", className)}
54+
{...props}
55+
/>
56+
);
57+
}
58+
59+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
60+
return (
61+
<div
62+
data-slot="card-action"
63+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
64+
{...props}
65+
/>
66+
);
67+
}
68+
69+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
70+
return (
71+
<div data-slot="card-content" className={cn("px-(--card-spacing)", className)} {...props} />
72+
);
73+
}
74+
75+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
76+
return (
77+
<div
78+
data-slot="card-footer"
79+
className={cn(
80+
"flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",
81+
className
82+
)}
83+
{...props}
84+
/>
85+
);
86+
}
87+
88+
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };

0 commit comments

Comments
 (0)