@actuate-media/cms-admin 0.67.0 → 0.68.0

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 (146) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +29 -5
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/hooks/useIdleLogout.test.d.ts +2 -0
  6. package/dist/__tests__/hooks/useIdleLogout.test.d.ts.map +1 -0
  7. package/dist/__tests__/hooks/useIdleLogout.test.js +64 -0
  8. package/dist/__tests__/hooks/useIdleLogout.test.js.map +1 -0
  9. package/dist/__tests__/lib/post-types-service.test.d.ts +2 -0
  10. package/dist/__tests__/lib/post-types-service.test.d.ts.map +1 -0
  11. package/dist/__tests__/lib/post-types-service.test.js +88 -0
  12. package/dist/__tests__/lib/post-types-service.test.js.map +1 -0
  13. package/dist/__tests__/views/collection-list-nav.render.test.js +43 -5
  14. package/dist/__tests__/views/collection-list-nav.render.test.js.map +1 -1
  15. package/dist/__tests__/views/forms-list.render.test.js +25 -6
  16. package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
  17. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +2 -0
  18. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +1 -0
  19. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +121 -0
  20. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +1 -0
  21. package/dist/__tests__/views/page-templates.render.test.d.ts +2 -0
  22. package/dist/__tests__/views/page-templates.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/views/page-templates.render.test.js +105 -0
  24. package/dist/__tests__/views/page-templates.render.test.js.map +1 -0
  25. package/dist/__tests__/views/post-type-editor.render.test.d.ts +2 -0
  26. package/dist/__tests__/views/post-type-editor.render.test.d.ts.map +1 -0
  27. package/dist/__tests__/views/post-type-editor.render.test.js +102 -0
  28. package/dist/__tests__/views/post-type-editor.render.test.js.map +1 -0
  29. package/dist/__tests__/views/security-settings.render.test.js +85 -4
  30. package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
  31. package/dist/__tests__/views/seo-content-pane.render.test.d.ts +2 -0
  32. package/dist/__tests__/views/seo-content-pane.render.test.d.ts.map +1 -0
  33. package/dist/__tests__/views/seo-content-pane.render.test.js +117 -0
  34. package/dist/__tests__/views/seo-content-pane.render.test.js.map +1 -0
  35. package/dist/actuate-admin.css +1 -1
  36. package/dist/components/collections/EntryDetailPane.d.ts +10 -0
  37. package/dist/components/collections/EntryDetailPane.d.ts.map +1 -0
  38. package/dist/components/collections/EntryDetailPane.js +92 -0
  39. package/dist/components/collections/EntryDetailPane.js.map +1 -0
  40. package/dist/components/forms/FieldsPanel.js +1 -1
  41. package/dist/components/forms/FieldsPanel.js.map +1 -1
  42. package/dist/components/forms/FormSchemaPane.d.ts +8 -0
  43. package/dist/components/forms/FormSchemaPane.d.ts.map +1 -0
  44. package/dist/components/forms/FormSchemaPane.js +95 -0
  45. package/dist/components/forms/FormSchemaPane.js.map +1 -0
  46. package/dist/components/seo/SeoEditorPane.d.ts +9 -0
  47. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -0
  48. package/dist/components/seo/SeoEditorPane.js +175 -0
  49. package/dist/components/seo/SeoEditorPane.js.map +1 -0
  50. package/dist/components/ui/Badge.d.ts +1 -1
  51. package/dist/components/ui/Button.d.ts +1 -1
  52. package/dist/components/ui/StatusBadge.d.ts +1 -1
  53. package/dist/hooks/useIdleLogout.d.ts +18 -0
  54. package/dist/hooks/useIdleLogout.d.ts.map +1 -0
  55. package/dist/hooks/useIdleLogout.js +66 -0
  56. package/dist/hooks/useIdleLogout.js.map +1 -0
  57. package/dist/lib/api.d.ts +1 -0
  58. package/dist/lib/api.d.ts.map +1 -1
  59. package/dist/lib/api.js +7 -1
  60. package/dist/lib/api.js.map +1 -1
  61. package/dist/lib/post-types-service.d.ts +44 -0
  62. package/dist/lib/post-types-service.d.ts.map +1 -0
  63. package/dist/lib/post-types-service.js +83 -0
  64. package/dist/lib/post-types-service.js.map +1 -0
  65. package/dist/views/CollectionList.d.ts.map +1 -1
  66. package/dist/views/CollectionList.js +23 -4
  67. package/dist/views/CollectionList.js.map +1 -1
  68. package/dist/views/Forms.d.ts.map +1 -1
  69. package/dist/views/Forms.js +25 -21
  70. package/dist/views/Forms.js.map +1 -1
  71. package/dist/views/Posts/PostTypeEditor.d.ts +16 -0
  72. package/dist/views/Posts/PostTypeEditor.d.ts.map +1 -0
  73. package/dist/views/Posts/PostTypeEditor.js +159 -0
  74. package/dist/views/Posts/PostTypeEditor.js.map +1 -0
  75. package/dist/views/Posts/PostTypesView.d.ts +3 -2
  76. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  77. package/dist/views/Posts/PostTypesView.js +1 -1
  78. package/dist/views/Posts/PostTypesView.js.map +1 -1
  79. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +20 -0
  80. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +1 -0
  81. package/dist/views/page-builder/BuilderSectionsPanel.js +105 -0
  82. package/dist/views/page-builder/BuilderSectionsPanel.js.map +1 -0
  83. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  84. package/dist/views/page-builder/PageBuilder.js +51 -2
  85. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  86. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  87. package/dist/views/page-builder/PageTemplates.js +113 -6
  88. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  89. package/dist/views/seo/ContentTab.d.ts.map +1 -1
  90. package/dist/views/seo/ContentTab.js +22 -6
  91. package/dist/views/seo/ContentTab.js.map +1 -1
  92. package/dist/views/settings/AccessProtectionCard.d.ts +5 -1
  93. package/dist/views/settings/AccessProtectionCard.d.ts.map +1 -1
  94. package/dist/views/settings/AccessProtectionCard.js +13 -10
  95. package/dist/views/settings/AccessProtectionCard.js.map +1 -1
  96. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  97. package/dist/views/settings/SecurityTab.js +3 -3
  98. package/dist/views/settings/SecurityTab.js.map +1 -1
  99. package/dist/views/settings/SessionPolicyCard.d.ts +1 -2
  100. package/dist/views/settings/SessionPolicyCard.d.ts.map +1 -1
  101. package/dist/views/settings/SessionPolicyCard.js +12 -5
  102. package/dist/views/settings/SessionPolicyCard.js.map +1 -1
  103. package/dist/views/settings/useSecuritySettings.d.ts +13 -1
  104. package/dist/views/settings/useSecuritySettings.d.ts.map +1 -1
  105. package/dist/views/settings/useSecuritySettings.js +47 -4
  106. package/dist/views/settings/useSecuritySettings.js.map +1 -1
  107. package/package.json +2 -2
  108. package/src/AdminRoot.tsx +36 -7
  109. package/src/__tests__/hooks/useIdleLogout.test.tsx +86 -0
  110. package/src/__tests__/lib/post-types-service.test.ts +112 -0
  111. package/src/__tests__/views/collection-list-nav.render.test.tsx +51 -5
  112. package/src/__tests__/views/forms-list.render.test.tsx +34 -6
  113. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +138 -0
  114. package/src/__tests__/views/page-templates.render.test.tsx +142 -0
  115. package/src/__tests__/views/post-type-editor.render.test.tsx +116 -0
  116. package/src/__tests__/views/security-settings.render.test.tsx +111 -4
  117. package/src/__tests__/views/seo-content-pane.render.test.tsx +142 -0
  118. package/src/components/collections/EntryDetailPane.tsx +182 -0
  119. package/src/components/forms/FieldsPanel.tsx +3 -1
  120. package/src/components/forms/FormSchemaPane.tsx +192 -0
  121. package/src/components/seo/SeoEditorPane.tsx +533 -0
  122. package/src/hooks/useIdleLogout.ts +77 -0
  123. package/src/lib/api.ts +8 -2
  124. package/src/lib/post-types-service.ts +129 -0
  125. package/src/views/CollectionList.tsx +158 -110
  126. package/src/views/Forms.tsx +154 -126
  127. package/src/views/Posts/PostTypeEditor.tsx +568 -0
  128. package/src/views/Posts/PostTypesView.tsx +5 -4
  129. package/src/views/page-builder/BuilderSectionsPanel.tsx +304 -0
  130. package/src/views/page-builder/PageBuilder.tsx +69 -2
  131. package/src/views/page-builder/PageTemplates.tsx +340 -57
  132. package/src/views/seo/ContentTab.tsx +113 -80
  133. package/src/views/settings/AccessProtectionCard.tsx +83 -53
  134. package/src/views/settings/SecurityTab.tsx +23 -2
  135. package/src/views/settings/SessionPolicyCard.tsx +45 -9
  136. package/src/views/settings/useSecuritySettings.ts +78 -22
  137. package/dist/components/forms/FormSchemaDrawer.d.ts +0 -9
  138. package/dist/components/forms/FormSchemaDrawer.d.ts.map +0 -1
  139. package/dist/components/forms/FormSchemaDrawer.js +0 -99
  140. package/dist/components/forms/FormSchemaDrawer.js.map +0 -1
  141. package/dist/components/seo/SeoEditorDrawer.d.ts +0 -8
  142. package/dist/components/seo/SeoEditorDrawer.d.ts.map +0 -1
  143. package/dist/components/seo/SeoEditorDrawer.js +0 -172
  144. package/dist/components/seo/SeoEditorDrawer.js.map +0 -1
  145. package/src/components/forms/FormSchemaDrawer.tsx +0 -192
  146. package/src/components/seo/SeoEditorDrawer.tsx +0 -529
@@ -5,7 +5,8 @@
5
5
  * Forms list screenshot: summary chips, search + status filter pills, and a
6
6
  * compact table (Form / Fields / Status / Entries / Last Entry / Notify /
7
7
  * Schema). All data flows through `lib/forms-service.ts`; the Schema action
8
- * opens the right-side `FormSchemaDrawer`.
8
+ * opens the inline `FormSchemaPane` beside the table (design's 390px schema
9
+ * panel — previously an overlay drawer).
9
10
  */
10
11
  import { ChevronRight, ClipboardList, Plus, Search, Settings2 } from 'lucide-react'
11
12
  import { Toggle } from '../components/ui/Toggle.js'
@@ -21,7 +22,8 @@ import {
21
22
  type FormStatus,
22
23
  } from '../lib/forms-service.js'
23
24
  import { emitFormsChanged, onFormsChanged } from '../lib/forms-events.js'
24
- import { FormSchemaDrawer } from '../components/forms/FormSchemaDrawer.js'
25
+ import { InspectorPane, SplitPaneLayout, SplitPaneList } from '../components/ui/InspectorPane.js'
26
+ import { FormSchemaPane } from '../components/forms/FormSchemaPane.js'
25
27
  import {
26
28
  FieldTypeBadge,
27
29
  FormStatusBadge,
@@ -76,8 +78,9 @@ export function Forms({ onNavigate }: FormsProps) {
76
78
  const [search, setSearch] = useState('')
77
79
  const [filter, setFilter] = useState<StatusFilter>('all')
78
80
  const [page, setPage] = useState(1)
79
- const [schemaForm, setSchemaForm] = useState<FormListItem | null>(null)
80
- const [schemaOpen, setSchemaOpen] = useState(false)
81
+ // Form whose schema pane is open. Derived against `forms` so a list refetch
82
+ // (e.g. after saving) keeps the pane's header data fresh.
83
+ const [schemaFormId, setSchemaFormId] = useState<string | null>(null)
81
84
 
82
85
  const load = useCallback(async () => {
83
86
  setLoading(true)
@@ -137,9 +140,13 @@ export function Forms({ onNavigate }: FormsProps) {
137
140
  [filtered, page],
138
141
  )
139
142
 
140
- const openSchema = useCallback((form: FormListItem) => {
141
- setSchemaForm(form)
142
- setSchemaOpen(true)
143
+ const schemaForm = useMemo(
144
+ () => (schemaFormId ? (forms.find((f) => f.id === schemaFormId) ?? null) : null),
145
+ [forms, schemaFormId],
146
+ )
147
+
148
+ const toggleSchema = useCallback((form: FormListItem) => {
149
+ setSchemaFormId((prev) => (prev === form.id ? null : form.id))
143
150
  }, [])
144
151
 
145
152
  const handleNotifyToggle = useCallback(async (form: FormListItem, next: boolean) => {
@@ -155,139 +162,154 @@ export function Forms({ onNavigate }: FormsProps) {
155
162
  }, [])
156
163
 
157
164
  return (
158
- <div className="space-y-5 p-6">
159
- <header className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
160
- <div>
161
- <h1 className="text-foreground">Forms</h1>
162
- <p className="text-muted-foreground mt-1 text-sm">
163
- Manage forms and view incoming submissions
164
- </p>
165
- </div>
166
- <button className={btnPrimary} onClick={() => onNavigate?.('/forms/new')}>
167
- <Plus className="h-4 w-4" aria-hidden />
168
- New Form
169
- </button>
170
- </header>
165
+ <div className="flex h-full min-h-0 flex-col">
166
+ <div className="space-y-5 p-6 pb-4">
167
+ <header className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
168
+ <div>
169
+ <h1 className="text-foreground">Forms</h1>
170
+ <p className="text-muted-foreground mt-1 text-sm">
171
+ Manage forms and view incoming submissions
172
+ </p>
173
+ </div>
174
+ <button className={btnPrimary} onClick={() => onNavigate?.('/forms/new')}>
175
+ <Plus className="h-4 w-4" aria-hidden />
176
+ New Form
177
+ </button>
178
+ </header>
171
179
 
172
- {stats && (
173
- <div className="flex flex-wrap gap-2">
174
- <SummaryChip value={stats.totalForms} label={stats.totalForms === 1 ? 'form' : 'forms'} />
175
- <SummaryChip value={stats.activeForms} label="active" tone="success" />
176
- <SummaryChip value={stats.totalEntries} label="total entries" />
177
- <SummaryChip
178
- value={stats.unreadEntries}
179
- label="unread"
180
- tone={stats.unreadEntries > 0 ? 'destructive' : 'neutral'}
181
- />
182
- </div>
183
- )}
180
+ {stats && (
181
+ <div className="flex flex-wrap gap-2">
182
+ <SummaryChip
183
+ value={stats.totalForms}
184
+ label={stats.totalForms === 1 ? 'form' : 'forms'}
185
+ />
186
+ <SummaryChip value={stats.activeForms} label="active" tone="success" />
187
+ <SummaryChip value={stats.totalEntries} label="total entries" />
188
+ <SummaryChip
189
+ value={stats.unreadEntries}
190
+ label="unread"
191
+ tone={stats.unreadEntries > 0 ? 'destructive' : 'neutral'}
192
+ />
193
+ </div>
194
+ )}
184
195
 
185
- <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
186
- <div className="relative w-full sm:max-w-xs">
187
- <Search
188
- className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2"
189
- aria-hidden
190
- />
191
- <input
192
- type="search"
193
- placeholder="Search forms…"
194
- value={search}
195
- onChange={(e) => {
196
- setSearch(e.target.value)
197
- setPage(1)
198
- }}
199
- aria-label="Search forms"
200
- className="border-border bg-input-background text-foreground focus-visible:ring-ring w-full rounded-lg border py-2 pr-3 pl-9 text-sm focus-visible:ring-2 focus-visible:outline-none"
201
- />
202
- </div>
203
- <div className="flex gap-1" role="group" aria-label="Filter by status">
204
- {FILTERS.map((f) => (
205
- <button
206
- key={f.id}
207
- type="button"
208
- onClick={() => {
209
- setFilter(f.id)
196
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
197
+ <div className="relative w-full sm:max-w-xs">
198
+ <Search
199
+ className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2"
200
+ aria-hidden
201
+ />
202
+ <input
203
+ type="search"
204
+ placeholder="Search forms…"
205
+ value={search}
206
+ onChange={(e) => {
207
+ setSearch(e.target.value)
210
208
  setPage(1)
211
209
  }}
212
- aria-pressed={filter === f.id}
213
- className={`rounded-full px-3 py-1.5 text-sm font-medium transition-colors ${
214
- filter === f.id
215
- ? 'bg-primary text-primary-foreground'
216
- : 'text-muted-foreground hover:bg-accent hover:text-foreground'
217
- }`}
218
- >
219
- {f.label}
220
- </button>
221
- ))}
210
+ aria-label="Search forms"
211
+ className="border-border bg-input-background text-foreground focus-visible:ring-ring w-full rounded-lg border py-2 pr-3 pl-9 text-sm focus-visible:ring-2 focus-visible:outline-none"
212
+ />
213
+ </div>
214
+ <div className="flex gap-1" role="group" aria-label="Filter by status">
215
+ {FILTERS.map((f) => (
216
+ <button
217
+ key={f.id}
218
+ type="button"
219
+ onClick={() => {
220
+ setFilter(f.id)
221
+ setPage(1)
222
+ }}
223
+ aria-pressed={filter === f.id}
224
+ className={`rounded-full px-3 py-1.5 text-sm font-medium transition-colors ${
225
+ filter === f.id
226
+ ? 'bg-primary text-primary-foreground'
227
+ : 'text-muted-foreground hover:bg-accent hover:text-foreground'
228
+ }`}
229
+ >
230
+ {f.label}
231
+ </button>
232
+ ))}
233
+ </div>
222
234
  </div>
223
235
  </div>
224
236
 
225
- {error ? (
226
- <FormsErrorState message={error} onRetry={load} />
227
- ) : loading ? (
228
- <FormsLoading label="Loading forms…" />
229
- ) : forms.length === 0 ? (
230
- <FormsEmptyState
231
- icon={<ClipboardList className="h-6 w-6" aria-hidden />}
232
- title="No forms yet"
233
- description="Create your first form to start collecting submissions."
234
- action={
235
- <button className={btnPrimary} onClick={() => onNavigate?.('/forms/new')}>
236
- <Plus className="h-4 w-4" aria-hidden />
237
- Create New Form
238
- </button>
239
- }
240
- />
241
- ) : filtered.length === 0 ? (
242
- <FormsEmptyState
243
- title="No forms match your filters"
244
- description="Try a different search term or status filter."
245
- action={
246
- <button
247
- type="button"
248
- onClick={() => {
249
- setSearch('')
250
- setFilter('all')
251
- setPage(1)
252
- }}
253
- className="text-primary text-sm font-medium hover:underline"
254
- >
255
- Clear filters
256
- </button>
257
- }
258
- />
259
- ) : (
260
- <FormsTable
261
- forms={paged}
262
- onOpenForm={(id) => onNavigate?.(`/forms/${id}/edit`)}
263
- onOpenEntries={(id) => onNavigate?.(`/forms/${id}/submissions`)}
264
- onOpenSchema={openSchema}
265
- onNotifyToggle={handleNotifyToggle}
266
- footer={
267
- <FormsPagination
268
- page={page}
269
- pageSize={PAGE_SIZE}
270
- total={filtered.length}
271
- onPageChange={setPage}
272
- noun="form"
273
- label="Forms pagination"
237
+ <SplitPaneLayout>
238
+ <SplitPaneList className="px-6 pb-6">
239
+ {error ? (
240
+ <FormsErrorState message={error} onRetry={load} />
241
+ ) : loading ? (
242
+ <FormsLoading label="Loading forms…" />
243
+ ) : forms.length === 0 ? (
244
+ <FormsEmptyState
245
+ icon={<ClipboardList className="h-6 w-6" aria-hidden />}
246
+ title="No forms yet"
247
+ description="Create your first form to start collecting submissions."
248
+ action={
249
+ <button className={btnPrimary} onClick={() => onNavigate?.('/forms/new')}>
250
+ <Plus className="h-4 w-4" aria-hidden />
251
+ Create New Form
252
+ </button>
253
+ }
274
254
  />
275
- }
276
- />
277
- )}
255
+ ) : filtered.length === 0 ? (
256
+ <FormsEmptyState
257
+ title="No forms match your filters"
258
+ description="Try a different search term or status filter."
259
+ action={
260
+ <button
261
+ type="button"
262
+ onClick={() => {
263
+ setSearch('')
264
+ setFilter('all')
265
+ setPage(1)
266
+ }}
267
+ className="text-primary text-sm font-medium hover:underline"
268
+ >
269
+ Clear filters
270
+ </button>
271
+ }
272
+ />
273
+ ) : (
274
+ <FormsTable
275
+ forms={paged}
276
+ selectedId={schemaFormId}
277
+ onOpenForm={(id) => onNavigate?.(`/forms/${id}/edit`)}
278
+ onOpenEntries={(id) => onNavigate?.(`/forms/${id}/submissions`)}
279
+ onOpenSchema={toggleSchema}
280
+ onNotifyToggle={handleNotifyToggle}
281
+ footer={
282
+ <FormsPagination
283
+ page={page}
284
+ pageSize={PAGE_SIZE}
285
+ total={filtered.length}
286
+ onPageChange={setPage}
287
+ noun="form"
288
+ label="Forms pagination"
289
+ />
290
+ }
291
+ />
292
+ )}
293
+ </SplitPaneList>
278
294
 
279
- <FormSchemaDrawer
280
- form={schemaForm}
281
- open={schemaOpen}
282
- onOpenChange={setSchemaOpen}
283
- onSaved={load}
284
- />
295
+ <InspectorPane open={schemaForm !== null} width={390} aria-label="Form schema">
296
+ {schemaForm && (
297
+ <FormSchemaPane
298
+ key={schemaForm.id}
299
+ form={schemaForm}
300
+ onClose={() => setSchemaFormId(null)}
301
+ onSaved={load}
302
+ />
303
+ )}
304
+ </InspectorPane>
305
+ </SplitPaneLayout>
285
306
  </div>
286
307
  )
287
308
  }
288
309
 
289
310
  function FormsTable({
290
311
  forms,
312
+ selectedId,
291
313
  onOpenForm,
292
314
  onOpenEntries,
293
315
  onOpenSchema,
@@ -295,6 +317,8 @@ function FormsTable({
295
317
  footer,
296
318
  }: {
297
319
  forms: FormListItem[]
320
+ /** Form whose schema pane is currently open (row highlight). */
321
+ selectedId: string | null
298
322
  onOpenForm: (id: string) => void
299
323
  onOpenEntries: (id: string) => void
300
324
  onOpenSchema: (form: FormListItem) => void
@@ -335,10 +359,13 @@ function FormsTable({
335
359
  const fieldCount = form.fieldCount ?? form.fields.length
336
360
  const unread = form.unreadEntries ?? 0
337
361
  const muted = form.status === 'archived'
362
+ const isSelected = form.id === selectedId
338
363
  return (
339
364
  <tr
340
365
  key={form.id}
341
- className={`border-border hover:bg-accent/40 border-b transition-colors last:border-b-0 ${muted ? 'opacity-60' : ''}`}
366
+ className={`border-border border-b transition-colors last:border-b-0 ${
367
+ isSelected ? 'bg-[var(--acc-l)]' : 'hover:bg-accent/40'
368
+ } ${muted ? 'opacity-60' : ''}`}
342
369
  >
343
370
  <td className="px-4 py-3">
344
371
  <button
@@ -393,6 +420,7 @@ function FormsTable({
393
420
  <button
394
421
  type="button"
395
422
  onClick={() => onOpenSchema(form)}
423
+ aria-expanded={isSelected}
396
424
  className="border-border text-foreground hover:bg-accent focus-visible:ring-ring inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none"
397
425
  >
398
426
  <Settings2 className="h-3.5 w-3.5" aria-hidden />