@shamar/adonis 0.1.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.
- package/assets/admin.css +2 -0
- package/assets/alpine.min.js +5 -0
- package/assets/input.css +2286 -0
- package/assets/shamar-ui.js +3658 -0
- package/dist/assets-controller.d.ts +12 -0
- package/dist/assets-controller.d.ts.map +1 -0
- package/dist/assets-controller.js +32 -0
- package/dist/assets-controller.js.map +1 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/configure.d.ts +21 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +20 -0
- package/dist/configure.js.map +1 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/controller.d.ts +31 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +45 -0
- package/dist/controller.js.map +1 -0
- package/dist/controllers/admin_controller.d.ts +72 -0
- package/dist/controllers/admin_controller.d.ts.map +1 -0
- package/dist/controllers/admin_controller.js +1075 -0
- package/dist/controllers/admin_controller.js.map +1 -0
- package/dist/discover.d.ts +7 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +50 -0
- package/dist/discover.js.map +1 -0
- package/dist/form-state.d.ts +41 -0
- package/dist/form-state.d.ts.map +1 -0
- package/dist/form-state.js +74 -0
- package/dist/form-state.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/require_api_key_middleware.d.ts +30 -0
- package/dist/middleware/require_api_key_middleware.d.ts.map +1 -0
- package/dist/middleware/require_api_key_middleware.js +59 -0
- package/dist/middleware/require_api_key_middleware.js.map +1 -0
- package/dist/provider.d.ts +9 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +66 -0
- package/dist/provider.js.map +1 -0
- package/dist/resources/api_key_resource.d.ts +35 -0
- package/dist/resources/api_key_resource.d.ts.map +1 -0
- package/dist/resources/api_key_resource.js +225 -0
- package/dist/resources/api_key_resource.js.map +1 -0
- package/dist/routes.d.ts +5 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +97 -0
- package/dist/routes.js.map +1 -0
- package/dist/runtime.d.ts +27 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +74 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shamar/auth.d.ts +30 -0
- package/dist/shamar/auth.d.ts.map +1 -0
- package/dist/shamar/auth.js +218 -0
- package/dist/shamar/auth.js.map +1 -0
- package/dist/shamar/branding.d.ts +24 -0
- package/dist/shamar/branding.d.ts.map +1 -0
- package/dist/shamar/branding.js +91 -0
- package/dist/shamar/branding.js.map +1 -0
- package/dist/shamar/display-relations.d.ts +7 -0
- package/dist/shamar/display-relations.d.ts.map +1 -0
- package/dist/shamar/display-relations.js +89 -0
- package/dist/shamar/display-relations.js.map +1 -0
- package/dist/shamar/list-headers.d.ts +38 -0
- package/dist/shamar/list-headers.d.ts.map +1 -0
- package/dist/shamar/list-headers.js +218 -0
- package/dist/shamar/list-headers.js.map +1 -0
- package/dist/shamar/list-query.d.ts +229 -0
- package/dist/shamar/list-query.d.ts.map +1 -0
- package/dist/shamar/list-query.js +564 -0
- package/dist/shamar/list-query.js.map +1 -0
- package/dist/shamar/menu.d.ts +48 -0
- package/dist/shamar/menu.d.ts.map +1 -0
- package/dist/shamar/menu.js +93 -0
- package/dist/shamar/menu.js.map +1 -0
- package/dist/shamar/paths.d.ts +5 -0
- package/dist/shamar/paths.d.ts.map +1 -0
- package/dist/shamar/paths.js +16 -0
- package/dist/shamar/paths.js.map +1 -0
- package/dist/shamar/relation-fields.d.ts +54 -0
- package/dist/shamar/relation-fields.d.ts.map +1 -0
- package/dist/shamar/relation-fields.js +94 -0
- package/dist/shamar/relation-fields.js.map +1 -0
- package/dist/shamar/resource-actions.d.ts +9 -0
- package/dist/shamar/resource-actions.d.ts.map +1 -0
- package/dist/shamar/resource-actions.js +42 -0
- package/dist/shamar/resource-actions.js.map +1 -0
- package/dist/shamar/view-context.d.ts +79 -0
- package/dist/shamar/view-context.d.ts.map +1 -0
- package/dist/shamar/view-context.js +74 -0
- package/dist/shamar/view-context.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
- package/resources/views/shamar/dashboard.edge +24 -0
- package/resources/views/shamar/form.edge +87 -0
- package/resources/views/shamar/index.edge +149 -0
- package/resources/views/shamar/partials/boolean-value.edge +25 -0
- package/resources/views/shamar/partials/csrf.edge +1 -0
- package/resources/views/shamar/partials/detail-entry.edge +72 -0
- package/resources/views/shamar/partials/dialog-host.edge +125 -0
- package/resources/views/shamar/partials/field-affix-icon.edge +23 -0
- package/resources/views/shamar/partials/field-input.edge +585 -0
- package/resources/views/shamar/partials/field-relation-checkbox.edge +104 -0
- package/resources/views/shamar/partials/field-relation-m2m.edge +110 -0
- package/resources/views/shamar/partials/field-relation-m2o.edge +116 -0
- package/resources/views/shamar/partials/field-relation-table.edge +139 -0
- package/resources/views/shamar/partials/form-container.edge +82 -0
- package/resources/views/shamar/partials/icon.edge +14 -0
- package/resources/views/shamar/partials/list-row.edge +73 -0
- package/resources/views/shamar/partials/list-toolbar.edge +269 -0
- package/resources/views/shamar/partials/nav-mobile.edge +21 -0
- package/resources/views/shamar/partials/nav.edge +58 -0
- package/resources/views/shamar/partials/page-heading.edge +130 -0
- package/resources/views/shamar/partials/pagination.edge +73 -0
- package/resources/views/shamar/partials/record-pager.edge +37 -0
- package/resources/views/shamar/partials/schema-node.edge +289 -0
- package/resources/views/shamar/partials/shell-close.edge +20 -0
- package/resources/views/shamar/partials/shell-open.edge +51 -0
- package/resources/views/shamar/partials/toast.edge +5 -0
- package/resources/views/shamar/partials/topbar.edge +115 -0
- package/resources/views/shamar/show.edge +17 -0
- package/stubs/config/shamar.stub +29 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="px-4 py-3 space-y-3"
|
|
3
|
+
x-data='shamarListToolbar({
|
|
4
|
+
search: {{ jsonAttr(query.search) }},
|
|
5
|
+
filters: {{ jsonAttr(query.filters) }},
|
|
6
|
+
filtersLockedEmpty: {{ jsonAttr(filtersLockedEmpty) }},
|
|
7
|
+
groupBy: {{ jsonAttr(query.groupBy) }},
|
|
8
|
+
groupLockedEmpty: {{ jsonAttr(groupLockedEmpty) }},
|
|
9
|
+
sort: {{ jsonAttr(query.sort) }},
|
|
10
|
+
direction: {{ jsonAttr(query.direction) }},
|
|
11
|
+
perPage: {{ jsonAttr(query.perPage) }},
|
|
12
|
+
trashed: {{ jsonAttr(query.trashed) }},
|
|
13
|
+
headers: {{ jsonAttr(listHeaders) }},
|
|
14
|
+
basePath: {{ jsonAttr(basePath) }},
|
|
15
|
+
slug: {{ jsonAttr(resource.slug) }},
|
|
16
|
+
view: {{ jsonAttr('table') }},
|
|
17
|
+
allPerPage: {{ jsonAttr(listAllPerPage) }}
|
|
18
|
+
})'
|
|
19
|
+
>
|
|
20
|
+
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
|
21
|
+
<div
|
|
22
|
+
class="relative flex-1 min-w-0 bg-neutral-primary border border-default rounded-lg transition-shadow focus-within:ring-2 focus-within:ring-fg-brand focus-within:border-fg-brand"
|
|
23
|
+
>
|
|
24
|
+
<div class="flex items-center flex-wrap gap-1.5 px-2.5 py-1.5 min-h-[40px]">
|
|
25
|
+
<svg class="w-4 h-4 text-body-subtle shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z"/>
|
|
27
|
+
</svg>
|
|
28
|
+
|
|
29
|
+
<template x-for="(chip, i) in chips" :key="'c-' + i + '-' + chip.field">
|
|
30
|
+
<span class="shamar-chip">
|
|
31
|
+
<span x-text="chip.label || (chip.field + ': ' + chip.value)" class="max-w-[200px] truncate"></span>
|
|
32
|
+
<button type="button" @click="removeChip(i)" aria-label="Remove filter" class="shamar-chip__remove">
|
|
33
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3">
|
|
34
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
35
|
+
</svg>
|
|
36
|
+
</button>
|
|
37
|
+
</span>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<template x-if="groupBy">
|
|
41
|
+
<span class="shamar-chip shamar-chip--group">
|
|
42
|
+
<span>Grouped by <span class="font-semibold" x-text="groupByLabel()"></span></span>
|
|
43
|
+
<button type="button" @click="clearGroupBy()" aria-label="Clear grouping" class="shamar-chip__remove">
|
|
44
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="3">
|
|
45
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
46
|
+
</svg>
|
|
47
|
+
</button>
|
|
48
|
+
</span>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<input
|
|
52
|
+
type="search"
|
|
53
|
+
x-ref="listSearchInput"
|
|
54
|
+
x-model="searchInput"
|
|
55
|
+
@input.debounce.350ms="onSearchChange()"
|
|
56
|
+
@keydown.escape="clearSearch()"
|
|
57
|
+
placeholder="Search…"
|
|
58
|
+
class="flex-1 min-w-[120px] bg-transparent border-0 px-1 py-1 text-sm text-body placeholder:text-body-subtle focus:outline-none focus:ring-0"
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<button
|
|
62
|
+
x-show="chips.length || groupBy || searchInput"
|
|
63
|
+
x-cloak
|
|
64
|
+
type="button"
|
|
65
|
+
@click="clearAll()"
|
|
66
|
+
class="text-[10px] text-body-subtle hover:text-fg-brand underline shrink-0 px-1"
|
|
67
|
+
>Clear all</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="flex items-center gap-2 shrink-0 flex-wrap justify-end">
|
|
72
|
+
<div
|
|
73
|
+
class="shamar-toolbar-menu"
|
|
74
|
+
x-show="filterableHeaders().length"
|
|
75
|
+
x-cloak
|
|
76
|
+
@click.outside="panelOpen = false; openFilterField = null"
|
|
77
|
+
@keydown.escape.window="if (panelOpen) { panelOpen = false; openFilterField = null }"
|
|
78
|
+
>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
class="shamar-toolbar-btn"
|
|
82
|
+
:class="{ 'is-active': panelOpen || chips.length }"
|
|
83
|
+
:aria-expanded="panelOpen.toString()"
|
|
84
|
+
aria-haspopup="menu"
|
|
85
|
+
@click="panelOpen = !panelOpen; if (!panelOpen) openFilterField = null"
|
|
86
|
+
>
|
|
87
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75" aria-hidden="true">
|
|
88
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z"/>
|
|
89
|
+
</svg>
|
|
90
|
+
Filters
|
|
91
|
+
<span x-show="chips.length" x-cloak class="shamar-toolbar-btn__badge" x-text="chips.length"></span>
|
|
92
|
+
</button>
|
|
93
|
+
|
|
94
|
+
<div
|
|
95
|
+
x-show="panelOpen"
|
|
96
|
+
x-cloak
|
|
97
|
+
x-transition.opacity.duration.120ms
|
|
98
|
+
class="shamar-toolbar-panel"
|
|
99
|
+
role="menu"
|
|
100
|
+
aria-label="Filters"
|
|
101
|
+
>
|
|
102
|
+
<div class="shamar-toolbar-panel__head">
|
|
103
|
+
<p class="text-xs font-semibold uppercase tracking-wide text-body-subtle">Filter by</p>
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
class="text-xs text-body-subtle underline hover:text-fg-brand"
|
|
107
|
+
x-show="chips.length"
|
|
108
|
+
@click="chips = []; filtersLockedEmpty = true; applyFilters()"
|
|
109
|
+
>Clear filters</button>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<template x-if="!openFilterField">
|
|
113
|
+
<div class="shamar-toolbar-panel__list">
|
|
114
|
+
<template x-for="h in filterableHeaders()" :key="'f-' + h.name">
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
class="shamar-toolbar-panel__item"
|
|
118
|
+
role="menuitem"
|
|
119
|
+
@click="toggleFilterField(h.name); if (h.filter_kind === 'm2o') searchM2o(h)"
|
|
120
|
+
>
|
|
121
|
+
<span x-text="h.label"></span>
|
|
122
|
+
<svg class="w-3.5 h-3.5 text-body-subtle" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/></svg>
|
|
123
|
+
</button>
|
|
124
|
+
</template>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
127
|
+
|
|
128
|
+
<template x-if="openFilterField">
|
|
129
|
+
<div>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
class="shamar-toolbar-panel__back"
|
|
133
|
+
@click="openFilterField = null"
|
|
134
|
+
>
|
|
135
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/></svg>
|
|
136
|
+
<span x-text="(headers.find(h => h.name === openFilterField) || {}).label || 'Back'"></span>
|
|
137
|
+
</button>
|
|
138
|
+
|
|
139
|
+
<template x-for="h in filterableHeaders().filter(item => item.name === openFilterField)" :key="'fv-' + h.name">
|
|
140
|
+
<div class="shamar-toolbar-panel__list">
|
|
141
|
+
<template x-if="h.filter_kind === 'boolean'">
|
|
142
|
+
<div>
|
|
143
|
+
<button type="button" class="shamar-toolbar-panel__item" @click="addBooleanFilter(h, true)">Yes</button>
|
|
144
|
+
<button type="button" class="shamar-toolbar-panel__item" @click="addBooleanFilter(h, false)">No</button>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
|
|
148
|
+
<template x-if="h.filter_kind === 'select'">
|
|
149
|
+
<div>
|
|
150
|
+
<template x-for="opt in (h.options || [])" :key="'so-' + opt.value">
|
|
151
|
+
<button type="button" class="shamar-toolbar-panel__item" @click="addSelectFilter(h, opt)" x-text="opt.label"></button>
|
|
152
|
+
</template>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<template x-if="h.filter_kind === 'm2o'">
|
|
157
|
+
<div class="p-2 space-y-2">
|
|
158
|
+
<input
|
|
159
|
+
type="search"
|
|
160
|
+
class="shamar-select w-full"
|
|
161
|
+
placeholder="Search…"
|
|
162
|
+
x-model="m2oQuery[h.name]"
|
|
163
|
+
@input.debounce.300ms="searchM2o(h)"
|
|
164
|
+
/>
|
|
165
|
+
<div class="max-h-48 overflow-auto rounded-md border border-default">
|
|
166
|
+
<template x-for="opt in (m2oResults[h.name] || [])" :key="'m2o-' + opt.id">
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
class="shamar-toolbar-panel__item"
|
|
170
|
+
@click="addM2oFilter(h, opt)"
|
|
171
|
+
x-text="opt.label"
|
|
172
|
+
></button>
|
|
173
|
+
</template>
|
|
174
|
+
<p
|
|
175
|
+
x-show="!(m2oResults[h.name] || []).length"
|
|
176
|
+
class="px-3 py-2 text-xs text-body-subtle"
|
|
177
|
+
>No matches</p>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</template>
|
|
181
|
+
</div>
|
|
182
|
+
</template>
|
|
183
|
+
</div>
|
|
184
|
+
</template>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div
|
|
189
|
+
class="shamar-toolbar-menu"
|
|
190
|
+
x-show="groupableHeaders().length"
|
|
191
|
+
x-cloak
|
|
192
|
+
@click.outside="groupOpen = false"
|
|
193
|
+
@keydown.escape.window="if (groupOpen) groupOpen = false"
|
|
194
|
+
>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
class="shamar-toolbar-btn"
|
|
198
|
+
:class="{ 'is-active': groupOpen || groupBy }"
|
|
199
|
+
:aria-expanded="groupOpen.toString()"
|
|
200
|
+
aria-haspopup="menu"
|
|
201
|
+
@click="groupOpen = !groupOpen"
|
|
202
|
+
>
|
|
203
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75" aria-hidden="true">
|
|
204
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"/>
|
|
205
|
+
</svg>
|
|
206
|
+
Group
|
|
207
|
+
</button>
|
|
208
|
+
|
|
209
|
+
<div
|
|
210
|
+
x-show="groupOpen"
|
|
211
|
+
x-cloak
|
|
212
|
+
x-transition.opacity.duration.120ms
|
|
213
|
+
class="shamar-toolbar-panel"
|
|
214
|
+
role="menu"
|
|
215
|
+
aria-label="Group by"
|
|
216
|
+
>
|
|
217
|
+
<div class="shamar-toolbar-panel__head">
|
|
218
|
+
<p class="text-xs font-semibold uppercase tracking-wide text-body-subtle">Group by</p>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
class="text-xs text-body-subtle underline hover:text-fg-brand"
|
|
222
|
+
x-show="groupBy"
|
|
223
|
+
@click="clearGroupBy()"
|
|
224
|
+
>Clear</button>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="shamar-toolbar-panel__list">
|
|
227
|
+
<template x-for="h in groupableHeaders()" :key="'g-' + h.name">
|
|
228
|
+
<button
|
|
229
|
+
type="button"
|
|
230
|
+
class="shamar-toolbar-panel__item"
|
|
231
|
+
:class="{ 'is-selected': groupBy === h.name }"
|
|
232
|
+
role="menuitem"
|
|
233
|
+
@click="setGroupBy(h.name)"
|
|
234
|
+
>
|
|
235
|
+
<span x-text="h.label"></span>
|
|
236
|
+
<svg x-show="groupBy === h.name" class="w-3.5 h-3.5 text-fg-brand" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>
|
|
237
|
+
</button>
|
|
238
|
+
</template>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<label for="list-per-page" class="text-xs font-medium text-body-subtle whitespace-nowrap">Rows</label>
|
|
244
|
+
<select
|
|
245
|
+
id="list-per-page"
|
|
246
|
+
x-model="perPage"
|
|
247
|
+
@change="reload()"
|
|
248
|
+
class="shamar-select w-auto min-w-[5.5rem]"
|
|
249
|
+
>
|
|
250
|
+
<option value="10">10</option>
|
|
251
|
+
<option value="15">15</option>
|
|
252
|
+
<option value="20">20</option>
|
|
253
|
+
<option value="50">50</option>
|
|
254
|
+
<option value="all">All records</option>
|
|
255
|
+
</select>
|
|
256
|
+
<button
|
|
257
|
+
type="button"
|
|
258
|
+
class="shamar-list-refresh"
|
|
259
|
+
data-shamar-list-refresh
|
|
260
|
+
title="Refresh (double-click for auto-refresh every 10s)"
|
|
261
|
+
aria-label="Refresh list"
|
|
262
|
+
>
|
|
263
|
+
<svg class="shamar-list-refresh__icon h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
264
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182"/>
|
|
265
|
+
</svg>
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<nav class="flex-1 px-2 py-3 overflow-y-auto space-y-4" aria-label="Navigation">
|
|
2
|
+
@each(group in navGroups)
|
|
3
|
+
<div>
|
|
4
|
+
<p class="px-3 mb-1 text-[11px] font-semibold uppercase tracking-wider text-body-subtle">{{ group.name }}</p>
|
|
5
|
+
<div class="space-y-0.5">
|
|
6
|
+
@each(item in group.items)
|
|
7
|
+
<a
|
|
8
|
+
href="{{ basePath }}/{{ item.slug }}"
|
|
9
|
+
class="nav-item flex items-center gap-2.5 px-3 py-2 rounded-md text-sm {{ resource && item.slug === resource.slug ? 'nav-active' : '' }}"
|
|
10
|
+
@click="sidebarOpen = false"
|
|
11
|
+
>
|
|
12
|
+
<span class="shrink-0 w-4 h-4">
|
|
13
|
+
@include('shamar::partials/icon')
|
|
14
|
+
</span>
|
|
15
|
+
<span class="truncate">{{ item.label }}</span>
|
|
16
|
+
</a>
|
|
17
|
+
@endeach
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
@endeach
|
|
21
|
+
</nav>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<aside
|
|
2
|
+
class="fixed md:relative inset-y-0 left-0 z-30 w-64 min-h-screen bg-shamar-shell flex flex-col flex-shrink-0 md:translate-x-0 transition-transform duration-200 ease-in-out"
|
|
3
|
+
:class="{ '-translate-x-full': !sidebarOpen }"
|
|
4
|
+
>
|
|
5
|
+
<div class="relative px-4 shrink-0 flex items-center min-h-[60px]">
|
|
6
|
+
<a href="{{ basePath }}" class="flex min-w-0 items-center gap-2.5">
|
|
7
|
+
@if(branding.logoUrl)
|
|
8
|
+
<img
|
|
9
|
+
src="{{ branding.logoUrl }}"
|
|
10
|
+
alt="{{ branding.brandName }}"
|
|
11
|
+
class="h-8 w-auto max-w-[9rem] object-contain dark:hidden"
|
|
12
|
+
/>
|
|
13
|
+
<img
|
|
14
|
+
src="{{ branding.logoDarkUrl ?? branding.logoUrl }}"
|
|
15
|
+
alt="{{ branding.brandName }}"
|
|
16
|
+
class="hidden h-8 w-auto max-w-[9rem] object-contain dark:block"
|
|
17
|
+
/>
|
|
18
|
+
@endif
|
|
19
|
+
<span class="truncate text-base font-semibold text-heading">{{ panelTitle }}</span>
|
|
20
|
+
</a>
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
@click="sidebarOpen = false"
|
|
24
|
+
aria-label="Close sidebar"
|
|
25
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 md:hidden p-1 rounded-md text-body-subtle hover:text-heading hover:bg-surface-hover"
|
|
26
|
+
>
|
|
27
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
28
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
29
|
+
</svg>
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<nav class="hidden md:block flex-1 px-2 py-3 space-y-0.5 overflow-y-auto" aria-label="Applications">
|
|
34
|
+
@each(root in menuRoots)
|
|
35
|
+
<a
|
|
36
|
+
href="{{ root.href }}"
|
|
37
|
+
class="nav-item flex items-center gap-2.5 px-3 py-2 rounded-md text-sm {{ root.active ? 'nav-active' : '' }}"
|
|
38
|
+
@click="sidebarOpen = false"
|
|
39
|
+
>
|
|
40
|
+
<span class="shrink-0 w-4 h-4">
|
|
41
|
+
@include('shamar::partials/icon')
|
|
42
|
+
</span>
|
|
43
|
+
<span class="truncate">{{ root.label }}</span>
|
|
44
|
+
</a>
|
|
45
|
+
@endeach
|
|
46
|
+
</nav>
|
|
47
|
+
|
|
48
|
+
<div class="md:hidden flex-1 min-h-0 flex flex-col overflow-hidden">
|
|
49
|
+
@include('shamar::partials/nav-mobile')
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="mt-auto px-4 py-3 text-xs text-body-subtle hidden md:block">
|
|
53
|
+
@if(branding.copyrightText)
|
|
54
|
+
<p class="mb-2 leading-relaxed">{{ branding.copyrightText }}</p>
|
|
55
|
+
@endif
|
|
56
|
+
<p class="truncate">Signed in as <span class="font-semibold text-heading">{{ user.name }}</span></p>
|
|
57
|
+
</div>
|
|
58
|
+
</aside>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<div class="pt-1 pb-2">
|
|
2
|
+
@if(breadcrumbs && breadcrumbs.length)
|
|
3
|
+
<nav aria-label="Breadcrumb" class="mb-2 flex items-center gap-1.5 text-xs text-body-subtle flex-wrap">
|
|
4
|
+
@each(crumb in breadcrumbs)
|
|
5
|
+
@if(!$last)
|
|
6
|
+
@if(crumb.href)
|
|
7
|
+
<a href="{{ crumb.href }}" class="hover:text-fg-brand hover:underline transition-colors truncate max-w-[140px]">{{ crumb.label }}</a>
|
|
8
|
+
@else
|
|
9
|
+
<span class="truncate max-w-[140px]">{{ crumb.label }}</span>
|
|
10
|
+
@endif
|
|
11
|
+
<svg class="w-3 h-3 flex-shrink-0 text-body-subtle/50" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
|
|
13
|
+
</svg>
|
|
14
|
+
@else
|
|
15
|
+
@if(crumb.href)
|
|
16
|
+
<a href="{{ crumb.href }}" class="text-body hover:text-fg-brand hover:underline transition-colors truncate max-w-[200px]">{{ crumb.label }}</a>
|
|
17
|
+
@else
|
|
18
|
+
<span class="text-body truncate max-w-[200px]">{{ crumb.label }}</span>
|
|
19
|
+
@endif
|
|
20
|
+
@endif
|
|
21
|
+
@endeach
|
|
22
|
+
</nav>
|
|
23
|
+
@endif
|
|
24
|
+
|
|
25
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
26
|
+
<div class="min-w-0 flex items-center gap-3 flex-wrap">
|
|
27
|
+
<div class="min-w-0">
|
|
28
|
+
<h1 class="text-xl font-semibold text-heading truncate">{{ pageTitle }}</h1>
|
|
29
|
+
@if(pageSubtitle)
|
|
30
|
+
<p class="mt-1 text-sm text-body-subtle">{{ pageSubtitle }}</p>
|
|
31
|
+
@endif
|
|
32
|
+
</div>
|
|
33
|
+
@if(mode === 'edit' || mode === 'create' || mode === 'show')
|
|
34
|
+
<span class="inline-flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full
|
|
35
|
+
{{ mode === 'edit' ? 'bg-warning-soft text-warning-strong' : mode === 'create' ? 'bg-success-soft text-success-strong' : 'bg-badge-neutral text-body-subtle' }}">
|
|
36
|
+
{{ mode === 'show' ? 'detail' : mode }}
|
|
37
|
+
</span>
|
|
38
|
+
@endif
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="flex flex-wrap items-center gap-2 shrink-0">
|
|
42
|
+
@if(showCreateButton)
|
|
43
|
+
<a href="{{ basePath }}/{{ resource.slug }}/create" 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-opacity">
|
|
44
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
|
45
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
|
|
46
|
+
</svg>
|
|
47
|
+
New
|
|
48
|
+
</a>
|
|
49
|
+
@endif
|
|
50
|
+
|
|
51
|
+
@if(mode === 'edit' || mode === 'create')
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
data-shamar-form-save
|
|
55
|
+
title="Save (Ctrl+S)"
|
|
56
|
+
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"
|
|
57
|
+
>
|
|
58
|
+
{{ mode === 'create' ? 'Create' : 'Save' }}
|
|
59
|
+
</button>
|
|
60
|
+
@if(mode === 'edit')
|
|
61
|
+
<a
|
|
62
|
+
href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}{{ recordPager ? recordPager.navQuery : '' }}"
|
|
63
|
+
title="View details"
|
|
64
|
+
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"
|
|
65
|
+
>
|
|
66
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.75" aria-hidden="true">
|
|
67
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
|
|
68
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
69
|
+
</svg>
|
|
70
|
+
View
|
|
71
|
+
</a>
|
|
72
|
+
<a
|
|
73
|
+
href="{{ basePath }}/{{ resource.slug }}"
|
|
74
|
+
data-shamar-list-href="{{ resource.slug }}"
|
|
75
|
+
data-shamar-skip-autosave
|
|
76
|
+
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"
|
|
77
|
+
>Cancel</a>
|
|
78
|
+
@if(showDeleteButton)
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium border border-default text-fg-danger rounded-md hover:bg-danger-soft transition"
|
|
82
|
+
@click="$dispatch('shamar-open-confirm', { variant: 'danger', mode: 'confirm', title: 'Delete {{ resource.singularLabel }}?', message: 'This action cannot be undone.', confirmLabel: 'Delete', action: '{{ basePath }}/{{ resource.slug }}/{{ record.id }}/delete' })"
|
|
83
|
+
>Delete</button>
|
|
84
|
+
@endif
|
|
85
|
+
@else
|
|
86
|
+
<a
|
|
87
|
+
href="{{ basePath }}/{{ resource.slug }}"
|
|
88
|
+
data-shamar-list-href="{{ resource.slug }}"
|
|
89
|
+
data-shamar-skip-autosave
|
|
90
|
+
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"
|
|
91
|
+
>Cancel</a>
|
|
92
|
+
@endif
|
|
93
|
+
@elseif(mode === 'show')
|
|
94
|
+
@if(showEditButton !== false && showEditButton)
|
|
95
|
+
<a href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}/edit{{ recordPager ? recordPager.navQuery : '' }}" 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">Edit</a>
|
|
96
|
+
@endif
|
|
97
|
+
@if(recordActions)
|
|
98
|
+
@each(action in recordActions)
|
|
99
|
+
@if(action.name !== 'view' && action.name !== 'edit' && action.name !== 'delete')
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium border border-default rounded-md transition
|
|
103
|
+
{{ action.color === 'danger' ? 'text-fg-danger hover:bg-danger-soft' : 'text-body hover:bg-surface-hover' }}"
|
|
104
|
+
@click="$dispatch('shamar-open-confirm', { variant: {{ jsonAttr(action.color === 'danger' ? 'danger' : 'primary') }}, mode: 'confirm', title: {{ jsonAttr(action.label) }}, message: {{ jsonAttr(action.confirm || 'Are you sure?') }}, confirmLabel: {{ jsonAttr(action.label) }}, action: '{{ basePath }}/{{ resource.slug }}/{{ record.id }}/action/{{ action.name }}' })"
|
|
105
|
+
>{{ action.label }}</button>
|
|
106
|
+
@endif
|
|
107
|
+
@endeach
|
|
108
|
+
@endif
|
|
109
|
+
<a href="{{ basePath }}/{{ resource.slug }}" data-shamar-list-href="{{ resource.slug }}" 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">Back to list</a>
|
|
110
|
+
@if(showDeleteButton)
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium border border-default text-fg-danger rounded-md hover:bg-danger-soft transition"
|
|
114
|
+
@click="$dispatch('shamar-open-confirm', { variant: 'danger', mode: 'confirm', title: 'Delete {{ resource.singularLabel }}?', message: 'This action cannot be undone.', confirmLabel: 'Delete', action: '{{ basePath }}/{{ resource.slug }}/{{ record.id }}/delete' })"
|
|
115
|
+
>Delete</button>
|
|
116
|
+
@endif
|
|
117
|
+
@else
|
|
118
|
+
@if(showEditButton)
|
|
119
|
+
<a href="{{ basePath }}/{{ resource.slug }}/{{ id }}/edit" 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-opacity">Edit</a>
|
|
120
|
+
@endif
|
|
121
|
+
@if(showBackToList)
|
|
122
|
+
<a href="{{ basePath }}/{{ resource.slug }}" data-shamar-list-href="{{ resource.slug }}" 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">Back to list</a>
|
|
123
|
+
@endif
|
|
124
|
+
@endif
|
|
125
|
+
@if(mode === 'edit' || mode === 'create' || mode === 'show')
|
|
126
|
+
@include('shamar::partials/record-pager')
|
|
127
|
+
@endif
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
@if(pagination)
|
|
2
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between text-sm text-body-subtle">
|
|
3
|
+
<p>
|
|
4
|
+
Page {{ pagination.page }} of {{ pagination.pageCount }}
|
|
5
|
+
· {{ pagination.total }} records
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
@if(pagination.pageCount > 1)
|
|
9
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
10
|
+
@if(pagination.prevHref)
|
|
11
|
+
<a
|
|
12
|
+
href="{{ pagination.prevHref }}"
|
|
13
|
+
class="rounded-md border border-default bg-neutral-primary px-3 py-1.5 text-sm text-body hover:bg-surface-hover"
|
|
14
|
+
>Previous</a>
|
|
15
|
+
@else
|
|
16
|
+
<span class="rounded-md border border-default bg-neutral-primary/50 px-3 py-1.5 text-sm text-body-subtle cursor-not-allowed">Previous</span>
|
|
17
|
+
@endif
|
|
18
|
+
|
|
19
|
+
@each(link in pagination.links)
|
|
20
|
+
@if(link.type === 'ellipsis')
|
|
21
|
+
<span class="px-1.5 text-body-subtle select-none">…</span>
|
|
22
|
+
@else
|
|
23
|
+
<a
|
|
24
|
+
href="{{ link.href }}"
|
|
25
|
+
class="min-w-[2.25rem] rounded-md border px-2.5 py-1.5 text-center text-sm transition-colors {{ link.active ? 'border-brand bg-surface-inset font-medium text-fg-brand' : 'border-default bg-neutral-primary text-body hover:bg-surface-hover' }}"
|
|
26
|
+
{{ link.active ? 'aria-current="page"' : '' }}
|
|
27
|
+
>{{ link.label }}</a>
|
|
28
|
+
@endif
|
|
29
|
+
@endeach
|
|
30
|
+
|
|
31
|
+
@if(pagination.nextHref)
|
|
32
|
+
<a
|
|
33
|
+
href="{{ pagination.nextHref }}"
|
|
34
|
+
class="rounded-md border border-default bg-neutral-primary px-3 py-1.5 text-sm text-body hover:bg-surface-hover"
|
|
35
|
+
>Next</a>
|
|
36
|
+
@else
|
|
37
|
+
<span class="rounded-md border border-default bg-neutral-primary/50 px-3 py-1.5 text-sm text-body-subtle cursor-not-allowed">Next</span>
|
|
38
|
+
@endif
|
|
39
|
+
|
|
40
|
+
<form
|
|
41
|
+
method="get"
|
|
42
|
+
action="{{ pagination.formAction }}"
|
|
43
|
+
class="ml-1 flex items-center gap-1.5"
|
|
44
|
+
>
|
|
45
|
+
@if(query.search)
|
|
46
|
+
<input type="hidden" name="search" value="{{ query.search }}" />
|
|
47
|
+
@endif
|
|
48
|
+
@if(query.sort)
|
|
49
|
+
<input type="hidden" name="sort" value="{{ query.sort }}" />
|
|
50
|
+
<input type="hidden" name="direction" value="{{ query.direction }}" />
|
|
51
|
+
@endif
|
|
52
|
+
@if(query.perPage !== '15' && query.perPage !== 15)
|
|
53
|
+
<input type="hidden" name="perPage" value="{{ query.perPage }}" />
|
|
54
|
+
@endif
|
|
55
|
+
<label for="pagination-jump-page" class="sr-only">Jump to page</label>
|
|
56
|
+
<input
|
|
57
|
+
id="pagination-jump-page"
|
|
58
|
+
type="number"
|
|
59
|
+
name="page"
|
|
60
|
+
min="1"
|
|
61
|
+
max="{{ pagination.pageCount }}"
|
|
62
|
+
value="{{ pagination.page }}"
|
|
63
|
+
class="shamar-input w-16 px-2 py-1.5 text-center text-sm"
|
|
64
|
+
/>
|
|
65
|
+
<button
|
|
66
|
+
type="submit"
|
|
67
|
+
class="rounded-md border border-default bg-neutral-primary px-2.5 py-1.5 text-sm text-body hover:bg-surface-hover"
|
|
68
|
+
>Go</button>
|
|
69
|
+
</form>
|
|
70
|
+
</div>
|
|
71
|
+
@endif
|
|
72
|
+
</div>
|
|
73
|
+
@endif
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{{-- Cyclic prev/next record pager (PyVELM-style). Expects `recordPager`. --}}
|
|
2
|
+
@if(recordPager)
|
|
3
|
+
<div
|
|
4
|
+
class="inline-flex items-center gap-2 shrink-0"
|
|
5
|
+
data-shamar-record-pager
|
|
6
|
+
data-shamar-pager-prev="{{ recordPager.prevUrl }}"
|
|
7
|
+
data-shamar-pager-next="{{ recordPager.nextUrl }}"
|
|
8
|
+
>
|
|
9
|
+
<div class="inline-flex rounded-md border border-default overflow-hidden bg-neutral-primary">
|
|
10
|
+
<a
|
|
11
|
+
href="{{ recordPager.prevUrl }}"
|
|
12
|
+
class="inline-flex items-center justify-center w-8 h-8 text-body-subtle hover:bg-surface-hover hover:text-heading transition-colors"
|
|
13
|
+
title="Previous record (wraps at start)"
|
|
14
|
+
aria-label="Previous record"
|
|
15
|
+
data-shamar-pager-link
|
|
16
|
+
>
|
|
17
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
|
|
19
|
+
</svg>
|
|
20
|
+
</a>
|
|
21
|
+
<a
|
|
22
|
+
href="{{ recordPager.nextUrl }}"
|
|
23
|
+
class="inline-flex items-center justify-center w-8 h-8 text-body-subtle border-l border-default hover:bg-surface-hover hover:text-heading transition-colors"
|
|
24
|
+
title="Next record (wraps at end)"
|
|
25
|
+
aria-label="Next record"
|
|
26
|
+
data-shamar-pager-link
|
|
27
|
+
>
|
|
28
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
29
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
|
|
30
|
+
</svg>
|
|
31
|
+
</a>
|
|
32
|
+
</div>
|
|
33
|
+
<span class="text-xs text-body-subtle tabular-nums whitespace-nowrap" title="Position in the current list">
|
|
34
|
+
{{ recordPager.index }} / {{ recordPager.total }}
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
@endif
|