@majapisoftwares/ui 6.1.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 -179
  60. package/src/patterns/data-table.tsx +2486 -2486
  61. package/src/patterns/details-sheet.tsx +54 -54
  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 -218
  68. package/src/patterns/workspace-switcher.tsx +176 -173
  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/styles.css CHANGED
@@ -1,165 +1,165 @@
1
- @import "tailwindcss";
2
- @import "tw-animate-css";
3
- @import "shadcn/tailwind.css";
4
- @import "@fontsource-variable/geist";
5
- @import "./components/ui/orb.css";
6
- @source "./";
7
-
8
- @custom-variant dark (&:is(.dark *));
9
-
10
- @theme inline {
11
- --font-heading: var(--font-sans);
12
- --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
13
- --color-sidebar-ring: var(--sidebar-ring);
14
- --color-sidebar-border: var(--sidebar-border);
15
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
16
- --color-sidebar-accent: var(--sidebar-accent);
17
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
18
- --color-sidebar-primary: var(--sidebar-primary);
19
- --color-sidebar-foreground: var(--sidebar-foreground);
20
- --color-sidebar: var(--sidebar);
21
- --color-chart-5: var(--chart-5);
22
- --color-chart-4: var(--chart-4);
23
- --color-chart-3: var(--chart-3);
24
- --color-chart-2: var(--chart-2);
25
- --color-chart-1: var(--chart-1);
26
- --color-ring: var(--ring);
27
- --color-input: var(--input);
28
- --color-border: var(--border);
29
- --color-destructive: var(--destructive);
30
- --color-accent-foreground: var(--accent-foreground);
31
- --color-accent: var(--accent);
32
- --color-muted-foreground: var(--muted-foreground);
33
- --color-muted: var(--muted);
34
- --color-secondary-foreground: var(--secondary-foreground);
35
- --color-secondary: var(--secondary);
36
- --color-primary-foreground: var(--primary-foreground);
37
- --color-primary: var(--primary);
38
- --color-popover-foreground: var(--popover-foreground);
39
- --color-popover: var(--popover);
40
- --color-card-foreground: var(--card-foreground);
41
- --color-card: var(--card);
42
- --color-foreground: var(--foreground);
43
- --color-background: var(--background);
44
- --color-success: var(--success);
45
- --color-success-foreground: var(--success-foreground);
46
- --color-warning: var(--warning);
47
- --color-warning-foreground: var(--warning-foreground);
48
- --radius-sm: calc(var(--radius) * 0.6);
49
- --radius-md: calc(var(--radius) * 0.8);
50
- --radius-lg: var(--radius);
51
- --radius-xl: calc(var(--radius) * 1.4);
52
- --radius-2xl: calc(var(--radius) * 1.8);
53
- --radius-3xl: calc(var(--radius) * 2.2);
54
- --radius-4xl: calc(var(--radius) * 2.6);
55
- }
56
-
57
- :root {
58
- --radius: 0.625rem;
59
- --background: oklch(1 0 0);
60
- --foreground: oklch(0.145 0 0);
61
- --card: oklch(1 0 0);
62
- --card-foreground: oklch(0.145 0 0);
63
- --popover: oklch(1 0 0);
64
- --popover-foreground: oklch(0.145 0 0);
65
- --primary: oklch(0.205 0 0);
66
- --primary-foreground: oklch(0.985 0 0);
67
- --secondary: oklch(0.97 0 0);
68
- --secondary-foreground: oklch(0.205 0 0);
69
- --muted: oklch(0.97 0 0);
70
- --muted-foreground: oklch(0.556 0 0);
71
- --accent: oklch(0.97 0 0);
72
- --accent-foreground: oklch(0.205 0 0);
73
- --destructive: oklch(0.577 0.245 27.325);
74
- --border: oklch(0.922 0 0);
75
- --input: oklch(0.922 0 0);
76
- --ring: oklch(0.708 0 0);
77
- --chart-1: oklch(0.646 0.222 41.116);
78
- --chart-2: oklch(0.6 0.118 184.704);
79
- --chart-3: oklch(0.398 0.07 227.392);
80
- --chart-4: oklch(0.828 0.189 84.429);
81
- --chart-5: oklch(0.769 0.188 70.08);
82
- --success: oklch(0.95 0.05 155);
83
- --success-foreground: oklch(0.42 0.1 155);
84
- --warning: oklch(0.96 0.06 85);
85
- --warning-foreground: oklch(0.45 0.1 65);
86
- --sidebar: oklch(0.985 0 0);
87
- --sidebar-foreground: oklch(0.145 0 0);
88
- --sidebar-primary: oklch(0.205 0 0);
89
- --sidebar-primary-foreground: oklch(0.985 0 0);
90
- --sidebar-accent: oklch(0.97 0 0);
91
- --sidebar-accent-foreground: oklch(0.205 0 0);
92
- --sidebar-border: oklch(0.922 0 0);
93
- --sidebar-ring: oklch(0.708 0 0);
94
- }
95
-
96
- .dark {
97
- --background: oklch(0.145 0 0);
98
- --foreground: oklch(0.985 0 0);
99
- --card: oklch(0.205 0 0);
100
- --card-foreground: oklch(0.985 0 0);
101
- --popover: oklch(0.205 0 0);
102
- --popover-foreground: oklch(0.985 0 0);
103
- --primary: oklch(0.922 0 0);
104
- --primary-foreground: oklch(0.205 0 0);
105
- --secondary: oklch(0.269 0 0);
106
- --secondary-foreground: oklch(0.985 0 0);
107
- --muted: oklch(0.269 0 0);
108
- --muted-foreground: oklch(0.708 0 0);
109
- --accent: oklch(0.269 0 0);
110
- --accent-foreground: oklch(0.985 0 0);
111
- --destructive: oklch(0.704 0.191 22.216);
112
- --border: oklch(1 0 0 / 10%);
113
- --input: oklch(1 0 0 / 15%);
114
- --ring: oklch(0.556 0 0);
115
- --chart-1: oklch(0.488 0.243 264.376);
116
- --chart-2: oklch(0.696 0.17 162.48);
117
- --chart-3: oklch(0.769 0.188 70.08);
118
- --chart-4: oklch(0.627 0.265 303.9);
119
- --chart-5: oklch(0.645 0.246 16.439);
120
- --success: oklch(0.3 0.06 155);
121
- --success-foreground: oklch(0.85 0.12 155);
122
- --warning: oklch(0.32 0.06 85);
123
- --warning-foreground: oklch(0.88 0.12 85);
124
- --sidebar: oklch(0.205 0 0);
125
- --sidebar-foreground: oklch(0.985 0 0);
126
- --sidebar-primary: oklch(0.488 0.243 264.376);
127
- --sidebar-primary-foreground: oklch(0.985 0 0);
128
- --sidebar-accent: oklch(0.269 0 0);
129
- --sidebar-accent-foreground: oklch(0.985 0 0);
130
- --sidebar-border: oklch(1 0 0 / 10%);
131
- --sidebar-ring: oklch(0.556 0 0);
132
- }
133
-
134
- @layer base {
135
- /* Native controls (scrollbars, date pickers) follow the active theme. */
136
- :root {
137
- color-scheme: light;
138
- }
139
- .dark {
140
- color-scheme: dark;
141
- }
142
- /* Thin scrollbars that stay hidden until the pointer is over the scrolling
143
- area, tinted with the theme foreground. */
144
- * {
145
- scrollbar-width: thin;
146
- scrollbar-color: transparent transparent;
147
- }
148
- *:hover {
149
- scrollbar-color: color-mix(in oklch, var(--foreground) 25%, transparent)
150
- transparent;
151
- }
152
- /* Set by preferencesScript until the saved language has rendered. */
153
- html[data-locale-pending] body {
154
- visibility: hidden;
155
- }
156
- * {
157
- @apply border-border outline-ring/50;
158
- }
159
- body {
160
- @apply bg-background text-foreground;
161
- }
162
- html {
163
- @apply font-sans;
164
- }
165
- }
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+ @import "@fontsource-variable/geist";
5
+ @import "./components/ui/orb.css";
6
+ @source "./";
7
+
8
+ @custom-variant dark (&:is(.dark *));
9
+
10
+ @theme inline {
11
+ --font-heading: var(--font-sans);
12
+ --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
13
+ --color-sidebar-ring: var(--sidebar-ring);
14
+ --color-sidebar-border: var(--sidebar-border);
15
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
16
+ --color-sidebar-accent: var(--sidebar-accent);
17
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
18
+ --color-sidebar-primary: var(--sidebar-primary);
19
+ --color-sidebar-foreground: var(--sidebar-foreground);
20
+ --color-sidebar: var(--sidebar);
21
+ --color-chart-5: var(--chart-5);
22
+ --color-chart-4: var(--chart-4);
23
+ --color-chart-3: var(--chart-3);
24
+ --color-chart-2: var(--chart-2);
25
+ --color-chart-1: var(--chart-1);
26
+ --color-ring: var(--ring);
27
+ --color-input: var(--input);
28
+ --color-border: var(--border);
29
+ --color-destructive: var(--destructive);
30
+ --color-accent-foreground: var(--accent-foreground);
31
+ --color-accent: var(--accent);
32
+ --color-muted-foreground: var(--muted-foreground);
33
+ --color-muted: var(--muted);
34
+ --color-secondary-foreground: var(--secondary-foreground);
35
+ --color-secondary: var(--secondary);
36
+ --color-primary-foreground: var(--primary-foreground);
37
+ --color-primary: var(--primary);
38
+ --color-popover-foreground: var(--popover-foreground);
39
+ --color-popover: var(--popover);
40
+ --color-card-foreground: var(--card-foreground);
41
+ --color-card: var(--card);
42
+ --color-foreground: var(--foreground);
43
+ --color-background: var(--background);
44
+ --color-success: var(--success);
45
+ --color-success-foreground: var(--success-foreground);
46
+ --color-warning: var(--warning);
47
+ --color-warning-foreground: var(--warning-foreground);
48
+ --radius-sm: calc(var(--radius) * 0.6);
49
+ --radius-md: calc(var(--radius) * 0.8);
50
+ --radius-lg: var(--radius);
51
+ --radius-xl: calc(var(--radius) * 1.4);
52
+ --radius-2xl: calc(var(--radius) * 1.8);
53
+ --radius-3xl: calc(var(--radius) * 2.2);
54
+ --radius-4xl: calc(var(--radius) * 2.6);
55
+ }
56
+
57
+ :root {
58
+ --radius: 0.625rem;
59
+ --background: oklch(1 0 0);
60
+ --foreground: oklch(0.145 0 0);
61
+ --card: oklch(1 0 0);
62
+ --card-foreground: oklch(0.145 0 0);
63
+ --popover: oklch(1 0 0);
64
+ --popover-foreground: oklch(0.145 0 0);
65
+ --primary: oklch(0.205 0 0);
66
+ --primary-foreground: oklch(0.985 0 0);
67
+ --secondary: oklch(0.97 0 0);
68
+ --secondary-foreground: oklch(0.205 0 0);
69
+ --muted: oklch(0.97 0 0);
70
+ --muted-foreground: oklch(0.556 0 0);
71
+ --accent: oklch(0.97 0 0);
72
+ --accent-foreground: oklch(0.205 0 0);
73
+ --destructive: oklch(0.577 0.245 27.325);
74
+ --border: oklch(0.922 0 0);
75
+ --input: oklch(0.922 0 0);
76
+ --ring: oklch(0.708 0 0);
77
+ --chart-1: oklch(0.646 0.222 41.116);
78
+ --chart-2: oklch(0.6 0.118 184.704);
79
+ --chart-3: oklch(0.398 0.07 227.392);
80
+ --chart-4: oklch(0.828 0.189 84.429);
81
+ --chart-5: oklch(0.769 0.188 70.08);
82
+ --success: oklch(0.95 0.05 155);
83
+ --success-foreground: oklch(0.42 0.1 155);
84
+ --warning: oklch(0.96 0.06 85);
85
+ --warning-foreground: oklch(0.45 0.1 65);
86
+ --sidebar: oklch(0.985 0 0);
87
+ --sidebar-foreground: oklch(0.145 0 0);
88
+ --sidebar-primary: oklch(0.205 0 0);
89
+ --sidebar-primary-foreground: oklch(0.985 0 0);
90
+ --sidebar-accent: oklch(0.97 0 0);
91
+ --sidebar-accent-foreground: oklch(0.205 0 0);
92
+ --sidebar-border: oklch(0.922 0 0);
93
+ --sidebar-ring: oklch(0.708 0 0);
94
+ }
95
+
96
+ .dark {
97
+ --background: oklch(0.145 0 0);
98
+ --foreground: oklch(0.985 0 0);
99
+ --card: oklch(0.205 0 0);
100
+ --card-foreground: oklch(0.985 0 0);
101
+ --popover: oklch(0.205 0 0);
102
+ --popover-foreground: oklch(0.985 0 0);
103
+ --primary: oklch(0.922 0 0);
104
+ --primary-foreground: oklch(0.205 0 0);
105
+ --secondary: oklch(0.269 0 0);
106
+ --secondary-foreground: oklch(0.985 0 0);
107
+ --muted: oklch(0.269 0 0);
108
+ --muted-foreground: oklch(0.708 0 0);
109
+ --accent: oklch(0.269 0 0);
110
+ --accent-foreground: oklch(0.985 0 0);
111
+ --destructive: oklch(0.704 0.191 22.216);
112
+ --border: oklch(1 0 0 / 10%);
113
+ --input: oklch(1 0 0 / 15%);
114
+ --ring: oklch(0.556 0 0);
115
+ --chart-1: oklch(0.488 0.243 264.376);
116
+ --chart-2: oklch(0.696 0.17 162.48);
117
+ --chart-3: oklch(0.769 0.188 70.08);
118
+ --chart-4: oklch(0.627 0.265 303.9);
119
+ --chart-5: oklch(0.645 0.246 16.439);
120
+ --success: oklch(0.3 0.06 155);
121
+ --success-foreground: oklch(0.85 0.12 155);
122
+ --warning: oklch(0.32 0.06 85);
123
+ --warning-foreground: oklch(0.88 0.12 85);
124
+ --sidebar: oklch(0.205 0 0);
125
+ --sidebar-foreground: oklch(0.985 0 0);
126
+ --sidebar-primary: oklch(0.488 0.243 264.376);
127
+ --sidebar-primary-foreground: oklch(0.985 0 0);
128
+ --sidebar-accent: oklch(0.269 0 0);
129
+ --sidebar-accent-foreground: oklch(0.985 0 0);
130
+ --sidebar-border: oklch(1 0 0 / 10%);
131
+ --sidebar-ring: oklch(0.556 0 0);
132
+ }
133
+
134
+ @layer base {
135
+ /* Native controls (scrollbars, date pickers) follow the active theme. */
136
+ :root {
137
+ color-scheme: light;
138
+ }
139
+ .dark {
140
+ color-scheme: dark;
141
+ }
142
+ /* Thin scrollbars that stay hidden until the pointer is over the scrolling
143
+ area, tinted with the theme foreground. */
144
+ * {
145
+ scrollbar-width: thin;
146
+ scrollbar-color: transparent transparent;
147
+ }
148
+ *:hover {
149
+ scrollbar-color: color-mix(in oklch, var(--foreground) 25%, transparent)
150
+ transparent;
151
+ }
152
+ /* Set by preferencesScript until the saved language has rendered. */
153
+ html[data-locale-pending] body {
154
+ visibility: hidden;
155
+ }
156
+ * {
157
+ @apply border-border outline-ring/50;
158
+ }
159
+ body {
160
+ @apply bg-background text-foreground;
161
+ }
162
+ html {
163
+ @apply font-sans;
164
+ }
165
+ }
@@ -1,44 +1,44 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
- import { AppShell, type AppShellProps } from "../patterns/app-shell";
5
- import { PageHeader, MetricStrip, type Metric } from "../patterns/page-header";
6
- import { DataTable, type DataTableProps } from "../patterns/data-table";
7
-
8
- export interface AdminDashboardProps<T> extends Omit<
9
- AppShellProps,
10
- "children"
11
- > {
12
- title: string;
13
- description?: string;
14
- metrics: Metric[];
15
- table: DataTableProps<T>;
16
- /** Page-level actions next to the title. */
17
- pageActions?: ReactNode;
18
- beforeTable?: ReactNode;
19
- afterTable?: ReactNode;
20
- }
21
- export function AdminDashboard<T>({
22
- title,
23
- description,
24
- metrics,
25
- table,
26
- pageActions,
27
- beforeTable,
28
- afterTable,
29
- ...shell
30
- }: AdminDashboardProps<T>) {
31
- return (
32
- <AppShell {...shell}>
33
- <PageHeader
34
- title={title}
35
- description={description}
36
- actions={pageActions}
37
- />
38
- <MetricStrip items={metrics} />
39
- {beforeTable}
40
- <DataTable {...table} />
41
- {afterTable}
42
- </AppShell>
43
- );
44
- }
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import { AppShell, type AppShellProps } from "../patterns/app-shell";
5
+ import { PageHeader, MetricStrip, type Metric } from "../patterns/page-header";
6
+ import { DataTable, type DataTableProps } from "../patterns/data-table";
7
+
8
+ export interface AdminDashboardProps<T> extends Omit<
9
+ AppShellProps,
10
+ "children"
11
+ > {
12
+ title: string;
13
+ description?: string;
14
+ metrics: Metric[];
15
+ table: DataTableProps<T>;
16
+ /** Page-level actions next to the title. */
17
+ pageActions?: ReactNode;
18
+ beforeTable?: ReactNode;
19
+ afterTable?: ReactNode;
20
+ }
21
+ export function AdminDashboard<T>({
22
+ title,
23
+ description,
24
+ metrics,
25
+ table,
26
+ pageActions,
27
+ beforeTable,
28
+ afterTable,
29
+ ...shell
30
+ }: AdminDashboardProps<T>) {
31
+ return (
32
+ <AppShell {...shell}>
33
+ <PageHeader
34
+ title={title}
35
+ description={description}
36
+ actions={pageActions}
37
+ />
38
+ <MetricStrip items={metrics} />
39
+ {beforeTable}
40
+ <DataTable {...table} />
41
+ {afterTable}
42
+ </AppShell>
43
+ );
44
+ }
@@ -1,30 +1,30 @@
1
- import type { ReactNode } from "react";
2
-
3
- /** Centered auth page: brand on top, form card below, optional footer. */
4
- export function AuthLayout({
5
- brand,
6
- children,
7
- footer,
8
- }: {
9
- brand?: ReactNode;
10
- children: ReactNode;
11
- footer?: ReactNode;
12
- }) {
13
- return (
14
- <main className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
15
- <div className="flex w-full max-w-sm flex-col gap-6">
16
- {brand ? (
17
- <div className="flex items-center gap-2 self-center font-medium">
18
- {brand}
19
- </div>
20
- ) : null}
21
- {children}
22
- {footer ? (
23
- <div className="text-center text-xs text-balance text-muted-foreground">
24
- {footer}
25
- </div>
26
- ) : null}
27
- </div>
28
- </main>
29
- );
30
- }
1
+ import type { ReactNode } from "react";
2
+
3
+ /** Centered auth page: brand on top, form card below, optional footer. */
4
+ export function AuthLayout({
5
+ brand,
6
+ children,
7
+ footer,
8
+ }: {
9
+ brand?: ReactNode;
10
+ children: ReactNode;
11
+ footer?: ReactNode;
12
+ }) {
13
+ return (
14
+ <main className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
15
+ <div className="flex w-full max-w-sm flex-col gap-6">
16
+ {brand ? (
17
+ <div className="flex items-center gap-2 self-center font-medium">
18
+ {brand}
19
+ </div>
20
+ ) : null}
21
+ {children}
22
+ {footer ? (
23
+ <div className="text-center text-xs text-balance text-muted-foreground">
24
+ {footer}
25
+ </div>
26
+ ) : null}
27
+ </div>
28
+ </main>
29
+ );
30
+ }