@shamar/adonis 0.1.1 → 0.1.3

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 (46) hide show
  1. package/README.md +38 -0
  2. package/assets/admin.css +1 -1
  3. package/assets/input.css +42 -2
  4. package/assets/shamar-ui.js +290 -5
  5. package/dist/config.d.ts +27 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/controllers/admin_controller.d.ts +2 -0
  9. package/dist/controllers/admin_controller.d.ts.map +1 -1
  10. package/dist/controllers/admin_controller.js +80 -3
  11. package/dist/controllers/admin_controller.js.map +1 -1
  12. package/dist/provider.d.ts.map +1 -1
  13. package/dist/provider.js +2 -1
  14. package/dist/provider.js.map +1 -1
  15. package/dist/routes.d.ts.map +1 -1
  16. package/dist/routes.js +27 -5
  17. package/dist/routes.js.map +1 -1
  18. package/dist/shamar/branding.d.ts +11 -10
  19. package/dist/shamar/branding.d.ts.map +1 -1
  20. package/dist/shamar/branding.js +42 -2
  21. package/dist/shamar/branding.js.map +1 -1
  22. package/dist/shamar/list-query.d.ts +11 -2
  23. package/dist/shamar/list-query.d.ts.map +1 -1
  24. package/dist/shamar/list-query.js +29 -1
  25. package/dist/shamar/list-query.js.map +1 -1
  26. package/dist/shamar/relation-fields.d.ts +15 -0
  27. package/dist/shamar/relation-fields.d.ts.map +1 -1
  28. package/dist/shamar/relation-fields.js +22 -2
  29. package/dist/shamar/relation-fields.js.map +1 -1
  30. package/dist/shamar/relation-table.d.ts +37 -0
  31. package/dist/shamar/relation-table.d.ts.map +1 -0
  32. package/dist/shamar/relation-table.js +61 -0
  33. package/dist/shamar/relation-table.js.map +1 -0
  34. package/dist/shamar/view-context.d.ts +6 -0
  35. package/dist/shamar/view-context.d.ts.map +1 -1
  36. package/dist/shamar/view-context.js +4 -0
  37. package/dist/shamar/view-context.js.map +1 -1
  38. package/package.json +5 -5
  39. package/resources/views/shamar/form.edge +7 -1
  40. package/resources/views/shamar/index.edge +3 -2
  41. package/resources/views/shamar/partials/detail-entry.edge +9 -0
  42. package/resources/views/shamar/partials/field-relation-table.edge +309 -105
  43. package/resources/views/shamar/partials/list-row.edge +17 -4
  44. package/resources/views/shamar/partials/list-toolbar.edge +1 -1
  45. package/resources/views/shamar/partials/shell-open.edge +4 -0
  46. package/resources/views/shamar/show.edge +59 -14
@@ -18,122 +18,326 @@
18
18
  @endif
19
19
 
20
20
  <div class="shamar-m2m-table__card rounded-md border border-default">
21
- <div class="shamar-m2m-table__toolbar flex flex-wrap items-center justify-between gap-2 border-b border-default bg-surface-header px-3 py-2">
22
- <p class="text-xs font-medium text-body-subtle">
23
- <span x-text="selected.length"></span> linked
24
- <span x-text="singularLabel.toLowerCase()"></span><span x-show="selected.length !== 1">s</span>
25
- </p>
26
- <div class="shamar-m2m-table__add relative" x-show="!readonly">
27
- <button
28
- type="button"
29
- class="inline-flex items-center gap-1 rounded-md border border-default bg-neutral-primary px-2.5 py-1 text-xs font-medium text-body hover:border-fg-brand hover:text-fg-brand"
30
- @click="toggleAdd()"
31
- >
32
- <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
33
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
34
- </svg>
35
- Add
36
- </button>
37
- <div
38
- x-show="addOpen"
39
- x-cloak
40
- x-transition.opacity.duration.100ms
41
- class="shamar-m2m-table__add-dropdown absolute right-0 z-50 mt-1 w-72 rounded-md border border-default bg-neutral-primary text-sm shadow-lg"
42
- >
43
- <div class="border-b border-default p-2">
44
- <input
45
- type="text"
46
- x-ref="addInput"
47
- x-model="query"
48
- @input.debounce.250ms="fetchResults()"
49
- @keydown.arrow-down.prevent="moveCursor(1)"
50
- @keydown.arrow-up.prevent="moveCursor(-1)"
51
- @keydown.enter.prevent="onEnter()"
52
- placeholder="Search to add…"
53
- autocomplete="off"
54
- class="shamar-input w-full text-sm"
55
- />
56
- </div>
57
- <div class="max-h-56 overflow-auto">
58
- <template x-if="loading">
59
- <div class="px-3 py-2 text-xs text-body-subtle">Searching…</div>
60
- </template>
61
- <template x-if="!loading && availableResults.length === 0 && !createCandidate">
62
- <div class="px-3 py-2 text-xs text-body-subtle">No matches.</div>
63
- </template>
64
- <ul x-show="!loading && (availableResults.length > 0 || createCandidate || canCreateAndEdit)">
65
- <template x-for="(item, i) in availableResults" :key="item.id">
66
- <li
67
- class="cursor-pointer truncate px-3 py-1.5"
68
- :class="i === cursor ? 'bg-brand-softer text-fg-brand' : 'text-body hover:bg-neutral-secondary'"
69
- @mousedown.prevent="pick(item)"
70
- @mousemove="cursor = i"
71
- x-text="item.label"
72
- ></li>
73
- </template>
74
- <template x-if="createCandidate">
75
- <li
76
- class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-fg-success hover:bg-neutral-secondary"
77
- @click="createFromQuery()"
78
- >
79
- <span>Create "<span x-text="query"></span>"</span>
80
- </li>
21
+ <div class="shamar-m2m-table__toolbar flex flex-col gap-2 border-b border-default bg-surface-header px-3 py-2">
22
+ <div class="flex flex-wrap items-center justify-between gap-2">
23
+ <p class="text-xs font-medium text-body-subtle">
24
+ <span x-show="listUrl">
25
+ <span x-text="total"></span>
26
+ <span x-text="singularLabel.toLowerCase()"></span><span x-show="total !== 1">s</span>
27
+ </span>
28
+ <span x-show="!listUrl">
29
+ <span x-text="selected.length"></span> linked
30
+ <span x-text="singularLabel.toLowerCase()"></span><span x-show="selected.length !== 1">s</span>
31
+ </span>
32
+ </p>
33
+ <div class="flex flex-wrap items-center gap-2">
34
+ <div class="relative" x-show="listUrl && filterableHeaders.length">
35
+ <button
36
+ type="button"
37
+ class="inline-flex items-center gap-1 rounded-md border border-default bg-neutral-primary px-2.5 py-1 text-xs font-medium text-body hover:border-fg-brand hover:text-fg-brand"
38
+ @click="filterPanelOpen = !filterPanelOpen"
39
+ >
40
+ Filters
41
+ <span
42
+ x-show="listFilters.length"
43
+ class="rounded-full bg-brand-softer px-1.5 text-[10px] font-semibold text-fg-brand"
44
+ x-text="listFilters.length"
45
+ ></span>
46
+ </button>
47
+ <div
48
+ x-show="filterPanelOpen"
49
+ x-cloak
50
+ @click.outside="filterPanelOpen = false"
51
+ class="absolute right-0 z-50 mt-1 w-64 rounded-md border border-default bg-neutral-primary p-2 text-sm shadow-lg"
52
+ >
53
+ <template x-for="header in filterableHeaders" :key="header.name">
54
+ <div class="mb-2 last:mb-0">
55
+ <p class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-body-subtle" x-text="header.label"></p>
56
+ <div class="flex gap-1" x-show="header.filter_kind === 'boolean'">
57
+ <button
58
+ type="button"
59
+ class="rounded-md border px-2 py-0.5 text-xs"
60
+ :class="hasFilter(header, true) ? 'border-fg-brand bg-brand-softer text-fg-brand' : 'border-default text-body'"
61
+ @click="toggleBooleanFilter(header, true)"
62
+ >Yes</button>
63
+ <button
64
+ type="button"
65
+ class="rounded-md border px-2 py-0.5 text-xs"
66
+ :class="hasFilter(header, false) ? 'border-fg-brand bg-brand-softer text-fg-brand' : 'border-default text-body'"
67
+ @click="toggleBooleanFilter(header, false)"
68
+ >No</button>
69
+ </div>
70
+ <div
71
+ class="flex max-h-36 flex-col gap-0.5 overflow-auto"
72
+ x-show="header.filter_kind === 'select' && header.options"
73
+ >
74
+ <template x-for="opt in (header.options || [])" :key="String(opt.value)">
75
+ <button
76
+ type="button"
77
+ class="rounded-md px-2 py-1 text-left text-xs hover:bg-surface-hover"
78
+ :class="hasFilter(header, opt.value) ? 'bg-brand-softer text-fg-brand' : 'text-body'"
79
+ @click="toggleSelectFilter(header, opt)"
80
+ x-text="opt.label"
81
+ ></button>
82
+ </template>
83
+ </div>
84
+ </div>
81
85
  </template>
82
- <template x-if="canCreateAndEdit">
83
- <li
84
- class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-body hover:bg-neutral-secondary"
85
- @click="createAndEdit()"
86
- >
87
- Create and edit…
88
- </li>
89
- </template>
90
- </ul>
86
+ <button
87
+ type="button"
88
+ class="mt-1 w-full rounded-md border border-default px-2 py-1 text-xs text-body hover:bg-surface-hover"
89
+ x-show="listFilters.length"
90
+ @click="clearListFilters()"
91
+ >Clear filters</button>
92
+ </div>
93
+ </div>
94
+
95
+ <div class="shamar-m2m-table__add relative" x-show="!readonly">
96
+ <button
97
+ type="button"
98
+ class="inline-flex items-center gap-1 rounded-md border border-default bg-neutral-primary px-2.5 py-1 text-xs font-medium text-body hover:border-fg-brand hover:text-fg-brand"
99
+ @click="toggleAdd()"
100
+ >
101
+ <svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
102
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
103
+ </svg>
104
+ Add
105
+ </button>
106
+ <div
107
+ x-show="addOpen"
108
+ x-cloak
109
+ x-transition.opacity.duration.100ms
110
+ class="shamar-m2m-table__add-dropdown absolute right-0 z-50 mt-1 w-72 rounded-md border border-default bg-neutral-primary text-sm shadow-lg"
111
+ >
112
+ <div class="border-b border-default p-2">
113
+ <input
114
+ type="text"
115
+ x-ref="addInput"
116
+ x-model="query"
117
+ @input.debounce.250ms="fetchResults()"
118
+ @keydown.arrow-down.prevent="moveCursor(1)"
119
+ @keydown.arrow-up.prevent="moveCursor(-1)"
120
+ @keydown.enter.prevent="onEnter()"
121
+ placeholder="Search to add…"
122
+ autocomplete="off"
123
+ class="shamar-input w-full text-sm"
124
+ />
125
+ </div>
126
+ <div class="max-h-56 overflow-auto">
127
+ <template x-if="loading">
128
+ <div class="px-3 py-2 text-xs text-body-subtle">Searching…</div>
129
+ </template>
130
+ <template x-if="!loading && availableResults.length === 0 && !createCandidate">
131
+ <div class="px-3 py-2 text-xs text-body-subtle">No matches.</div>
132
+ </template>
133
+ <ul x-show="!loading && (availableResults.length > 0 || createCandidate || canCreateAndEdit)">
134
+ <template x-for="(item, i) in availableResults" :key="item.id">
135
+ <li
136
+ class="cursor-pointer truncate px-3 py-1.5"
137
+ :class="i === cursor ? 'bg-brand-softer text-fg-brand' : 'text-body hover:bg-neutral-secondary'"
138
+ @mousedown.prevent="pick(item)"
139
+ @mousemove="cursor = i"
140
+ x-text="item.label"
141
+ ></li>
142
+ </template>
143
+ <template x-if="createCandidate">
144
+ <li
145
+ class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-fg-success hover:bg-neutral-secondary"
146
+ @click="createFromQuery()"
147
+ >
148
+ <span>Create "<span x-text="query"></span>"</span>
149
+ </li>
150
+ </template>
151
+ <template x-if="canCreateAndEdit">
152
+ <li
153
+ class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-body hover:bg-neutral-secondary"
154
+ @click="createAndEdit()"
155
+ >
156
+ Create and edit…
157
+ </li>
158
+ </template>
159
+ </ul>
160
+ </div>
161
+ </div>
91
162
  </div>
92
163
  </div>
93
164
  </div>
165
+
166
+ <div class="flex flex-wrap items-center gap-2" x-show="listUrl">
167
+ <input
168
+ type="search"
169
+ x-model="listSearch"
170
+ @input.debounce.300ms="applyListSearch()"
171
+ placeholder="Search…"
172
+ class="shamar-input max-w-xs flex-1 text-sm"
173
+ autocomplete="off"
174
+ />
175
+ <template x-for="chip in listFilters" :key="chip.field + String(chip.value)">
176
+ <button
177
+ type="button"
178
+ class="inline-flex items-center gap-1 rounded-full border border-default bg-neutral-primary px-2 py-0.5 text-[11px] text-body hover:border-fg-danger hover:text-fg-danger"
179
+ @click="removeListFilter(chip)"
180
+ >
181
+ <span x-text="chip.label || chip.field"></span>
182
+ <span aria-hidden="true">×</span>
183
+ </button>
184
+ </template>
185
+ </div>
94
186
  </div>
95
187
 
96
- <table class="w-full text-sm">
97
- <thead>
98
- <tr class="border-b border-default text-left text-xs uppercase tracking-wider text-body-subtle">
99
- <th class="px-3 py-2 font-semibold">Record</th>
100
- <th class="px-3 py-2 text-right font-semibold" x-show="!readonly">Actions</th>
101
- </tr>
102
- </thead>
103
- <tbody>
104
- <template x-if="selected.length === 0">
105
- <tr>
106
- <td colspan="2" class="px-3 py-8 text-center text-xs text-body-subtle">No linked records yet.</td>
188
+ <div class="overflow-x-auto">
189
+ <table class="w-full text-sm">
190
+ <thead>
191
+ <tr class="border-b border-default text-left text-xs uppercase tracking-wider text-body-subtle">
192
+ <th class="px-3 py-2 font-semibold" x-show="!listUrl || !columns.length">Record</th>
193
+ <template x-for="column in columns" :key="column.name">
194
+ <th
195
+ class="px-3 py-2 font-semibold"
196
+ x-show="listUrl"
197
+ :class="[
198
+ column.alignmentClass || '',
199
+ column.sortable ? 'cursor-pointer select-none hover:text-fg-brand' : '',
200
+ ]"
201
+ @click="column.sortable && toggleListSort(column.name)"
202
+ >
203
+ <span class="inline-flex items-center gap-1">
204
+ <span x-text="column.label"></span>
205
+ <span
206
+ x-show="listSort === column.name"
207
+ class="text-fg-brand"
208
+ x-text="listDirection === 'asc' ? '↑' : '↓'"
209
+ ></span>
210
+ </span>
211
+ </th>
212
+ </template>
213
+ <th class="px-3 py-2 text-right font-semibold" x-show="!readonly">Actions</th>
107
214
  </tr>
108
- </template>
109
- <template x-for="item in selected" :key="item.id">
110
- <tr class="border-b border-default last:border-0 hover:bg-surface-row-hover">
111
- <td class="px-3 py-2">
112
- <button
113
- type="button"
114
- class="text-left text-heading hover:text-fg-brand hover:underline"
115
- @click="openRecord(item)"
116
- x-text="item.label"
117
- ></button>
215
+ </thead>
216
+ <tbody>
217
+ {{-- List mode --}}
218
+ <tr x-show="listUrl && listLoading">
219
+ <td :colspan="Math.max(columns.length, 1) + (readonly ? 0 : 1)" class="px-3 py-8 text-center text-xs text-body-subtle">
220
+ Loading…
118
221
  </td>
119
- <td class="px-3 py-2 text-right" x-show="!readonly">
120
- <div class="inline-flex items-center gap-1 text-xs">
222
+ </tr>
223
+ <tr x-show="listUrl && !listLoading && rows.length === 0">
224
+ <td :colspan="Math.max(columns.length, 1) + (readonly ? 0 : 1)" class="px-3 py-8 text-center text-xs text-body-subtle">
225
+ No linked records yet.
226
+ </td>
227
+ </tr>
228
+ <template x-for="row in rows" :key="row.id">
229
+ <tr
230
+ class="cursor-pointer border-b border-default last:border-0 hover:bg-surface-row-hover"
231
+ x-show="listUrl && !listLoading"
232
+ @click="openRecord(row)"
233
+ >
234
+ <template x-for="cell in row.cells" :key="row.id + ':' + cell.name">
235
+ <td class="px-3 py-2 text-body" :class="cellAlignment(cell.name)">
236
+ <span x-show="cell.boolean !== undefined" x-text="cell.boolean ? 'Yes' : 'No'"></span>
237
+ <div class="shamar-badges" x-show="cell.badges && cell.badges.length">
238
+ <template x-for="badge in (cell.badges || [])" :key="badge">
239
+ <span class="shamar-badge" x-text="badge"></span>
240
+ </template>
241
+ </div>
242
+ <a
243
+ x-show="cell.boolean === undefined && !(cell.badges && cell.badges.length) && cell.href"
244
+ :href="cell.href"
245
+ class="text-fg-brand hover:underline"
246
+ @click.stop
247
+ x-text="cell.text"
248
+ ></a>
249
+ <span
250
+ class="text-heading"
251
+ x-show="cell.boolean === undefined && !(cell.badges && cell.badges.length) && !cell.href"
252
+ x-text="cell.text"
253
+ ></span>
254
+ </td>
255
+ </template>
256
+ <td class="px-3 py-2 text-right" x-show="!readonly" @click.stop>
257
+ <div class="inline-flex items-center gap-1 text-xs">
258
+ <button
259
+ type="button"
260
+ class="rounded-md px-2 py-1 text-body hover:bg-surface-hover"
261
+ @click="openRecord(row)"
262
+ >Open</button>
263
+ <button
264
+ type="button"
265
+ class="rounded-md px-2 py-1 text-fg-danger hover:bg-danger-soft"
266
+ @click="remove(row.id)"
267
+ >Unlink</button>
268
+ </div>
269
+ </td>
270
+ </tr>
271
+ </template>
272
+
273
+ {{-- Legacy single-column mode --}}
274
+ <tr x-show="!listUrl && selected.length === 0">
275
+ <td colspan="2" class="px-3 py-8 text-center text-xs text-body-subtle">No linked records yet.</td>
276
+ </tr>
277
+ <template x-for="item in selected" :key="'legacy-' + item.id">
278
+ <tr class="border-b border-default last:border-0 hover:bg-surface-row-hover" x-show="!listUrl">
279
+ <td class="px-3 py-2">
121
280
  <button
122
281
  type="button"
123
- class="rounded-md px-2 py-1 text-body hover:bg-surface-hover"
282
+ class="text-left text-heading hover:text-fg-brand hover:underline"
124
283
  @click="openRecord(item)"
125
- >Open</button>
126
- <button
127
- type="button"
128
- class="rounded-md px-2 py-1 text-fg-danger hover:bg-danger-soft"
129
- @click="remove(item.id)"
130
- >Unlink</button>
131
- </div>
132
- </td>
133
- </tr>
134
- </template>
135
- </tbody>
136
- </table>
284
+ x-text="item.label"
285
+ ></button>
286
+ </td>
287
+ <td class="px-3 py-2 text-right" x-show="!readonly">
288
+ <div class="inline-flex items-center gap-1 text-xs">
289
+ <button
290
+ type="button"
291
+ class="rounded-md px-2 py-1 text-body hover:bg-surface-hover"
292
+ @click="openRecord(item)"
293
+ >Open</button>
294
+ <button
295
+ type="button"
296
+ class="rounded-md px-2 py-1 text-fg-danger hover:bg-danger-soft"
297
+ @click="remove(item.id)"
298
+ >Unlink</button>
299
+ </div>
300
+ </td>
301
+ </tr>
302
+ </template>
303
+ </tbody>
304
+ </table>
305
+ </div>
306
+
307
+ <div
308
+ class="flex flex-wrap items-center justify-between gap-2 border-t border-default px-3 py-2 text-xs text-body-subtle"
309
+ x-show="listUrl && (pageCount > 1 || total > 0)"
310
+ >
311
+ <p>
312
+ Page <span x-text="page"></span> of <span x-text="pageCount"></span>
313
+ <span class="text-body-subtle">·</span>
314
+ <span x-text="total"></span> total
315
+ </p>
316
+ <div class="inline-flex items-center gap-1">
317
+ <button
318
+ type="button"
319
+ class="rounded-md border border-default px-2 py-1 hover:bg-surface-hover disabled:opacity-40"
320
+ :disabled="page <= 1 || listLoading"
321
+ @click="setListPage(page - 1)"
322
+ >Prev</button>
323
+ <button
324
+ type="button"
325
+ class="rounded-md border border-default px-2 py-1 hover:bg-surface-hover disabled:opacity-40"
326
+ :disabled="page >= pageCount || listLoading"
327
+ @click="setListPage(page + 1)"
328
+ >Next</button>
329
+ <select
330
+ class="shamar-input py-1 text-xs"
331
+ x-model="perPage"
332
+ @change="setListPage(1)"
333
+ >
334
+ <option value="5">5</option>
335
+ <option value="10">10</option>
336
+ <option value="25">25</option>
337
+ <option value="50">50</option>
338
+ </select>
339
+ </div>
340
+ </div>
137
341
  </div>
138
342
  </div>
139
343
  @endif
@@ -1,5 +1,9 @@
1
- <tr class="hover:bg-surface-row-hover transition-colors" data-shamar-record-id="{{ record.id }}">
2
- <td class="px-3 py-3">
1
+ <tr
2
+ class="hover:bg-surface-row-hover transition-colors cursor-pointer"
3
+ data-shamar-record-id="{{ record.id }}"
4
+ data-shamar-row-href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}{{ recordNavQuery(query) }}"
5
+ >
6
+ <td class="px-3 py-3" data-shamar-stop-row-nav>
3
7
  @if(bulkActions && bulkActions.length)
4
8
  <input
5
9
  type="checkbox"
@@ -29,11 +33,20 @@
29
33
 
30
34
  @endif
31
35
  @else
32
- <a href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}{{ recordNavQuery(query) }}" class="text-heading hover:text-fg-brand hover:underline">{{ cellValue(record, column) }}</a>
36
+ @let(relatedHref = relatedListLink(resource, record, column, basePath))
37
+ @if(relatedHref)
38
+ <a
39
+ href="{{ relatedHref }}"
40
+ data-shamar-stop-row-nav
41
+ class="text-fg-brand hover:underline"
42
+ >{{ cellValue(record, column) }}</a>
43
+ @else
44
+ <span class="text-heading">{{ cellValue(record, column) }}</span>
45
+ @endif
33
46
  @endif
34
47
  </td>
35
48
  @endeach
36
- <td class="px-4 py-3 text-right">
49
+ <td class="px-4 py-3 text-right" data-shamar-stop-row-nav>
37
50
  <div class="inline-flex flex-wrap items-center justify-end gap-1 text-xs">
38
51
  @if(rowActions)
39
52
  @each(action in rowActions)
@@ -1,5 +1,5 @@
1
1
  <div
2
- class="px-4 py-3 space-y-3"
2
+ class="shamar-list-toolbar px-4 py-3 space-y-3"
3
3
  x-data='shamarListToolbar({
4
4
  search: {{ jsonAttr(query.search) }},
5
5
  filters: {{ jsonAttr(query.filters) }},
@@ -4,6 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>{{ pageTitle }} · {{ panelTitle }}</title>
7
+ @if(branding.fontPreconnect)
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ @endif
7
11
  @if(branding.fontUrl)
8
12
  <link rel="stylesheet" href="{{ branding.fontUrl }}" />
9
13
  @endif
@@ -1,17 +1,62 @@
1
1
  @let(mode = 'show')
2
- @include('shamar::partials/shell-open')
2
+ @if(embed)
3
+ <div
4
+ class="space-y-4"
5
+ data-shamar-embed-detail
6
+ data-shamar-record-view="show"
7
+ >
8
+ @each(node in detailSchema)
9
+ @!component('shamar::partials/schema-node', {
10
+ node: node,
11
+ schemaMode: 'detail',
12
+ mode: mode,
13
+ record: record,
14
+ formErrors: formErrors,
15
+ relationUi: relationUi,
16
+ })
17
+ @endeach
3
18
 
4
- <div class="mx-auto max-w-5xl space-y-6" data-shamar-record-view="show">
5
- @each(node in detailSchema)
6
- @!component('shamar::partials/schema-node', {
7
- node: node,
8
- schemaMode: 'detail',
9
- mode: mode,
10
- record: record,
11
- formErrors: formErrors,
12
- relationUi: relationUi,
13
- })
14
- @endeach
15
- </div>
19
+ <div data-shamar-dialog-actions class="flex flex-wrap items-center justify-end gap-2 pt-2">
20
+ <button
21
+ type="button"
22
+ data-shamar-dialog-close
23
+ class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium border border-default text-body rounded-md hover:bg-surface-hover transition"
24
+ >Close</button>
25
+ @if(showEditButton)
26
+ <button
27
+ type="button"
28
+ class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white rounded-md bg-fg-brand hover:opacity-90 transition"
29
+ @click="$dispatch('shamar-open-dialog', {
30
+ url: {{ jsonAttr(`${basePath}/${resource.slug}/${record.id}/edit`) }},
31
+ title: {{ jsonAttr(`Edit ${pageTitle}`) }},
32
+ slug: {{ jsonAttr(resource.slug) }},
33
+ replace: true,
34
+ })"
35
+ >Edit</button>
36
+ @endif
37
+ </div>
38
+ </div>
39
+ @else
40
+ @include('shamar::partials/shell-open')
16
41
 
17
- @include('shamar::partials/shell-close')
42
+ <div
43
+ class="mx-auto space-y-6 {{ contentMaxWidthClass || 'max-w-5xl' }}"
44
+ @if(contentMaxWidthStyle)
45
+ style="{{ contentMaxWidthStyle }}"
46
+ @endif
47
+ data-shamar-record-view="show"
48
+ >
49
+ @each(node in detailSchema)
50
+ @!component('shamar::partials/schema-node', {
51
+ node: node,
52
+ schemaMode: 'detail',
53
+ mode: mode,
54
+ record: record,
55
+ formErrors: formErrors,
56
+ relationUi: relationUi,
57
+ })
58
+ @endeach
59
+ </div>
60
+
61
+ @include('shamar::partials/shell-close')
62
+ @endif