@greatapps/greatauth-ui 0.3.13 → 0.3.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/greatauth-ui",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -4,7 +4,7 @@ import { Fragment, useMemo } from "react";
4
4
  import { usePathname } from "next/navigation";
5
5
  import Link from "next/link";
6
6
  import type { AppShellConfig } from "../types";
7
- import { SidebarTrigger } from "./ui/sidebar";
7
+ import { SidebarTrigger, useSidebar } from "./ui/sidebar";
8
8
  import { Separator } from "./ui/separator";
9
9
  import {
10
10
  Breadcrumb,
@@ -23,6 +23,7 @@ interface AppHeaderProps {
23
23
 
24
24
  export function AppHeader({ config }: AppHeaderProps) {
25
25
  const pathname = usePathname();
26
+ const { state } = useSidebar();
26
27
  const segments = pathname.split("/").filter(Boolean);
27
28
 
28
29
  const breadcrumbs = useMemo(() => {
@@ -43,6 +44,12 @@ export function AppHeader({ config }: AppHeaderProps) {
43
44
  return (
44
45
  <header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
45
46
  <SidebarTrigger className="-ml-1" />
47
+ {state === "collapsed" && config.appIcon && (
48
+ <>
49
+ <Separator orientation="vertical" className="!h-4" />
50
+ <div className="shrink-0 text-sidebar-foreground">{config.appIcon}</div>
51
+ </>
52
+ )}
46
53
  <Separator orientation="vertical" className="mr-2 !h-4" />
47
54
 
48
55
  <Breadcrumb className="flex-1">
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { usePathname, useRouter } from "next/navigation";
4
4
  import Link from "next/link";
5
- import { ChevronUp, ChevronRight, LogOut } from "lucide-react";
5
+ import { ChevronsUpDown, ChevronRight, LogOut } from "lucide-react";
6
6
  import { useSession } from "../auth";
7
7
  import type { AppShellConfig, MenuGroup, MenuItem } from "../types";
8
8
  import { signOut } from "../auth";
@@ -17,6 +17,7 @@ import {
17
17
  SidebarMenu,
18
18
  SidebarMenuButton,
19
19
  SidebarMenuItem,
20
+ SidebarRail,
20
21
  } from "./ui/sidebar";
21
22
  import {
22
23
  Collapsible,
@@ -49,15 +50,20 @@ function getUserInitials(name: string, email: string): string {
49
50
  }
50
51
 
51
52
  function SimpleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
52
- const isActive = pathname.startsWith(item.href);
53
+ const isActive = item.isActive ?? pathname.startsWith(item.href);
53
54
  const Icon = item.icon;
54
55
 
55
56
  return (
56
57
  <SidebarMenuItem>
57
58
  <SidebarMenuButton asChild isActive={isActive} tooltip={item.label}>
58
- <Link href={item.href} onClick={item.onClick}>
59
+ <Link href={item.href} onClick={item.onClick} aria-current={isActive ? "page" : undefined}>
59
60
  <Icon className="size-4" />
60
61
  <span>{item.label}</span>
62
+ {item.badge != null && (
63
+ <span className="ml-auto flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/10 px-1.5 text-[10px] font-semibold text-primary tabular-nums">
64
+ {item.badge}
65
+ </span>
66
+ )}
61
67
  </Link>
62
68
  </SidebarMenuButton>
63
69
  </SidebarMenuItem>
@@ -66,22 +72,33 @@ function SimpleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }
66
72
 
67
73
  function CollapsibleMenuItem({ item, pathname }: { item: MenuItem; pathname: string }) {
68
74
  const Icon = item.icon;
69
- const isChildActive = item.children?.some((child) => pathname.startsWith(child.href)) ?? false;
75
+ const isParentActive = pathname.startsWith(item.href);
76
+ const isChildActive = item.children?.some((child) =>
77
+ child.isActive !== undefined ? child.isActive : pathname.startsWith(child.href)
78
+ ) ?? false;
70
79
 
71
80
  return (
72
- <Collapsible defaultOpen={isChildActive} className="group/collapsible">
81
+ <Collapsible defaultOpen={isParentActive || isChildActive} className="group/collapsible">
73
82
  <SidebarMenuItem>
74
- <CollapsibleTrigger asChild>
75
- <SidebarMenuButton tooltip={item.label}>
83
+ <SidebarMenuButton asChild isActive={isParentActive} tooltip={item.label}>
84
+ <Link href={item.href} onClick={item.onClick}>
76
85
  <Icon className="size-4" />
77
86
  <span>{item.label}</span>
78
- <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
79
- </SidebarMenuButton>
87
+ </Link>
88
+ </SidebarMenuButton>
89
+ <CollapsibleTrigger asChild>
90
+ <button
91
+ data-sidebar="menu-action"
92
+ className="absolute right-1 top-1.5 flex h-5 w-5 items-center justify-center rounded-md text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground outline-hidden focus-visible:ring-2 ring-sidebar-ring transition-colors group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 md:after:hidden"
93
+ aria-label="Expandir submenu"
94
+ >
95
+ <ChevronRight className="size-3.5 shrink-0 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
96
+ </button>
80
97
  </CollapsibleTrigger>
81
98
  <CollapsibleContent>
82
99
  <SidebarMenu className="ml-4 border-l pl-2">
83
100
  {item.children?.map((child) => (
84
- <SimpleMenuItem key={child.href} item={child} pathname={pathname} />
101
+ <SimpleMenuItem key={child.href + child.label} item={child} pathname={pathname} />
85
102
  ))}
86
103
  </SidebarMenu>
87
104
  </CollapsibleContent>
@@ -156,7 +173,7 @@ export function AppSidebar({ config }: AppSidebarProps) {
156
173
  <span className="truncate font-semibold">{userName}</span>
157
174
  <span className="truncate text-xs text-muted-foreground">{userEmail}</span>
158
175
  </div>
159
- <ChevronUp className="ml-auto size-4 group-data-[collapsible=icon]:hidden" />
176
+ <ChevronsUpDown className="ml-auto size-4 group-data-[collapsible=icon]:hidden" />
160
177
  </SidebarMenuButton>
161
178
  </DropdownMenuTrigger>
162
179
  <DropdownMenuContent
@@ -182,6 +199,7 @@ export function AppSidebar({ config }: AppSidebarProps) {
182
199
  </SidebarMenuItem>
183
200
  </SidebarMenu>
184
201
  </SidebarFooter>
202
+ <SidebarRail />
185
203
  </Sidebar>
186
204
  );
187
205
  }
@@ -3,6 +3,8 @@
3
3
  import * as React from "react"
4
4
  import { Collapsible as CollapsiblePrimitive } from "radix-ui"
5
5
 
6
+ import { cn } from "../../lib/utils"
7
+
6
8
  function Collapsible({
7
9
  ...props
8
10
  }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
@@ -21,11 +23,16 @@ function CollapsibleTrigger({
21
23
  }
22
24
 
23
25
  function CollapsibleContent({
26
+ className,
24
27
  ...props
25
28
  }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
26
29
  return (
27
30
  <CollapsiblePrimitive.CollapsibleContent
28
31
  data-slot="collapsible-content"
32
+ className={cn(
33
+ "overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down",
34
+ className
35
+ )}
29
36
  {...props}
30
37
  />
31
38
  )
@@ -27,7 +27,7 @@ import {
27
27
  const SIDEBAR_STORAGE_KEY = "sidebar_state"
28
28
  const SIDEBAR_WIDTH = "16rem"
29
29
  const SIDEBAR_WIDTH_MOBILE = "18rem"
30
- const SIDEBAR_WIDTH_ICON = "3rem"
30
+ const SIDEBAR_WIDTH_ICON = "3.5rem"
31
31
  const SIDEBAR_KEYBOARD_SHORTCUT = "b"
32
32
 
33
33
  type SidebarContextProps = {
package/src/theme.css CHANGED
@@ -51,6 +51,8 @@
51
51
  --radius-2xl: calc(var(--radius) + 8px);
52
52
  --radius-3xl: calc(var(--radius) + 12px);
53
53
  --radius-4xl: calc(var(--radius) + 16px);
54
+ --animate-collapsible-down: collapsible-down 200ms ease-out;
55
+ --animate-collapsible-up: collapsible-up 150ms ease-out;
54
56
  }
55
57
 
56
58
  :root {
@@ -62,35 +64,35 @@
62
64
  --popover-foreground: oklch(0.141 0.005 285.823);
63
65
  --primary: oklch(0.21 0.006 285.885);
64
66
  --primary-foreground: oklch(0.985 0 0);
65
- --secondary: oklch(0.967 0.001 286.375);
67
+ --secondary: oklch(0.955 0.004 285);
66
68
  --secondary-foreground: oklch(0.21 0.006 285.885);
67
- --muted: oklch(0.967 0.001 286.375);
68
- --muted-foreground: oklch(0.50 0.016 285.938);
69
- --accent: oklch(0.967 0.001 286.375);
69
+ --muted: oklch(0.965 0.002 286);
70
+ --muted-foreground: oklch(0.45 0.02 285);
71
+ --accent: oklch(0.945 0.012 270);
70
72
  --accent-foreground: oklch(0.21 0.006 285.885);
71
73
  --destructive: oklch(0.577 0.245 27.325);
72
74
  --success: oklch(0.527 0.154 150.069);
73
- --border: oklch(0.92 0.004 286.32);
75
+ --border: oklch(0.91 0.005 286);
74
76
  --input: oklch(0.92 0.004 286.32);
75
- --ring: oklch(0.588 0.158 241.966);
77
+ --ring: oklch(0.55 0.06 275);
76
78
  --chart-1: oklch(0.588 0.158 241.966);
77
79
  --chart-2: oklch(0.637 0.179 163.223);
78
80
  --chart-3: oklch(0.553 0.195 255.065);
79
81
  --chart-4: oklch(0.705 0.213 47.604);
80
82
  --chart-5: oklch(0.637 0.237 25.331);
81
83
  --radius: 0.625rem;
82
- --sidebar: oklch(0.985 0 0);
84
+ --sidebar: oklch(0.975 0.003 285);
83
85
  --sidebar-foreground: oklch(0.141 0.005 285.823);
84
86
  --sidebar-primary: oklch(0.21 0.006 285.885);
85
87
  --sidebar-primary-foreground: oklch(0.985 0 0);
86
- --sidebar-accent: oklch(0.967 0.001 286.375);
87
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
88
- --sidebar-border: oklch(0.92 0.004 286.32);
89
- --sidebar-ring: oklch(0.705 0.015 286.067);
88
+ --sidebar-accent: oklch(0.935 0.018 270);
89
+ --sidebar-accent-foreground: oklch(0.15 0.010 270);
90
+ --sidebar-border: oklch(0.90 0.008 280);
91
+ --sidebar-ring: oklch(0.60 0.05 275);
90
92
  }
91
93
 
92
94
  .dark {
93
- --background: oklch(0.141 0.005 285.823);
95
+ --background: oklch(0.145 0.006 285);
94
96
  --foreground: oklch(0.985 0 0);
95
97
  --card: oklch(0.21 0.006 285.885);
96
98
  --card-foreground: oklch(0.985 0 0);
@@ -98,30 +100,30 @@
98
100
  --popover-foreground: oklch(0.985 0 0);
99
101
  --primary: oklch(0.92 0.004 286.32);
100
102
  --primary-foreground: oklch(0.21 0.006 285.885);
101
- --secondary: oklch(0.274 0.006 286.033);
103
+ --secondary: oklch(0.25 0.008 285);
102
104
  --secondary-foreground: oklch(0.985 0 0);
103
- --muted: oklch(0.274 0.006 286.033);
104
- --muted-foreground: oklch(0.705 0.015 286.067);
105
- --accent: oklch(0.274 0.006 286.033);
105
+ --muted: oklch(0.23 0.006 286);
106
+ --muted-foreground: oklch(0.65 0.015 286);
107
+ --accent: oklch(0.28 0.018 270);
106
108
  --accent-foreground: oklch(0.985 0 0);
107
109
  --destructive: oklch(0.704 0.191 22.216);
108
110
  --success: oklch(0.696 0.17 162.48);
109
- --border: oklch(1 0 0 / 10%);
111
+ --border: oklch(1 0 0 / 12%);
110
112
  --input: oklch(1 0 0 / 15%);
111
- --ring: oklch(0.552 0.016 285.938);
113
+ --ring: oklch(0.50 0.04 275);
112
114
  --chart-1: oklch(0.688 0.158 241.966);
113
115
  --chart-2: oklch(0.737 0.179 163.223);
114
116
  --chart-3: oklch(0.653 0.195 255.065);
115
117
  --chart-4: oklch(0.765 0.183 47.604);
116
118
  --chart-5: oklch(0.717 0.217 25.331);
117
- --sidebar: oklch(0.21 0.006 285.885);
119
+ --sidebar: oklch(0.185 0.008 280);
118
120
  --sidebar-foreground: oklch(0.985 0 0);
119
- --sidebar-primary: oklch(0.488 0.243 264.376);
121
+ --sidebar-primary: oklch(0.70 0.04 275);
120
122
  --sidebar-primary-foreground: oklch(0.985 0 0);
121
- --sidebar-accent: oklch(0.274 0.006 286.033);
122
- --sidebar-accent-foreground: oklch(0.985 0 0);
123
- --sidebar-border: oklch(1 0 0 / 10%);
124
- --sidebar-ring: oklch(0.552 0.016 285.938);
123
+ --sidebar-accent: oklch(0.30 0.025 270);
124
+ --sidebar-accent-foreground: oklch(0.95 0.005 270);
125
+ --sidebar-border: oklch(1 0 0 / 14%);
126
+ --sidebar-ring: oklch(0.55 0.04 275);
125
127
  }
126
128
 
127
129
  @layer base {
@@ -136,6 +138,29 @@
136
138
  }
137
139
  }
138
140
 
141
+ /* Collapsible animation */
142
+ @keyframes collapsible-down {
143
+ from {
144
+ height: 0;
145
+ opacity: 0;
146
+ }
147
+ to {
148
+ height: var(--radix-collapsible-content-height);
149
+ opacity: 1;
150
+ }
151
+ }
152
+
153
+ @keyframes collapsible-up {
154
+ from {
155
+ height: var(--radix-collapsible-content-height);
156
+ opacity: 1;
157
+ }
158
+ to {
159
+ height: 0;
160
+ opacity: 0;
161
+ }
162
+ }
163
+
139
164
  /* View Transition — dark/light mode circle reveal */
140
165
  ::view-transition-old(root),
141
166
  ::view-transition-new(root) {
@@ -25,6 +25,8 @@ export interface MenuItem {
25
25
  href: string;
26
26
  children?: MenuItem[];
27
27
  onClick?: () => void;
28
+ isActive?: boolean;
29
+ badge?: string | number;
28
30
  }
29
31
 
30
32
  export interface HeaderConfig {