@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,104 @@
|
|
|
1
|
+
{{-- Checkbox list for manyToMany. Expects `relationConfig`. --}}
|
|
2
|
+
<div
|
|
3
|
+
class="shamar-m2m-checkboxes relative w-full"
|
|
4
|
+
data-shamar-m2m-config="{{ jsonAttr(relationConfig) }}"
|
|
5
|
+
x-data="shamarM2mCheckboxFromEl($el)"
|
|
6
|
+
>
|
|
7
|
+
<template x-if="!readonly">
|
|
8
|
+
<template x-for="item in selected" :key="'h-' + itemValue(item)">
|
|
9
|
+
<input type="hidden" name="{{ relationConfig.name }}[]" :value="itemValue(item)" />
|
|
10
|
+
</template>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<div class="shamar-m2m-checkboxes__toolbar mb-2 flex items-center gap-2" x-show="!readonly">
|
|
14
|
+
<input
|
|
15
|
+
type="search"
|
|
16
|
+
x-model="filter"
|
|
17
|
+
placeholder="Filter…"
|
|
18
|
+
class="shamar-input w-full max-w-xs text-sm"
|
|
19
|
+
autocomplete="off"
|
|
20
|
+
/>
|
|
21
|
+
<span class="shrink-0 text-xs text-body-subtle" x-text="selected.length + ' selected'"></span>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div
|
|
25
|
+
class="shamar-m2m-checkboxes__body"
|
|
26
|
+
:class="framed ? 'shamar-m2m-checkboxes__body--framed' : 'shamar-m2m-checkboxes__body--open'"
|
|
27
|
+
>
|
|
28
|
+
<template x-if="loading">
|
|
29
|
+
<div class="px-2 py-3 text-xs text-body-subtle">Loading…</div>
|
|
30
|
+
</template>
|
|
31
|
+
<template x-if="!loading && filteredGroups.length === 0">
|
|
32
|
+
<div class="px-2 py-3 text-xs text-body-subtle">No related records.</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
x-show="!loading && groupBy && filteredGroups.length > 0"
|
|
37
|
+
class="shamar-m2m-checkboxes__clusters"
|
|
38
|
+
:class="'shamar-m2m-checkboxes__clusters--cols-' + columns"
|
|
39
|
+
:style="'--shamar-checkbox-cols:' + columns"
|
|
40
|
+
>
|
|
41
|
+
<template x-for="group in filteredGroups" :key="group.key">
|
|
42
|
+
<section class="shamar-m2m-checkboxes__cluster">
|
|
43
|
+
<header class="shamar-m2m-checkboxes__cluster-header">
|
|
44
|
+
<div class="min-w-0">
|
|
45
|
+
<h4 class="truncate text-xs font-semibold text-heading" x-text="group.title"></h4>
|
|
46
|
+
</div>
|
|
47
|
+
<span
|
|
48
|
+
class="shamar-m2m-checkboxes__cluster-count shrink-0"
|
|
49
|
+
x-text="groupSelectedCount(group) + '/' + group.items.length"
|
|
50
|
+
></span>
|
|
51
|
+
</header>
|
|
52
|
+
<div class="shamar-m2m-checkboxes__cluster-body">
|
|
53
|
+
<template x-for="item in group.items" :key="item.id">
|
|
54
|
+
<label
|
|
55
|
+
class="shamar-m2m-checkboxes__item flex items-center gap-2 rounded px-1.5 py-1 text-sm"
|
|
56
|
+
:class="{
|
|
57
|
+
'opacity-50 cursor-not-allowed': isCovered(item) && !isSelected(item),
|
|
58
|
+
'cursor-pointer hover:bg-neutral-secondary': !readonly && !(isCovered(item) && !isSelected(item)),
|
|
59
|
+
'opacity-60': readonly
|
|
60
|
+
}"
|
|
61
|
+
>
|
|
62
|
+
<input
|
|
63
|
+
type="checkbox"
|
|
64
|
+
class="shamar-checkbox__input"
|
|
65
|
+
:checked="isSelected(item)"
|
|
66
|
+
:disabled="readonly || (isCovered(item) && !isSelected(item))"
|
|
67
|
+
@change="toggle(item)"
|
|
68
|
+
/>
|
|
69
|
+
<span class="min-w-0 truncate text-body" x-text="itemDisplay(item)"></span>
|
|
70
|
+
</label>
|
|
71
|
+
</template>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
</template>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div
|
|
78
|
+
x-show="!loading && !groupBy && filteredOptions.length > 0"
|
|
79
|
+
class="shamar-m2m-checkboxes__list"
|
|
80
|
+
:class="'shamar-m2m-checkboxes__list--cols-' + columns"
|
|
81
|
+
:style="'--shamar-checkbox-cols:' + columns"
|
|
82
|
+
>
|
|
83
|
+
<template x-for="item in filteredOptions" :key="item.id">
|
|
84
|
+
<label
|
|
85
|
+
class="shamar-m2m-checkboxes__item flex items-center gap-2 rounded px-2 py-1.5 text-sm"
|
|
86
|
+
:class="{
|
|
87
|
+
'opacity-50 cursor-not-allowed': isCovered(item) && !isSelected(item),
|
|
88
|
+
'cursor-pointer hover:bg-neutral-secondary': !readonly && !(isCovered(item) && !isSelected(item)),
|
|
89
|
+
'opacity-60': readonly
|
|
90
|
+
}"
|
|
91
|
+
>
|
|
92
|
+
<input
|
|
93
|
+
type="checkbox"
|
|
94
|
+
class="shamar-checkbox__input"
|
|
95
|
+
:checked="isSelected(item)"
|
|
96
|
+
:disabled="readonly || (isCovered(item) && !isSelected(item))"
|
|
97
|
+
@change="toggle(item)"
|
|
98
|
+
/>
|
|
99
|
+
<span class="min-w-0 truncate text-body" x-text="itemDisplay(item)"></span>
|
|
100
|
+
</label>
|
|
101
|
+
</template>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{{-- ManyToMany / multi relation combobox. Expects `relationConfig`. --}}
|
|
2
|
+
<div
|
|
3
|
+
class="shamar-m2m relative w-full"
|
|
4
|
+
data-shamar-m2m-config="{{ jsonAttr(relationConfig) }}"
|
|
5
|
+
x-data="shamarM2mFromEl($el)"
|
|
6
|
+
@click.outside="close()"
|
|
7
|
+
@focusout="onFocusOut($event)"
|
|
8
|
+
@keydown.escape.prevent.stop="close()"
|
|
9
|
+
>
|
|
10
|
+
<template x-for="item in selected" :key="'h-' + item.id">
|
|
11
|
+
<input type="hidden" name="{{ relationConfig.name }}[]" :value="item.id" />
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
class="shamar-m2m__box shamar-input flex min-h-[2.375rem] w-full flex-wrap items-center gap-1.5 py-1.5 pl-2 pr-2"
|
|
16
|
+
:class="{ 'is-readonly': readonly, 'is-open': open }"
|
|
17
|
+
@click="focusInput()"
|
|
18
|
+
>
|
|
19
|
+
<template x-for="item in selected" :key="item.id">
|
|
20
|
+
<span class="shamar-chip inline-flex max-w-full items-center gap-1 rounded-md bg-brand-softer px-1.5 py-0.5 text-xs text-fg-brand">
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
class="shamar-chip__label max-w-[12rem] truncate hover:underline"
|
|
24
|
+
@click.stop="openRecord(item)"
|
|
25
|
+
x-text="item.label"
|
|
26
|
+
></button>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
x-show="!readonly"
|
|
30
|
+
@click.stop="remove(item.id)"
|
|
31
|
+
aria-label="Remove"
|
|
32
|
+
class="shamar-chip__remove rounded p-0.5 text-fg-brand/70 hover:bg-danger-soft hover:text-fg-danger"
|
|
33
|
+
>
|
|
34
|
+
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" aria-hidden="true">
|
|
35
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
36
|
+
</svg>
|
|
37
|
+
</button>
|
|
38
|
+
</span>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<input
|
|
42
|
+
type="text"
|
|
43
|
+
x-ref="input"
|
|
44
|
+
x-model="query"
|
|
45
|
+
x-show="!readonly"
|
|
46
|
+
@focus="onFocus()"
|
|
47
|
+
@input.debounce.250ms="onInput()"
|
|
48
|
+
@keydown.arrow-down.prevent="moveCursor(1)"
|
|
49
|
+
@keydown.arrow-up.prevent="moveCursor(-1)"
|
|
50
|
+
@keydown.enter.prevent="onEnter()"
|
|
51
|
+
@keydown.backspace="onBackspace()"
|
|
52
|
+
placeholder="{{ field.placeholder ?? 'Search…' }}"
|
|
53
|
+
autocomplete="off"
|
|
54
|
+
class="shamar-m2m__input min-w-[7rem] flex-1 border-0 bg-transparent p-0 text-sm outline-none focus:ring-0"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
x-show="open"
|
|
60
|
+
x-cloak
|
|
61
|
+
x-transition.opacity.duration.100ms
|
|
62
|
+
class="shamar-m2m__dropdown absolute left-0 right-0 top-full z-50 mt-1 max-h-72 overflow-auto rounded-md border border-default bg-neutral-primary text-sm shadow-lg"
|
|
63
|
+
role="listbox"
|
|
64
|
+
>
|
|
65
|
+
<template x-if="loading">
|
|
66
|
+
<div class="px-3 py-2 text-xs text-body-subtle">Searching…</div>
|
|
67
|
+
</template>
|
|
68
|
+
<template x-if="!loading && availableResults.length === 0 && !createCandidate">
|
|
69
|
+
<div class="px-3 py-2 text-xs text-body-subtle">No matches.</div>
|
|
70
|
+
</template>
|
|
71
|
+
<ul x-show="!loading && (availableResults.length > 0 || createCandidate || canCreateAndEdit)">
|
|
72
|
+
<template x-for="(item, i) in availableResults" :key="item.id">
|
|
73
|
+
<li
|
|
74
|
+
role="option"
|
|
75
|
+
@mousedown.prevent="pick(item)"
|
|
76
|
+
@mousemove="cursor = i"
|
|
77
|
+
:class="i === cursor ? 'bg-brand-softer text-fg-brand' : 'text-body hover:bg-neutral-secondary'"
|
|
78
|
+
class="cursor-pointer truncate px-3 py-1.5"
|
|
79
|
+
x-text="item.label"
|
|
80
|
+
></li>
|
|
81
|
+
</template>
|
|
82
|
+
<template x-if="createCandidate">
|
|
83
|
+
<li
|
|
84
|
+
role="option"
|
|
85
|
+
@click="createFromQuery()"
|
|
86
|
+
@mousemove="cursor = availableResults.length"
|
|
87
|
+
:class="cursor === availableResults.length ? 'bg-success-soft text-success-strong' : 'text-fg-success hover:bg-neutral-secondary'"
|
|
88
|
+
class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5"
|
|
89
|
+
>
|
|
90
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
91
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
|
|
92
|
+
</svg>
|
|
93
|
+
<span>Create "<span x-text="query"></span>"</span>
|
|
94
|
+
</li>
|
|
95
|
+
</template>
|
|
96
|
+
<template x-if="canCreateAndEdit">
|
|
97
|
+
<li
|
|
98
|
+
role="option"
|
|
99
|
+
@click="createAndEdit()"
|
|
100
|
+
class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-body hover:bg-neutral-secondary"
|
|
101
|
+
>
|
|
102
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
103
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"/>
|
|
104
|
+
</svg>
|
|
105
|
+
<span>Create and edit…</span>
|
|
106
|
+
</li>
|
|
107
|
+
</template>
|
|
108
|
+
</ul>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{{-- BelongsTo async combobox (Create / Create & Edit). Expects `relationConfig`. --}}
|
|
2
|
+
<div
|
|
3
|
+
class="shamar-m2o relative w-full"
|
|
4
|
+
data-shamar-m2o-config="{{ jsonAttr(relationConfig) }}"
|
|
5
|
+
x-data="shamarM2oFromEl($el)"
|
|
6
|
+
@click.outside="close()"
|
|
7
|
+
@focusout="onFocusOut($event)"
|
|
8
|
+
@keydown.escape.prevent.stop="close()"
|
|
9
|
+
>
|
|
10
|
+
<input
|
|
11
|
+
type="hidden"
|
|
12
|
+
name="{{ relationConfig.name }}"
|
|
13
|
+
:value="value === null || value === '' ? '' : value"
|
|
14
|
+
@change="$dispatch('input')"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<div class="shamar-m2o__row flex w-full items-stretch gap-1">
|
|
18
|
+
<div class="relative min-w-0 flex-1">
|
|
19
|
+
<input
|
|
20
|
+
type="text"
|
|
21
|
+
x-ref="input"
|
|
22
|
+
x-model="query"
|
|
23
|
+
:readonly="readonly"
|
|
24
|
+
@focus="onFocus()"
|
|
25
|
+
@input.debounce.250ms="onInput()"
|
|
26
|
+
@keydown.arrow-down.prevent="moveCursor(1)"
|
|
27
|
+
@keydown.arrow-up.prevent="moveCursor(-1)"
|
|
28
|
+
@keydown.enter.prevent="onEnter()"
|
|
29
|
+
placeholder="{{ field.placeholder ?? 'Search…' }}"
|
|
30
|
+
autocomplete="off"
|
|
31
|
+
class="shamar-input shamar-m2o__input w-full pr-8"
|
|
32
|
+
:class="{ 'is-readonly': readonly }"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
x-show="value !== null && value !== '' && !readonly"
|
|
38
|
+
@click.stop="clearSelection()"
|
|
39
|
+
aria-label="Clear"
|
|
40
|
+
class="shamar-m2o__clear absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-body-subtle hover:bg-danger-soft hover:text-fg-danger"
|
|
41
|
+
>
|
|
42
|
+
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" aria-hidden="true">
|
|
43
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
44
|
+
</svg>
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
x-show="value !== null && value !== ''"
|
|
51
|
+
@click.stop="openRecord()"
|
|
52
|
+
title="Open record"
|
|
53
|
+
aria-label="Open record"
|
|
54
|
+
class="shamar-m2o__open inline-flex h-[2.375rem] w-9 shrink-0 items-center justify-center rounded-md border border-default bg-neutral-primary text-body-subtle hover:border-fg-brand hover:text-fg-brand"
|
|
55
|
+
>
|
|
56
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
57
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"/>
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div
|
|
63
|
+
x-show="open"
|
|
64
|
+
x-cloak
|
|
65
|
+
x-transition.opacity.duration.100ms
|
|
66
|
+
class="shamar-m2o__dropdown absolute left-0 right-0 top-full z-50 mt-1 max-h-72 overflow-auto rounded-md border border-default bg-neutral-primary text-sm shadow-lg"
|
|
67
|
+
role="listbox"
|
|
68
|
+
>
|
|
69
|
+
<template x-if="loading">
|
|
70
|
+
<div class="px-3 py-2 text-xs text-body-subtle">Searching…</div>
|
|
71
|
+
</template>
|
|
72
|
+
<template x-if="!loading && results.length === 0 && !createCandidate">
|
|
73
|
+
<div class="px-3 py-2 text-xs text-body-subtle">No matches.</div>
|
|
74
|
+
</template>
|
|
75
|
+
<ul x-show="!loading && (results.length > 0 || createCandidate || canCreateAndEdit)">
|
|
76
|
+
<template x-for="(item, i) in results" :key="item.id">
|
|
77
|
+
<li
|
|
78
|
+
role="option"
|
|
79
|
+
@mousedown.prevent="pick(item)"
|
|
80
|
+
@mousemove="cursor = i"
|
|
81
|
+
:class="i === cursor ? 'bg-brand-softer text-fg-brand' : 'text-body hover:bg-neutral-secondary'"
|
|
82
|
+
class="flex cursor-pointer items-center justify-between px-3 py-1.5"
|
|
83
|
+
>
|
|
84
|
+
<span x-text="item.label" class="truncate"></span>
|
|
85
|
+
<span x-show="String(item.id) === String(value)" class="text-2xs font-medium text-fg-brand">selected</span>
|
|
86
|
+
</li>
|
|
87
|
+
</template>
|
|
88
|
+
<template x-if="createCandidate">
|
|
89
|
+
<li
|
|
90
|
+
role="option"
|
|
91
|
+
@mousedown.prevent="createFromQuery()"
|
|
92
|
+
@mousemove="cursor = results.length"
|
|
93
|
+
:class="cursor === results.length ? 'bg-success-soft text-success-strong' : 'text-fg-success hover:bg-neutral-secondary'"
|
|
94
|
+
class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5"
|
|
95
|
+
>
|
|
96
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
97
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
|
|
98
|
+
</svg>
|
|
99
|
+
<span>Create "<span x-text="query"></span>"</span>
|
|
100
|
+
</li>
|
|
101
|
+
</template>
|
|
102
|
+
<template x-if="canCreateAndEdit">
|
|
103
|
+
<li
|
|
104
|
+
role="option"
|
|
105
|
+
@mousedown.prevent="createAndEdit()"
|
|
106
|
+
class="flex cursor-pointer items-center gap-1.5 border-t border-default px-3 py-1.5 text-body hover:bg-neutral-secondary"
|
|
107
|
+
>
|
|
108
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true">
|
|
109
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"/>
|
|
110
|
+
</svg>
|
|
111
|
+
<span>Create and edit…</span>
|
|
112
|
+
</li>
|
|
113
|
+
</template>
|
|
114
|
+
</ul>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
{{-- Relation table (HasMany / M2M). Expects `relationConfig`. --}}
|
|
2
|
+
@if(relationConfig.requiresParent)
|
|
3
|
+
<div class="rounded-md border border-dashed border-default px-4 py-8 text-center text-sm text-body-subtle">
|
|
4
|
+
Save this record first to manage related {{ relationConfig.singularLabel }} records.
|
|
5
|
+
</div>
|
|
6
|
+
@else
|
|
7
|
+
<div
|
|
8
|
+
class="shamar-m2m-table relative w-full"
|
|
9
|
+
data-shamar-m2m-config="{{ jsonAttr(relationConfig) }}"
|
|
10
|
+
x-data="shamarM2mTableFromEl($el)"
|
|
11
|
+
@click.outside="closeAdd()"
|
|
12
|
+
@keydown.escape.prevent.stop="closeAdd()"
|
|
13
|
+
>
|
|
14
|
+
@if(relationConfig.kind !== 'hasMany')
|
|
15
|
+
<template x-for="item in selected" :key="'h-' + item.id">
|
|
16
|
+
<input type="hidden" name="{{ relationConfig.name }}[]" :value="item.id" />
|
|
17
|
+
</template>
|
|
18
|
+
@endif
|
|
19
|
+
|
|
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>
|
|
81
|
+
</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>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
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>
|
|
107
|
+
</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>
|
|
118
|
+
</td>
|
|
119
|
+
<td class="px-3 py-2 text-right" x-show="!readonly">
|
|
120
|
+
<div class="inline-flex items-center gap-1 text-xs">
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="rounded-md px-2 py-1 text-body hover:bg-surface-hover"
|
|
124
|
+
@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>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
@endif
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{{--
|
|
2
|
+
Renders one form container: Section card, Fieldset legend, or plain grid.
|
|
3
|
+
Expects: section, mode
|
|
4
|
+
--}}
|
|
5
|
+
@if((section.kind ?? 'section') === 'plain')
|
|
6
|
+
<div class="shamar-form-plain">
|
|
7
|
+
<div class="shamar-form-grid" style="--shamar-form-cols: {{ section.columns ?? 2 }}">
|
|
8
|
+
@each(field in section.fields)
|
|
9
|
+
@if(!field.hiddenOnForm && !(field.createOnly && mode === 'edit'))
|
|
10
|
+
<div
|
|
11
|
+
class="shamar-field-cell"
|
|
12
|
+
style="{{ resolveGridItemStyle(field, section.columns ?? 2) }}"
|
|
13
|
+
x-show="isVisible('{{ field.name }}')"
|
|
14
|
+
>
|
|
15
|
+
@include('shamar::partials/field-input')
|
|
16
|
+
</div>
|
|
17
|
+
@endif
|
|
18
|
+
@endeach
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
@elseif((section.kind ?? 'section') === 'fieldset')
|
|
22
|
+
<fieldset class="shamar-form-fieldset{{ section.card ? ' shamar-form-fieldset--card' : '' }}">
|
|
23
|
+
@if(section.title)
|
|
24
|
+
<legend class="shamar-form-fieldset__legend">{{ section.title }}</legend>
|
|
25
|
+
@endif
|
|
26
|
+
@if(section.description)
|
|
27
|
+
<p class="shamar-form-fieldset__description">{{ section.description }}</p>
|
|
28
|
+
@endif
|
|
29
|
+
<div class="shamar-form-grid" style="--shamar-form-cols: {{ section.columns ?? 2 }}">
|
|
30
|
+
@each(field in section.fields)
|
|
31
|
+
@if(!field.hiddenOnForm && !(field.createOnly && mode === 'edit'))
|
|
32
|
+
<div
|
|
33
|
+
class="shamar-field-cell"
|
|
34
|
+
style="{{ resolveGridItemStyle(field, section.columns ?? 2) }}"
|
|
35
|
+
x-show="isVisible('{{ field.name }}')"
|
|
36
|
+
>
|
|
37
|
+
@include('shamar::partials/field-input')
|
|
38
|
+
</div>
|
|
39
|
+
@endif
|
|
40
|
+
@endeach
|
|
41
|
+
</div>
|
|
42
|
+
</fieldset>
|
|
43
|
+
@else
|
|
44
|
+
<section class="shamar-form-section rounded-xl overflow-hidden{{ section.card !== false ? ' shamar-card' : ' shamar-form-section--plain' }}">
|
|
45
|
+
@if(section.title || section.icon || section.description)
|
|
46
|
+
<header class="shamar-form-section__header{{ section.description ? '' : ' shamar-form-section__header--centered' }}">
|
|
47
|
+
@if(section.icon)
|
|
48
|
+
<span class="shamar-form-section__icon" aria-hidden="true" data-icon="{{ section.icon }}">
|
|
49
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="w-5 h-5">
|
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
|
51
|
+
</svg>
|
|
52
|
+
</span>
|
|
53
|
+
@endif
|
|
54
|
+
@if(section.title || section.description)
|
|
55
|
+
<div class="shamar-form-section__titles min-w-0">
|
|
56
|
+
@if(section.title)
|
|
57
|
+
<h3 class="shamar-form-section__title">{{ section.title }}</h3>
|
|
58
|
+
@endif
|
|
59
|
+
@if(section.description)
|
|
60
|
+
<p class="shamar-form-section__description">{{ section.description }}</p>
|
|
61
|
+
@endif
|
|
62
|
+
</div>
|
|
63
|
+
@endif
|
|
64
|
+
</header>
|
|
65
|
+
@endif
|
|
66
|
+
<div class="shamar-form-section__body">
|
|
67
|
+
<div class="shamar-form-grid" style="--shamar-form-cols: {{ section.columns ?? 2 }}">
|
|
68
|
+
@each(field in section.fields)
|
|
69
|
+
@if(!field.hiddenOnForm && !(field.createOnly && mode === 'edit'))
|
|
70
|
+
<div
|
|
71
|
+
class="shamar-field-cell"
|
|
72
|
+
style="{{ resolveGridItemStyle(field, section.columns ?? 2) }}"
|
|
73
|
+
x-show="isVisible('{{ field.name }}')"
|
|
74
|
+
>
|
|
75
|
+
@include('shamar::partials/field-input')
|
|
76
|
+
</div>
|
|
77
|
+
@endif
|
|
78
|
+
@endeach
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
@endif
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.8" aria-hidden="true">
|
|
2
|
+
@if((root && root.icon === 'home') || (item && (item.icon ?? 'squares-2x2') === 'home'))
|
|
3
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/>
|
|
4
|
+
@elseif((root && root.icon === 'cog') || (item && (item.icon ?? 'squares-2x2') === 'cog'))
|
|
5
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"/>
|
|
6
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
7
|
+
@elseif((root && root.icon === 'building') || (item && (item.icon ?? 'squares-2x2') === 'building'))
|
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21"/>
|
|
9
|
+
@elseif((root && root.icon === 'users') || (item && (item.icon ?? 'squares-2x2') === 'users'))
|
|
10
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766C4.09 17.49 6 15.5 6 13.5V11.25m9 7.878V11.25M12 7.875a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0z"/>
|
|
11
|
+
@else
|
|
12
|
+
<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"/>
|
|
13
|
+
@endif
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<tr class="hover:bg-surface-row-hover transition-colors" data-shamar-record-id="{{ record.id }}">
|
|
2
|
+
<td class="px-3 py-3">
|
|
3
|
+
@if(bulkActions && bulkActions.length)
|
|
4
|
+
<input
|
|
5
|
+
type="checkbox"
|
|
6
|
+
class="rounded border-default"
|
|
7
|
+
:checked="selected.includes({{ jsonAttr(String(record.id)) }})"
|
|
8
|
+
@change="toggleId({{ jsonAttr(String(record.id)) }})"
|
|
9
|
+
aria-label="Select record"
|
|
10
|
+
/>
|
|
11
|
+
@endif
|
|
12
|
+
</td>
|
|
13
|
+
@each(column in resource.columns)
|
|
14
|
+
<td class="px-4 py-3 text-body {{ resolveAlignmentClass(column) }}">
|
|
15
|
+
@if(column.type === 'boolean' || column.format === 'boolean' || column.format === 'toggle')
|
|
16
|
+
@!component('shamar::partials/boolean-value', {
|
|
17
|
+
checked: !!record[column.name],
|
|
18
|
+
label: cellValue(record, column),
|
|
19
|
+
})
|
|
20
|
+
@elseif(column.format === 'badge' || column.type === 'tags')
|
|
21
|
+
@let(badges = badgeValues(record, column))
|
|
22
|
+
@if(badges.length)
|
|
23
|
+
<div class="shamar-badges">
|
|
24
|
+
@each(badge in badges)
|
|
25
|
+
<span class="shamar-badge">{{ badge }}</span>
|
|
26
|
+
@endeach
|
|
27
|
+
</div>
|
|
28
|
+
@else
|
|
29
|
+
—
|
|
30
|
+
@endif
|
|
31
|
+
@else
|
|
32
|
+
<a href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}{{ recordNavQuery(query) }}" class="text-heading hover:text-fg-brand hover:underline">{{ cellValue(record, column) }}</a>
|
|
33
|
+
@endif
|
|
34
|
+
</td>
|
|
35
|
+
@endeach
|
|
36
|
+
<td class="px-4 py-3 text-right">
|
|
37
|
+
<div class="inline-flex flex-wrap items-center justify-end gap-1 text-xs">
|
|
38
|
+
@if(rowActions)
|
|
39
|
+
@each(action in rowActions)
|
|
40
|
+
@if(action.name === 'view')
|
|
41
|
+
<a href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}{{ recordNavQuery(query) }}" class="px-2 py-1 rounded-md text-body hover:bg-surface-hover">{{ action.label }}</a>
|
|
42
|
+
@elseif(action.name === 'edit' && showEditButton)
|
|
43
|
+
<a href="{{ basePath }}/{{ resource.slug }}/{{ record.id }}/edit{{ recordNavQuery(query) }}" class="px-2 py-1 rounded-md text-body hover:bg-surface-hover">{{ action.label }}</a>
|
|
44
|
+
@elseif(action.name === 'delete' && showDeleteButton)
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="px-2 py-1 rounded-md text-fg-danger hover:bg-danger-soft"
|
|
48
|
+
@click="$dispatch('shamar-open-confirm', { variant: 'danger', mode: 'confirm', title: {{ jsonAttr(action.label) }}, message: {{ jsonAttr(action.confirm || 'Are you sure?') }}, confirmLabel: 'Delete', action: '{{ basePath }}/{{ resource.slug }}/{{ record.id }}/delete' })"
|
|
49
|
+
>{{ action.label }}</button>
|
|
50
|
+
@elseif(action.name === 'revoke' && !record.revokedAt)
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
class="px-2 py-1 rounded-md text-fg-danger hover:bg-danger-soft"
|
|
54
|
+
@click="$dispatch('shamar-open-confirm', { variant: 'danger', 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 }}' })"
|
|
55
|
+
>{{ action.label }}</button>
|
|
56
|
+
@elseif(action.name === 'reactivate' && record.revokedAt)
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
class="px-2 py-1 rounded-md text-body hover:bg-surface-hover"
|
|
60
|
+
@click="$dispatch('shamar-open-confirm', { variant: '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 }}' })"
|
|
61
|
+
>{{ action.label }}</button>
|
|
62
|
+
@elseif(action.name !== 'view' && action.name !== 'edit' && action.name !== 'delete' && action.name !== 'revoke' && action.name !== 'reactivate')
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
class="px-2 py-1 rounded-md text-body hover:bg-surface-hover {{ action.color === 'danger' ? 'text-fg-danger hover:bg-danger-soft' : '' }}"
|
|
66
|
+
@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 }}' })"
|
|
67
|
+
>{{ action.label }}</button>
|
|
68
|
+
@endif
|
|
69
|
+
@endeach
|
|
70
|
+
@endif
|
|
71
|
+
</div>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|