@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.
Files changed (134) hide show
  1. package/assets/admin.css +2 -0
  2. package/assets/alpine.min.js +5 -0
  3. package/assets/input.css +2286 -0
  4. package/assets/shamar-ui.js +3658 -0
  5. package/dist/assets-controller.d.ts +12 -0
  6. package/dist/assets-controller.d.ts.map +1 -0
  7. package/dist/assets-controller.js +32 -0
  8. package/dist/assets-controller.js.map +1 -0
  9. package/dist/config.d.ts +79 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +38 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/configure.d.ts +21 -0
  14. package/dist/configure.d.ts.map +1 -0
  15. package/dist/configure.js +20 -0
  16. package/dist/configure.js.map +1 -0
  17. package/dist/context.d.ts +22 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +2 -0
  20. package/dist/context.js.map +1 -0
  21. package/dist/controller.d.ts +31 -0
  22. package/dist/controller.d.ts.map +1 -0
  23. package/dist/controller.js +45 -0
  24. package/dist/controller.js.map +1 -0
  25. package/dist/controllers/admin_controller.d.ts +72 -0
  26. package/dist/controllers/admin_controller.d.ts.map +1 -0
  27. package/dist/controllers/admin_controller.js +1075 -0
  28. package/dist/controllers/admin_controller.js.map +1 -0
  29. package/dist/discover.d.ts +7 -0
  30. package/dist/discover.d.ts.map +1 -0
  31. package/dist/discover.js +50 -0
  32. package/dist/discover.js.map +1 -0
  33. package/dist/form-state.d.ts +41 -0
  34. package/dist/form-state.d.ts.map +1 -0
  35. package/dist/form-state.js +74 -0
  36. package/dist/form-state.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +10 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/middleware/require_api_key_middleware.d.ts +30 -0
  42. package/dist/middleware/require_api_key_middleware.d.ts.map +1 -0
  43. package/dist/middleware/require_api_key_middleware.js +59 -0
  44. package/dist/middleware/require_api_key_middleware.js.map +1 -0
  45. package/dist/provider.d.ts +9 -0
  46. package/dist/provider.d.ts.map +1 -0
  47. package/dist/provider.js +66 -0
  48. package/dist/provider.js.map +1 -0
  49. package/dist/resources/api_key_resource.d.ts +35 -0
  50. package/dist/resources/api_key_resource.d.ts.map +1 -0
  51. package/dist/resources/api_key_resource.js +225 -0
  52. package/dist/resources/api_key_resource.js.map +1 -0
  53. package/dist/routes.d.ts +5 -0
  54. package/dist/routes.d.ts.map +1 -0
  55. package/dist/routes.js +97 -0
  56. package/dist/routes.js.map +1 -0
  57. package/dist/runtime.d.ts +27 -0
  58. package/dist/runtime.d.ts.map +1 -0
  59. package/dist/runtime.js +74 -0
  60. package/dist/runtime.js.map +1 -0
  61. package/dist/shamar/auth.d.ts +30 -0
  62. package/dist/shamar/auth.d.ts.map +1 -0
  63. package/dist/shamar/auth.js +218 -0
  64. package/dist/shamar/auth.js.map +1 -0
  65. package/dist/shamar/branding.d.ts +24 -0
  66. package/dist/shamar/branding.d.ts.map +1 -0
  67. package/dist/shamar/branding.js +91 -0
  68. package/dist/shamar/branding.js.map +1 -0
  69. package/dist/shamar/display-relations.d.ts +7 -0
  70. package/dist/shamar/display-relations.d.ts.map +1 -0
  71. package/dist/shamar/display-relations.js +89 -0
  72. package/dist/shamar/display-relations.js.map +1 -0
  73. package/dist/shamar/list-headers.d.ts +38 -0
  74. package/dist/shamar/list-headers.d.ts.map +1 -0
  75. package/dist/shamar/list-headers.js +218 -0
  76. package/dist/shamar/list-headers.js.map +1 -0
  77. package/dist/shamar/list-query.d.ts +229 -0
  78. package/dist/shamar/list-query.d.ts.map +1 -0
  79. package/dist/shamar/list-query.js +564 -0
  80. package/dist/shamar/list-query.js.map +1 -0
  81. package/dist/shamar/menu.d.ts +48 -0
  82. package/dist/shamar/menu.d.ts.map +1 -0
  83. package/dist/shamar/menu.js +93 -0
  84. package/dist/shamar/menu.js.map +1 -0
  85. package/dist/shamar/paths.d.ts +5 -0
  86. package/dist/shamar/paths.d.ts.map +1 -0
  87. package/dist/shamar/paths.js +16 -0
  88. package/dist/shamar/paths.js.map +1 -0
  89. package/dist/shamar/relation-fields.d.ts +54 -0
  90. package/dist/shamar/relation-fields.d.ts.map +1 -0
  91. package/dist/shamar/relation-fields.js +94 -0
  92. package/dist/shamar/relation-fields.js.map +1 -0
  93. package/dist/shamar/resource-actions.d.ts +9 -0
  94. package/dist/shamar/resource-actions.d.ts.map +1 -0
  95. package/dist/shamar/resource-actions.js +42 -0
  96. package/dist/shamar/resource-actions.js.map +1 -0
  97. package/dist/shamar/view-context.d.ts +79 -0
  98. package/dist/shamar/view-context.d.ts.map +1 -0
  99. package/dist/shamar/view-context.js +74 -0
  100. package/dist/shamar/view-context.js.map +1 -0
  101. package/dist/types.d.ts +15 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +2 -0
  104. package/dist/types.js.map +1 -0
  105. package/package.json +67 -0
  106. package/resources/views/shamar/dashboard.edge +24 -0
  107. package/resources/views/shamar/form.edge +87 -0
  108. package/resources/views/shamar/index.edge +149 -0
  109. package/resources/views/shamar/partials/boolean-value.edge +25 -0
  110. package/resources/views/shamar/partials/csrf.edge +1 -0
  111. package/resources/views/shamar/partials/detail-entry.edge +72 -0
  112. package/resources/views/shamar/partials/dialog-host.edge +125 -0
  113. package/resources/views/shamar/partials/field-affix-icon.edge +23 -0
  114. package/resources/views/shamar/partials/field-input.edge +585 -0
  115. package/resources/views/shamar/partials/field-relation-checkbox.edge +104 -0
  116. package/resources/views/shamar/partials/field-relation-m2m.edge +110 -0
  117. package/resources/views/shamar/partials/field-relation-m2o.edge +116 -0
  118. package/resources/views/shamar/partials/field-relation-table.edge +139 -0
  119. package/resources/views/shamar/partials/form-container.edge +82 -0
  120. package/resources/views/shamar/partials/icon.edge +14 -0
  121. package/resources/views/shamar/partials/list-row.edge +73 -0
  122. package/resources/views/shamar/partials/list-toolbar.edge +269 -0
  123. package/resources/views/shamar/partials/nav-mobile.edge +21 -0
  124. package/resources/views/shamar/partials/nav.edge +58 -0
  125. package/resources/views/shamar/partials/page-heading.edge +130 -0
  126. package/resources/views/shamar/partials/pagination.edge +73 -0
  127. package/resources/views/shamar/partials/record-pager.edge +37 -0
  128. package/resources/views/shamar/partials/schema-node.edge +289 -0
  129. package/resources/views/shamar/partials/shell-close.edge +20 -0
  130. package/resources/views/shamar/partials/shell-open.edge +51 -0
  131. package/resources/views/shamar/partials/toast.edge +5 -0
  132. package/resources/views/shamar/partials/topbar.edge +115 -0
  133. package/resources/views/shamar/show.edge +17 -0
  134. package/stubs/config/shamar.stub +29 -0
@@ -0,0 +1,289 @@
1
+ {{--
2
+ Recursive Filament-style schema node.
3
+ Expects: node, schemaMode ('form' | 'detail'), mode (create|edit for forms), record (for detail)
4
+ --}}
5
+ @if(node.kind === 'field' && schemaMode === 'form' && node.field)
6
+ @let(field = node.field)
7
+ @if(!field.hiddenOnForm && !(field.createOnly && mode === 'edit'))
8
+ <div
9
+ class="shamar-field-cell"
10
+ style="{{ resolveGridItemStyle(field, 2) }}"
11
+ x-show="isVisible('{{ field.name }}')"
12
+ >
13
+ @include('shamar::partials/field-input')
14
+ </div>
15
+ @endif
16
+
17
+ @elseif(node.kind === 'entry' && schemaMode === 'detail' && node.entry)
18
+ @let(field = node.entry)
19
+ @include('shamar::partials/detail-entry')
20
+
21
+ @elseif(node.kind === 'placeholder')
22
+ <div class="shamar-placeholder" style="{{ resolveGridItemStyle(node, 2) }}">
23
+ @if(node.title)
24
+ <div class="shamar-placeholder__label">{{ node.title }}</div>
25
+ @endif
26
+ @if(node.content)
27
+ <div class="shamar-placeholder__content">{{ node.content }}</div>
28
+ @endif
29
+ </div>
30
+
31
+ @elseif(node.kind === 'callout')
32
+ <div class="shamar-callout shamar-callout--{{ node.status ?? 'info' }}" style="{{ resolveGridItemStyle(node, 2) }}">
33
+ @if(node.title)
34
+ <div class="shamar-callout__title">{{ node.title }}</div>
35
+ @endif
36
+ @if(node.content || node.description)
37
+ <div class="shamar-callout__body">{{ node.content ?? node.description }}</div>
38
+ @endif
39
+ @if(node.children && node.children.length)
40
+ <div class="shamar-callout__children">
41
+ @each(childNode in node.children)
42
+ @!component('shamar::partials/schema-node', {
43
+ node: childNode,
44
+ schemaMode: schemaMode,
45
+ mode: mode,
46
+ record: record,
47
+ formErrors: formErrors,
48
+ relationUi: relationUi,
49
+ })
50
+ @endeach
51
+ </div>
52
+ @endif
53
+ </div>
54
+
55
+ @elseif(node.kind === 'empty_state')
56
+ <div class="shamar-empty-state" style="{{ resolveGridItemStyle(node, 2) }}">
57
+ @if(node.title)
58
+ <h3 class="shamar-empty-state__title">{{ node.title }}</h3>
59
+ @endif
60
+ @if(node.description)
61
+ <p class="shamar-empty-state__description">{{ node.description }}</p>
62
+ @endif
63
+ </div>
64
+
65
+ @elseif(node.kind === 'tabs')
66
+ <div
67
+ class="shamar-tabs{{ node.card !== false ? ' shamar-tabs--contained' : '' }}{{ node.vertical ? ' shamar-tabs--vertical' : '' }}"
68
+ style="{{ resolveGridItemStyle(node, 2) }}"
69
+ x-data='shamarTabs({{ node.activeTab ?? 1 }})'
70
+ >
71
+ <div class="shamar-tabs__list" role="tablist">
72
+ @each((tab, tabIndex) in (node.children || []))
73
+ <button
74
+ type="button"
75
+ class="shamar-tabs__tab"
76
+ role="tab"
77
+ :class="{ 'is-active': active === {{ tabIndex + 1 }} }"
78
+ @click="active = {{ tabIndex + 1 }}"
79
+ >
80
+ <span>{{ tab.title || ('Tab ' + (tabIndex + 1)) }}</span>
81
+ @if(tab.badge !== undefined && tab.badge !== null)
82
+ <span class="shamar-tabs__badge">{{ tab.badge }}</span>
83
+ @endif
84
+ </button>
85
+ @endeach
86
+ </div>
87
+ <div class="shamar-tabs__panels">
88
+ @each((tab, tabIndex) in (node.children || []))
89
+ <div class="shamar-tabs__panel" x-show="active === {{ tabIndex + 1 }}" role="tabpanel">
90
+ <div
91
+ class="shamar-form-grid{{ tab.dense ? ' is-dense' : '' }}{{ tab.gap === false ? ' is-gapless' : '' }}"
92
+ style="--shamar-form-cols: {{ tab.columns ?? node.columns ?? 2 }}"
93
+ >
94
+ @each(childNode in (tab.children || []))
95
+ @!component('shamar::partials/schema-node', {
96
+ node: childNode,
97
+ schemaMode: schemaMode,
98
+ mode: mode,
99
+ record: record,
100
+ formErrors: formErrors,
101
+ relationUi: relationUi,
102
+ })
103
+ @endeach
104
+ </div>
105
+ </div>
106
+ @endeach
107
+ </div>
108
+ </div>
109
+
110
+ @elseif(node.kind === 'wizard')
111
+ <div
112
+ class="shamar-wizard{{ node.card !== false ? ' shamar-card rounded-xl' : '' }}"
113
+ style="{{ resolveGridItemStyle(node, 2) }}"
114
+ x-data='shamarWizard({{ (node.children || []).length }})'
115
+ >
116
+ <ol class="shamar-wizard__steps">
117
+ @each((step, stepIndex) in (node.children || []))
118
+ <li class="shamar-wizard__step" :class="{ 'is-active': step === {{ stepIndex + 1 }}, 'is-done': step > {{ stepIndex + 1 }} }">
119
+ <span class="shamar-wizard__index">{{ stepIndex + 1 }}</span>
120
+ <span class="shamar-wizard__label">{{ step.title || ('Step ' + (stepIndex + 1)) }}</span>
121
+ </li>
122
+ @endeach
123
+ </ol>
124
+ <div class="shamar-wizard__panels">
125
+ @each((step, stepIndex) in (node.children || []))
126
+ <div class="shamar-wizard__panel" x-show="step === {{ stepIndex + 1 }}">
127
+ @if(step.description)
128
+ <p class="shamar-wizard__description">{{ step.description }}</p>
129
+ @endif
130
+ <div
131
+ class="shamar-form-grid{{ step.dense ? ' is-dense' : '' }}{{ step.gap === false ? ' is-gapless' : '' }}"
132
+ style="--shamar-form-cols: {{ step.columns ?? 2 }}"
133
+ >
134
+ @each(childNode in (step.children || []))
135
+ @!component('shamar::partials/schema-node', {
136
+ node: childNode,
137
+ schemaMode: schemaMode,
138
+ mode: mode,
139
+ record: record,
140
+ formErrors: formErrors,
141
+ relationUi: relationUi,
142
+ })
143
+ @endeach
144
+ </div>
145
+ </div>
146
+ @endeach
147
+ </div>
148
+ <div class="shamar-wizard__nav">
149
+ <button type="button" class="shamar-wizard__btn" x-show="step > 1" @click="prev()">Previous</button>
150
+ <button type="button" class="shamar-wizard__btn shamar-wizard__btn--primary" x-show="step < total" @click="next()">Next</button>
151
+ </div>
152
+ </div>
153
+
154
+ @elseif(node.kind === 'flex')
155
+ <div
156
+ class="shamar-flex shamar-flex--from-{{ node.from ?? 'md' }}"
157
+ style="{{ resolveGridItemStyle(node, 2) }}"
158
+ >
159
+ @each(childNode in (node.children || []))
160
+ <div class="shamar-flex__item{{ childNode.grow === false ? ' shamar-flex__item--fixed' : '' }}">
161
+ @!component('shamar::partials/schema-node', {
162
+ node: childNode,
163
+ schemaMode: schemaMode,
164
+ mode: mode,
165
+ record: record,
166
+ formErrors: formErrors,
167
+ relationUi: relationUi,
168
+ })
169
+ </div>
170
+ @endeach
171
+ </div>
172
+
173
+ @elseif(node.kind === 'grid' || node.kind === 'group' || node.kind === 'plain')
174
+ <div
175
+ class="{{ node.kind === 'group' ? 'shamar-group' : (node.kind === 'grid' ? 'shamar-grid-wrap' : 'shamar-form-plain') }}"
176
+ style="{{ resolveGridItemStyle(node, 2) }}"
177
+ >
178
+ <div
179
+ class="{{ schemaMode === 'detail' ? 'shamar-detail-grid' : 'shamar-form-grid' }}{{ node.dense ? ' is-dense' : '' }}{{ node.gap === false ? ' is-gapless' : '' }}"
180
+ style="--shamar-form-cols: {{ node.columns || 2 }}; --shamar-detail-cols: {{ node.columns || 2 }}"
181
+ >
182
+ @each(childNode in (node.children || []))
183
+ @!component('shamar::partials/schema-node', {
184
+ node: childNode,
185
+ schemaMode: schemaMode,
186
+ mode: mode,
187
+ record: record,
188
+ formErrors: formErrors,
189
+ relationUi: relationUi,
190
+ })
191
+ @endeach
192
+ </div>
193
+ </div>
194
+
195
+ @elseif(node.kind === 'fieldset')
196
+ <fieldset
197
+ class="shamar-form-fieldset{{ node.card ? ' shamar-form-fieldset--card' : '' }}{{ node.dense ? ' is-dense' : '' }}"
198
+ style="{{ resolveGridItemStyle(node, 2) }}"
199
+ >
200
+ @if(node.title)
201
+ <legend class="shamar-form-fieldset__legend">{{ node.title }}</legend>
202
+ @endif
203
+ @if(node.description)
204
+ <p class="shamar-form-fieldset__description">{{ node.description }}</p>
205
+ @endif
206
+ <div
207
+ class="{{ schemaMode === 'detail' ? 'shamar-detail-grid' : 'shamar-form-grid' }}{{ node.gap === false ? ' is-gapless' : '' }}"
208
+ style="--shamar-form-cols: {{ node.columns || 2 }}; --shamar-detail-cols: {{ node.columns || 2 }}"
209
+ >
210
+ @each(childNode in (node.children || []))
211
+ @!component('shamar::partials/schema-node', {
212
+ node: childNode,
213
+ schemaMode: schemaMode,
214
+ mode: mode,
215
+ record: record,
216
+ formErrors: formErrors,
217
+ relationUi: relationUi,
218
+ })
219
+ @endeach
220
+ </div>
221
+ </fieldset>
222
+
223
+ @elseif(node.kind === 'section' || node.kind === 'tab' || node.kind === 'step')
224
+ <section
225
+ class="shamar-form-section rounded-xl{{ node.card !== false && node.kind === 'section' ? ' shamar-card' : '' }}{{ node.card === false && node.kind === 'section' ? ' shamar-form-section--plain' : '' }}"
226
+ style="{{ resolveGridItemStyle(node, 2) }}"
227
+ @if(node.collapsible)
228
+ x-data="{ open: {{ node.collapsed ? 'false' : 'true' }} }"
229
+ @endif
230
+ >
231
+ @if(node.title || node.icon || node.description || node.collapsible)
232
+ <header
233
+ class="shamar-form-section__header{{ node.description ? '' : ' shamar-form-section__header--centered' }}{{ node.collapsible ? ' shamar-form-section__header--collapsible' : '' }}"
234
+ @if(node.collapsible)
235
+ @click="open = !open"
236
+ role="button"
237
+ :aria-expanded="open"
238
+ @endif
239
+ >
240
+ @if(node.icon)
241
+ <span class="shamar-form-section__icon" aria-hidden="true" data-icon="{{ node.icon }}">
242
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="w-5 h-5">
243
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
244
+ </svg>
245
+ </span>
246
+ @endif
247
+ @if(node.title || node.description)
248
+ <div class="shamar-form-section__titles min-w-0">
249
+ @if(node.title)
250
+ <h3 class="shamar-form-section__title">{{ node.title }}</h3>
251
+ @endif
252
+ @if(node.description)
253
+ <p class="shamar-form-section__description">{{ node.description }}</p>
254
+ @endif
255
+ </div>
256
+ @endif
257
+ @if(node.collapsible)
258
+ <span class="shamar-form-section__chevron" aria-hidden="true" :class="{ 'is-open': open }">
259
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
260
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
261
+ </svg>
262
+ </span>
263
+ @endif
264
+ </header>
265
+ @endif
266
+ <div
267
+ class="shamar-form-section__body"
268
+ @if(node.collapsible)
269
+ x-show="open"
270
+ @endif
271
+ >
272
+ <div
273
+ class="{{ schemaMode === 'detail' ? 'shamar-detail-grid' : 'shamar-form-grid' }}{{ node.dense ? ' is-dense' : '' }}{{ node.gap === false ? ' is-gapless' : '' }}"
274
+ style="--shamar-form-cols: {{ node.columns || 2 }}; --shamar-detail-cols: {{ node.columns || 2 }}"
275
+ >
276
+ @each(childNode in (node.children || []))
277
+ @!component('shamar::partials/schema-node', {
278
+ node: childNode,
279
+ schemaMode: schemaMode,
280
+ mode: mode,
281
+ record: record,
282
+ formErrors: formErrors,
283
+ relationUi: relationUi,
284
+ })
285
+ @endeach
286
+ </div>
287
+ </div>
288
+ </section>
289
+ @endif
@@ -0,0 +1,20 @@
1
+ </main>
2
+ </div>
3
+ </div>
4
+
5
+ @include('shamar::partials/dialog-host')
6
+
7
+ <script>
8
+ function shamarShell() {
9
+ return {
10
+ sidebarOpen: false,
11
+ toggleTheme() {
12
+ const root = document.documentElement;
13
+ const next = root.classList.toggle('dark');
14
+ localStorage.setItem('shamar-theme', next ? 'dark' : 'light');
15
+ },
16
+ };
17
+ }
18
+ </script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,51 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{ pageTitle }} · {{ panelTitle }}</title>
7
+ @if(branding.fontUrl)
8
+ <link rel="stylesheet" href="{{ branding.fontUrl }}" />
9
+ @endif
10
+ <link rel="stylesheet" href="{{ basePath }}/assets/admin.css" />
11
+ <link rel="stylesheet" href="{{ basePath }}/assets/branding.css" />
12
+ <meta name="csrf-token" content="{{ csrfToken }}" />
13
+ <script>
14
+ (function () {
15
+ const stored = localStorage.getItem('shamar-theme');
16
+ if (stored === 'dark' || (!stored && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
17
+ document.documentElement.classList.add('dark');
18
+ }
19
+ })();
20
+ </script>
21
+ <script defer src="{{ basePath }}/assets/shamar-ui.js"></script>
22
+ <script defer src="{{ basePath }}/assets/alpine.min.js"></script>
23
+ <style>[x-cloak] { display: none !important; }</style>
24
+ </head>
25
+ <body
26
+ class="min-h-screen bg-shamar-shell text-body antialiased flex md:flex-row"
27
+ x-data="shamarShell()"
28
+ data-shamar-base-path="{{ basePath }}"
29
+ >
30
+ <div
31
+ x-show="sidebarOpen"
32
+ x-cloak
33
+ x-transition.opacity
34
+ class="fixed inset-0 z-20 bg-black/30 md:hidden"
35
+ @click="sidebarOpen = false"
36
+ ></div>
37
+
38
+ @include('shamar::partials/nav')
39
+
40
+ <div class="flex min-w-0 flex-1 flex-col min-h-screen w-full">
41
+ @include('shamar::partials/topbar')
42
+
43
+ <div class="flex-1 flex flex-col min-h-0 overflow-x-hidden w-full p-3 md:p-4 gap-4">
44
+
45
+ @include('shamar::partials/toast')
46
+
47
+ @if(pageTitle)
48
+ @include('shamar::partials/page-heading')
49
+ @endif
50
+
51
+ <main class="flex-1 min-h-0 overflow-auto">
@@ -0,0 +1,5 @@
1
+ @if(flashJson)
2
+ <script id="shamar-initial-flash" type="application/json">{{{ flashJson }}}</script>
3
+ @endif
4
+
5
+ <div id="shamar-toast-stack" aria-live="polite" aria-atomic="true"></div>
@@ -0,0 +1,115 @@
1
+ <header class="flex items-center gap-3 px-4 md:px-6 min-h-[60px] shrink-0 bg-shamar-shell sticky top-0 z-30 overflow-visible">
2
+ <button
3
+ type="button"
4
+ @click="sidebarOpen = true"
5
+ aria-label="Open menu"
6
+ class="md:hidden p-1.5 -ml-1.5 rounded-md text-body hover:bg-surface-hover"
7
+ >
8
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
9
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"/>
10
+ </svg>
11
+ </button>
12
+
13
+ <div class="hidden md:flex flex-1 min-w-0 overflow-visible">
14
+ <nav
15
+ class="shamar-app-menu flex-1 min-w-0 flex flex-wrap items-center gap-1 overflow-visible"
16
+ x-data="{ openKey: '' }"
17
+ @click.outside="openKey = ''"
18
+ @keydown.escape.window="openKey = ''"
19
+ aria-label="{{ menuActiveRoot ? `${menuActiveRoot.label} menu` : 'App menu' }}"
20
+ >
21
+ @if(menuActiveRoot)
22
+ @if(!menuSecondary.length)
23
+ <span class="shrink-0 text-sm font-semibold text-heading truncate max-w-[12rem]">{{ menuActiveRoot.label }}</span>
24
+ @endif
25
+ @endif
26
+
27
+ @each(item in menuSecondary)
28
+ @if(item.children && item.children.length)
29
+ <div class="relative shrink-0">
30
+ <button
31
+ type="button"
32
+ @click="openKey = openKey === 'sec-{{ $index }}' ? '' : 'sec-{{ $index }}'"
33
+ class="flex items-center gap-1 px-2.5 py-1.5 rounded-md text-sm whitespace-nowrap {{ item.active ? 'bg-surface-inset text-fg-brand font-medium' : 'text-body hover:text-heading hover:bg-surface-hover' }}"
34
+ :aria-expanded="(openKey === 'sec-{{ $index }}').toString()"
35
+ >
36
+ {{ item.label }}
37
+ <svg class="w-3 h-3 text-body-subtle transition-transform" :class="{ 'rotate-180': openKey === 'sec-{{ $index }}' }" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
38
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
39
+ </svg>
40
+ </button>
41
+ <div
42
+ x-show="openKey === 'sec-{{ $index }}'"
43
+ x-cloak
44
+ x-transition
45
+ class="absolute left-0 top-full z-50 mt-1 min-w-[10rem] shamar-card rounded-xl shadow-lg py-1"
46
+ >
47
+ @each(child in item.children)
48
+ <a href="{{ child.href }}" class="block px-3 py-1.5 text-sm whitespace-nowrap {{ child.active ? 'text-fg-brand font-medium bg-surface-inset' : 'text-body hover:bg-surface-hover hover:text-heading' }}">{{ child.label }}</a>
49
+ @endeach
50
+ </div>
51
+ </div>
52
+ @else
53
+ <a href="{{ item.href }}" class="shrink-0 px-2.5 py-1.5 rounded-md text-sm whitespace-nowrap {{ item.active ? 'bg-surface-inset text-fg-brand font-medium' : 'text-body hover:text-heading hover:bg-surface-hover' }}">{{ item.label }}</a>
54
+ @endif
55
+ @endeach
56
+ </nav>
57
+ </div>
58
+
59
+ @if(menuActiveRoot)
60
+ <p class="md:hidden flex-1 min-w-0 text-sm font-semibold text-heading truncate px-0.5">{{ menuActiveRoot.label }}</p>
61
+ @else
62
+ <div class="md:hidden flex-1 min-w-0" aria-hidden="true"></div>
63
+ @endif
64
+
65
+ <div class="flex items-center gap-2 shrink-0">
66
+ <button
67
+ type="button"
68
+ @click="toggleTheme()"
69
+ aria-label="Toggle dark mode"
70
+ class="p-1.5 rounded-md text-body hover:bg-surface-hover hover:text-heading"
71
+ >
72
+ <svg class="w-4 h-4 dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.8">
73
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
74
+ </svg>
75
+ <svg class="w-4 h-4 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.8">
76
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707M17.657 17.657l-.707-.707M6.343 6.343l-.707-.707M12 8a4 4 0 100 8 4 4 0 000-8z"/>
77
+ </svg>
78
+ </button>
79
+
80
+ <div class="relative" x-data="{ open: false }" @click.outside="open = false">
81
+ <button
82
+ type="button"
83
+ @click="open = !open"
84
+ aria-haspopup="true"
85
+ :aria-expanded="open.toString()"
86
+ class="flex items-center gap-2 pl-1 pr-2 py-1 rounded-md hover:bg-surface-hover"
87
+ >
88
+ <span class="w-7 h-7 rounded-full bg-fg-brand text-white text-xs font-semibold flex items-center justify-center shrink-0">{{ userInitial }}</span>
89
+ <span class="hidden sm:inline text-sm font-medium text-body max-w-[120px] truncate">{{ user.name }}</span>
90
+ <svg class="w-3 h-3 text-body-subtle hidden sm:block" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
91
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
92
+ </svg>
93
+ </button>
94
+ <div x-show="open" x-cloak x-transition class="absolute right-0 top-full mt-2 w-56 shamar-card rounded-xl shadow-lg overflow-hidden z-50">
95
+ <div class="px-4 py-3 border-b border-default">
96
+ <p class="text-sm font-semibold text-heading truncate">{{ user.name }}</p>
97
+ @if(user.email)
98
+ <p class="text-xs text-body-subtle">{{ user.email }}</p>
99
+ @endif
100
+ </div>
101
+ <div class="py-1 text-sm">
102
+ <a href="{{ basePath }}" class="block px-4 py-1.5 text-body hover:bg-surface-hover hover:text-heading">Admin</a>
103
+ @if(logoutPath)
104
+ <form method="post" action="{{ logoutPath }}" class="block">
105
+ @include('shamar::partials/csrf')
106
+ <button type="submit" class="w-full text-left px-4 py-1.5 text-body hover:bg-surface-hover hover:text-heading">
107
+ Sign out
108
+ </button>
109
+ </form>
110
+ @endif
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </header>
@@ -0,0 +1,17 @@
1
+ @let(mode = 'show')
2
+ @include('shamar::partials/shell-open')
3
+
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>
16
+
17
+ @include('shamar::partials/shell-close')
@@ -0,0 +1,29 @@
1
+ {{{
2
+ exports({ to: app.configPath('shamar.ts') })
3
+ }}}
4
+ import { defineConfig, panel } from '@shamar/adonis'
5
+ // import UserResource from '#resources/user_resource'
6
+
7
+ /**
8
+ * Persistence: {{ orm }}
9
+ * - lucid → Lucid SQL models (@shamar/lucid)
10
+ * - mongoose → Mongoose models (@shamar/mongoose)
11
+ *
12
+ * App owns the DB connection lifecycle (Lucid database provider or mongoose.connect).
13
+ *
14
+ * Prefer panels + discoverResources. Legacy `path` + `resources` still works
15
+ * as a single default panel.
16
+ */
17
+ export default defineConfig({
18
+ orm: '{{ orm }}',
19
+ branding: {
20
+ name: 'Shamar Admin',
21
+ },
22
+ panels: [
23
+ panel('admin')
24
+ .path('/admin')
25
+ .discoverResources('app/resources/admin')
26
+ // .resources([UserResource])
27
+ ,
28
+ ],
29
+ })