@hznrkv/sidebar 1.0.0 → 1.1.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 (2) hide show
  1. package/index.js +239 -82
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -44,37 +44,79 @@ import {
44
44
  SidebarFooter,
45
45
  SidebarHeader,
46
46
  } from "@/components/ui/sidebar"
47
- import { LayoutDashboardIcon, SettingsIcon, UserIcon } from "lucide-react"
47
+ import {
48
+ LayoutDashboardIcon,
49
+ UsersIcon,
50
+ FolderIcon,
51
+ BarChart2Icon,
52
+ SettingsIcon,
53
+ BoxIcon,
54
+ } from "lucide-react"
48
55
 
49
56
  const data = {
50
57
  user: {
51
58
  name: "Usuário",
52
- email: "usuario@exemplo.com",
59
+ email: "usuario@empresa.com",
53
60
  avatar: "",
54
61
  },
55
62
  teams: [
56
63
  {
57
- name: "Meu Projeto",
58
- logo: <LayoutDashboardIcon />,
59
- plan: "Free",
64
+ name: "Minha Empresa",
65
+ logo: <BoxIcon />,
66
+ plan: "Pro",
60
67
  },
61
68
  ],
62
69
  navMain: [
63
70
  {
64
71
  title: "Dashboard",
65
- url: "/",
72
+ url: "/dashboard",
66
73
  icon: <LayoutDashboardIcon />,
67
74
  isActive: true,
75
+ items: [
76
+ { title: "Visão geral", url: "/dashboard" },
77
+ { title: "Atividade recente", url: "/dashboard/atividade" },
78
+ { title: "Relatórios", url: "/dashboard/relatorios" },
79
+ ],
80
+ },
81
+ {
82
+ title: "Projetos",
83
+ url: "/projetos",
84
+ icon: <FolderIcon />,
85
+ items: [
86
+ { title: "Todos os projetos", url: "/projetos" },
87
+ { title: "Em andamento", url: "/projetos/andamento" },
88
+ { title: "Arquivados", url: "/projetos/arquivados" },
89
+ ],
90
+ },
91
+ {
92
+ title: "Equipe",
93
+ url: "/equipe",
94
+ icon: <UsersIcon />,
95
+ items: [
96
+ { title: "Membros", url: "/equipe" },
97
+ { title: "Funções", url: "/equipe/funcoes" },
98
+ { title: "Convites", url: "/equipe/convites" },
99
+ ],
68
100
  },
69
101
  {
70
- title: "Perfil",
71
- url: "/perfil",
72
- icon: <UserIcon />,
102
+ title: "Análises",
103
+ url: "/analises",
104
+ icon: <BarChart2Icon />,
105
+ items: [
106
+ { title: "Desempenho", url: "/analises" },
107
+ { title: "Tendências", url: "/analises/tendencias" },
108
+ { title: "Exportar", url: "/analises/exportar" },
109
+ ],
73
110
  },
74
111
  {
75
112
  title: "Configurações",
76
113
  url: "/configuracoes",
77
114
  icon: <SettingsIcon />,
115
+ items: [
116
+ { title: "Geral", url: "/configuracoes" },
117
+ { title: "Segurança", url: "/configuracoes/seguranca" },
118
+ { title: "Integrações", url: "/configuracoes/integracoes" },
119
+ ],
78
120
  },
79
121
  ],
80
122
  }
@@ -98,7 +140,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
98
140
 
99
141
  "src/components/nav-main.tsx": `"use client"
100
142
 
101
- import { ChevronRightIcon } from "lucide-react"
143
+ import { useState } from "react"
102
144
  import {
103
145
  Collapsible,
104
146
  CollapsibleContent,
@@ -114,6 +156,7 @@ import {
114
156
  SidebarMenuSubButton,
115
157
  SidebarMenuSubItem,
116
158
  } from "@/components/ui/sidebar"
159
+ import { ChevronRightIcon } from "lucide-react"
117
160
 
118
161
  type NavItem = {
119
162
  title: string
@@ -123,43 +166,52 @@ type NavItem = {
123
166
  items?: { title: string; url: string }[]
124
167
  }
125
168
 
169
+ function NavItemCollapsible({ item }: { item: NavItem }) {
170
+ const [open, setOpen] = useState(false)
171
+
172
+ return (
173
+ <Collapsible
174
+ open={open}
175
+ onOpenChange={setOpen}
176
+ className="group/collapsible"
177
+ >
178
+ <SidebarMenuItem>
179
+ <CollapsibleTrigger asChild>
180
+ <SidebarMenuButton tooltip={item.title}>
181
+ {item.icon}
182
+ <span>{item.title}</span>
183
+ <ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
184
+ </SidebarMenuButton>
185
+ </CollapsibleTrigger>
186
+ <CollapsibleContent>
187
+ <SidebarMenuSub>
188
+ {item.items?.map((subItem) => (
189
+ <SidebarMenuSubItem key={subItem.title}>
190
+ <SidebarMenuSubButton asChild>
191
+ <a href={subItem.url}>
192
+ <span>{subItem.title}</span>
193
+ </a>
194
+ </SidebarMenuSubButton>
195
+ </SidebarMenuSubItem>
196
+ ))}
197
+ </SidebarMenuSub>
198
+ </CollapsibleContent>
199
+ </SidebarMenuItem>
200
+ </Collapsible>
201
+ )
202
+ }
203
+
126
204
  export function NavMain({ items }: { items: NavItem[] }) {
127
205
  return (
128
206
  <SidebarGroup>
129
- <SidebarGroupLabel>Menu</SidebarGroupLabel>
207
+ <SidebarGroupLabel>Módulos</SidebarGroupLabel>
130
208
  <SidebarMenu>
131
209
  {items.map((item) =>
132
- item.items?.length ? (
133
- <Collapsible
134
- key={item.title}
135
- asChild
136
- defaultOpen={item.isActive}
137
- className="group/collapsible"
138
- >
139
- <SidebarMenuItem>
140
- <CollapsibleTrigger asChild>
141
- <SidebarMenuButton tooltip={item.title}>
142
- {item.icon}
143
- <span>{item.title}</span>
144
- <ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
145
- </SidebarMenuButton>
146
- </CollapsibleTrigger>
147
- <CollapsibleContent>
148
- <SidebarMenuSub>
149
- {item.items.map((sub) => (
150
- <SidebarMenuSubItem key={sub.title}>
151
- <SidebarMenuSubButton asChild>
152
- <a href={sub.url}>{sub.title}</a>
153
- </SidebarMenuSubButton>
154
- </SidebarMenuSubItem>
155
- ))}
156
- </SidebarMenuSub>
157
- </CollapsibleContent>
158
- </SidebarMenuItem>
159
- </Collapsible>
210
+ item.items ? (
211
+ <NavItemCollapsible key={item.title} item={item} />
160
212
  ) : (
161
213
  <SidebarMenuItem key={item.title}>
162
- <SidebarMenuButton asChild tooltip={item.title} isActive={item.isActive}>
214
+ <SidebarMenuButton tooltip={item.title} asChild>
163
215
  <a href={item.url}>
164
216
  {item.icon}
165
217
  <span>{item.title}</span>
@@ -177,14 +229,10 @@ export function NavMain({ items }: { items: NavItem[] }) {
177
229
  "src/components/nav-user.tsx": `"use client"
178
230
 
179
231
  import {
180
- BadgeCheckIcon,
181
- BellIcon,
182
- ChevronsUpDownIcon,
183
- CreditCardIcon,
184
- LogOutIcon,
185
- SparklesIcon,
186
- } from "lucide-react"
187
- import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
232
+ Avatar,
233
+ AvatarFallback,
234
+ AvatarImage,
235
+ } from "@/components/ui/avatar"
188
236
  import {
189
237
  DropdownMenu,
190
238
  DropdownMenuContent,
@@ -200,10 +248,17 @@ import {
200
248
  SidebarMenuItem,
201
249
  useSidebar,
202
250
  } from "@/components/ui/sidebar"
251
+ import { ChevronsUpDownIcon, SparklesIcon, BadgeCheckIcon, CreditCardIcon, BellIcon, LogOutIcon } from "lucide-react"
203
252
 
204
- type User = { name: string; email: string; avatar: string }
205
-
206
- export function NavUser({ user }: { user: User }) {
253
+ export function NavUser({
254
+ user,
255
+ }: {
256
+ user: {
257
+ name: string
258
+ email: string
259
+ avatar: string
260
+ }
261
+ }) {
207
262
  const { isMobile } = useSidebar()
208
263
 
209
264
  return (
@@ -217,19 +272,17 @@ export function NavUser({ user }: { user: User }) {
217
272
  >
218
273
  <Avatar className="h-8 w-8 rounded-lg">
219
274
  <AvatarImage src={user.avatar} alt={user.name} />
220
- <AvatarFallback className="rounded-lg">
221
- {user.name.slice(0, 2).toUpperCase()}
222
- </AvatarFallback>
275
+ <AvatarFallback className="rounded-lg">CN</AvatarFallback>
223
276
  </Avatar>
224
277
  <div className="grid flex-1 text-left text-sm leading-tight">
225
- <span className="truncate font-semibold">{user.name}</span>
278
+ <span className="truncate font-medium">{user.name}</span>
226
279
  <span className="truncate text-xs">{user.email}</span>
227
280
  </div>
228
281
  <ChevronsUpDownIcon className="ml-auto size-4" />
229
282
  </SidebarMenuButton>
230
283
  </DropdownMenuTrigger>
231
284
  <DropdownMenuContent
232
- className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
285
+ className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
233
286
  side={isMobile ? "bottom" : "right"}
234
287
  align="end"
235
288
  sideOffset={4}
@@ -238,12 +291,10 @@ export function NavUser({ user }: { user: User }) {
238
291
  <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
239
292
  <Avatar className="h-8 w-8 rounded-lg">
240
293
  <AvatarImage src={user.avatar} alt={user.name} />
241
- <AvatarFallback className="rounded-lg">
242
- {user.name.slice(0, 2).toUpperCase()}
243
- </AvatarFallback>
294
+ <AvatarFallback className="rounded-lg">CN</AvatarFallback>
244
295
  </Avatar>
245
296
  <div className="grid flex-1 text-left text-sm leading-tight">
246
- <span className="truncate font-semibold">{user.name}</span>
297
+ <span className="truncate font-medium">{user.name}</span>
247
298
  <span className="truncate text-xs">{user.email}</span>
248
299
  </div>
249
300
  </div>
@@ -252,28 +303,28 @@ export function NavUser({ user }: { user: User }) {
252
303
  <DropdownMenuGroup>
253
304
  <DropdownMenuItem>
254
305
  <SparklesIcon />
255
- Upgrade para Pro
306
+ Upgrade to Pro
256
307
  </DropdownMenuItem>
257
308
  </DropdownMenuGroup>
258
309
  <DropdownMenuSeparator />
259
310
  <DropdownMenuGroup>
260
311
  <DropdownMenuItem>
261
312
  <BadgeCheckIcon />
262
- Conta
313
+ Account
263
314
  </DropdownMenuItem>
264
315
  <DropdownMenuItem>
265
316
  <CreditCardIcon />
266
- Faturamento
317
+ Billing
267
318
  </DropdownMenuItem>
268
319
  <DropdownMenuItem>
269
320
  <BellIcon />
270
- Notificações
321
+ Notifications
271
322
  </DropdownMenuItem>
272
323
  </DropdownMenuGroup>
273
324
  <DropdownMenuSeparator />
274
325
  <DropdownMenuItem>
275
326
  <LogOutIcon />
276
- Sair
327
+ Log out
277
328
  </DropdownMenuItem>
278
329
  </DropdownMenuContent>
279
330
  </DropdownMenu>
@@ -286,13 +337,13 @@ export function NavUser({ user }: { user: User }) {
286
337
  "src/components/team-switcher.tsx": `"use client"
287
338
 
288
339
  import * as React from "react"
289
- import { ChevronsUpDownIcon, PlusIcon } from "lucide-react"
290
340
  import {
291
341
  DropdownMenu,
292
342
  DropdownMenuContent,
293
343
  DropdownMenuItem,
294
344
  DropdownMenuLabel,
295
345
  DropdownMenuSeparator,
346
+ DropdownMenuShortcut,
296
347
  DropdownMenuTrigger,
297
348
  } from "@/components/ui/dropdown-menu"
298
349
  import {
@@ -301,12 +352,21 @@ import {
301
352
  SidebarMenuItem,
302
353
  useSidebar,
303
354
  } from "@/components/ui/sidebar"
355
+ import { ChevronsUpDownIcon, PlusIcon } from "lucide-react"
304
356
 
305
- type Team = { name: string; logo: React.ReactNode; plan: string }
306
-
307
- export function TeamSwitcher({ teams }: { teams: Team[] }) {
357
+ export function TeamSwitcher({
358
+ teams,
359
+ }: {
360
+ teams: {
361
+ name: string
362
+ logo: React.ReactNode
363
+ plan: string
364
+ }[]
365
+ }) {
308
366
  const { isMobile } = useSidebar()
309
- const [active, setActive] = React.useState(teams[0])
367
+ const [activeTeam, setActiveTeam] = React.useState(teams[0])
368
+
369
+ if (!activeTeam) return null
310
370
 
311
371
  return (
312
372
  <SidebarMenu>
@@ -317,18 +377,18 @@ export function TeamSwitcher({ teams }: { teams: Team[] }) {
317
377
  size="lg"
318
378
  className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
319
379
  >
320
- <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
321
- {active.logo}
380
+ <div className="flex aspect-square size-8 items-center justify-center rounded-full bg-sidebar-primary text-sidebar-primary-foreground">
381
+ {activeTeam.logo}
322
382
  </div>
323
383
  <div className="grid flex-1 text-left text-sm leading-tight">
324
- <span className="truncate font-semibold">{active.name}</span>
325
- <span className="truncate text-xs">{active.plan}</span>
384
+ <span className="truncate font-medium">{activeTeam.name}</span>
385
+ <span className="truncate text-xs">{activeTeam.plan}</span>
326
386
  </div>
327
387
  <ChevronsUpDownIcon className="ml-auto" />
328
388
  </SidebarMenuButton>
329
389
  </DropdownMenuTrigger>
330
390
  <DropdownMenuContent
331
- className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
391
+ className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
332
392
  align="start"
333
393
  side={isMobile ? "bottom" : "right"}
334
394
  sideOffset={4}
@@ -336,17 +396,22 @@ export function TeamSwitcher({ teams }: { teams: Team[] }) {
336
396
  <DropdownMenuLabel className="text-xs text-muted-foreground">
337
397
  Times
338
398
  </DropdownMenuLabel>
339
- {teams.map((team) => (
340
- <DropdownMenuItem key={team.name} onClick={() => setActive(team)} className="gap-2 p-2">
341
- <div className="flex size-6 items-center justify-center rounded-sm border">
399
+ {teams.map((team, index) => (
400
+ <DropdownMenuItem
401
+ key={team.name}
402
+ onClick={() => setActiveTeam(team)}
403
+ className="gap-2 p-2"
404
+ >
405
+ <div className="flex size-6 items-center justify-center rounded-md border">
342
406
  {team.logo}
343
407
  </div>
344
408
  {team.name}
409
+ <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
345
410
  </DropdownMenuItem>
346
411
  ))}
347
412
  <DropdownMenuSeparator />
348
413
  <DropdownMenuItem className="gap-2 p-2">
349
- <div className="flex size-6 items-center justify-center rounded-md border bg-background">
414
+ <div className="flex size-6 items-center justify-center rounded-md border bg-transparent">
350
415
  <PlusIcon className="size-4" />
351
416
  </div>
352
417
  <div className="font-medium text-muted-foreground">Adicionar time</div>
@@ -374,7 +439,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
374
439
  }
375
440
  `,
376
441
 
377
- "src/app/(app)/page.tsx": `export default function Home() {
442
+ "src/app/(app)/dashboard/page.tsx": `export default function Home() {
378
443
  return (
379
444
  <div className="p-6">
380
445
  <h1 className="text-2xl font-semibold">Dashboard</h1>
@@ -387,7 +452,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
387
452
  "src/app/page.tsx": `import { redirect } from "next/navigation"
388
453
 
389
454
  export default function Root() {
390
- redirect("/")
455
+ redirect("/dashboard")
391
456
  }
392
457
  `,
393
458
  }
@@ -459,13 +524,105 @@ async function main() {
459
524
  console.log("✔ lucide-react encontrado")
460
525
  }
461
526
 
462
- // 5. Escrever arquivos da sidebar
527
+ // 5. Patch no sidebar.tsx: centralizar verticalmente, ajustar altura ao conteúdo e efeito cápsula
528
+ const sidebarUiPath = path.join(cwd, "src/components/ui/sidebar.tsx")
529
+ if (fs.existsSync(sidebarUiPath)) {
530
+ let sidebarUi = fs.readFileSync(sidebarUiPath, "utf8")
531
+ sidebarUi = sidebarUi.replace(
532
+ "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear",
533
+ "fixed top-1/2 -translate-y-1/2 z-10 hidden h-fit w-(--sidebar-width) transition-[left,right,width,height,transform] duration-300 ease-in-out"
534
+ )
535
+ sidebarUi = sidebarUi.replace(
536
+ "group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border",
537
+ "group-data-[variant=floating]:rounded-2xl group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border group-data-[collapsible=icon]:group-data-[variant=floating]:[animation:sidebar-collapse_300ms_ease-in-out_forwards] group-data-[state=expanded]:group-data-[variant=floating]:[animation:sidebar-expand_0ms_forwards]"
538
+ )
539
+ sidebarUi = sidebarUi.replace(
540
+ "transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8",
541
+ "transition-[width,height,padding] duration-200 group-has-data-[sidebar=menu-action]/menu-item:pr-8"
542
+ )
543
+ sidebarUi = sidebarUi.replace(
544
+ "[&>span:last-child]:truncate",
545
+ "[&>span:last-child]:truncate [&>span:last-child]:transition-[opacity] [&>span:last-child]:duration-100 [&>span:last-child]:ease-in group-data-[collapsible=icon]:[&>span:last-child]:opacity-0"
546
+ )
547
+ fs.writeFileSync(sidebarUiPath, sidebarUi, "utf8")
548
+ console.log(" ✔ src/components/ui/sidebar.tsx (centralizada + cápsula)")
549
+ }
550
+
551
+ // 6. Escrever arquivos da sidebar
463
552
  console.log("\n→ Gerando arquivos da sidebar...")
464
553
  for (const [filePath, content] of Object.entries(FILES)) {
465
554
  write(filePath, content)
466
555
  console.log(` ✔ ${filePath}`)
467
556
  }
468
557
 
558
+ // 7. Patch no globals.css: animação dos collapsibles
559
+ const globalsCssPath = path.join(cwd, "src/app/globals.css")
560
+ if (fs.existsSync(globalsCssPath)) {
561
+ let css = fs.readFileSync(globalsCssPath, "utf8")
562
+ const needsCollapsible = !css.includes("collapsible-down")
563
+ const needsCapsule = !css.includes("sidebar-collapse")
564
+ if (needsCollapsible || needsCapsule) {
565
+ const collapsibleCss = needsCollapsible ? `
566
+ @keyframes collapsible-down {
567
+ from { height: 0; opacity: 0; }
568
+ to { height: var(--radix-collapsible-content-height); opacity: 1; }
569
+ }
570
+
571
+ @keyframes collapsible-up {
572
+ from { height: var(--radix-collapsible-content-height); opacity: 1; }
573
+ to { height: 0; opacity: 0; }
574
+ }
575
+
576
+ [data-slot="collapsible-content"] {
577
+ overflow: hidden;
578
+ }
579
+
580
+ [data-slot="collapsible-content"][data-state="open"] {
581
+ animation: collapsible-down 200ms ease-out;
582
+ }
583
+
584
+ [data-slot="collapsible-content"][data-state="closed"] {
585
+ animation: collapsible-up 200ms ease-out;
586
+ }
587
+
588
+ ` : ""
589
+ const capsuleCss = needsCapsule ? `@keyframes sidebar-collapse {
590
+ 0% { border-radius: calc(var(--radius) * 1.8); }
591
+ 79% { border-radius: calc(var(--radius) * 1.8); }
592
+ 100% { border-radius: 9999px; }
593
+ }
594
+
595
+ @keyframes sidebar-expand {
596
+ from { border-radius: calc(var(--radius) * 1.8); }
597
+ to { border-radius: calc(var(--radius) * 1.8); }
598
+ }
599
+
600
+ ` : ""
601
+ css = css.replace(/@layer base \{/, capsuleCss + collapsibleCss + "@layer base {")
602
+ fs.writeFileSync(globalsCssPath, css, "utf8")
603
+ console.log(" ✔ src/app/globals.css (animação collapsible)")
604
+ }
605
+ }
606
+
607
+ // 6. Patch no root layout para adicionar TooltipProvider
608
+ const rootLayoutPath = path.join(cwd, "src/app/layout.tsx")
609
+ if (fs.existsSync(rootLayoutPath)) {
610
+ let rootLayout = fs.readFileSync(rootLayoutPath, "utf8")
611
+ if (!rootLayout.includes("TooltipProvider")) {
612
+ rootLayout = rootLayout.replace(
613
+ /^(import .+from .+;\n)/m,
614
+ `$1import { TooltipProvider } from "@/components/ui/tooltip"\n`
615
+ )
616
+ rootLayout = rootLayout.replace(/<body([^>]*)>(\s*)\{children\}/, `<body$1>$2<TooltipProvider>{children}</TooltipProvider>`)
617
+ rootLayout = rootLayout.replace(/<html([^>]*?)className="([^"]*)"/, (_, attrs, cls) => {
618
+ if (cls.includes("dark")) return `<html${attrs}className="${cls}"`
619
+ return `<html${attrs}className="${cls} dark"`
620
+ })
621
+ fs.writeFileSync(rootLayoutPath, rootLayout, "utf8")
622
+ console.log(" ✔ src/app/layout.tsx (TooltipProvider + dark mode)")
623
+ }
624
+ }
625
+
469
626
  console.log("\n✔ Sidebar instalada com sucesso!")
470
627
  console.log("\nPróximo passo:\n pnpm dev\n")
471
628
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@hznrkv/sidebar",
3
- "version": "1.0.0",
3
+ "version": "1.1.2",
4
4
  "description": "CLI para instalar a sidebar @hznrkv em projetos Next.js + Shadcn",
5
5
  "bin": {
6
- "sidebar": "./index.js"
6
+ "@hznrkv/sidebar": "./index.js"
7
7
  },
8
8
  "type": "module",
9
9
  "dependencies": {
@@ -13,4 +13,4 @@
13
13
  "publishConfig": {
14
14
  "access": "public"
15
15
  }
16
- }
16
+ }