qdadm 0.16.0 → 0.18.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 (60) hide show
  1. package/README.md +153 -1
  2. package/package.json +15 -2
  3. package/src/components/forms/FormField.vue +64 -6
  4. package/src/components/forms/FormPage.vue +276 -0
  5. package/src/components/index.js +11 -0
  6. package/src/components/layout/BaseLayout.vue +183 -0
  7. package/src/components/layout/DashboardLayout.vue +100 -0
  8. package/src/components/layout/FormLayout.vue +261 -0
  9. package/src/components/layout/ListLayout.vue +334 -0
  10. package/src/components/layout/Zone.vue +165 -0
  11. package/src/components/layout/defaults/DefaultBreadcrumb.vue +140 -0
  12. package/src/components/layout/defaults/DefaultFooter.vue +56 -0
  13. package/src/components/layout/defaults/DefaultFormActions.vue +53 -0
  14. package/src/components/layout/defaults/DefaultHeader.vue +69 -0
  15. package/src/components/layout/defaults/DefaultMenu.vue +197 -0
  16. package/src/components/layout/defaults/DefaultPagination.vue +79 -0
  17. package/src/components/layout/defaults/DefaultTable.vue +130 -0
  18. package/src/components/layout/defaults/DefaultToaster.vue +16 -0
  19. package/src/components/layout/defaults/DefaultUserInfo.vue +96 -0
  20. package/src/components/layout/defaults/index.js +17 -0
  21. package/src/composables/index.js +6 -6
  22. package/src/composables/useForm.js +135 -0
  23. package/src/composables/useFormPageBuilder.js +1154 -0
  24. package/src/composables/useHooks.js +53 -0
  25. package/src/composables/useLayoutResolver.js +260 -0
  26. package/src/composables/useListPageBuilder.js +336 -52
  27. package/src/composables/useNavigation.js +38 -2
  28. package/src/composables/useSignals.js +49 -0
  29. package/src/composables/useZoneRegistry.js +162 -0
  30. package/src/core/bundles.js +406 -0
  31. package/src/core/decorator.js +322 -0
  32. package/src/core/extension.js +386 -0
  33. package/src/core/index.js +28 -0
  34. package/src/entity/EntityManager.js +359 -16
  35. package/src/entity/auth/AuthAdapter.js +184 -0
  36. package/src/entity/auth/PermissiveAdapter.js +64 -0
  37. package/src/entity/auth/RoleHierarchy.js +153 -0
  38. package/src/entity/auth/SecurityChecker.js +167 -0
  39. package/src/entity/auth/index.js +18 -0
  40. package/src/entity/index.js +3 -0
  41. package/src/entity/storage/MockApiStorage.js +349 -0
  42. package/src/entity/storage/SdkStorage.js +478 -0
  43. package/src/entity/storage/index.js +2 -0
  44. package/src/hooks/HookRegistry.js +411 -0
  45. package/src/hooks/index.js +12 -0
  46. package/src/index.js +13 -0
  47. package/src/kernel/Kernel.js +206 -5
  48. package/src/kernel/SignalBus.js +180 -0
  49. package/src/kernel/index.js +7 -0
  50. package/src/module/moduleRegistry.js +155 -28
  51. package/src/orchestrator/Orchestrator.js +73 -1
  52. package/src/zones/ZoneRegistry.js +828 -0
  53. package/src/zones/index.js +16 -0
  54. package/src/zones/zones.js +189 -0
  55. package/src/composables/useEntityTitle.js +0 -121
  56. package/src/composables/useManager.js +0 -20
  57. package/src/composables/usePageBuilder.js +0 -334
  58. package/src/composables/useStatus.js +0 -146
  59. package/src/composables/useSubEditor.js +0 -165
  60. package/src/composables/useTabSync.js +0 -110
package/README.md CHANGED
@@ -44,7 +44,7 @@ kernel.createApp().mount('#app')
44
44
 
45
45
  ```js
46
46
  // Main
47
- import { Kernel, createQdadm, EntityManager, ApiStorage, LocalStorage } from 'qdadm'
47
+ import { Kernel, createQdadm, EntityManager, ApiStorage, LocalStorage, SdkStorage } from 'qdadm'
48
48
 
49
49
  // Composables
50
50
  import { useForm, useBareForm, useListPageBuilder } from 'qdadm/composables'
@@ -62,6 +62,158 @@ import { formatDate, truncate } from 'qdadm/utils'
62
62
  import 'qdadm/styles'
63
63
  ```
64
64
 
65
+ ## SdkStorage
66
+
67
+ Adapter for generated SDK clients (hey-api, openapi-generator, etc.). Maps SDK methods to standard CRUD operations with optional transforms.
68
+
69
+ ### Basic Usage
70
+
71
+ ```js
72
+ import { EntityManager, SdkStorage } from 'qdadm'
73
+ import { Sdk } from './generated/sdk.gen.js'
74
+
75
+ const sdk = new Sdk({ client: myClient })
76
+
77
+ const storage = new SdkStorage({
78
+ sdk,
79
+ methods: {
80
+ list: 'getApiAdminTasks',
81
+ get: 'getApiAdminTasksById',
82
+ create: 'postApiAdminTasks',
83
+ update: 'patchApiAdminTasksById',
84
+ delete: 'deleteApiAdminTasksById'
85
+ }
86
+ })
87
+
88
+ const manager = new EntityManager({
89
+ name: 'tasks',
90
+ storage,
91
+ labelField: 'name'
92
+ })
93
+ ```
94
+
95
+ ### Configuration Options
96
+
97
+ | Option | Type | Description |
98
+ |--------|------|-------------|
99
+ | `sdk` | object | SDK instance |
100
+ | `getSdk` | function | Callback for lazy SDK loading |
101
+ | `methods` | object | Map operations to SDK method names or callbacks |
102
+ | `transformRequest` | function | Global request transform `(operation, params) => params` |
103
+ | `transformResponse` | function | Global response transform `(operation, data) => data` |
104
+ | `transforms` | object | Per-method transforms (override global) |
105
+ | `responseFormat` | object | Configure response normalization |
106
+ | `clientSidePagination` | boolean | Handle pagination locally (default: false) |
107
+
108
+ ### Method Mapping
109
+
110
+ Methods can be strings (SDK method name) or callbacks for full control:
111
+
112
+ ```js
113
+ methods: {
114
+ // String: calls sdk.getItems({ query: params })
115
+ list: 'getItems',
116
+
117
+ // Callback: full control over SDK invocation
118
+ get: async (sdk, id) => {
119
+ const result = await sdk.getItemById({ path: { id } })
120
+ return result.data
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Transform Callbacks
126
+
127
+ **Global transforms** apply to all operations:
128
+
129
+ ```js
130
+ new SdkStorage({
131
+ sdk,
132
+ methods: { list: 'getItems', get: 'getItemById' },
133
+ transformRequest: (operation, params) => {
134
+ if (operation === 'list') {
135
+ return { query: params }
136
+ }
137
+ return params
138
+ },
139
+ transformResponse: (operation, response) => response.data
140
+ })
141
+ ```
142
+
143
+ **Per-method transforms** override global ones:
144
+
145
+ ```js
146
+ new SdkStorage({
147
+ sdk,
148
+ methods: { list: 'getItems' },
149
+ transforms: {
150
+ list: {
151
+ request: (params) => ({ query: { ...params, active: true } }),
152
+ response: (data) => ({ items: data.results, total: data.count })
153
+ }
154
+ }
155
+ })
156
+ ```
157
+
158
+ ### Response Format Normalization
159
+
160
+ For APIs with non-standard response shapes, configure normalization before transforms:
161
+
162
+ ```js
163
+ new SdkStorage({
164
+ sdk,
165
+ methods: { list: 'getItems' },
166
+ responseFormat: {
167
+ dataField: 'results', // Field containing array (e.g., 'data', 'results')
168
+ totalField: 'count', // Field for total count (null = compute from array)
169
+ itemsField: 'data.items' // Nested path (takes precedence over dataField)
170
+ }
171
+ })
172
+ ```
173
+
174
+ ### Client-Side Pagination
175
+
176
+ For SDKs that return all items without server-side pagination:
177
+
178
+ ```js
179
+ new SdkStorage({
180
+ sdk,
181
+ methods: { list: 'getAllItems' },
182
+ clientSidePagination: true // Fetches all, paginates/sorts/filters in-memory
183
+ })
184
+ ```
185
+
186
+ ### Real-World Example (hey-api SDK)
187
+
188
+ ```js
189
+ import { Sdk } from '@/generated/sdk.gen.js'
190
+ import { client } from '@/generated/client.gen.js'
191
+
192
+ // Configure client
193
+ client.setConfig({ baseUrl: '/api' })
194
+
195
+ const sdk = new Sdk({ client })
196
+
197
+ const taskStorage = new SdkStorage({
198
+ sdk,
199
+ methods: {
200
+ list: 'getApiAdminTasks',
201
+ get: 'getApiAdminTasksById',
202
+ create: 'postApiAdminTasks',
203
+ patch: 'patchApiAdminTasksById',
204
+ delete: 'deleteApiAdminTasksById'
205
+ },
206
+ transforms: {
207
+ list: {
208
+ response: (data) => ({
209
+ items: data.items.map(t => ({ ...t, statusLabel: t.status.toUpperCase() })),
210
+ total: data.total
211
+ })
212
+ }
213
+ }
214
+ })
215
+ ```
216
+
65
217
  ## Peer Dependencies
66
218
 
67
219
  - vue ^3.3.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdadm",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Vue 3 framework for admin dashboards with PrimeVue",
5
5
  "author": "quazardous",
6
6
  "license": "MIT",
@@ -14,6 +14,10 @@
14
14
  },
15
15
  "type": "module",
16
16
  "main": "src/index.js",
17
+ "scripts": {
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
17
21
  "exports": {
18
22
  ".": "./src/index.js",
19
23
  "./composables": "./src/composables/index.js",
@@ -27,6 +31,9 @@
27
31
  "README.md",
28
32
  "LICENSE"
29
33
  ],
34
+ "dependencies": {
35
+ "@quazardous/quarkernel": "^2.1.0"
36
+ },
30
37
  "peerDependencies": {
31
38
  "vue": "^3.3.0",
32
39
  "vue-router": "^4.0.0",
@@ -43,5 +50,11 @@
43
50
  "primevue",
44
51
  "crud",
45
52
  "entity-manager"
46
- ]
53
+ ],
54
+ "devDependencies": {
55
+ "@vitejs/plugin-vue": "^5.2.1",
56
+ "@vue/test-utils": "^2.4.6",
57
+ "jsdom": "^25.0.1",
58
+ "vitest": "^2.1.8"
59
+ }
47
60
  }
@@ -1,13 +1,17 @@
1
1
  <script setup>
2
2
  /**
3
- * FormField - Wrapper for form fields with automatic dirty state styling
3
+ * FormField - Wrapper for form fields with automatic dirty state and error display
4
4
  *
5
5
  * Usage:
6
6
  * <FormField name="username" label="Username *">
7
7
  * <InputText v-model="form.username" />
8
8
  * </FormField>
9
9
  *
10
- * The parent form must provide isFieldDirty via useForm's provideFormContext()
10
+ * The parent form (useFormPageBuilder) provides:
11
+ * - isFieldDirty: function to check if field is dirty
12
+ * - getFieldError: function to get field error message
13
+ * - handleFieldBlur: function to trigger validation on blur
14
+ * - formSubmitted: ref indicating if form was submitted
11
15
  */
12
16
  import { inject, computed } from 'vue'
13
17
 
@@ -27,31 +31,63 @@ const props = defineProps({
27
31
  fullWidth: {
28
32
  type: Boolean,
29
33
  default: false
34
+ },
35
+ /** Override error message (useful for custom validation) */
36
+ error: {
37
+ type: String,
38
+ default: null
39
+ },
40
+ /** Show error only after form submission */
41
+ showErrorOnSubmit: {
42
+ type: Boolean,
43
+ default: false
30
44
  }
31
45
  })
32
46
 
33
- // Inject isFieldDirty from parent form (provided by useForm)
47
+ // Inject from parent form (provided by useFormPageBuilder)
34
48
  const isFieldDirty = inject('isFieldDirty', () => false)
49
+ const getFieldError = inject('getFieldError', () => null)
50
+ const handleFieldBlur = inject('handleFieldBlur', () => {})
51
+ const formSubmitted = inject('formSubmitted', { value: false })
35
52
 
36
53
  const isDirty = computed(() => isFieldDirty(props.name))
37
54
 
55
+ // Get error from prop or from form validation
56
+ const fieldError = computed(() => {
57
+ if (props.error) return props.error
58
+ return getFieldError(props.name)
59
+ })
60
+
61
+ // Show error if: form submitted OR field was touched (validated on blur)
62
+ const showError = computed(() => {
63
+ if (!fieldError.value) return false
64
+ if (props.showErrorOnSubmit) return formSubmitted.value
65
+ return true
66
+ })
67
+
38
68
  const fieldClasses = computed(() => [
39
69
  'form-field',
40
70
  {
41
- 'field-dirty': isDirty.value
71
+ 'field-dirty': isDirty.value,
72
+ 'field-invalid': showError.value
42
73
  }
43
74
  ])
44
75
 
45
76
  const fieldStyle = computed(() =>
46
77
  props.fullWidth ? { gridColumn: '1 / -1' } : {}
47
78
  )
79
+
80
+ function onBlur() {
81
+ handleFieldBlur(props.name)
82
+ }
48
83
  </script>
49
84
 
50
85
  <template>
51
86
  <div :class="fieldClasses" :style="fieldStyle">
52
87
  <label v-if="label" :for="name">{{ label }}</label>
53
- <slot ></slot>
54
- <small v-if="hint" class="field-hint">{{ hint }}</small>
88
+ <slot :onBlur="onBlur"></slot>
89
+ <small v-if="showError" class="field-error">{{ fieldError }}</small>
90
+ <small v-else-if="hint" class="field-hint">{{ hint }}</small>
55
91
  </div>
56
92
  </template>
57
93
 
@@ -61,4 +97,26 @@ const fieldStyle = computed(() =>
61
97
  margin-top: 0.25rem;
62
98
  display: block;
63
99
  }
100
+
101
+ .field-error {
102
+ color: var(--p-red-500);
103
+ margin-top: 0.25rem;
104
+ display: block;
105
+ }
106
+
107
+ .field-invalid :deep(input),
108
+ .field-invalid :deep(textarea),
109
+ .field-invalid :deep(.p-inputtext),
110
+ .field-invalid :deep(.p-select),
111
+ .field-invalid :deep(.p-dropdown) {
112
+ border-color: var(--p-red-500);
113
+ }
114
+
115
+ .field-invalid :deep(input:focus),
116
+ .field-invalid :deep(textarea:focus),
117
+ .field-invalid :deep(.p-inputtext:focus),
118
+ .field-invalid :deep(.p-select:focus),
119
+ .field-invalid :deep(.p-dropdown:focus) {
120
+ box-shadow: 0 0 0 1px var(--p-red-500);
121
+ }
64
122
  </style>
@@ -0,0 +1,276 @@
1
+ <script setup>
2
+ /**
3
+ * FormPage - Unified form page component
4
+ *
5
+ * Renders a complete CRUD form page with:
6
+ * - PageHeader with title and action buttons
7
+ * - Loading and error states
8
+ * - Form content via slots
9
+ * - FormActions footer
10
+ * - UnsavedChangesDialog integration
11
+ *
12
+ * Props come from useFormPageBuilder composable:
13
+ *
14
+ * ```vue
15
+ * const form = useFormPageBuilder({ entity: 'books' })
16
+ * form.generateFields()
17
+ * form.addSaveAction()
18
+ *
19
+ * <FormPage v-bind="form.props" v-on="form.events">
20
+ * <template #fields>
21
+ * <FormField v-model="form.data.title" name="title" />
22
+ * </template>
23
+ * </FormPage>
24
+ * ```
25
+ *
26
+ * Slots:
27
+ * - #nav: PageNav for breadcrumb customization
28
+ * - #toolbar: Custom toolbar actions (between header and form)
29
+ * - #header-actions: Custom header action buttons
30
+ * - #fields: Form fields content (required)
31
+ * - #footer: Custom footer (replaces FormActions if provided)
32
+ * - #error: Custom error display
33
+ * - #loading: Custom loading display
34
+ */
35
+ import { computed } from 'vue'
36
+ import PageHeader from '../layout/PageHeader.vue'
37
+ import FormActions from './FormActions.vue'
38
+ import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
39
+ import Card from 'primevue/card'
40
+ import Button from 'primevue/button'
41
+ import Message from 'primevue/message'
42
+
43
+ const props = defineProps({
44
+ // Mode
45
+ isEdit: { type: Boolean, default: false },
46
+ mode: { type: String, default: 'create' },
47
+
48
+ // State
49
+ loading: { type: Boolean, default: false },
50
+ saving: { type: Boolean, default: false },
51
+ dirty: { type: Boolean, default: false },
52
+
53
+ // Title (use title OR titleParts)
54
+ title: { type: String, default: null },
55
+ titleParts: { type: Object, default: null },
56
+
57
+ // Fields (for auto-rendering - optional, can use #fields slot instead)
58
+ fields: { type: Array, default: () => [] },
59
+
60
+ // Actions (from builder.actions)
61
+ actions: { type: Array, default: () => [] },
62
+
63
+ // Validation state
64
+ errors: { type: Object, default: () => ({}) },
65
+ hasErrors: { type: Boolean, default: false },
66
+ errorSummary: { type: Array, default: null },
67
+ submitted: { type: Boolean, default: false },
68
+
69
+ // Guard dialog (from useUnsavedChangesGuard)
70
+ guardDialog: { type: Object, default: null },
71
+
72
+ // Error for fetch failures (separate from validation errors)
73
+ fetchError: { type: [String, Object], default: null },
74
+
75
+ // UI options
76
+ showFormActions: { type: Boolean, default: true },
77
+ showSaveAndClose: { type: Boolean, default: true },
78
+ cardWrapper: { type: Boolean, default: true }
79
+ })
80
+
81
+ const emit = defineEmits(['save', 'saveAndClose', 'cancel', 'delete'])
82
+
83
+ // Computed: has any action buttons to show
84
+ const hasActions = computed(() => props.actions.length > 0)
85
+
86
+ // Get action by name
87
+ function getAction(name) {
88
+ return props.actions.find(a => a.name === name)
89
+ }
90
+
91
+ // Check if action exists and is visible
92
+ function hasAction(name) {
93
+ return props.actions.some(a => a.name === name)
94
+ }
95
+
96
+ // Extract specific actions for FormActions component
97
+ const saveAction = computed(() => getAction('save'))
98
+ const deleteAction = computed(() => getAction('delete'))
99
+ const cancelAction = computed(() => getAction('cancel'))
100
+
101
+ // Header actions: all actions except save, delete, cancel (those go in footer)
102
+ const headerActions = computed(() =>
103
+ props.actions.filter(a => !['save', 'delete', 'cancel'].includes(a.name))
104
+ )
105
+
106
+ // Get error message from fetchError
107
+ const fetchErrorMessage = computed(() => {
108
+ if (!props.fetchError) return null
109
+ if (typeof props.fetchError === 'string') return props.fetchError
110
+ return props.fetchError.message || props.fetchError.detail || 'Failed to load entity'
111
+ })
112
+
113
+ // Guard dialog handlers
114
+ function onGuardSaveAndLeave() {
115
+ if (props.guardDialog?.onSave) {
116
+ props.guardDialog.onSave()
117
+ }
118
+ }
119
+
120
+ function onGuardLeave() {
121
+ if (props.guardDialog?.onLeave) {
122
+ props.guardDialog.onLeave()
123
+ }
124
+ }
125
+
126
+ function onGuardStay() {
127
+ if (props.guardDialog?.onStay) {
128
+ props.guardDialog.onStay()
129
+ }
130
+ }
131
+ </script>
132
+
133
+ <template>
134
+ <div class="form-page">
135
+ <!-- Nav slot for PageNav (child routes) -->
136
+ <slot name="nav" />
137
+
138
+ <PageHeader :title="title" :title-parts="titleParts">
139
+ <template #actions>
140
+ <slot name="header-actions" />
141
+ <!-- Header actions from builder (excludes save/delete/cancel) -->
142
+ <Button
143
+ v-for="action in headerActions"
144
+ :key="action.name"
145
+ :label="action.label"
146
+ :icon="action.icon"
147
+ :severity="action.severity"
148
+ :loading="action.isLoading"
149
+ :disabled="action.isDisabled"
150
+ @click="action.onClick"
151
+ />
152
+ </template>
153
+ </PageHeader>
154
+
155
+ <!-- Toolbar slot (between header and form) -->
156
+ <slot name="toolbar" />
157
+
158
+ <!-- Loading State -->
159
+ <template v-if="loading">
160
+ <slot name="loading">
161
+ <div class="loading-state">
162
+ <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
163
+ </div>
164
+ </slot>
165
+ </template>
166
+
167
+ <!-- Error State (fetch error) -->
168
+ <template v-else-if="fetchError">
169
+ <slot name="error" :error="fetchError">
170
+ <Message severity="error" :closable="false" class="form-error-message">
171
+ {{ fetchErrorMessage }}
172
+ </Message>
173
+ </slot>
174
+ </template>
175
+
176
+ <!-- Form Content -->
177
+ <template v-else>
178
+ <!-- Validation Error Summary -->
179
+ <Message
180
+ v-if="errorSummary && errorSummary.length > 0"
181
+ severity="warn"
182
+ :closable="false"
183
+ class="validation-summary"
184
+ >
185
+ <ul class="validation-errors">
186
+ <li v-for="error in errorSummary" :key="error.field">
187
+ <strong>{{ error.label }}:</strong> {{ error.message }}
188
+ </li>
189
+ </ul>
190
+ </Message>
191
+
192
+ <!-- Card wrapper or direct content -->
193
+ <Card v-if="cardWrapper">
194
+ <template #content>
195
+ <slot name="fields" />
196
+
197
+ <!-- Form Actions (in footer) -->
198
+ <template v-if="showFormActions">
199
+ <slot name="footer">
200
+ <FormActions
201
+ :isEdit="isEdit"
202
+ :saving="saving"
203
+ :dirty="dirty"
204
+ :showSaveAndClose="showSaveAndClose"
205
+ @save="emit('save')"
206
+ @saveAndClose="emit('saveAndClose')"
207
+ @cancel="emit('cancel')"
208
+ />
209
+ </slot>
210
+ </template>
211
+ </template>
212
+ </Card>
213
+
214
+ <template v-else>
215
+ <slot name="fields" />
216
+
217
+ <!-- Form Actions (in footer) -->
218
+ <template v-if="showFormActions">
219
+ <slot name="footer">
220
+ <FormActions
221
+ :isEdit="isEdit"
222
+ :saving="saving"
223
+ :dirty="dirty"
224
+ :showSaveAndClose="showSaveAndClose"
225
+ @save="emit('save')"
226
+ @saveAndClose="emit('saveAndClose')"
227
+ @cancel="emit('cancel')"
228
+ />
229
+ </slot>
230
+ </template>
231
+ </template>
232
+ </template>
233
+
234
+ <!-- Unsaved Changes Dialog -->
235
+ <UnsavedChangesDialog
236
+ v-if="guardDialog"
237
+ v-model:visible="guardDialog.visible"
238
+ :saving="saving"
239
+ :hasOnSave="!!guardDialog.onSave"
240
+ @saveAndLeave="onGuardSaveAndLeave"
241
+ @leave="onGuardLeave"
242
+ @stay="onGuardStay"
243
+ />
244
+ </div>
245
+ </template>
246
+
247
+ <style scoped>
248
+ .form-page {
249
+ display: flex;
250
+ flex-direction: column;
251
+ gap: 1rem;
252
+ }
253
+
254
+ .loading-state {
255
+ display: flex;
256
+ justify-content: center;
257
+ padding: 3rem;
258
+ }
259
+
260
+ .form-error-message {
261
+ margin: 1rem 0;
262
+ }
263
+
264
+ .validation-summary {
265
+ margin-bottom: 1rem;
266
+ }
267
+
268
+ .validation-errors {
269
+ margin: 0;
270
+ padding-left: 1.5rem;
271
+ }
272
+
273
+ .validation-errors li {
274
+ margin: 0.25rem 0;
275
+ }
276
+ </style>
@@ -4,12 +4,23 @@
4
4
 
5
5
  // Layout
6
6
  export { default as AppLayout } from './layout/AppLayout.vue'
7
+ export { default as BaseLayout } from './layout/BaseLayout.vue'
7
8
  export { default as PageLayout } from './layout/PageLayout.vue'
8
9
  export { default as PageHeader } from './layout/PageHeader.vue'
9
10
  export { default as Breadcrumb } from './layout/Breadcrumb.vue'
10
11
  export { default as PageNav } from './layout/PageNav.vue'
12
+ export { default as Zone } from './layout/Zone.vue'
13
+
14
+ // Default zone components
15
+ export { default as DefaultHeader } from './layout/defaults/DefaultHeader.vue'
16
+ export { default as DefaultMenu } from './layout/defaults/DefaultMenu.vue'
17
+ export { default as DefaultFooter } from './layout/defaults/DefaultFooter.vue'
18
+ export { default as DefaultUserInfo } from './layout/defaults/DefaultUserInfo.vue'
19
+ export { default as DefaultBreadcrumb } from './layout/defaults/DefaultBreadcrumb.vue'
20
+ export { default as DefaultToaster } from './layout/defaults/DefaultToaster.vue'
11
21
 
12
22
  // Forms
23
+ export { default as FormPage } from './forms/FormPage.vue'
13
24
  export { default as FormField } from './forms/FormField.vue'
14
25
  export { default as FormActions } from './forms/FormActions.vue'
15
26
  export { default as FormTabs } from './forms/FormTabs.vue'