@brightweblabs/ui 0.3.1 → 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.
Files changed (39) hide show
  1. package/package.json +45 -7
  2. package/src/components/action.tsx +12 -0
  3. package/src/components/activity-message.tsx +24 -0
  4. package/src/components/alert-dialog.tsx +4 -4
  5. package/src/components/avatar.tsx +80 -0
  6. package/src/components/badge.tsx +20 -30
  7. package/src/components/button-variants.ts +47 -0
  8. package/src/components/button.tsx +102 -53
  9. package/src/components/calendar.tsx +5 -4
  10. package/src/components/chart.tsx +4 -4
  11. package/src/components/dropdown-menu.tsx +5 -5
  12. package/src/components/empty-state.tsx +26 -0
  13. package/src/components/field.tsx +2 -2
  14. package/src/components/initials-avatar.tsx +30 -0
  15. package/src/components/input.tsx +1 -1
  16. package/src/components/kpi-breakdown-bar.tsx +71 -0
  17. package/src/components/label.tsx +1 -1
  18. package/src/components/pagination.tsx +2 -3
  19. package/src/components/password-input.tsx +10 -43
  20. package/src/components/password-strength.tsx +9 -10
  21. package/src/components/phone-input.tsx +114 -0
  22. package/src/components/popover.tsx +1 -1
  23. package/src/components/role-badge.tsx +34 -0
  24. package/src/components/search-field.tsx +63 -0
  25. package/src/components/section-heading.tsx +28 -0
  26. package/src/components/sheet.tsx +34 -5
  27. package/src/components/skeleton-table.tsx +27 -0
  28. package/src/components/skeleton.tsx +45 -0
  29. package/src/components/sonner.tsx +2 -1
  30. package/src/components/stat-tile.tsx +30 -0
  31. package/src/components/status-pill.tsx +37 -0
  32. package/src/components/surface-card.tsx +11 -0
  33. package/src/components/table-pagination.tsx +107 -0
  34. package/src/components/table.tsx +7 -4
  35. package/src/components/theme-provider.tsx +130 -0
  36. package/src/components/tooltip.tsx +2 -2
  37. package/src/index.ts +31 -1
  38. package/src/lib/activity-format.ts +96 -0
  39. package/src/lib/patterns.ts +52 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brightweblabs/ui",
3
3
  "private": false,
4
- "version": "0.3.1",
4
+ "version": "1.0.0",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "files": [
@@ -17,33 +17,71 @@
17
17
  },
18
18
  "exports": {
19
19
  ".": "./src/index.ts",
20
+ "./action": "./src/components/action.tsx",
21
+ "./activity-format": "./src/lib/activity-format.ts",
22
+ "./activity-message": "./src/components/activity-message.tsx",
20
23
  "./alert-dialog": "./src/components/alert-dialog.tsx",
24
+ "./avatar": "./src/components/avatar.tsx",
21
25
  "./badge": "./src/components/badge.tsx",
26
+ "./breadcrumb": "./src/components/breadcrumb.tsx",
22
27
  "./button": "./src/components/button.tsx",
28
+ "./button-variants": "./src/components/button-variants.ts",
29
+ "./calendar": "./src/components/calendar.tsx",
30
+ "./card": "./src/components/card.tsx",
31
+ "./chart": "./src/components/chart.tsx",
23
32
  "./dropdown-menu": "./src/components/dropdown-menu.tsx",
33
+ "./empty-state": "./src/components/empty-state.tsx",
34
+ "./field": "./src/components/field.tsx",
24
35
  "./input": "./src/components/input.tsx",
36
+ "./initials-avatar": "./src/components/initials-avatar.tsx",
37
+ "./kpi-breakdown-bar": "./src/components/kpi-breakdown-bar.tsx",
38
+ "./label": "./src/components/label.tsx",
39
+ "./pagination": "./src/components/pagination.tsx",
40
+ "./password-input": "./src/components/password-input.tsx",
41
+ "./password-strength": "./src/components/password-strength.tsx",
42
+ "./phone-input": "./src/components/phone-input.tsx",
43
+ "./popover": "./src/components/popover.tsx",
44
+ "./role-badge": "./src/components/role-badge.tsx",
45
+ "./search-field": "./src/components/search-field.tsx",
46
+ "./section-heading": "./src/components/section-heading.tsx",
47
+ "./separator": "./src/components/separator.tsx",
48
+ "./sheet": "./src/components/sheet.tsx",
49
+ "./skeleton": "./src/components/skeleton.tsx",
50
+ "./skeleton-table": "./src/components/skeleton-table.tsx",
51
+ "./stat-tile": "./src/components/stat-tile.tsx",
52
+ "./status-pill": "./src/components/status-pill.tsx",
53
+ "./surface-card": "./src/components/surface-card.tsx",
54
+ "./sonner": "./src/components/sonner.tsx",
25
55
  "./table": "./src/components/table.tsx",
56
+ "./table-pagination": "./src/components/table-pagination.tsx",
57
+ "./theme-provider": "./src/components/theme-provider.tsx",
26
58
  "./tooltip": "./src/components/tooltip.tsx"
27
59
  },
28
60
  "dependencies": {
61
+ "@radix-ui/react-alert-dialog": "^1.1.15",
62
+ "@radix-ui/react-avatar": "^1.1.10",
29
63
  "@radix-ui/react-dialog": "^1.1.15",
30
64
  "@radix-ui/react-dropdown-menu": "^2.1.16",
31
65
  "@radix-ui/react-label": "^2.1.8",
32
66
  "@radix-ui/react-popover": "^1.1.15",
33
67
  "@radix-ui/react-separator": "^1.1.8",
34
68
  "@radix-ui/react-slot": "^1.2.4",
69
+ "@radix-ui/react-tooltip": "^1.2.8",
35
70
  "class-variance-authority": "^0.7.1",
36
71
  "clsx": "^2.1.1",
37
72
  "date-fns": "^4.1.0",
38
- "lucide-react": "^0.577.0",
39
- "next": "16.1.6",
40
- "next-themes": "^0.4.6",
41
73
  "radix-ui": "^1.4.3",
42
- "react": "19.2.4",
43
74
  "react-day-picker": "^9.13.2",
44
- "react-dom": "19.2.4",
75
+ "react-international-phone": "^4.8.0",
45
76
  "recharts": "^2.15.4",
46
77
  "sonner": "^2.0.7",
47
- "tailwind-merge": "^3.4.0"
78
+ "tailwind-merge": "^3.4.0",
79
+ "@brightweblabs/theme": "0.2.0"
80
+ },
81
+ "peerDependencies": {
82
+ "lucide-react": "^1.8.0",
83
+ "next": "^16.0.0",
84
+ "react": "^19.0.0",
85
+ "react-dom": "^19.0.0"
48
86
  }
49
87
  }
@@ -0,0 +1,12 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+
3
+ import { cn } from "../lib/utils";
4
+
5
+ export const actionClassName =
6
+ "inline-flex shrink-0 items-center justify-center gap-1.5 rounded-full border border-[color:var(--brand-accent)] bg-[color:var(--brand-accent)] px-3.5 py-1.5 text-ui-meta font-semibold text-[color:var(--accent-foreground)] transition-colors hover:bg-[color:var(--brand-accent-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-3.5";
7
+
8
+ export type ActionButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
9
+
10
+ export function ActionButton({ className, type = "button", ...props }: ActionButtonProps) {
11
+ return <button type={type} className={cn(actionClassName, className)} {...props} />;
12
+ }
@@ -0,0 +1,24 @@
1
+ import type { MsgSeg } from "../lib/activity-format";
2
+
3
+ /** Render a composed activity message — actor/entity names semibold, values medium. */
4
+ export function ActivityMessage({ segs }: { segs: MsgSeg[] }) {
5
+ return (
6
+ <>
7
+ {segs.map((seg, index) => {
8
+ if (typeof seg === "string") return <span key={index}>{seg}</span>;
9
+ if ("b" in seg) {
10
+ return (
11
+ <span key={index} className="font-semibold text-[color:var(--foreground)]">
12
+ {seg.b}
13
+ </span>
14
+ );
15
+ }
16
+ return (
17
+ <span key={index} className="font-semibold text-[color:var(--foreground)]">
18
+ {seg.v}
19
+ </span>
20
+ );
21
+ })}
22
+ </>
23
+ );
24
+ }
@@ -1,10 +1,10 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
- import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
5
 
6
6
  import { cn } from "../lib/utils"
7
- import { buttonVariants } from "./button"
7
+ import { buttonVariants } from "./button-variants"
8
8
 
9
9
  function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
10
10
  return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
@@ -30,7 +30,7 @@ function AlertDialogOverlay({
30
30
  <AlertDialogPrimitive.Overlay
31
31
  data-slot="alert-dialog-overlay"
32
32
  className={cn(
33
- "fixed inset-0 z-50 bg-black/55 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
33
+ "fixed inset-0 z-50 bg-[color:color-mix(in_srgb,var(--foreground)_55%,transparent)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
34
34
  className
35
35
  )}
36
36
  {...props}
@@ -48,7 +48,7 @@ function AlertDialogContent({
48
48
  <AlertDialogPrimitive.Content
49
49
  data-slot="alert-dialog-content"
50
50
  className={cn(
51
- "bg-background fixed top-1/2 left-1/2 z-50 grid w-full max-w-md -translate-x-1/2 -translate-y-1/2 gap-4 rounded-2xl border border-black/10 p-6 shadow-[0_24px_64px_rgba(8,24,16,0.25)] duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 dark:border-white/12",
51
+ "fixed left-1/2 top-1/2 z-50 grid w-full max-w-[28rem] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[var(--radius-panel)] border border-[color:var(--hairline-strong)] bg-background p-6 shadow-[0_24px_64px_var(--elevate-3)] duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
52
52
  className
53
53
  )}
54
54
  {...props}
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
+
6
+ import { cn } from "../lib/utils";
7
+
8
+ function Avatar({ className, size = "default", ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
9
+ size?: "default" | "sm" | "lg";
10
+ }) {
11
+ return (
12
+ <AvatarPrimitive.Root
13
+ data-slot="avatar"
14
+ data-size={size}
15
+ className={cn(
16
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
25
+ return <AvatarPrimitive.Image data-slot="avatar-image" className={cn("aspect-square size-full", className)} {...props} />;
26
+ }
27
+
28
+ function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
29
+ return (
30
+ <AvatarPrimitive.Fallback
31
+ data-slot="avatar-fallback"
32
+ className={cn(
33
+ "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
34
+ className,
35
+ )}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
42
+ return (
43
+ <span
44
+ data-slot="avatar-badge"
45
+ className={cn(
46
+ "absolute bottom-0 right-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none",
47
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
48
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
49
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
50
+ className,
51
+ )}
52
+ {...props}
53
+ />
54
+ );
55
+ }
56
+
57
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
58
+ return (
59
+ <div
60
+ data-slot="avatar-group"
61
+ className={cn("group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background", className)}
62
+ {...props}
63
+ />
64
+ );
65
+ }
66
+
67
+ function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">) {
68
+ return (
69
+ <div
70
+ data-slot="avatar-group-count"
71
+ className={cn(
72
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
73
+ className,
74
+ )}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ export { Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount };
@@ -1,48 +1,38 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
- import { Slot } from "radix-ui"
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { Slot } from "@radix-ui/react-slot";
4
4
 
5
- import { cn } from "../lib/utils"
5
+ import { cn } from "../lib/utils";
6
6
 
7
7
  const badgeVariants = cva(
8
- "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
8
+ "inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap rounded-full border border-transparent transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
9
9
  {
10
10
  variants: {
11
11
  variant: {
12
12
  default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
- secondary:
14
- "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
13
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
14
  destructive:
16
- "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
- outline:
18
- "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
15
+ "bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
16
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
17
  ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
18
  link: "text-primary underline-offset-4 [a&]:hover:underline",
19
+ tint: "text-current [background-color:color-mix(in_srgb,currentColor_14%,transparent)]",
20
+ },
21
+ size: {
22
+ default: "gap-1 px-2 py-0.5 text-xs font-semibold",
23
+ sm: "gap-1.5 px-xs py-0.5 text-[length:var(--text-ui-micro)] font-semibold leading-none",
21
24
  },
22
25
  },
23
26
  defaultVariants: {
24
27
  variant: "default",
28
+ size: "default",
25
29
  },
26
- }
27
- )
28
-
29
- function Badge({
30
- className,
31
- variant = "default",
32
- asChild = false,
33
- ...props
34
- }: React.ComponentProps<"span"> &
35
- VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
36
- const Comp = asChild ? Slot.Root : "span"
30
+ },
31
+ );
37
32
 
38
- return (
39
- <Comp
40
- data-slot="badge"
41
- data-variant={variant}
42
- className={cn(badgeVariants({ variant }), className)}
43
- {...props}
44
- />
45
- )
33
+ function Badge({ className, variant = "default", size = "default", asChild = false, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
34
+ const Comp = asChild ? Slot : "span";
35
+ return <Comp data-slot="badge" data-variant={variant} data-size={size} className={cn(badgeVariants({ variant, size }), className)} {...props} />;
46
36
  }
47
37
 
48
- export { Badge, badgeVariants }
38
+ export { Badge, badgeVariants };
@@ -0,0 +1,47 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+
3
+ const buttonVariants = cva(
4
+ "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius-control)] text-ui-body !font-semibold transition-all outline-none disabled:pointer-events-none disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
5
+ {
6
+ variants: {
7
+ variant: {
8
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
9
+ accent:
10
+ "relative isolate h-auto overflow-hidden !rounded-none px-0 py-0 font-display !font-bold text-[length:var(--text-ui-body)] leading-[1.6] text-foreground [padding:var(--space-button-y)_var(--space-button-x)] hover:text-foreground focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-[color:var(--brand-accent)] focus-visible:ring-offset-2",
11
+ accentLink:
12
+ "h-auto min-h-0 gap-[var(--space-sm)] !rounded-none px-0 py-0 font-display !font-bold text-[length:var(--text-ui-subhead)] leading-[1.66] text-[color:var(--brand-accent)] no-underline hover:opacity-70 hover:no-underline",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
15
+ brand:
16
+ "bg-[color:var(--brand-accent)] text-[color:var(--accent-foreground)] !font-extrabold shadow-[0_4px_14px_color-mix(in_srgb,var(--brand-accent)_26%,transparent)] hover:brightness-[1.05]",
17
+ soft:
18
+ "border border-[color:var(--border)] bg-[color:color-mix(in_srgb,var(--foreground)_3.5%,var(--card))] text-foreground hover:border-[color:color-mix(in_srgb,var(--foreground)_16%,transparent)] hover:bg-[color:color-mix(in_srgb,var(--foreground)_7%,var(--card))]",
19
+ outline:
20
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:hover:bg-input/50",
21
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
22
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
23
+ link: "text-foreground underline-offset-4 transition-colors hover:text-primary hover:underline",
24
+ },
25
+ size: {
26
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
27
+ xs: "h-6 gap-1 rounded-[var(--radius-control)] px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
28
+ sm: "h-8 gap-1.5 rounded-[var(--radius-control)] px-3 has-[>svg]:px-2.5",
29
+ lg: "h-10 rounded-[var(--radius-control)] px-6 has-[>svg]:px-4",
30
+ icon: "size-9",
31
+ "icon-xs": "size-6 rounded-[var(--radius-control)] [&_svg:not([class*='size-'])]:size-3",
32
+ "icon-sm": "size-8",
33
+ "icon-lg": "size-10",
34
+ link: "h-auto min-h-0 px-0 py-0",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ },
42
+ );
43
+
44
+ type ButtonVariantProps = VariantProps<typeof buttonVariants>;
45
+
46
+ export { buttonVariants };
47
+ export type { ButtonVariantProps };
@@ -1,64 +1,113 @@
1
+ "use client";
2
+
1
3
  import * as React from "react";
4
+ import Link from "next/link";
2
5
  import { Slot } from "@radix-ui/react-slot";
3
- import { cva, type VariantProps } from "class-variance-authority";
4
6
 
5
7
  import { cn } from "../lib/utils";
8
+ import { buttonVariants, type ButtonVariantProps } from "./button-variants";
9
+
10
+ type ButtonProps = React.ComponentProps<"button"> &
11
+ ButtonVariantProps & {
12
+ asChild?: boolean;
13
+ href?: string;
14
+ prefetch?: React.ComponentProps<typeof Link>["prefetch"];
15
+ };
16
+
17
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
18
+ ({ className, variant = "default", size = "default", asChild = false, href, prefetch, ...props }, ref) => {
19
+ const { onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, children, ...restProps } = props;
20
+ const hasCornerMotion = !asChild && variant === "accent";
21
+ const [isActive, setIsActive] = React.useState(false);
22
+ const borderSize = hasCornerMotion && isActive ? "100%" : "30%";
23
+ const resolvedClassName = cn(buttonVariants({ variant, size, className }));
6
24
 
7
- const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md paragraph-small font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
- {
10
- variants: {
11
- variant: {
12
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
- destructive:
14
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
- outline:
16
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
19
- link: "text-foreground underline-offset-4 transition-colors hover:text-primary hover:underline",
25
+ const activate = () => hasCornerMotion && setIsActive(true);
26
+ const deactivate = () => hasCornerMotion && setIsActive(false);
20
27
 
21
- },
22
- size: {
23
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
24
- xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
25
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
- icon: "size-9",
28
- "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
29
- "icon-sm": "size-8",
30
- "icon-lg": "size-10",
31
- link: "h-auto min-h-0 px-0 py-0",
32
- },
33
- },
34
- defaultVariants: {
35
- variant: "default",
36
- size: "default",
37
- },
28
+ const content = hasCornerMotion ? (
29
+ <>
30
+ <span className="relative z-2">{children}</span>
31
+ <span
32
+ aria-hidden="true"
33
+ className="pointer-events-none absolute right-0 top-0 border-r border-t border-[color:var(--brand-accent)] transition-[width,height] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
34
+ style={{ width: borderSize, height: borderSize }}
35
+ />
36
+ <span
37
+ aria-hidden="true"
38
+ className="pointer-events-none absolute bottom-0 left-0 border-b border-l border-[color:var(--brand-accent)] transition-[width,height] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
39
+ style={{ width: borderSize, height: borderSize }}
40
+ />
41
+ </>
42
+ ) : children;
43
+
44
+ if (!asChild && href) {
45
+ return (
46
+ <Link
47
+ href={href}
48
+ data-slot="button"
49
+ data-variant={variant}
50
+ data-size={size}
51
+ className={resolvedClassName}
52
+ prefetch={prefetch}
53
+ onClick={onClick as React.MouseEventHandler<HTMLAnchorElement> | undefined}
54
+ onMouseEnter={(event) => {
55
+ activate();
56
+ onMouseEnter?.(event as unknown as React.MouseEvent<HTMLButtonElement>);
57
+ }}
58
+ onMouseLeave={(event) => {
59
+ deactivate();
60
+ onMouseLeave?.(event as unknown as React.MouseEvent<HTMLButtonElement>);
61
+ }}
62
+ onFocus={(event) => {
63
+ activate();
64
+ onFocus?.(event as unknown as React.FocusEvent<HTMLButtonElement>);
65
+ }}
66
+ onBlur={(event) => {
67
+ deactivate();
68
+ onBlur?.(event as unknown as React.FocusEvent<HTMLButtonElement>);
69
+ }}
70
+ {...(restProps as Omit<React.ComponentProps<typeof Link>, "href" | "prefetch">)}
71
+ >
72
+ {content}
73
+ </Link>
74
+ );
75
+ }
76
+
77
+ const Comp = asChild ? Slot : "button";
78
+ return (
79
+ <Comp
80
+ ref={ref}
81
+ data-slot="button"
82
+ data-variant={variant}
83
+ data-size={size}
84
+ className={resolvedClassName}
85
+ onClick={onClick}
86
+ onMouseEnter={(event) => {
87
+ activate();
88
+ onMouseEnter?.(event as unknown as React.MouseEvent<HTMLButtonElement>);
89
+ }}
90
+ onMouseLeave={(event) => {
91
+ deactivate();
92
+ onMouseLeave?.(event as unknown as React.MouseEvent<HTMLButtonElement>);
93
+ }}
94
+ onFocus={(event) => {
95
+ activate();
96
+ onFocus?.(event as unknown as React.FocusEvent<HTMLButtonElement>);
97
+ }}
98
+ onBlur={(event) => {
99
+ deactivate();
100
+ onBlur?.(event as unknown as React.FocusEvent<HTMLButtonElement>);
101
+ }}
102
+ {...restProps}
103
+ >
104
+ {content}
105
+ </Comp>
106
+ );
38
107
  },
39
108
  );
40
109
 
41
- function Button({
42
- className,
43
- variant = "default",
44
- size = "default",
45
- asChild = false,
46
- ...props
47
- }: React.ComponentProps<"button"> &
48
- VariantProps<typeof buttonVariants> & {
49
- asChild?: boolean;
50
- }) {
51
- const Comp = asChild ? Slot : "button";
52
-
53
- return (
54
- <Comp
55
- data-slot="button"
56
- data-variant={variant}
57
- data-size={size}
58
- className={cn(buttonVariants({ variant, size, className }))}
59
- {...props}
60
- />
61
- );
62
- }
110
+ Button.displayName = "Button";
63
111
 
64
112
  export { Button, buttonVariants };
113
+ export type { ButtonProps, ButtonVariantProps };
@@ -13,7 +13,8 @@ import {
13
13
  } from "react-day-picker"
14
14
 
15
15
  import { cn } from "../lib/utils"
16
- import { Button, buttonVariants } from "./button"
16
+ import { Button } from "./button"
17
+ import { buttonVariants, type ButtonVariantProps } from "./button-variants"
17
18
 
18
19
  function Calendar({
19
20
  className,
@@ -25,7 +26,7 @@ function Calendar({
25
26
  components,
26
27
  ...props
27
28
  }: React.ComponentProps<typeof DayPicker> & {
28
- buttonVariant?: React.ComponentProps<typeof Button>["variant"]
29
+ buttonVariant?: ButtonVariantProps["variant"]
29
30
  }) {
30
31
  const defaultClassNames = getDefaultClassNames()
31
32
 
@@ -70,7 +71,7 @@ function Calendar({
70
71
  defaultClassNames.month_caption
71
72
  ),
72
73
  dropdowns: cn(
73
- "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
74
+ "w-full flex items-center text-sm font-semibold justify-center h-(--cell-size) gap-1.5",
74
75
  defaultClassNames.dropdowns
75
76
  ),
76
77
  dropdown_root: cn(
@@ -82,7 +83,7 @@ function Calendar({
82
83
  defaultClassNames.dropdown
83
84
  ),
84
85
  caption_label: cn(
85
- "select-none font-medium",
86
+ "select-none font-semibold",
86
87
  captionLayout === "label"
87
88
  ? "text-sm"
88
89
  : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
@@ -79,7 +79,7 @@ function ChartContainer({
79
79
  data-slot="chart"
80
80
  data-chart={chartId}
81
81
  className={cn(
82
- "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
82
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='var(--border)']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='var(--border)']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='var(--border)']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='var(--card)']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='var(--card)']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
83
83
  className
84
84
  )}
85
85
  {...props}
@@ -167,7 +167,7 @@ function ChartTooltipContent({
167
167
 
168
168
  if (labelFormatter) {
169
169
  return (
170
- <div className={cn("font-medium", labelClassName)}>
170
+ <div className={cn("font-semibold", labelClassName)}>
171
171
  {labelFormatter(value, payload)}
172
172
  </div>
173
173
  )
@@ -177,7 +177,7 @@ function ChartTooltipContent({
177
177
  return null
178
178
  }
179
179
 
180
- return <div className={cn("font-medium", labelClassName)}>{value}</div>
180
+ return <div className={cn("font-semibold", labelClassName)}>{value}</div>
181
181
  }, [
182
182
  label,
183
183
  labelFormatter,
@@ -259,7 +259,7 @@ function ChartTooltipContent({
259
259
  </span>
260
260
  </div>
261
261
  {item.value && (
262
- <span className="text-foreground font-mono font-medium tabular-nums">
262
+ <span className="font-mono font-semibold tabular-nums text-foreground">
263
263
  {item.value.toLocaleString()}
264
264
  </span>
265
265
  )}
@@ -74,7 +74,7 @@ function DropdownMenuItem({
74
74
  data-inset={inset}
75
75
  data-variant={variant}
76
76
  className={cn(
77
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
77
+ "focus:bg-accent/15 focus:text-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-foreground/70 focus:[&_svg:not([class*='text-'])]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
78
  className
79
79
  )}
80
80
  {...props}
@@ -92,7 +92,7 @@ function DropdownMenuCheckboxItem({
92
92
  <DropdownMenuPrimitive.CheckboxItem
93
93
  data-slot="dropdown-menu-checkbox-item"
94
94
  className={cn(
95
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
95
+ "focus:bg-accent/15 focus:text-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
96
  className
97
97
  )}
98
98
  checked={checked}
@@ -128,7 +128,7 @@ function DropdownMenuRadioItem({
128
128
  <DropdownMenuPrimitive.RadioItem
129
129
  data-slot="dropdown-menu-radio-item"
130
130
  className={cn(
131
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
131
+ "focus:bg-accent/15 focus:text-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
132
  className
133
133
  )}
134
134
  {...props}
@@ -155,7 +155,7 @@ function DropdownMenuLabel({
155
155
  data-slot="dropdown-menu-label"
156
156
  data-inset={inset}
157
157
  className={cn(
158
- "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
158
+ "px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8",
159
159
  className
160
160
  )}
161
161
  {...props}
@@ -211,7 +211,7 @@ function DropdownMenuSubTrigger({
211
211
  data-slot="dropdown-menu-sub-trigger"
212
212
  data-inset={inset}
213
213
  className={cn(
214
- "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
214
+ "focus:bg-accent/15 focus:text-foreground data-[state=open]:bg-accent/15 data-[state=open]:text-foreground [&_svg:not([class*='text-'])]:text-foreground/70 focus:[&_svg:not([class*='text-'])]:text-foreground data-[state=open]:[&_svg:not([class*='text-'])]:text-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
215
215
  className
216
216
  )}
217
217
  {...props}