@prenta/admin 1.17.0 → 1.17.1
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/CHANGELOG.md +8 -0
- package/dist/__tests__/lib/seo-service-errors.test.js +2 -6
- package/dist/__tests__/lib/seo-service-errors.test.js.map +1 -1
- package/dist/lib/seo-service.d.ts +0 -5
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js +0 -11
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/package.json +5 -5
- package/src/__tests__/lib/seo-service-errors.test.ts +1 -9
- package/src/lib/seo-service.ts +0 -14
- package/dist/components/ContentOverviewChart.d.ts +0 -8
- package/dist/components/ContentOverviewChart.d.ts.map +0 -1
- package/dist/components/ContentOverviewChart.js +0 -20
- package/dist/components/ContentOverviewChart.js.map +0 -1
- package/dist/components/SEOPerformance.d.ts +0 -5
- package/dist/components/SEOPerformance.d.ts.map +0 -1
- package/dist/components/SEOPerformance.js +0 -29
- package/dist/components/SEOPerformance.js.map +0 -1
- package/dist/components/ui/Avatar.d.ts +0 -9
- package/dist/components/ui/Avatar.d.ts.map +0 -1
- package/dist/components/ui/Avatar.js +0 -21
- package/dist/components/ui/Avatar.js.map +0 -1
- package/dist/components/ui/CommandPalette.d.ts +0 -14
- package/dist/components/ui/CommandPalette.d.ts.map +0 -1
- package/dist/components/ui/CommandPalette.js +0 -58
- package/dist/components/ui/CommandPalette.js.map +0 -1
- package/dist/components/ui/DataTable.d.ts +0 -30
- package/dist/components/ui/DataTable.d.ts.map +0 -1
- package/dist/components/ui/DataTable.js +0 -44
- package/dist/components/ui/DataTable.js.map +0 -1
- package/dist/components/ui/Pagination.d.ts +0 -9
- package/dist/components/ui/Pagination.d.ts.map +0 -1
- package/dist/components/ui/Pagination.js +0 -26
- package/dist/components/ui/Pagination.js.map +0 -1
- package/dist/components/ui/Select.d.ts +0 -16
- package/dist/components/ui/Select.d.ts.map +0 -1
- package/dist/components/ui/Select.js +0 -24
- package/dist/components/ui/Select.js.map +0 -1
- package/dist/components/ui/Toast.d.ts +0 -18
- package/dist/components/ui/Toast.d.ts.map +0 -1
- package/dist/components/ui/Toast.js +0 -29
- package/dist/components/ui/Toast.js.map +0 -1
- package/dist/components/ui/index.d.ts +0 -46
- package/dist/components/ui/index.d.ts.map +0 -1
- package/dist/components/ui/index.js +0 -24
- package/dist/components/ui/index.js.map +0 -1
- package/dist/hooks/useContentLock.d.ts +0 -12
- package/dist/hooks/useContentLock.d.ts.map +0 -1
- package/dist/hooks/useContentLock.js +0 -38
- package/dist/hooks/useContentLock.js.map +0 -1
- package/dist/hooks/useDebounce.d.ts +0 -2
- package/dist/hooks/useDebounce.d.ts.map +0 -1
- package/dist/hooks/useDebounce.js +0 -11
- package/dist/hooks/useDebounce.js.map +0 -1
- package/src/components/ContentOverviewChart.tsx +0 -75
- package/src/components/SEOPerformance.tsx +0 -163
- package/src/components/ui/Avatar.tsx +0 -42
- package/src/components/ui/CommandPalette.tsx +0 -133
- package/src/components/ui/DataTable.tsx +0 -204
- package/src/components/ui/Pagination.tsx +0 -94
- package/src/components/ui/Select.tsx +0 -53
- package/src/components/ui/Toast.tsx +0 -72
- package/src/components/ui/index.ts +0 -61
- package/src/hooks/useContentLock.ts +0 -55
- package/src/hooks/useDebounce.ts +0 -14
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, useEffect } from 'react'
|
|
4
|
-
|
|
5
|
-
interface Column {
|
|
6
|
-
key: string
|
|
7
|
-
label: string
|
|
8
|
-
sortable?: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface Row {
|
|
12
|
-
id: string
|
|
13
|
-
[key: string]: any
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface RowAction {
|
|
17
|
-
key: string
|
|
18
|
-
label: string
|
|
19
|
-
icon?: React.ReactNode
|
|
20
|
-
destructive?: boolean
|
|
21
|
-
onClick: (row: Row) => void
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DataTableProps {
|
|
25
|
-
columns: Column[]
|
|
26
|
-
rows: Row[]
|
|
27
|
-
selectedIds: string[]
|
|
28
|
-
onSelectionChange: (ids: string[]) => void
|
|
29
|
-
sortField?: string
|
|
30
|
-
sortDir?: 'asc' | 'desc'
|
|
31
|
-
onSort?: (field: string) => void
|
|
32
|
-
rowActions?: RowAction[]
|
|
33
|
-
onRowClick?: (row: Row) => void
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function DataTable({
|
|
37
|
-
columns,
|
|
38
|
-
rows,
|
|
39
|
-
selectedIds,
|
|
40
|
-
onSelectionChange,
|
|
41
|
-
sortField,
|
|
42
|
-
sortDir,
|
|
43
|
-
onSort,
|
|
44
|
-
rowActions,
|
|
45
|
-
onRowClick,
|
|
46
|
-
}: DataTableProps) {
|
|
47
|
-
const allSelected = rows.length > 0 && selectedIds.length === rows.length
|
|
48
|
-
|
|
49
|
-
function toggleAll() {
|
|
50
|
-
onSelectionChange(allSelected ? [] : rows.map((r) => r.id))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function toggleRow(id: string) {
|
|
54
|
-
onSelectionChange(
|
|
55
|
-
selectedIds.includes(id) ? selectedIds.filter((s) => s !== id) : [...selectedIds, id],
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="overflow-x-auto rounded-lg border border-[var(--border)]">
|
|
61
|
-
<table className="w-full text-left text-sm">
|
|
62
|
-
<thead className="bg-muted border-b border-[var(--border)]">
|
|
63
|
-
<tr>
|
|
64
|
-
<th className="w-10 px-4 py-3">
|
|
65
|
-
<input
|
|
66
|
-
type="checkbox"
|
|
67
|
-
checked={allSelected}
|
|
68
|
-
onChange={toggleAll}
|
|
69
|
-
className="rounded border-[var(--border)]"
|
|
70
|
-
/>
|
|
71
|
-
</th>
|
|
72
|
-
{columns.map((col) => (
|
|
73
|
-
<th
|
|
74
|
-
key={col.key}
|
|
75
|
-
className="px-4 py-3 font-medium"
|
|
76
|
-
aria-sort={
|
|
77
|
-
col.sortable && onSort
|
|
78
|
-
? sortField === col.key
|
|
79
|
-
? sortDir === 'asc'
|
|
80
|
-
? 'ascending'
|
|
81
|
-
: 'descending'
|
|
82
|
-
: 'none'
|
|
83
|
-
: undefined
|
|
84
|
-
}
|
|
85
|
-
>
|
|
86
|
-
{col.sortable && onSort ? (
|
|
87
|
-
<button
|
|
88
|
-
onClick={() => onSort(col.key)}
|
|
89
|
-
className="inline-flex items-center gap-1 hover:text-[var(--foreground)]"
|
|
90
|
-
>
|
|
91
|
-
{col.label}
|
|
92
|
-
{sortField === col.key && (
|
|
93
|
-
<span className="text-xs">{sortDir === 'asc' ? '↑' : '↓'}</span>
|
|
94
|
-
)}
|
|
95
|
-
</button>
|
|
96
|
-
) : (
|
|
97
|
-
col.label
|
|
98
|
-
)}
|
|
99
|
-
</th>
|
|
100
|
-
))}
|
|
101
|
-
{rowActions && rowActions.length > 0 && <th className="w-20 px-4 py-3" />}
|
|
102
|
-
</tr>
|
|
103
|
-
</thead>
|
|
104
|
-
<tbody className="divide-y divide-[var(--border)]">
|
|
105
|
-
{rows.map((row) => (
|
|
106
|
-
<tr
|
|
107
|
-
key={row.id}
|
|
108
|
-
className={`hover:bg-[var(--accent)] ${selectedIds.includes(row.id) ? 'bg-[var(--accent)]' : ''}`}
|
|
109
|
-
onClick={() => onRowClick?.(row)}
|
|
110
|
-
style={onRowClick ? { cursor: 'pointer' } : undefined}
|
|
111
|
-
>
|
|
112
|
-
<td className="px-4 py-3" onClick={(e) => e.stopPropagation()}>
|
|
113
|
-
<input
|
|
114
|
-
type="checkbox"
|
|
115
|
-
checked={selectedIds.includes(row.id)}
|
|
116
|
-
onChange={() => toggleRow(row.id)}
|
|
117
|
-
className="rounded border-[var(--border)]"
|
|
118
|
-
/>
|
|
119
|
-
</td>
|
|
120
|
-
{columns.map((col) => (
|
|
121
|
-
<td key={col.key} className="px-4 py-3">
|
|
122
|
-
{row[col.key]}
|
|
123
|
-
</td>
|
|
124
|
-
))}
|
|
125
|
-
{rowActions && rowActions.length > 0 && (
|
|
126
|
-
<td className="px-4 py-3" onClick={(e) => e.stopPropagation()}>
|
|
127
|
-
<RowActionsMenu actions={rowActions} row={row} />
|
|
128
|
-
</td>
|
|
129
|
-
)}
|
|
130
|
-
</tr>
|
|
131
|
-
))}
|
|
132
|
-
</tbody>
|
|
133
|
-
</table>
|
|
134
|
-
</div>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function RowActionsMenu({ actions, row }: { actions: RowAction[]; row: Row }) {
|
|
139
|
-
const [open, setOpen] = useState(false)
|
|
140
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
141
|
-
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (!open) return
|
|
144
|
-
function handleClickOutside(e: MouseEvent) {
|
|
145
|
-
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
146
|
-
setOpen(false)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
document.addEventListener('mousedown', handleClickOutside)
|
|
150
|
-
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
151
|
-
}, [open])
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<div className="relative flex items-center justify-end gap-1" ref={ref}>
|
|
155
|
-
{actions
|
|
156
|
-
.filter((a) => a.icon)
|
|
157
|
-
.map((action) => (
|
|
158
|
-
<button
|
|
159
|
-
key={action.key}
|
|
160
|
-
onClick={() => action.onClick(row)}
|
|
161
|
-
className="hover:bg-muted rounded p-1 text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
|
|
162
|
-
aria-label={action.label}
|
|
163
|
-
title={action.label}
|
|
164
|
-
>
|
|
165
|
-
{action.icon}
|
|
166
|
-
</button>
|
|
167
|
-
))}
|
|
168
|
-
<button
|
|
169
|
-
onClick={() => setOpen((o) => !o)}
|
|
170
|
-
className="hover:bg-muted rounded p-1"
|
|
171
|
-
aria-label="More actions"
|
|
172
|
-
>
|
|
173
|
-
<MoreVerticalIcon />
|
|
174
|
-
</button>
|
|
175
|
-
{open && (
|
|
176
|
-
<div className="absolute top-full right-0 z-50 mt-1 w-40 rounded-md border border-[var(--border)] bg-[var(--popover)] py-1 shadow-lg">
|
|
177
|
-
{actions.map((action) => (
|
|
178
|
-
<button
|
|
179
|
-
key={action.key}
|
|
180
|
-
className={`flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-[var(--accent)] ${
|
|
181
|
-
action.destructive ? 'text-[var(--destructive)]' : ''
|
|
182
|
-
}`}
|
|
183
|
-
onClick={() => {
|
|
184
|
-
action.onClick(row)
|
|
185
|
-
setOpen(false)
|
|
186
|
-
}}
|
|
187
|
-
>
|
|
188
|
-
{action.icon && <span className="h-4 w-4">{action.icon}</span>}
|
|
189
|
-
{action.label}
|
|
190
|
-
</button>
|
|
191
|
-
))}
|
|
192
|
-
</div>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function MoreVerticalIcon() {
|
|
199
|
-
return (
|
|
200
|
-
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
201
|
-
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v.01M12 12v.01M12 19v.01" />
|
|
202
|
-
</svg>
|
|
203
|
-
)
|
|
204
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
export interface PaginationProps {
|
|
4
|
-
page: number
|
|
5
|
-
perPage: number
|
|
6
|
-
total: number
|
|
7
|
-
onPageChange: (page: number) => void
|
|
8
|
-
onPerPageChange: (perPage: number) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function Pagination({
|
|
12
|
-
page,
|
|
13
|
-
perPage,
|
|
14
|
-
total,
|
|
15
|
-
onPageChange,
|
|
16
|
-
onPerPageChange,
|
|
17
|
-
}: PaginationProps) {
|
|
18
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage))
|
|
19
|
-
const start = (page - 1) * perPage + 1
|
|
20
|
-
const end = Math.min(page * perPage, total)
|
|
21
|
-
|
|
22
|
-
function pageNumbers(): (number | 'ellipsis')[] {
|
|
23
|
-
const pages: (number | 'ellipsis')[] = []
|
|
24
|
-
for (let i = 1; i <= totalPages; i++) {
|
|
25
|
-
if (i === 1 || i === totalPages || Math.abs(i - page) <= 1) {
|
|
26
|
-
pages.push(i)
|
|
27
|
-
} else if (pages[pages.length - 1] !== 'ellipsis') {
|
|
28
|
-
pages.push('ellipsis')
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return pages
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="flex items-center justify-between text-sm">
|
|
36
|
-
<span className="text-[var(--muted-foreground)]">
|
|
37
|
-
{start}–{end} of {total} results
|
|
38
|
-
</span>
|
|
39
|
-
|
|
40
|
-
<div className="flex items-center gap-2">
|
|
41
|
-
<select
|
|
42
|
-
value={perPage}
|
|
43
|
-
onChange={(e) => {
|
|
44
|
-
onPerPageChange(Number(e.target.value))
|
|
45
|
-
onPageChange(1)
|
|
46
|
-
}}
|
|
47
|
-
className="rounded-md border border-[var(--border)] bg-[var(--input-background)] px-2 py-1 text-sm"
|
|
48
|
-
>
|
|
49
|
-
{[10, 25, 50, 100].map((n) => (
|
|
50
|
-
<option key={n} value={n}>
|
|
51
|
-
{n} / page
|
|
52
|
-
</option>
|
|
53
|
-
))}
|
|
54
|
-
</select>
|
|
55
|
-
|
|
56
|
-
<nav className="flex items-center gap-1">
|
|
57
|
-
<button
|
|
58
|
-
onClick={() => onPageChange(page - 1)}
|
|
59
|
-
disabled={page <= 1}
|
|
60
|
-
className="rounded-md px-2 py-1 hover:bg-[var(--accent)] disabled:opacity-50"
|
|
61
|
-
>
|
|
62
|
-
Prev
|
|
63
|
-
</button>
|
|
64
|
-
{pageNumbers().map((p, i) =>
|
|
65
|
-
p === 'ellipsis' ? (
|
|
66
|
-
<span key={`e${i}`} className="px-1 text-[var(--muted-foreground)]">
|
|
67
|
-
…
|
|
68
|
-
</span>
|
|
69
|
-
) : (
|
|
70
|
-
<button
|
|
71
|
-
key={p}
|
|
72
|
-
onClick={() => onPageChange(p)}
|
|
73
|
-
className={`min-w-[2rem] rounded-md px-2 py-1 ${
|
|
74
|
-
p === page
|
|
75
|
-
? 'bg-[var(--primary)] text-[var(--primary-foreground)]'
|
|
76
|
-
: 'hover:bg-[var(--accent)]'
|
|
77
|
-
}`}
|
|
78
|
-
>
|
|
79
|
-
{p}
|
|
80
|
-
</button>
|
|
81
|
-
),
|
|
82
|
-
)}
|
|
83
|
-
<button
|
|
84
|
-
onClick={() => onPageChange(page + 1)}
|
|
85
|
-
disabled={page >= totalPages}
|
|
86
|
-
className="rounded-md px-2 py-1 hover:bg-[var(--accent)] disabled:opacity-50"
|
|
87
|
-
>
|
|
88
|
-
Next
|
|
89
|
-
</button>
|
|
90
|
-
</nav>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { forwardRef, type SelectHTMLAttributes, type ReactNode } from 'react'
|
|
2
|
-
import { cv, type VariantProps } from '../../lib/cv.js'
|
|
3
|
-
import { formControlBase, formControlState } from './form-control.js'
|
|
4
|
-
|
|
5
|
-
const select = cv(`${formControlBase} appearance-none pr-8`, {
|
|
6
|
-
variants: {
|
|
7
|
-
size: {
|
|
8
|
-
sm: 'py-1.5 pr-7 pl-3 text-[length:var(--fs-sub)]',
|
|
9
|
-
md: '',
|
|
10
|
-
lg: 'py-2.5 pr-9 pl-3',
|
|
11
|
-
},
|
|
12
|
-
state: {
|
|
13
|
-
default: formControlState.default,
|
|
14
|
-
invalid: formControlState.invalid,
|
|
15
|
-
valid: formControlState.valid,
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
defaultVariants: { size: 'md', state: 'default' },
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
export type SelectVariants = VariantProps<typeof select>
|
|
22
|
-
|
|
23
|
-
export interface SelectProps
|
|
24
|
-
extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'>, SelectVariants {
|
|
25
|
-
children?: ReactNode
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(
|
|
29
|
-
{ size, state, className, children, ...rest },
|
|
30
|
-
ref,
|
|
31
|
-
) {
|
|
32
|
-
return (
|
|
33
|
-
<div className="relative">
|
|
34
|
-
<select ref={ref} className={select({ size, state, class: className })} {...rest}>
|
|
35
|
-
{children}
|
|
36
|
-
</select>
|
|
37
|
-
<svg
|
|
38
|
-
aria-hidden
|
|
39
|
-
className="pointer-events-none absolute top-1/2 right-2 h-4 w-4 -translate-y-1/2 text-[var(--muted)]"
|
|
40
|
-
viewBox="0 0 20 20"
|
|
41
|
-
fill="currentColor"
|
|
42
|
-
>
|
|
43
|
-
<path
|
|
44
|
-
fillRule="evenodd"
|
|
45
|
-
d="M5.23 7.21a.75.75 0 011.06.02L10 11.06l3.71-3.83a.75.75 0 111.08 1.04l-4.25 4.39a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z"
|
|
46
|
-
clipRule="evenodd"
|
|
47
|
-
/>
|
|
48
|
-
</svg>
|
|
49
|
-
</div>
|
|
50
|
-
)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
export { select as selectVariants }
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useCallback } from 'react'
|
|
4
|
-
|
|
5
|
-
type ToastType = 'success' | 'error' | 'warning' | 'info'
|
|
6
|
-
|
|
7
|
-
interface Toast {
|
|
8
|
-
id: string
|
|
9
|
-
type: ToastType
|
|
10
|
-
message: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const typeClasses: Record<ToastType, string> = {
|
|
14
|
-
success: 'border-success bg-success/10 text-success',
|
|
15
|
-
error: 'border-destructive bg-destructive/10 text-destructive',
|
|
16
|
-
warning: 'border-warning bg-warning/10 text-warning',
|
|
17
|
-
info: 'border-info bg-info/10 text-info',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function useToast() {
|
|
21
|
-
const [toasts, setToasts] = useState<Toast[]>([])
|
|
22
|
-
|
|
23
|
-
const addToast = useCallback((type: ToastType, message: string) => {
|
|
24
|
-
const id = crypto.randomUUID()
|
|
25
|
-
setToasts((prev) => [...prev, { id, type, message }])
|
|
26
|
-
setTimeout(() => {
|
|
27
|
-
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
28
|
-
}, 5000)
|
|
29
|
-
}, [])
|
|
30
|
-
|
|
31
|
-
const dismissToast = useCallback((id: string) => {
|
|
32
|
-
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
33
|
-
}, [])
|
|
34
|
-
|
|
35
|
-
return { toasts, addToast, dismissToast }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ToastContainerProps {
|
|
39
|
-
toasts: Toast[]
|
|
40
|
-
onDismiss: (id: string) => void
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function ToastContainer({ toasts, onDismiss }: ToastContainerProps) {
|
|
44
|
-
if (toasts.length === 0) return null
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<div className="fixed right-4 bottom-4 z-[100] flex flex-col gap-2">
|
|
48
|
-
{toasts.map((toast) => (
|
|
49
|
-
<div
|
|
50
|
-
key={toast.id}
|
|
51
|
-
className={`flex items-center gap-3 rounded-md border-l-4 px-4 py-3 shadow-lg ${typeClasses[toast.type]}`}
|
|
52
|
-
>
|
|
53
|
-
<p className="flex-1 text-sm">{toast.message}</p>
|
|
54
|
-
<button
|
|
55
|
-
onClick={() => onDismiss(toast.id)}
|
|
56
|
-
className="shrink-0 opacity-60 hover:opacity-100"
|
|
57
|
-
>
|
|
58
|
-
<svg
|
|
59
|
-
className="h-4 w-4"
|
|
60
|
-
fill="none"
|
|
61
|
-
viewBox="0 0 24 24"
|
|
62
|
-
stroke="currentColor"
|
|
63
|
-
strokeWidth={2}
|
|
64
|
-
>
|
|
65
|
-
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
66
|
-
</svg>
|
|
67
|
-
</button>
|
|
68
|
-
</div>
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
export { Button, buttonVariants } from './Button.js'
|
|
2
|
-
export type { ButtonProps, ButtonVariants } from './Button.js'
|
|
3
|
-
export { Badge, badgeVariants, STATUS_TONE, STATUS_LABEL } from './Badge.js'
|
|
4
|
-
export type { BadgeProps, BadgeVariants, DocumentStatus } from './Badge.js'
|
|
5
|
-
export {
|
|
6
|
-
Card,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardContent,
|
|
11
|
-
CardFooter,
|
|
12
|
-
cardVariants,
|
|
13
|
-
} from './Card.js'
|
|
14
|
-
export type { CardProps, CardVariants } from './Card.js'
|
|
15
|
-
export { Input, inputVariants } from './Input.js'
|
|
16
|
-
export type { InputProps, InputVariants } from './Input.js'
|
|
17
|
-
export { Select, selectVariants } from './Select.js'
|
|
18
|
-
export type { SelectProps, SelectVariants } from './Select.js'
|
|
19
|
-
export { Avatar } from './Avatar.js'
|
|
20
|
-
export type { AvatarProps } from './Avatar.js'
|
|
21
|
-
export { EmptyState } from './EmptyState.js'
|
|
22
|
-
export type { EmptyStateProps } from './EmptyState.js'
|
|
23
|
-
export { Skeleton } from './Skeleton.js'
|
|
24
|
-
export type { SkeletonProps } from './Skeleton.js'
|
|
25
|
-
export { useToast, ToastContainer } from './Toast.js'
|
|
26
|
-
export type { ToastContainerProps } from './Toast.js'
|
|
27
|
-
export { Modal } from './Modal.js'
|
|
28
|
-
export type { ModalProps } from './Modal.js'
|
|
29
|
-
export { ConfirmDialog } from './ConfirmDialog.js'
|
|
30
|
-
export type { ConfirmDialogProps } from './ConfirmDialog.js'
|
|
31
|
-
export { ReauthDialog } from './ReauthDialog.js'
|
|
32
|
-
export type { ReauthDialogProps } from './ReauthDialog.js'
|
|
33
|
-
export { DataTable } from './DataTable.js'
|
|
34
|
-
export type { DataTableProps } from './DataTable.js'
|
|
35
|
-
export { Pagination } from './Pagination.js'
|
|
36
|
-
export type { PaginationProps } from './Pagination.js'
|
|
37
|
-
export { SearchInput } from './SearchInput.js'
|
|
38
|
-
export type { SearchInputProps } from './SearchInput.js'
|
|
39
|
-
export { Toggle, toggleTrackVariants, toggleThumbVariants } from './Toggle.js'
|
|
40
|
-
export type { ToggleProps, ToggleVariants } from './Toggle.js'
|
|
41
|
-
export { StatusBadge, statusBadgeVariants } from './StatusBadge.js'
|
|
42
|
-
export type { StatusBadgeProps, StatusBadgeVariants, StatusBadgeTone } from './StatusBadge.js'
|
|
43
|
-
export { FilterPill } from './FilterPill.js'
|
|
44
|
-
export type { FilterPillProps } from './FilterPill.js'
|
|
45
|
-
export { StatPill } from './StatPill.js'
|
|
46
|
-
export type { StatPillProps } from './StatPill.js'
|
|
47
|
-
export { RowActions, RowActionButton } from './RowActions.js'
|
|
48
|
-
export type { RowActionButtonProps } from './RowActions.js'
|
|
49
|
-
export {
|
|
50
|
-
SplitPaneLayout,
|
|
51
|
-
SplitPaneList,
|
|
52
|
-
InspectorPane,
|
|
53
|
-
InspectorPaneHeader,
|
|
54
|
-
InspectorPaneBody,
|
|
55
|
-
InspectorPaneSection,
|
|
56
|
-
InspectorPaneFooter,
|
|
57
|
-
} from './InspectorPane.js'
|
|
58
|
-
export type { InspectorPaneProps } from './InspectorPane.js'
|
|
59
|
-
export { formControlBase, formControlState } from './form-control.js'
|
|
60
|
-
export { CommandPalette } from './CommandPalette.js'
|
|
61
|
-
export type { CommandPaletteProps } from './CommandPalette.js'
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
-
|
|
5
|
-
interface ContentLock {
|
|
6
|
-
lockedBy: string | null
|
|
7
|
-
lockedAt: Date | null
|
|
8
|
-
isLocked: boolean
|
|
9
|
-
isLockedByMe: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function useContentLock(
|
|
13
|
-
documentId: string | undefined,
|
|
14
|
-
userId: string,
|
|
15
|
-
): ContentLock & {
|
|
16
|
-
acquireLock: () => Promise<boolean>
|
|
17
|
-
releaseLock: () => Promise<void>
|
|
18
|
-
} {
|
|
19
|
-
const [lock, setLock] = useState<ContentLock>({
|
|
20
|
-
lockedBy: null,
|
|
21
|
-
lockedAt: null,
|
|
22
|
-
isLocked: false,
|
|
23
|
-
isLockedByMe: false,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const acquireLock = useCallback(async () => {
|
|
27
|
-
if (!documentId) return false
|
|
28
|
-
setLock({
|
|
29
|
-
lockedBy: userId,
|
|
30
|
-
lockedAt: new Date(),
|
|
31
|
-
isLocked: true,
|
|
32
|
-
isLockedByMe: true,
|
|
33
|
-
})
|
|
34
|
-
return true
|
|
35
|
-
}, [documentId, userId])
|
|
36
|
-
|
|
37
|
-
const releaseLock = useCallback(async () => {
|
|
38
|
-
setLock({
|
|
39
|
-
lockedBy: null,
|
|
40
|
-
lockedAt: null,
|
|
41
|
-
isLocked: false,
|
|
42
|
-
isLockedByMe: false,
|
|
43
|
-
})
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
return () => {
|
|
48
|
-
if (lock.isLockedByMe) {
|
|
49
|
-
releaseLock()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}, [lock.isLockedByMe, releaseLock])
|
|
53
|
-
|
|
54
|
-
return { ...lock, acquireLock, releaseLock }
|
|
55
|
-
}
|
package/src/hooks/useDebounce.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react'
|
|
4
|
-
|
|
5
|
-
export function useDebounce<T>(value: T, delayMs: number): T {
|
|
6
|
-
const [debouncedValue, setDebouncedValue] = useState(value)
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
const timer = setTimeout(() => setDebouncedValue(value), delayMs)
|
|
10
|
-
return () => clearTimeout(timer)
|
|
11
|
-
}, [value, delayMs])
|
|
12
|
-
|
|
13
|
-
return debouncedValue
|
|
14
|
-
}
|