betterstart-cli 0.0.13 → 0.0.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.
Files changed (76) hide show
  1. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +51 -11
  2. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +7 -1
  3. package/dist/assets/adapters/next/templates/init/components/layouts/sidebar-branding.tsx +1 -1
  4. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +107 -0
  5. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +58 -45
  6. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/lifecycle-hooks-tab.tsx +3 -3
  7. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -1
  8. package/dist/assets/adapters/next/templates/init/components/shared/entity-filters-bar.tsx +1 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +2 -2
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +1 -1
  11. package/dist/assets/adapters/next/templates/init/components/shared/sort-indicator.tsx +7 -3
  12. package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -0
  13. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-table-button.ts +85 -0
  14. package/dist/assets/adapters/next/templates/init/lib/actions/audit/get-audit-logs.ts +41 -16
  15. package/dist/assets/adapters/next/templates/init/lib/actions/audit/index.ts +2 -0
  16. package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +5 -0
  17. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +2 -1
  18. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +2 -1
  19. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-drawer.tsx +51 -43
  20. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-detail-row.tsx +2 -4
  21. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +465 -179
  22. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-skeleton.tsx +3 -15
  23. package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +1 -1
  24. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +1 -1
  25. package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +1 -1
  26. package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +1 -1
  27. package/dist/assets/adapters/next/templates/init/pages/users/users-page-content.tsx +83 -2
  28. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +126 -78
  29. package/dist/assets/adapters/next/templates/init/utils/editor/tiptap.ts +7 -8
  30. package/dist/assets/adapters/next/templates/init/utils/form/list-field.ts +17 -0
  31. package/dist/assets/adapters/next/templates/init/utils/user/user.ts +70 -0
  32. package/dist/assets/shared-assets/react-admin/custom/content-editor/math-bubble-menu.tsx +1 -1
  33. package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +1 -1
  34. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +0 -2
  35. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-button.tsx +17 -65
  36. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +3 -8
  37. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-popover.tsx +1 -2
  38. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +1 -1
  39. package/dist/assets/shared-assets/react-admin/custom/dynamic-list-field.tsx +20 -8
  40. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +2 -2
  41. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +2 -2
  42. package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +211 -0
  43. package/dist/assets/shared-assets/react-admin/custom/placeholder-card.tsx +1 -1
  44. package/dist/assets/shared-assets/react-admin/custom/placeholder.tsx +1 -1
  45. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +1 -1
  46. package/dist/assets/shared-assets/react-admin/schema.json +136 -0
  47. package/dist/assets/shared-assets/react-admin/ui/accordion.tsx +1 -1
  48. package/dist/assets/shared-assets/react-admin/ui/alert.tsx +1 -1
  49. package/dist/assets/shared-assets/react-admin/ui/card.tsx +2 -2
  50. package/dist/assets/shared-assets/react-admin/ui/chart.tsx +1 -1
  51. package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
  52. package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +2 -2
  53. package/dist/assets/shared-assets/react-admin/ui/command.tsx +1 -1
  54. package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +6 -6
  55. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
  56. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +6 -6
  57. package/dist/assets/shared-assets/react-admin/ui/empty.tsx +2 -2
  58. package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
  59. package/dist/assets/shared-assets/react-admin/ui/form.tsx +1 -1
  60. package/dist/assets/shared-assets/react-admin/ui/hover-card.tsx +1 -1
  61. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +1 -1
  62. package/dist/assets/shared-assets/react-admin/ui/item.tsx +2 -2
  63. package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +7 -7
  64. package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +4 -4
  65. package/dist/assets/shared-assets/react-admin/ui/popover.tsx +1 -1
  66. package/dist/assets/shared-assets/react-admin/ui/resizable.tsx +1 -1
  67. package/dist/assets/shared-assets/react-admin/ui/select.tsx +2 -2
  68. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +3 -3
  69. package/dist/assets/shared-assets/react-admin/ui/skeleton.tsx +1 -1
  70. package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +2 -2
  71. package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
  72. package/dist/assets/shared-assets/react-admin/ui/tooltip.tsx +1 -1
  73. package/dist/cli.js +2688 -1644
  74. package/dist/cli.js.map +1 -1
  75. package/package.json +10 -2
  76. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-filters.tsx +0 -152
@@ -4,7 +4,7 @@
4
4
  "description": "Manage Menus",
5
5
  "icon": "Menu",
6
6
  "navGroup": {
7
- "label": "Collections",
7
+ "label": "General",
8
8
  "position": 0
9
9
  },
10
10
  "navPosition": 1,
@@ -30,16 +30,23 @@
30
30
  "hint": "Items in the navigation menu",
31
31
  "fields": [
32
32
  {
33
- "name": "label",
34
- "type": "string",
35
- "label": "Label",
36
- "required": true
37
- },
38
- {
39
- "name": "href",
40
- "type": "string",
41
- "label": "Href",
42
- "required": true
33
+ "name": "parentFields",
34
+ "type": "group",
35
+ "columns": 2,
36
+ "fields": [
37
+ {
38
+ "name": "label",
39
+ "type": "string",
40
+ "label": "Label",
41
+ "required": true
42
+ },
43
+ {
44
+ "name": "href",
45
+ "type": "string",
46
+ "label": "Href",
47
+ "required": true
48
+ }
49
+ ]
43
50
  },
44
51
  {
45
52
  "name": "openInNewTab",
@@ -47,6 +54,39 @@
47
54
  "label": "Open in New Tab",
48
55
  "hint": "Open this link in a new browser tab",
49
56
  "default": false
57
+ },
58
+ {
59
+ "name": "subItems",
60
+ "type": "list",
61
+ "label": "Sub Menu Items",
62
+ "fields": [
63
+ {
64
+ "name": "subItemFields",
65
+ "type": "group",
66
+ "columns": 2,
67
+ "fields": [
68
+ {
69
+ "name": "subItemLabel",
70
+ "type": "string",
71
+ "label": "Label",
72
+ "required": true
73
+ },
74
+ {
75
+ "name": "subItemHref",
76
+ "type": "string",
77
+ "label": "Href",
78
+ "required": true
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "name": "subItemOpenInNewTab",
84
+ "type": "boolean",
85
+ "label": "Open in New Tab",
86
+ "hint": "Open this link in a new browser tab",
87
+ "default": false
88
+ }
89
+ ]
50
90
  }
51
91
  ]
52
92
  }
@@ -11,7 +11,7 @@ import {
11
11
  } from '@admin/components/ui/sidebar'
12
12
  import { adminNavigation } from '@admin/data/navigation'
13
13
  import { groupAdminNavigationItems } from '@admin/utils/navigation/sidebar'
14
- import { Settings, Users } from 'lucide-react'
14
+ import { History, Settings, Users } from 'lucide-react'
15
15
  import type { ComponentProps } from 'react'
16
16
  import { Fragment, Suspense } from 'react'
17
17
  import { SidebarBrandingRsc } from './admin-sidebar-branding-rsc'
@@ -64,6 +64,12 @@ export function AdminSidebar(props: ComponentProps<typeof Sidebar>) {
64
64
  <span>Settings</span>
65
65
  </SidebarNavLink>
66
66
  </SidebarMenuItem>
67
+ <SidebarMenuItem>
68
+ <SidebarNavLink href="/admin/system/audit-log">
69
+ <History strokeWidth={2} />
70
+ <span>Audit Log</span>
71
+ </SidebarNavLink>
72
+ </SidebarMenuItem>
67
73
  </SidebarMenu>
68
74
  </SidebarGroup>
69
75
  </SidebarContent>
@@ -20,7 +20,7 @@ export function SidebarBranding({ initialData }: SidebarBrandingProps) {
20
20
  return <BrandingSkeleton />
21
21
  }
22
22
 
23
- const dashboardName = settings?.dashboardName ?? admin.name
23
+ const dashboardName = settings?.title ?? admin.name
24
24
  const dashboardLogo = settings?.dashboardLogo || DEFAULT_LOGO_URL
25
25
 
26
26
  return (
@@ -0,0 +1,107 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@admin/utils/shared/cn'
4
+ import type * as React from 'react'
5
+
6
+ type DataGridProps = React.ComponentProps<'div'> & {
7
+ containerClassName?: string
8
+ gridTemplateColumns: string
9
+ }
10
+
11
+ const dataGridTableRole = { role: 'table' } as const
12
+ const dataGridRowGroupRole = { role: 'rowgroup' } as const
13
+ const dataGridRowRole = { role: 'row' } as const
14
+ const dataGridColumnHeaderRole = { role: 'columnheader' } as const
15
+ const dataGridCellRole = { role: 'cell' } as const
16
+
17
+ function DataGrid({
18
+ className,
19
+ containerClassName,
20
+ gridTemplateColumns,
21
+ style,
22
+ ...props
23
+ }: DataGridProps) {
24
+ return (
25
+ <div
26
+ data-slot="data-grid-container"
27
+ className={cn('relative min-w-0 overflow-auto', containerClassName)}
28
+ >
29
+ <div
30
+ {...dataGridTableRole}
31
+ data-slot="data-grid"
32
+ className={cn('grid min-w-full text-sm', className)}
33
+ style={{ ...style, gridTemplateColumns }}
34
+ {...props}
35
+ />
36
+ </div>
37
+ )
38
+ }
39
+
40
+ function DataGridHeader({ className, ...props }: React.ComponentProps<'div'>) {
41
+ return (
42
+ <div
43
+ {...dataGridRowGroupRole}
44
+ data-slot="data-grid-header"
45
+ className={cn('contents', className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function DataGridBody({ className, ...props }: React.ComponentProps<'div'>) {
52
+ return (
53
+ <div
54
+ {...dataGridRowGroupRole}
55
+ data-slot="data-grid-body"
56
+ className={cn('contents', className)}
57
+ {...props}
58
+ />
59
+ )
60
+ }
61
+
62
+ function DataGridRow({ className, style, ...props }: React.ComponentProps<'div'>) {
63
+ return (
64
+ <div
65
+ {...dataGridRowRole}
66
+ data-slot="data-grid-row"
67
+ className={cn(
68
+ 'grid border-b transition-colors has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
69
+ className
70
+ )}
71
+ style={{ ...style, gridColumn: '1 / -1', gridTemplateColumns: 'subgrid' }}
72
+ tabIndex={-1}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function DataGridHead({ className, ...props }: React.ComponentProps<'div'>) {
79
+ return (
80
+ <div
81
+ {...dataGridColumnHeaderRole}
82
+ data-slot="data-grid-head"
83
+ className={cn(
84
+ 'grid h-10 items-center px-3 text-left font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
85
+ className
86
+ )}
87
+ tabIndex={-1}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DataGridCell({ className, ...props }: React.ComponentProps<'div'>) {
94
+ return (
95
+ <div
96
+ {...dataGridCellRole}
97
+ data-slot="data-grid-cell"
98
+ className={cn(
99
+ 'grid min-h-10 items-center px-3 py-1.5 whitespace-nowrap [&:has([role=checkbox])]:pr-0',
100
+ className
101
+ )}
102
+ {...props}
103
+ />
104
+ )
105
+ }
106
+
107
+ export { DataGrid, DataGridHeader, DataGridBody, DataGridHead, DataGridRow, DataGridCell }
@@ -1,13 +1,13 @@
1
1
  'use client'
2
2
 
3
3
  import {
4
- Table,
5
- TableBody,
6
- TableCell,
7
- TableHead,
8
- TableHeader,
9
- TableRow
10
- } from '@admin/components/ui/table'
4
+ DataGrid,
5
+ DataGridBody,
6
+ DataGridCell,
7
+ DataGridHead,
8
+ DataGridHeader,
9
+ DataGridRow
10
+ } from '@admin/components/shared/data-table/data-grid'
11
11
  import { useTableUtils } from '@admin/hooks/use-table-utils'
12
12
  import { cn } from '@admin/utils/shared/cn'
13
13
  import {
@@ -108,19 +108,18 @@ export function DataTable<TData extends RowData, TValue>({
108
108
  }
109
109
  })
110
110
  registerTable(table)
111
+ const gridTemplateColumns = table
112
+ .getVisibleLeafColumns()
113
+ .map((column) => `${column.getSize()}px`)
114
+ .join(' ')
111
115
 
112
116
  return (
113
117
  <div className="space-y-6 pb-24">
114
- <div ref={tableContainerRef} className="overflow-hidden material-sm border-border">
115
- <Table className="table-fixed" style={{ width: table.getTotalSize() }}>
116
- <colgroup>
117
- {table.getVisibleLeafColumns().map((column) => (
118
- <col key={column.id} style={{ width: column.getSize() }} />
119
- ))}
120
- </colgroup>
121
- <TableHeader>
118
+ <div ref={tableContainerRef} className="min-w-0 overflow-hidden material-sm border-border">
119
+ <DataGrid gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
120
+ <DataGridHeader>
122
121
  {table.getHeaderGroups().map((headerGroup) => (
123
- <TableRow key={headerGroup.id}>
122
+ <DataGridRow key={headerGroup.id}>
124
123
  {headerGroup.headers.map((header, headerIndex) => {
125
124
  const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
126
125
  const resizeLabel =
@@ -131,12 +130,15 @@ export function DataTable<TData extends RowData, TValue>({
131
130
  const canResizeColumn =
132
131
  header.column.getCanResize() && nextHeader?.column.getCanResize() === true
133
132
  return (
134
- <TableHead
133
+ <DataGridHead
135
134
  key={header.id}
136
- className={cn('group/resize-header relative overflow-hidden', {
137
- 'text-center': meta?.align === 'center',
138
- 'text-right': meta?.align === 'right'
139
- })}
135
+ className={cn(
136
+ 'group/resize-header relative overflow-hidden text-foreground',
137
+ {
138
+ 'text-center': meta?.align === 'center',
139
+ 'text-right': meta?.align === 'right'
140
+ }
141
+ )}
140
142
  data-column-id={header.column.id}
141
143
  onMouseEnter={() => setHoveredColumnId(header.column.id)}
142
144
  onMouseLeave={() =>
@@ -144,7 +146,6 @@ export function DataTable<TData extends RowData, TValue>({
144
146
  current === header.column.id ? null : current
145
147
  )
146
148
  }
147
- style={{ width: header.getSize() }}
148
149
  >
149
150
  {header.isPlaceholder
150
151
  ? null
@@ -166,32 +167,42 @@ export function DataTable<TData extends RowData, TValue>({
166
167
  <GripVertical aria-hidden="true" className="size-3.5 text-foreground" />
167
168
  </button>
168
169
  ) : null}
169
- </TableHead>
170
+ </DataGridHead>
170
171
  )
171
172
  })}
172
- </TableRow>
173
+ </DataGridRow>
173
174
  ))}
174
- </TableHeader>
175
- <TableBody>
175
+ </DataGridHeader>
176
+ <DataGridBody>
176
177
  {isPending ? (
177
- <TableRow>
178
- <TableCell colSpan={columns.length} className="h-24 text-center">
178
+ <DataGridRow>
179
+ <DataGridCell
180
+ className="flex h-24 items-center justify-center text-center"
181
+ style={{ gridColumn: '1 / -1' }}
182
+ >
179
183
  <div className="text-muted-foreground">{loadingMessage}</div>
180
- </TableCell>
181
- </TableRow>
184
+ </DataGridCell>
185
+ </DataGridRow>
182
186
  ) : error ? (
183
- <TableRow>
184
- <TableCell colSpan={columns.length} className="h-24 text-center">
187
+ <DataGridRow>
188
+ <DataGridCell
189
+ className="flex h-24 items-center justify-center text-center"
190
+ style={{ gridColumn: '1 / -1' }}
191
+ >
185
192
  <div className="text-destructive">Error: {error.message}</div>
186
- </TableCell>
187
- </TableRow>
193
+ </DataGridCell>
194
+ </DataGridRow>
188
195
  ) : table.getRowModel().rows?.length ? (
189
196
  table.getRowModel().rows.map((row) => (
190
- <TableRow key={row.id} data-state={row.getIsSelected() ? 'selected' : undefined}>
197
+ <DataGridRow
198
+ key={row.id}
199
+ className="hover:bg-muted/50"
200
+ data-state={row.getIsSelected() ? 'selected' : undefined}
201
+ >
191
202
  {row.getVisibleCells().map((cell) => {
192
203
  const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
193
204
  return (
194
- <TableCell
205
+ <DataGridCell
195
206
  key={cell.id}
196
207
  className={cn({
197
208
  'text-center': meta?.align === 'center',
@@ -204,23 +215,25 @@ export function DataTable<TData extends RowData, TValue>({
204
215
  current === cell.column.id ? null : current
205
216
  )
206
217
  }
207
- style={{ width: cell.column.getSize() }}
208
218
  >
209
219
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
210
- </TableCell>
220
+ </DataGridCell>
211
221
  )
212
222
  })}
213
- </TableRow>
223
+ </DataGridRow>
214
224
  ))
215
225
  ) : (
216
- <TableRow>
217
- <TableCell colSpan={columns.length} className="h-24 text-center">
226
+ <DataGridRow>
227
+ <DataGridCell
228
+ className="flex h-24 items-center justify-center text-center"
229
+ style={{ gridColumn: '1 / -1' }}
230
+ >
218
231
  {emptyMessage}
219
- </TableCell>
220
- </TableRow>
232
+ </DataGridCell>
233
+ </DataGridRow>
221
234
  )}
222
- </TableBody>
223
- </Table>
235
+ </DataGridBody>
236
+ </DataGrid>
224
237
  </div>
225
238
 
226
239
  <DataTablePagination
@@ -53,14 +53,14 @@ export function LifecycleHooksTab({ lifecycleHooks }: LifecycleHooksTabProps) {
53
53
 
54
54
  if (!firstHook) {
55
55
  return (
56
- <div className="flex min-h-80 items-center justify-center rounded-xl border border-dashed border-border px-6 text-center text-sm text-muted-foreground">
57
- Lifecycle hook examples are generated for entity schemas in v1.
56
+ <div className="flex min-h-80 items-center justify-center rounded-lg border border-dashed border-border px-6 text-center text-sm text-muted-foreground">
57
+ Lifecycle hook examples are generated for entity and form schemas.
58
58
  </div>
59
59
  )
60
60
  }
61
61
 
62
62
  return (
63
- <Accordion type="single" defaultValue={firstHook.id} className="min-h-0 shrink-0 rounded-xl">
63
+ <Accordion type="single" defaultValue={firstHook.id} className="min-h-0 shrink-0 rounded-lg">
64
64
  {lifecycleHooks.map((hook) => {
65
65
  const HookIcon = LIFECYCLE_HOOK_ICONS[hook.icon]
66
66
  const phaseLabel = LIFECYCLE_HOOK_PHASE_LABELS[hook.phase]
@@ -56,7 +56,7 @@ export function SnippetsTab({ snippets }: SnippetsTabProps) {
56
56
  }
57
57
 
58
58
  return (
59
- <Accordion type="single" defaultValue={firstSnippet.id} className="min-h-0 shrink-0 rounded-xl">
59
+ <Accordion type="single" defaultValue={firstSnippet.id} className="min-h-0 shrink-0 rounded-lg">
60
60
  {snippets.map((snippet) => {
61
61
  const SnippetIcon = SNIPPET_ICONS[snippet.icon]
62
62
  const snippetKindLabel = SNIPPET_KIND_LABELS[snippet.kind]
@@ -181,4 +181,4 @@ export function EntityFiltersBar({
181
181
  )
182
182
  }
183
183
 
184
- export type { FilterDef, SelectFilterDef, DateRangeFilterDef, EntityFiltersBarProps }
184
+ export type { DateRangeFilterDef, EntityFiltersBarProps, FilterDef, SelectFilterDef }
@@ -93,7 +93,7 @@ export function EditMediaDialogContent({
93
93
  <DrawerContent className="h-full w-full overflow-hidden">
94
94
  <div
95
95
  ref={handleDrawerContainerRef}
96
- className="pointer-events-none absolute inset-3 z-20 overflow-hidden rounded-xl"
96
+ className="pointer-events-none absolute inset-3 z-20 overflow-hidden rounded-lg"
97
97
  />
98
98
  <div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden">
99
99
  <DrawerHeader>
@@ -107,7 +107,7 @@ export function EditMediaDialogContent({
107
107
  >
108
108
  <Card>
109
109
  <CardContent className="p-0">
110
- <div className="relative aspect-video overflow-hidden rounded-xl bg-background">
110
+ <div className="relative aspect-video overflow-hidden rounded-lg bg-background">
111
111
  <MediaPreview media={media} showMetadata />
112
112
  </div>
113
113
  </CardContent>
@@ -90,7 +90,7 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
90
90
  return (
91
91
  <div
92
92
  className={cn(
93
- 'flex items-center justify-center transition-colors rounded-xl border bg-input border-border overflow-hidden h-full flex-col p-5 gap-5',
93
+ 'flex items-center justify-center transition-colors rounded-lg border bg-input border-border overflow-hidden h-full flex-col p-5 gap-5',
94
94
  className
95
95
  )}
96
96
  >
@@ -16,9 +16,13 @@ export function SortIndicator({ direction }: { direction: false | 'asc' | 'desc'
16
16
 
17
17
  return (
18
18
  <Icon
19
- className={cn('size-4 text-muted-foreground shrink-0 opacity-50 group-hover:opacity-100', {
20
- 'opacity-100 text-foreground': direction !== false
21
- })}
19
+ aria-hidden="true"
20
+ className={cn(
21
+ 'size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-visible:opacity-100 group-hover/resize-header:opacity-100',
22
+ {
23
+ 'text-foreground': direction !== false
24
+ }
25
+ )}
22
26
  />
23
27
  )
24
28
  }
@@ -10,12 +10,14 @@ const coreNav: AdminNavigationItem[] = [
10
10
  label: 'Overview',
11
11
  href: '/admin',
12
12
  icon: House,
13
+ group: 'General',
13
14
  position: 0
14
15
  },
15
16
  {
16
17
  label: 'Media',
17
18
  href: '/admin/media',
18
19
  icon: ImagePlay,
20
+ group: 'General',
19
21
  position: 1
20
22
  }
21
23
  ]
@@ -0,0 +1,85 @@
1
+ 'use client'
2
+
3
+ import { normalizeCount } from '@admin/utils/editor/table-input'
4
+ import { insertContentEditorTable } from '@admin/utils/editor/tiptap'
5
+ import type { Editor } from '@tiptap/core'
6
+ import * as React from 'react'
7
+
8
+ const DEFAULT_TABLE_ROWS = 3
9
+ const DEFAULT_TABLE_COLS = 3
10
+ const MIN_TABLE_ROWS = 2
11
+ const MIN_TABLE_COLS = 1
12
+
13
+ interface UseTableButtonOptions {
14
+ disabled?: boolean
15
+ editor?: Editor | null
16
+ onInsertTable?: (size: { cols: number; rows: number }) => void
17
+ }
18
+
19
+ export function useTableButton({ disabled, editor, onInsertTable }: UseTableButtonOptions) {
20
+ const [isOpen, setIsOpen] = React.useState(false)
21
+ const [rows, setRows] = React.useState(String(DEFAULT_TABLE_ROWS))
22
+ const [cols, setCols] = React.useState(String(DEFAULT_TABLE_COLS))
23
+ const rowsId = React.useId()
24
+ const colsId = React.useId()
25
+ const canInsert = Boolean(onInsertTable || editor?.isEditable)
26
+ const isDisabled = disabled || !canInsert
27
+
28
+ const closePopover = React.useCallback(() => {
29
+ setIsOpen(false)
30
+ window.setTimeout(() => setIsOpen(false), 0)
31
+ }, [])
32
+
33
+ const insertTable = React.useCallback(() => {
34
+ if (isDisabled) return
35
+
36
+ const rowCount = normalizeCount(rows, DEFAULT_TABLE_ROWS, MIN_TABLE_ROWS)
37
+ const colCount = normalizeCount(cols, DEFAULT_TABLE_COLS, MIN_TABLE_COLS)
38
+
39
+ if (onInsertTable) {
40
+ onInsertTable({ cols: colCount, rows: rowCount })
41
+ closePopover()
42
+ return
43
+ }
44
+
45
+ if (!editor) return
46
+
47
+ insertContentEditorTable(editor, rowCount, colCount)
48
+ closePopover()
49
+ }, [closePopover, cols, editor, isDisabled, onInsertTable, rows])
50
+
51
+ const handleKeyDown = React.useCallback(
52
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
53
+ if (event.key !== 'Enter') return
54
+
55
+ event.preventDefault()
56
+ event.stopPropagation()
57
+ insertTable()
58
+ },
59
+ [insertTable]
60
+ )
61
+
62
+ const handleRowsChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
63
+ setRows(event.target.value)
64
+ }, [])
65
+
66
+ const handleColsChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
67
+ setCols(event.target.value)
68
+ }, [])
69
+
70
+ return {
71
+ cols,
72
+ colsId,
73
+ handleColsChange,
74
+ handleKeyDown,
75
+ handleRowsChange,
76
+ insertTable,
77
+ isDisabled,
78
+ isOpen,
79
+ minCols: MIN_TABLE_COLS,
80
+ minRows: MIN_TABLE_ROWS,
81
+ rows,
82
+ rowsId,
83
+ setIsOpen
84
+ }
85
+ }