@majapisoftwares/ui 6.0.0 → 6.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 (73) hide show
  1. package/AGENTS.md +27 -27
  2. package/README.md +79 -79
  3. package/THIRD_PARTY_NOTICES.md +39 -39
  4. package/components.json +25 -25
  5. package/docs/MIGRATION-0.2.md +20 -20
  6. package/docs/MIGRATION-6.0.md +33 -33
  7. package/docs/NEXT-STARTER-VALIDATION.md +14 -14
  8. package/docs/STANDARDS.md +41 -41
  9. package/docs/VALIDATION.md +11 -11
  10. package/docs/VERSIONING.md +28 -28
  11. package/docs/validation/preference-titles.md +22 -0
  12. package/package.json +88 -84
  13. package/scripts/check-standards.mjs +41 -41
  14. package/src/components/ui/accordion.tsx +82 -82
  15. package/src/components/ui/alert-dialog.tsx +187 -187
  16. package/src/components/ui/alert.tsx +77 -77
  17. package/src/components/ui/avatar.tsx +108 -108
  18. package/src/components/ui/badge.tsx +55 -55
  19. package/src/components/ui/breadcrumb.tsx +125 -125
  20. package/src/components/ui/button-group.tsx +89 -89
  21. package/src/components/ui/button.tsx +59 -59
  22. package/src/components/ui/card.tsx +101 -101
  23. package/src/components/ui/checkbox.tsx +27 -27
  24. package/src/components/ui/collapsible.tsx +21 -21
  25. package/src/components/ui/context-menu.tsx +271 -271
  26. package/src/components/ui/dialog.tsx +161 -161
  27. package/src/components/ui/dropdown-menu.tsx +271 -271
  28. package/src/components/ui/empty.tsx +105 -105
  29. package/src/components/ui/field.tsx +238 -238
  30. package/src/components/ui/hover-card.tsx +50 -50
  31. package/src/components/ui/input-group.tsx +155 -155
  32. package/src/components/ui/input.tsx +21 -21
  33. package/src/components/ui/kbd.tsx +28 -28
  34. package/src/components/ui/label.tsx +19 -19
  35. package/src/components/ui/orb.css +1002 -1002
  36. package/src/components/ui/orb.tsx +752 -752
  37. package/src/components/ui/popover.tsx +89 -89
  38. package/src/components/ui/progress.tsx +82 -82
  39. package/src/components/ui/radio-group.tsx +37 -37
  40. package/src/components/ui/scroll-area.tsx +54 -54
  41. package/src/components/ui/select.tsx +204 -204
  42. package/src/components/ui/separator.tsx +24 -24
  43. package/src/components/ui/sheet.tsx +139 -139
  44. package/src/components/ui/sidebar.tsx +1039 -1035
  45. package/src/components/ui/skeleton.tsx +15 -15
  46. package/src/components/ui/slider.tsx +53 -53
  47. package/src/components/ui/sonner.tsx +44 -44
  48. package/src/components/ui/spinner.tsx +32 -32
  49. package/src/components/ui/switch.tsx +31 -31
  50. package/src/components/ui/table.tsx +121 -121
  51. package/src/components/ui/tabs.tsx +81 -81
  52. package/src/components/ui/textarea.tsx +19 -19
  53. package/src/components/ui/toggle-group.tsx +89 -89
  54. package/src/components/ui/toggle.tsx +44 -44
  55. package/src/components/ui/tooltip.tsx +65 -65
  56. package/src/hooks/use-mobile.ts +20 -20
  57. package/src/lib/i18n.tsx +109 -109
  58. package/src/lib/utils.ts +1 -1
  59. package/src/patterns/app-shell.tsx +177 -175
  60. package/src/patterns/data-table.tsx +2486 -2486
  61. package/src/patterns/details-sheet.tsx +54 -53
  62. package/src/patterns/filter-tabs.tsx +53 -53
  63. package/src/patterns/majapi-logo.tsx +32 -32
  64. package/src/patterns/page-header.tsx +62 -62
  65. package/src/patterns/preferences.tsx +15 -7
  66. package/src/patterns/status-badge.tsx +24 -24
  67. package/src/patterns/user-menu.tsx +218 -0
  68. package/src/patterns/workspace-switcher.tsx +176 -0
  69. package/src/styles.css +165 -165
  70. package/src/templates/admin-dashboard.tsx +44 -44
  71. package/src/templates/auth-layout.tsx +30 -30
  72. package/src/templates/login-form.tsx +129 -129
  73. package/src/templates/signup-form.tsx +177 -177
package/src/lib/i18n.tsx CHANGED
@@ -1,109 +1,109 @@
1
- "use client";
2
-
3
- import { createContext, useContext, type ReactNode } from "react";
4
-
5
- export const locales = ["en", "pt-BR"] as const;
6
- export type Locale = (typeof locales)[number];
7
- export type Messages<T> = Record<Locale, T>;
8
-
9
- /** Declares a message catalog; every locale must provide every key. */
10
- export function defineMessages<T>(messages: Messages<T>) {
11
- return messages;
12
- }
13
-
14
- const LocaleContext = createContext<Locale>("en");
15
-
16
- export function LocaleProvider({
17
- locale,
18
- children,
19
- }: {
20
- locale: Locale;
21
- children: ReactNode;
22
- }) {
23
- return (
24
- <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
25
- );
26
- }
27
-
28
- export function useLocale() {
29
- return useContext(LocaleContext);
30
- }
31
-
32
- export function useMessages<T>(messages: Messages<T>): T {
33
- return messages[useLocale()];
34
- }
35
-
36
- /** Picks the supported locale that best matches a list such as navigator.languages. */
37
- export function matchLocale(preferred: readonly string[]): Locale {
38
- for (const tag of preferred) {
39
- const lower = tag.toLowerCase();
40
- if (lower.startsWith("pt")) return "pt-BR";
41
- if (lower.startsWith("en")) return "en";
42
- }
43
- return "en";
44
- }
45
-
46
- export const uiMessages = defineMessages({
47
- en: {
48
- sidebar: "Sidebar",
49
- toggleSidebar: "Toggle sidebar",
50
- collapseSidebar: "Collapse sidebar",
51
- expandSidebar: "Expand sidebar",
52
- resizeSidebar: "Resize sidebar",
53
- modeOpen: "Expanded",
54
- modeCompact: "Compact",
55
- modeClosed: "Hidden",
56
- sidebarHint: (shortcut: string) =>
57
- `Right-click for options · ${shortcut} hides`,
58
- search: "Search",
59
- noResults: "No results",
60
- noData: "Nothing here yet",
61
- clearSearch: "Clear search",
62
- retry: "Retry",
63
- loadFailed: "Couldn't load data",
64
- staleData: "Showing data from the last successful load",
65
- previous: "Previous",
66
- next: "Next",
67
- open: "Open",
68
- page: (page: number, total: number) => `Page ${page} of ${total}`,
69
- rows: (count: number) => `${count} ${count === 1 ? "row" : "rows"}`,
70
- close: "Close",
71
- language: "Language",
72
- toggleTheme: "Toggle theme",
73
- more: "More",
74
- loading: "Loading",
75
- },
76
- "pt-BR": {
77
- sidebar: "Barra lateral",
78
- toggleSidebar: "Alternar barra lateral",
79
- collapseSidebar: "Recolher barra lateral",
80
- expandSidebar: "Expandir barra lateral",
81
- resizeSidebar: "Redimensionar barra lateral",
82
- modeOpen: "Expandida",
83
- modeCompact: "Compacta",
84
- modeClosed: "Escondida",
85
- sidebarHint: (shortcut: string) =>
86
- `Clique direito para opções · ${shortcut} esconde`,
87
- search: "Buscar",
88
- noResults: "Nenhum resultado",
89
- noData: "Nada por aqui ainda",
90
- clearSearch: "Limpar busca",
91
- retry: "Tentar novamente",
92
- loadFailed: "Não foi possível carregar os dados",
93
- staleData: "Exibindo dados do último carregamento",
94
- previous: "Anterior",
95
- next: "Próxima",
96
- open: "Abrir",
97
- page: (page: number, total: number) => `Página ${page} de ${total}`,
98
- rows: (count: number) => `${count} ${count === 1 ? "linha" : "linhas"}`,
99
- close: "Fechar",
100
- language: "Idioma",
101
- toggleTheme: "Alternar tema",
102
- more: "Mais",
103
- loading: "Carregando",
104
- },
105
- });
106
-
107
- export function useUiMessages() {
108
- return useMessages(uiMessages);
109
- }
1
+ "use client";
2
+
3
+ import { createContext, useContext, type ReactNode } from "react";
4
+
5
+ export const locales = ["en", "pt-BR"] as const;
6
+ export type Locale = (typeof locales)[number];
7
+ export type Messages<T> = Record<Locale, T>;
8
+
9
+ /** Declares a message catalog; every locale must provide every key. */
10
+ export function defineMessages<T>(messages: Messages<T>) {
11
+ return messages;
12
+ }
13
+
14
+ const LocaleContext = createContext<Locale>("en");
15
+
16
+ export function LocaleProvider({
17
+ locale,
18
+ children,
19
+ }: {
20
+ locale: Locale;
21
+ children: ReactNode;
22
+ }) {
23
+ return (
24
+ <LocaleContext.Provider value={locale}>{children}</LocaleContext.Provider>
25
+ );
26
+ }
27
+
28
+ export function useLocale() {
29
+ return useContext(LocaleContext);
30
+ }
31
+
32
+ export function useMessages<T>(messages: Messages<T>): T {
33
+ return messages[useLocale()];
34
+ }
35
+
36
+ /** Picks the supported locale that best matches a list such as navigator.languages. */
37
+ export function matchLocale(preferred: readonly string[]): Locale {
38
+ for (const tag of preferred) {
39
+ const lower = tag.toLowerCase();
40
+ if (lower.startsWith("pt")) return "pt-BR";
41
+ if (lower.startsWith("en")) return "en";
42
+ }
43
+ return "en";
44
+ }
45
+
46
+ export const uiMessages = defineMessages({
47
+ en: {
48
+ sidebar: "Sidebar",
49
+ toggleSidebar: "Toggle sidebar",
50
+ collapseSidebar: "Collapse sidebar",
51
+ expandSidebar: "Expand sidebar",
52
+ resizeSidebar: "Resize sidebar",
53
+ modeOpen: "Expanded",
54
+ modeCompact: "Compact",
55
+ modeClosed: "Hidden",
56
+ sidebarHint: (shortcut: string) =>
57
+ `Right-click for options · ${shortcut} hides`,
58
+ search: "Search",
59
+ noResults: "No results",
60
+ noData: "Nothing here yet",
61
+ clearSearch: "Clear search",
62
+ retry: "Retry",
63
+ loadFailed: "Couldn't load data",
64
+ staleData: "Showing data from the last successful load",
65
+ previous: "Previous",
66
+ next: "Next",
67
+ open: "Open",
68
+ page: (page: number, total: number) => `Page ${page} of ${total}`,
69
+ rows: (count: number) => `${count} ${count === 1 ? "row" : "rows"}`,
70
+ close: "Close",
71
+ language: "Language",
72
+ toggleTheme: "Toggle theme",
73
+ more: "More",
74
+ loading: "Loading",
75
+ },
76
+ "pt-BR": {
77
+ sidebar: "Barra lateral",
78
+ toggleSidebar: "Alternar barra lateral",
79
+ collapseSidebar: "Recolher barra lateral",
80
+ expandSidebar: "Expandir barra lateral",
81
+ resizeSidebar: "Redimensionar barra lateral",
82
+ modeOpen: "Expandida",
83
+ modeCompact: "Compacta",
84
+ modeClosed: "Escondida",
85
+ sidebarHint: (shortcut: string) =>
86
+ `Clique direito para opções · ${shortcut} esconde`,
87
+ search: "Buscar",
88
+ noResults: "Nenhum resultado",
89
+ noData: "Nada por aqui ainda",
90
+ clearSearch: "Limpar busca",
91
+ retry: "Tentar novamente",
92
+ loadFailed: "Não foi possível carregar os dados",
93
+ staleData: "Exibindo dados do último carregamento",
94
+ previous: "Anterior",
95
+ next: "Próxima",
96
+ open: "Abrir",
97
+ page: (page: number, total: number) => `Página ${page} de ${total}`,
98
+ rows: (count: number) => `${count} ${count === 1 ? "linha" : "linhas"}`,
99
+ close: "Fechar",
100
+ language: "Idioma",
101
+ toggleTheme: "Alternar tema",
102
+ more: "Mais",
103
+ loading: "Carregando",
104
+ },
105
+ });
106
+
107
+ export function useUiMessages() {
108
+ return useMessages(uiMessages);
109
+ }
package/src/lib/utils.ts CHANGED
@@ -1 +1 @@
1
- export { cn } from "cn";
1
+ export { cn } from "cn";
@@ -1,175 +1,177 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
- import { cn } from "cn";
5
- import {
6
- Sidebar,
7
- SidebarCollapse,
8
- SidebarContent,
9
- SidebarFooter,
10
- SidebarGroup,
11
- SidebarGroupLabel,
12
- SidebarHeader,
13
- SidebarInset,
14
- SidebarMenu,
15
- SidebarMenuBadge,
16
- SidebarMenuButton,
17
- SidebarMenuItem,
18
- SidebarProvider,
19
- SidebarRail,
20
- SidebarTrigger,
21
- type SidebarMode,
22
- } from "../components/ui/sidebar";
23
- import { Separator } from "../components/ui/separator";
24
- import { TooltipProvider } from "../components/ui/tooltip";
25
-
26
- export interface NavigationItem {
27
- id: string;
28
- label: string;
29
- icon?: ReactNode;
30
- count?: number;
31
- }
32
- export interface NavigationGroup {
33
- label?: string;
34
- items: NavigationItem[];
35
- }
36
-
37
- export interface AppShellProps {
38
- /** Square logo; stays in place in every sidebar mode. */
39
- logo: ReactNode;
40
- /** App name next to the logo; fades out when the sidebar is compact. */
41
- name: string;
42
- navigation: NavigationGroup[];
43
- activeId: string;
44
- onNavigate: (id: string) => void;
45
- /** Rendered in the header next to the sidebar trigger, e.g. breadcrumbs. */
46
- header?: ReactNode;
47
- /** Rendered at the end of the header, e.g. locale or theme switchers. */
48
- actions?: ReactNode;
49
- /** Sidebar footer, e.g. a user menu. */
50
- footer?: ReactNode;
51
- defaultMode?: SidebarMode;
52
- defaultWidth?: number;
53
- /**
54
- * Locks the shell to the viewport height so content (e.g. a `DataTable
55
- * fillHeight`) fills the remaining space and scrolls internally.
56
- */
57
- fillHeight?: boolean;
58
- children: ReactNode;
59
- }
60
-
61
- // Items without an icon still need something to show in compact mode.
62
- function ItemIcon({ item }: { item: NavigationItem }) {
63
- return (
64
- item.icon ?? (
65
- <span
66
- aria-hidden="true"
67
- className="flex size-4 items-center justify-center text-xs font-semibold"
68
- >
69
- {item.label.charAt(0).toUpperCase()}
70
- </span>
71
- )
72
- );
73
- }
74
-
75
- // The collapse control lives inside the sidebar; reopening needs one outside.
76
- // Shown on mobile and, on desktop, only while the sidebar is hidden (CSS, so
77
- // it is right from the first paint).
78
- function HeaderTrigger() {
79
- return (
80
- <div className="flex items-center md:hidden md:group-has-data-[collapsible=offcanvas]/sidebar-wrapper:flex">
81
- <SidebarTrigger className="-ml-1" />
82
- <Separator
83
- orientation="vertical"
84
- className="mr-2 ml-2 h-4 self-center!"
85
- />
86
- </div>
87
- );
88
- }
89
-
90
- export function AppShell({
91
- logo,
92
- name,
93
- navigation,
94
- activeId,
95
- onNavigate,
96
- header,
97
- actions,
98
- footer,
99
- defaultMode,
100
- defaultWidth,
101
- fillHeight = false,
102
- children,
103
- }: AppShellProps) {
104
- return (
105
- <TooltipProvider delay={600}>
106
- <SidebarProvider defaultMode={defaultMode} defaultWidth={defaultWidth}>
107
- <Sidebar>
108
- {/* Same height as the page header so their controls line up. */}
109
- <SidebarHeader className="min-h-14 justify-center">
110
- {/* The logo keeps its position across modes; only the name fades,
111
- and it is clipped (never squeezed) by the sidebar width. */}
112
- <div className="flex h-8 items-center gap-2 overflow-hidden">
113
- <div className="flex size-8 shrink-0 items-center justify-center [&>*]:shrink-0">
114
- {logo}
115
- </div>
116
- <span className="flex-1 truncate font-semibold whitespace-nowrap transition-opacity duration-200 ease-linear group-data-[collapsible=icon]:opacity-0">
117
- {name}
118
- </span>
119
- <SidebarCollapse className="group-data-[collapsible=icon]:hidden" />
120
- </div>
121
- <div className="hidden group-data-[collapsible=icon]:block">
122
- <SidebarCollapse />
123
- </div>
124
- </SidebarHeader>
125
- <SidebarContent>
126
- {navigation.map((group, index) => (
127
- <SidebarGroup key={group.label ?? index}>
128
- {group.label ? (
129
- <SidebarGroupLabel>{group.label}</SidebarGroupLabel>
130
- ) : null}
131
- <SidebarMenu>
132
- {group.items.map((item) => (
133
- <SidebarMenuItem key={item.id}>
134
- <SidebarMenuButton
135
- isActive={item.id === activeId}
136
- tooltip={item.label}
137
- aria-current={item.id === activeId ? "page" : undefined}
138
- onClick={() => onNavigate(item.id)}
139
- >
140
- <ItemIcon item={item} />
141
- <span>{item.label}</span>
142
- </SidebarMenuButton>
143
- {item.count !== undefined ? (
144
- <SidebarMenuBadge>{item.count}</SidebarMenuBadge>
145
- ) : null}
146
- </SidebarMenuItem>
147
- ))}
148
- </SidebarMenu>
149
- </SidebarGroup>
150
- ))}
151
- </SidebarContent>
152
- {footer ? <SidebarFooter>{footer}</SidebarFooter> : null}
153
- <SidebarRail />
154
- </Sidebar>
155
- <SidebarInset className={fillHeight ? "h-svh min-h-0" : undefined}>
156
- <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4 md:px-6">
157
- <HeaderTrigger />
158
- <div className="min-w-0 flex-1">{header}</div>
159
- {actions ? (
160
- <div className="flex items-center gap-2">{actions}</div>
161
- ) : null}
162
- </header>
163
- <div
164
- className={cn(
165
- "flex flex-1 flex-col gap-4 p-4 md:gap-6 md:p-6",
166
- fillHeight && "min-h-0 overflow-hidden",
167
- )}
168
- >
169
- {children}
170
- </div>
171
- </SidebarInset>
172
- </SidebarProvider>
173
- </TooltipProvider>
174
- );
175
- }
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import { cn } from "cn";
5
+ import {
6
+ Sidebar,
7
+ SidebarCollapse,
8
+ SidebarContent,
9
+ SidebarFooter,
10
+ SidebarGroup,
11
+ SidebarGroupLabel,
12
+ SidebarInset,
13
+ SidebarMenu,
14
+ SidebarMenuBadge,
15
+ SidebarMenuButton,
16
+ SidebarMenuItem,
17
+ SidebarProvider,
18
+ SidebarRail,
19
+ SidebarTrigger,
20
+ type SidebarMode,
21
+ } from "../components/ui/sidebar";
22
+ import { Separator } from "../components/ui/separator";
23
+ import { TooltipProvider } from "../components/ui/tooltip";
24
+
25
+ export interface NavigationItem {
26
+ id: string;
27
+ label: string;
28
+ icon?: ReactNode;
29
+ count?: number;
30
+ }
31
+ export interface NavigationGroup {
32
+ label?: string;
33
+ items: NavigationItem[];
34
+ }
35
+
36
+ export interface AppShellProps {
37
+ /** Square logo; stays in place in every sidebar mode. */
38
+ logo: ReactNode;
39
+ /** App name next to the logo; fades out when the sidebar is compact. */
40
+ name: string;
41
+ navigation: NavigationGroup[];
42
+ activeId: string;
43
+ onNavigate: (id: string) => void;
44
+ /** Rendered in the header next to the sidebar trigger, e.g. breadcrumbs. */
45
+ header?: ReactNode;
46
+ /** Rendered at the end of the header, e.g. locale or theme switchers. */
47
+ actions?: ReactNode;
48
+ /** Below the logo in the sidebar header, e.g. `WorkspaceSwitcher`. */
49
+ sidebarHeader?: ReactNode;
50
+ /** Sidebar footer, e.g. `UserMenu`. */
51
+ footer?: ReactNode;
52
+ defaultMode?: SidebarMode;
53
+ defaultWidth?: number;
54
+ /**
55
+ * Locks the shell to the viewport height so content (e.g. a `DataTable
56
+ * fillHeight`) fills the remaining space and scrolls internally.
57
+ */
58
+ fillHeight?: boolean;
59
+ children: ReactNode;
60
+ }
61
+
62
+ // Items without an icon still need something to show in compact mode.
63
+ function ItemIcon({ item }: { item: NavigationItem }) {
64
+ return (
65
+ item.icon ?? (
66
+ <span
67
+ aria-hidden="true"
68
+ className="flex size-4 items-center justify-center text-xs font-semibold"
69
+ >
70
+ {item.label.charAt(0).toUpperCase()}
71
+ </span>
72
+ )
73
+ );
74
+ }
75
+
76
+ // The collapse control lives inside the sidebar; reopening needs one outside.
77
+ // Shown on mobile and, on desktop, only while the sidebar is hidden (CSS, so
78
+ // it is right from the first paint).
79
+ function HeaderTrigger() {
80
+ return (
81
+ <div className="flex items-center md:hidden md:group-has-data-[collapsible=offcanvas]/sidebar-wrapper:flex">
82
+ <SidebarTrigger className="-ml-1" />
83
+ <Separator
84
+ orientation="vertical"
85
+ className="mr-2 ml-2 h-4 self-center!"
86
+ />
87
+ </div>
88
+ );
89
+ }
90
+
91
+ export function AppShell({
92
+ logo,
93
+ name,
94
+ navigation,
95
+ activeId,
96
+ onNavigate,
97
+ header,
98
+ actions,
99
+ footer,
100
+ sidebarHeader,
101
+ defaultMode,
102
+ defaultWidth,
103
+ fillHeight = false,
104
+ children,
105
+ }: AppShellProps) {
106
+ return (
107
+ <TooltipProvider delay={600}>
108
+ <SidebarProvider defaultMode={defaultMode} defaultWidth={defaultWidth}>
109
+ <Sidebar>
110
+ {/* Top row: fixed h-14 to match the page header with identical vertical alignment in all apps */}
111
+ {/* Keep the mark anchored to the left while the sidebar width animates. */}
112
+ <div className="flex h-14 shrink-0 items-center gap-2 px-2 overflow-hidden">
113
+ <div className="flex size-8 shrink-0 items-center justify-center [&>*]:shrink-0">
114
+ {logo}
115
+ </div>
116
+ <span className="flex-1 truncate font-semibold whitespace-nowrap transition-opacity duration-200 ease-linear group-data-[collapsible=icon]:hidden">
117
+ {name}
118
+ </span>
119
+ <SidebarCollapse className="group-data-[collapsible=icon]:hidden" />
120
+ </div>
121
+ <div className="hidden group-data-[collapsible=icon]:flex justify-center pb-2">
122
+ <SidebarCollapse />
123
+ </div>
124
+ {sidebarHeader ? (
125
+ <div className="p-2 pt-0">{sidebarHeader}</div>
126
+ ) : null}
127
+ <SidebarContent>
128
+ {navigation.map((group, index) => (
129
+ <SidebarGroup key={group.label ?? index}>
130
+ {group.label ? (
131
+ <SidebarGroupLabel>{group.label}</SidebarGroupLabel>
132
+ ) : null}
133
+ <SidebarMenu>
134
+ {group.items.map((item) => (
135
+ <SidebarMenuItem key={item.id}>
136
+ <SidebarMenuButton
137
+ isActive={item.id === activeId}
138
+ tooltip={item.label}
139
+ aria-current={item.id === activeId ? "page" : undefined}
140
+ onClick={() => onNavigate(item.id)}
141
+ >
142
+ <ItemIcon item={item} />
143
+ <span>{item.label}</span>
144
+ </SidebarMenuButton>
145
+ {item.count !== undefined ? (
146
+ <SidebarMenuBadge>{item.count}</SidebarMenuBadge>
147
+ ) : null}
148
+ </SidebarMenuItem>
149
+ ))}
150
+ </SidebarMenu>
151
+ </SidebarGroup>
152
+ ))}
153
+ </SidebarContent>
154
+ {footer ? <SidebarFooter>{footer}</SidebarFooter> : null}
155
+ <SidebarRail />
156
+ </Sidebar>
157
+ <SidebarInset className={fillHeight ? "h-svh min-h-0" : undefined}>
158
+ <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4 md:px-6">
159
+ <HeaderTrigger />
160
+ <div className="min-w-0 flex-1">{header}</div>
161
+ {actions ? (
162
+ <div className="flex items-center gap-2">{actions}</div>
163
+ ) : null}
164
+ </header>
165
+ <div
166
+ className={cn(
167
+ "flex flex-1 flex-col gap-4 p-4 md:gap-6 md:p-6",
168
+ fillHeight && "min-h-0 overflow-hidden",
169
+ )}
170
+ >
171
+ {children}
172
+ </div>
173
+ </SidebarInset>
174
+ </SidebarProvider>
175
+ </TooltipProvider>
176
+ );
177
+ }