bmj-ui 1.0.0

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.
@@ -0,0 +1,99 @@
1
+ import { SidebarTrigger } from "../ui/sidebar";
2
+ import { ThemeToggle } from "../ThemeToggle";
3
+ import {
4
+ Search,
5
+ Bell,
6
+ User,
7
+ CreditCard,
8
+ Users,
9
+ Zap,
10
+ LogOut,
11
+ } from "lucide-react";
12
+ import { Input } from "../ui/input";
13
+ import { Button } from "../ui/button";
14
+ import { toast } from "sonner";
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuGroup,
20
+ DropdownMenuLabel,
21
+ DropdownMenuSeparator,
22
+ DropdownMenuTrigger,
23
+ } from "../ui/dropdown-menu";
24
+
25
+ export function Topbar() {
26
+ const handleItemClick = (label: string) => {
27
+ toast.info(`${label} clicked`, {
28
+ description: "This feature will be implemented soon.",
29
+ });
30
+ };
31
+
32
+ return (
33
+ <header className="h-16 border-b border-border/50 flex items-center justify-between px-4 sm:px-6 bg-background/80 backdrop-blur-md sticky top-0 z-30">
34
+ <div className="flex items-center gap-2 sm:gap-4 flex-1">
35
+ <SidebarTrigger />
36
+ <div className="relative w-full max-w-md hidden md:block">
37
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
38
+ <Input
39
+ placeholder="Search dashboard..."
40
+ className="pl-10 bg-muted/50 border-none focus-visible:ring-1 focus-visible:ring-primary/20"
41
+ />
42
+ </div>
43
+ <Button variant="ghost" size="icon" className="md:hidden">
44
+ <Search className="w-5 h-5" />
45
+ </Button>
46
+ </div>
47
+
48
+ <div className="flex items-center gap-1 sm:gap-2">
49
+ <div className="hidden xs:block">
50
+ <ThemeToggle />
51
+ </div>
52
+ <Button variant="ghost" size="icon" className="relative">
53
+ <Bell className="w-5 h-5" />
54
+ <span className="absolute top-2 right-2 w-2 h-2 bg-primary rounded-full border-2 border-background" />
55
+ </Button>
56
+
57
+ <DropdownMenu>
58
+ <DropdownMenuTrigger
59
+ render={
60
+ <Button variant="ghost" size="icon" className="rounded-full" />
61
+ }
62
+ >
63
+ <User className="w-5 h-5" />
64
+ </DropdownMenuTrigger>
65
+ <DropdownMenuContent align="end" className="w-56">
66
+ <DropdownMenuGroup>
67
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
68
+ <DropdownMenuSeparator />
69
+ <DropdownMenuItem onClick={() => handleItemClick("Profile")}>
70
+ <User className="mr-2 h-4 w-4" />
71
+ <span>Profile</span>
72
+ </DropdownMenuItem>
73
+ <DropdownMenuItem onClick={() => handleItemClick("Billing")}>
74
+ <CreditCard className="mr-2 h-4 w-4" />
75
+ <span>Billing</span>
76
+ </DropdownMenuItem>
77
+ <DropdownMenuItem onClick={() => handleItemClick("Team")}>
78
+ <Users className="mr-2 h-4 w-4" />
79
+ <span>Team</span>
80
+ </DropdownMenuItem>
81
+ <DropdownMenuItem onClick={() => handleItemClick("Subscription")}>
82
+ <Zap className="mr-2 h-4 w-4" />
83
+ <span>Subscription</span>
84
+ </DropdownMenuItem>
85
+ <DropdownMenuSeparator />
86
+ <DropdownMenuItem
87
+ variant="destructive"
88
+ onClick={() => handleItemClick("Log out")}
89
+ >
90
+ <LogOut className="mr-2 h-4 w-4" />
91
+ <span>Log out</span>
92
+ </DropdownMenuItem>
93
+ </DropdownMenuGroup>
94
+ </DropdownMenuContent>
95
+ </DropdownMenu>
96
+ </div>
97
+ </header>
98
+ );
99
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./DashboardLayout";
2
+ export * from "./StatCard";
3
+ export * from "./ChartCard";
4
+ export * from "./AppSidebar";
5
+ export * from "./Topbar";
@@ -0,0 +1,52 @@
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:
14
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
15
+ destructive:
16
+ "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",
17
+ outline:
18
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
19
+ ghost:
20
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ },
28
+ );
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ render,
34
+ ...props
35
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
36
+ return useRender({
37
+ defaultTagName: "span",
38
+ props: mergeProps<"span">(
39
+ {
40
+ className: cn(badgeVariants({ variant }), className),
41
+ },
42
+ props,
43
+ ),
44
+ render,
45
+ state: {
46
+ slot: "badge",
47
+ variant,
48
+ },
49
+ });
50
+ }
51
+
52
+ export { Badge, badgeVariants };
@@ -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-lg 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 [a]:hover:bg-primary/80",
12
+ outline:
13
+ "border-border bg-background 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-secondary/80 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-8 gap-1.5 px-2.5 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),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",
26
+ 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",
27
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
28
+ icon: "size-8",
29
+ "icon-xs":
30
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
31
+ "icon-sm":
32
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
33
+ "icon-lg": "size-9",
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 };
@@ -0,0 +1,103 @@
1
+ import * 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-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[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-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
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
+ "font-heading text-base leading-snug 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(
64
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+
72
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="card-content"
76
+ className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
77
+ {...props}
78
+ />
79
+ );
80
+ }
81
+
82
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="card-footer"
86
+ className={cn(
87
+ "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
88
+ className,
89
+ )}
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ export {
96
+ Card,
97
+ CardHeader,
98
+ CardFooter,
99
+ CardTitle,
100
+ CardAction,
101
+ CardDescription,
102
+ CardContent,
103
+ };
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import { Button } from "./button";
8
+ import { XIcon } from "lucide-react";
9
+
10
+ function Dialog({ ...props }: DialogPrimitive.Root.Props) {
11
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
12
+ }
13
+
14
+ function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
15
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
16
+ }
17
+
18
+ function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
19
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
20
+ }
21
+
22
+ function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
23
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
24
+ }
25
+
26
+ function DialogOverlay({
27
+ className,
28
+ ...props
29
+ }: DialogPrimitive.Backdrop.Props) {
30
+ return (
31
+ <DialogPrimitive.Backdrop
32
+ data-slot="dialog-overlay"
33
+ className={cn(
34
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function DialogContent({
43
+ className,
44
+ children,
45
+ showCloseButton = true,
46
+ ...props
47
+ }: DialogPrimitive.Popup.Props & {
48
+ showCloseButton?: boolean;
49
+ }) {
50
+ return (
51
+ <DialogPortal>
52
+ <DialogOverlay />
53
+ <DialogPrimitive.Popup
54
+ data-slot="dialog-content"
55
+ className={cn(
56
+ "fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
57
+ className,
58
+ )}
59
+ {...props}
60
+ >
61
+ {children}
62
+ {showCloseButton && (
63
+ <DialogPrimitive.Close
64
+ data-slot="dialog-close"
65
+ render={
66
+ <Button
67
+ variant="ghost"
68
+ className="absolute top-2 right-2"
69
+ size="icon-sm"
70
+ />
71
+ }
72
+ >
73
+ <XIcon />
74
+ <span className="sr-only">Close</span>
75
+ </DialogPrimitive.Close>
76
+ )}
77
+ </DialogPrimitive.Popup>
78
+ </DialogPortal>
79
+ );
80
+ }
81
+
82
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
83
+ return (
84
+ <div
85
+ data-slot="dialog-header"
86
+ className={cn("flex flex-col gap-2", className)}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ function DialogFooter({
93
+ className,
94
+ showCloseButton = false,
95
+ children,
96
+ ...props
97
+ }: React.ComponentProps<"div"> & {
98
+ showCloseButton?: boolean;
99
+ }) {
100
+ return (
101
+ <div
102
+ data-slot="dialog-footer"
103
+ className={cn(
104
+ "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
105
+ className,
106
+ )}
107
+ {...props}
108
+ >
109
+ {children}
110
+ {showCloseButton && (
111
+ <DialogPrimitive.Close render={<Button variant="outline" />}>
112
+ Close
113
+ </DialogPrimitive.Close>
114
+ )}
115
+ </div>
116
+ );
117
+ }
118
+
119
+ function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
120
+ return (
121
+ <DialogPrimitive.Title
122
+ data-slot="dialog-title"
123
+ className={cn(
124
+ "font-heading text-base leading-none font-medium",
125
+ className,
126
+ )}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ function DialogDescription({
133
+ className,
134
+ ...props
135
+ }: DialogPrimitive.Description.Props) {
136
+ return (
137
+ <DialogPrimitive.Description
138
+ data-slot="dialog-description"
139
+ className={cn(
140
+ "text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
141
+ className,
142
+ )}
143
+ {...props}
144
+ />
145
+ );
146
+ }
147
+
148
+ export {
149
+ Dialog,
150
+ DialogClose,
151
+ DialogContent,
152
+ DialogDescription,
153
+ DialogFooter,
154
+ DialogHeader,
155
+ DialogOverlay,
156
+ DialogPortal,
157
+ DialogTitle,
158
+ DialogTrigger,
159
+ };