@groupteknology/vuno 0.4.0 → 0.6.1

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 (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +198 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +6 -0
  5. package/dist/runtime/app/assets/css/main.css +1 -1
  6. package/dist/runtime/app/components/App/App.d.vue.ts +33 -0
  7. package/dist/runtime/app/components/App/App.vue +127 -0
  8. package/dist/runtime/app/components/App/App.vue.d.ts +33 -0
  9. package/dist/runtime/app/components/App/AppContext.d.vue.ts +3 -0
  10. package/dist/runtime/app/components/App/AppContext.vue +7 -0
  11. package/dist/runtime/app/components/App/AppContext.vue.d.ts +3 -0
  12. package/dist/runtime/app/components/App/AppModal.d.vue.ts +38 -0
  13. package/dist/runtime/app/components/App/AppModal.vue +103 -0
  14. package/dist/runtime/app/components/App/AppModal.vue.d.ts +38 -0
  15. package/dist/runtime/app/components/App/AppOverlay.d.vue.ts +7 -0
  16. package/dist/runtime/app/components/App/AppOverlay.vue +33 -0
  17. package/dist/runtime/app/components/App/AppOverlay.vue.d.ts +7 -0
  18. package/dist/runtime/app/components/App/AppToast.d.vue.ts +11 -0
  19. package/dist/runtime/app/components/App/AppToast.vue +53 -0
  20. package/dist/runtime/app/components/App/AppToast.vue.d.ts +11 -0
  21. package/dist/runtime/app/components/App/AppToaster.d.vue.ts +11 -0
  22. package/dist/runtime/app/components/App/AppToaster.vue +49 -0
  23. package/dist/runtime/app/components/App/AppToaster.vue.d.ts +11 -0
  24. package/dist/runtime/app/components/Cell/CellBadge.d.vue.ts +5 -0
  25. package/dist/runtime/app/components/Cell/CellBadge.vue +24 -0
  26. package/dist/runtime/app/components/Cell/CellBadge.vue.d.ts +5 -0
  27. package/dist/runtime/app/components/Cell/CellBoolean.d.vue.ts +5 -0
  28. package/dist/runtime/app/components/Cell/CellBoolean.vue +21 -0
  29. package/dist/runtime/app/components/Cell/CellBoolean.vue.d.ts +5 -0
  30. package/dist/runtime/app/components/Cell/CellCurrency.d.vue.ts +5 -0
  31. package/dist/runtime/app/components/Cell/CellCurrency.vue +14 -0
  32. package/dist/runtime/app/components/Cell/CellCurrency.vue.d.ts +5 -0
  33. package/dist/runtime/app/components/Cell/CellDate.d.vue.ts +5 -0
  34. package/dist/runtime/app/components/Cell/CellDate.vue +19 -0
  35. package/dist/runtime/app/components/Cell/CellDate.vue.d.ts +5 -0
  36. package/dist/runtime/app/components/Cell/CellImage.d.vue.ts +5 -0
  37. package/dist/runtime/app/components/Cell/CellImage.vue +22 -0
  38. package/dist/runtime/app/components/Cell/CellImage.vue.d.ts +5 -0
  39. package/dist/runtime/app/components/Cell/CellNumber.d.vue.ts +5 -0
  40. package/dist/runtime/app/components/Cell/CellNumber.vue +15 -0
  41. package/dist/runtime/app/components/Cell/CellNumber.vue.d.ts +5 -0
  42. package/dist/runtime/app/components/Cell/CellText.d.vue.ts +5 -0
  43. package/dist/runtime/app/components/Cell/CellText.vue +13 -0
  44. package/dist/runtime/app/components/Cell/CellText.vue.d.ts +5 -0
  45. package/dist/runtime/app/components/Confirm/Confirm.vue +20 -13
  46. package/dist/runtime/app/components/Field/FieldArray.vue +68 -68
  47. package/dist/runtime/app/components/Field/FieldColor.vue +36 -36
  48. package/dist/runtime/app/components/Form/Form.vue +61 -49
  49. package/dist/runtime/app/components/Form/FormModal.vue +23 -15
  50. package/dist/runtime/app/components/Form/FormSection.vue +27 -27
  51. package/dist/runtime/app/components/Page/Page.vue +46 -46
  52. package/dist/runtime/app/components/State/EmptyState.vue +20 -20
  53. package/dist/runtime/app/components/State/FailureState.vue +21 -21
  54. package/dist/runtime/app/components/Table/Table.vue +286 -373
  55. package/dist/runtime/app/components/Table/TableCell.vue +24 -73
  56. package/dist/runtime/app/composables/useVunoConfirm.d.ts +15 -0
  57. package/dist/runtime/app/composables/useVunoConfirm.js +5 -0
  58. package/dist/runtime/app/composables/useVunoContext.d.ts +13 -0
  59. package/dist/runtime/app/composables/useVunoContext.js +9 -0
  60. package/dist/runtime/app/composables/useVunoModal.d.ts +21 -0
  61. package/dist/runtime/app/composables/useVunoModal.js +26 -0
  62. package/dist/runtime/app/composables/useVunoOverlay.d.ts +22 -0
  63. package/dist/runtime/app/composables/useVunoOverlay.js +10 -0
  64. package/dist/runtime/app/composables/useVunoTableQuery.js +3 -3
  65. package/dist/runtime/app/composables/useVunoTheme.js +2 -1
  66. package/dist/runtime/app/composables/useVunoToast.d.ts +28 -0
  67. package/dist/runtime/app/composables/useVunoToast.js +13 -0
  68. package/dist/runtime/app/internal/useVunoTable.d.ts +65 -0
  69. package/dist/runtime/app/internal/useVunoTable.js +161 -0
  70. package/dist/runtime/app/theme/index.js +69 -2
  71. package/dist/runtime/app/types/app.d.ts +83 -0
  72. package/dist/runtime/app/types/app.js +0 -0
  73. package/dist/runtime/app/types/confirm.d.ts +8 -0
  74. package/dist/runtime/app/types/index.d.ts +1 -0
  75. package/dist/runtime/app/types/table.d.ts +80 -13
  76. package/dist/runtime/app/types/theme.d.ts +12 -0
  77. package/dist/runtime/app/utils/column.d.ts +9 -0
  78. package/dist/runtime/app/utils/column.js +5 -0
  79. package/dist/runtime/app/utils/pagination.d.ts +12 -0
  80. package/dist/runtime/app/utils/pagination.js +6 -0
  81. package/package.json +6 -3
@@ -57,72 +57,72 @@ function move(index, offset) {
57
57
  </script>
58
58
 
59
59
  <template>
60
- <div :class="ui.root">
61
- <div
62
- v-for="(item, index) in items"
63
- :key="index"
64
- :class="ui.item"
65
- >
66
- <div :class="ui.itemHeader">
67
- <span :class="ui.itemLabel">{{ index + 1 }}</span>
68
-
69
- <div :class="ui.itemActions">
70
- <UButton
71
- v-if="props.sortable && index > 0"
72
- :aria-label="messages.fileMoveUp"
73
- color="neutral"
74
- :icon="icons.fileMoveUp"
75
- size="xs"
76
- variant="ghost"
77
- @click="move(index, -1)"
78
- />
79
- <UButton
80
- v-if="props.sortable && index < items.length - 1"
81
- :aria-label="messages.fileMoveDown"
82
- color="neutral"
83
- :icon="icons.fileMoveDown"
84
- size="xs"
85
- variant="ghost"
86
- @click="move(index, 1)"
87
- />
88
- <UButton
89
- :aria-label="messages.removeItem"
90
- color="error"
91
- :disabled="!canRemove"
92
- :icon="icons.removeItem"
93
- size="xs"
94
- variant="ghost"
95
- @click="remove(index)"
96
- />
97
- </div>
98
- </div>
99
-
100
- <div :class="ui.itemFields">
101
- <div
102
- v-for="field in props.fields"
103
- :key="`${index}-${field.name}`"
104
- :class="getColumnSpanClass(field.columns)"
105
- >
106
- <FormField :field="fieldAt(field, index)" />
107
- </div>
108
- </div>
109
- </div>
110
-
111
- <div
112
- v-if="items.length === 0"
113
- :class="ui.empty"
114
- >
115
- {{ messages.emptyArray }}
116
- </div>
117
-
118
- <UButton
119
- class="self-start"
120
- color="neutral"
121
- :disabled="!canAdd"
122
- :icon="icons.addItem"
123
- :label="props.addLabel ?? messages.addItem"
124
- variant="outline"
125
- @click="add"
126
- />
127
- </div>
60
+ <div :class="ui.root">
61
+ <div
62
+ v-for="(item, index) in items"
63
+ :key="index"
64
+ :class="ui.item"
65
+ >
66
+ <div :class="ui.itemHeader">
67
+ <span :class="ui.itemLabel">{{ index + 1 }}</span>
68
+
69
+ <div :class="ui.itemActions">
70
+ <UButton
71
+ v-if="props.sortable && index > 0"
72
+ :aria-label="messages.fileMoveUp"
73
+ color="neutral"
74
+ :icon="icons.fileMoveUp"
75
+ size="xs"
76
+ variant="ghost"
77
+ @click="move(index, -1)"
78
+ />
79
+ <UButton
80
+ v-if="props.sortable && index < items.length - 1"
81
+ :aria-label="messages.fileMoveDown"
82
+ color="neutral"
83
+ :icon="icons.fileMoveDown"
84
+ size="xs"
85
+ variant="ghost"
86
+ @click="move(index, 1)"
87
+ />
88
+ <UButton
89
+ :aria-label="messages.removeItem"
90
+ color="error"
91
+ :disabled="!canRemove"
92
+ :icon="icons.removeItem"
93
+ size="xs"
94
+ variant="ghost"
95
+ @click="remove(index)"
96
+ />
97
+ </div>
98
+ </div>
99
+
100
+ <div :class="ui.itemFields">
101
+ <div
102
+ v-for="field in props.fields"
103
+ :key="`${index}-${field.name}`"
104
+ :class="getColumnSpanClass(field.columns)"
105
+ >
106
+ <FormField :field="fieldAt(field, index)" />
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div
112
+ v-if="items.length === 0"
113
+ :class="ui.empty"
114
+ >
115
+ {{ messages.emptyArray }}
116
+ </div>
117
+
118
+ <UButton
119
+ class="self-start"
120
+ color="neutral"
121
+ :disabled="!canAdd"
122
+ :icon="icons.addItem"
123
+ :label="props.addLabel ?? messages.addItem"
124
+ variant="outline"
125
+ @click="add"
126
+ />
127
+ </div>
128
128
  </template>
@@ -18,40 +18,40 @@ const ui = useVunoTheme("fieldColor", () => props.ui);
18
18
  </script>
19
19
 
20
20
  <template>
21
- <div :class="ui.root">
22
- <!-- El selector nativo y el texto comparten valor: se puede pegar un
23
- código de la guía de marca sin pelearse con la rueda de color. -->
24
- <UPopover>
25
- <UButton
26
- :aria-label="props.label"
27
- color="neutral"
28
- :disabled="props.disabled"
29
- :size="props.size"
30
- :ui="{ leadingIcon: 'rounded-full' }"
31
- variant="outline"
32
- >
33
- <span
34
- :class="ui.swatch"
35
- :style="{ backgroundColor: value || 'transparent' }"
36
- />
37
- </UButton>
38
-
39
- <template #content>
40
- <UColorPicker
41
- v-model="value"
42
- class="p-2"
43
- />
44
- </template>
45
- </UPopover>
46
-
47
- <UInput
48
- v-model="value"
49
- class="font-mono"
50
- :disabled="props.disabled"
51
- :name="props.name"
52
- :placeholder="props.placeholder ?? '#000000'"
53
- :size="props.size"
54
- :ui="{ root: 'w-full' }"
55
- />
56
- </div>
21
+ <div :class="ui.root">
22
+ <!-- El selector nativo y el texto comparten valor: se puede pegar un
23
+ código de la guía de marca sin pelearse con la rueda de color. -->
24
+ <UPopover>
25
+ <UButton
26
+ :aria-label="props.label"
27
+ color="neutral"
28
+ :disabled="props.disabled"
29
+ :size="props.size"
30
+ :ui="{ leadingIcon: 'rounded-full' }"
31
+ variant="outline"
32
+ >
33
+ <span
34
+ :class="ui.swatch"
35
+ :style="{ backgroundColor: value || 'transparent' }"
36
+ />
37
+ </UButton>
38
+
39
+ <template #content>
40
+ <UColorPicker
41
+ v-model="value"
42
+ class="p-2"
43
+ />
44
+ </template>
45
+ </UPopover>
46
+
47
+ <UInput
48
+ v-model="value"
49
+ class="font-mono"
50
+ :disabled="props.disabled"
51
+ :name="props.name"
52
+ :placeholder="props.placeholder ?? '#000000'"
53
+ :size="props.size"
54
+ :ui="{ root: 'w-full' }"
55
+ />
56
+ </div>
57
57
  </template>
@@ -86,53 +86,65 @@ defineExpose({ dirty, errors: errorNames, isDirty, reset, resetField, submit: ()
86
86
  </script>
87
87
 
88
88
  <template>
89
- <UForm
90
- :id="formId"
91
- ref="form"
92
- :schema="props.schema"
93
- :state="state"
94
- :ui="{ base: ui.base }"
95
- @submit="onSubmit"
96
- >
97
- <UTabs
98
- v-if="isTabs"
99
- :defaultValue="tabItems[0]?.value"
100
- :items="tabItems"
101
- :size="size"
102
- :ui="{ root: ui.tabs, content: ui.tabsContent }"
103
- :unmountOnHide="false"
104
- variant="link"
105
- >
106
- <template #content="{ item }">
107
- <FormSection
108
- plain
109
- :section="item.section"
110
- />
111
- </template>
112
- </UTabs>
113
-
114
- <div
115
- v-else
116
- :class="ui.sections"
117
- >
118
- <div
119
- v-for="(section, index) in props.definition.sections"
120
- :key="`${section.id}-${index}`"
121
- :class="getColumnSpanClass(section.columns)"
122
- >
123
- <FormSection :section="section" />
124
- </div>
125
- </div>
126
-
127
- <div
128
- v-if="slots.footer"
129
- :class="ui.footer"
130
- >
131
- <slot
132
- :dirty="dirty"
133
- name="footer"
134
- :reset="reset"
135
- />
136
- </div>
137
- </UForm>
89
+ <!--
90
+ `novalidate` apaga la validación nativa del navegador, no el `required`.
91
+ Sin él conviven dos validaciones: la del navegador corta el envío antes
92
+ de que el esquema llegue a ejecutarse, y avisa con un globo suyo, en el
93
+ idioma del navegador y sin pasar por el tema. Los mensajes del esquema
94
+ no llegaban a verse en los campos obligatorios.
95
+
96
+ El atributo `required` se queda en los inputs, que es lo que anuncia el
97
+ campo como obligatorio a un lector de pantalla y lo que permite
98
+ estilarlo; lo único que se apaga es la interfaz nativa.
99
+ -->
100
+ <UForm
101
+ :id="formId"
102
+ ref="form"
103
+ novalidate
104
+ :schema="props.schema"
105
+ :state="state"
106
+ :ui="{ base: ui.base }"
107
+ @submit="onSubmit"
108
+ >
109
+ <UTabs
110
+ v-if="isTabs"
111
+ :defaultValue="tabItems[0]?.value"
112
+ :items="tabItems"
113
+ :size="size"
114
+ :ui="{ root: ui.tabs, content: ui.tabsContent }"
115
+ :unmountOnHide="false"
116
+ variant="link"
117
+ >
118
+ <template #content="{ item }">
119
+ <FormSection
120
+ plain
121
+ :section="item.section"
122
+ />
123
+ </template>
124
+ </UTabs>
125
+
126
+ <div
127
+ v-else
128
+ :class="ui.sections"
129
+ >
130
+ <div
131
+ v-for="(section, index) in props.definition.sections"
132
+ :key="`${section.id}-${index}`"
133
+ :class="getColumnSpanClass(section.columns)"
134
+ >
135
+ <FormSection :section="section" />
136
+ </div>
137
+ </div>
138
+
139
+ <div
140
+ v-if="slots.footer"
141
+ :class="ui.footer"
142
+ >
143
+ <slot
144
+ :dirty="dirty"
145
+ name="footer"
146
+ :reset="reset"
147
+ />
148
+ </div>
149
+ </UForm>
138
150
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, useId, useTemplateRef } from "vue";
3
3
  import { useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ import AppModal from "#vuno/components/App/AppModal.vue";
4
5
  import Form from "./Form.vue";
5
6
  const props = defineProps({
6
7
  closeOnSubmit: { type: Boolean, required: false },
@@ -18,7 +19,7 @@ const state = defineModel("state", { type: null, ...{ required: true } });
18
19
  defineSlots();
19
20
  const messages = useVunoMessages();
20
21
  const ui = useVunoTheme("formModal", () => props.ui);
21
- const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer }));
22
+ const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer, root: ui.value.root }));
22
23
  const formId = useId();
23
24
  const form = useTemplateRef("form");
24
25
  function close() {
@@ -33,27 +34,34 @@ function onSubmit(event) {
33
34
  </script>
34
35
 
35
36
  <template>
36
- <UModal
37
+ <!--
38
+ Sobre `AppModal`: un `<dialog>` nativo, no el de Nuxt UI. El navegador lo
39
+ pone en su capa superior, que atrapa el foco, lo devuelve al cerrar y
40
+ apila los diálogos por orden de apertura.
41
+
42
+ Importa que sean todos del mismo sistema: la capa superior queda por
43
+ encima de cualquier z-index, así que un formulario posicionado con
44
+ z-index abierto sobre un diálogo nativo se pintaría detrás de él, en el
45
+ DOM y sin verse.
46
+ -->
47
+ <AppModal
37
48
  v-model:open="open"
38
49
  :description="props.description"
39
50
  :dismissible="!props.loading"
40
- scrollable
41
51
  :title="props.title"
42
52
  :ui="modalUi"
43
53
  >
44
54
  <slot name="trigger" />
45
55
 
46
- <template #body>
47
- <Form
48
- :id="formId"
49
- ref="form"
50
- v-model:state="state"
51
- :definition="props.definition"
52
- :schema="props.schema"
53
- :ui="props.formUi"
54
- @submit="onSubmit"
55
- />
56
- </template>
56
+ <Form
57
+ :id="formId"
58
+ ref="form"
59
+ v-model:state="state"
60
+ :definition="props.definition"
61
+ :schema="props.schema"
62
+ :ui="props.formUi"
63
+ @submit="onSubmit"
64
+ />
57
65
 
58
66
  <template #footer>
59
67
  <slot
@@ -79,5 +87,5 @@ function onSubmit(event) {
79
87
  />
80
88
  </slot>
81
89
  </template>
82
- </UModal>
90
+ </AppModal>
83
91
  </template>
@@ -13,31 +13,31 @@ const showHeader = computed(() => props.plain ? Boolean(props.section.descriptio
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div :class="root">
17
- <div
18
- v-if="showHeader"
19
- :class="ui.header"
20
- >
21
- <p
22
- v-if="props.section.title && !props.plain"
23
- :class="ui.title"
24
- >
25
- {{ props.section.title }}
26
- </p>
27
- <p
28
- v-if="props.section.description"
29
- :class="ui.description"
30
- >
31
- {{ props.section.description }}
32
- </p>
33
- </div>
34
-
35
- <div
36
- v-for="(field, index) in props.section.fields"
37
- :key="`${field.name}-${index}`"
38
- :class="getColumnSpanClass(field.columns)"
39
- >
40
- <FormField :field="field" />
41
- </div>
42
- </div>
16
+ <div :class="root">
17
+ <div
18
+ v-if="showHeader"
19
+ :class="ui.header"
20
+ >
21
+ <p
22
+ v-if="props.section.title && !props.plain"
23
+ :class="ui.title"
24
+ >
25
+ {{ props.section.title }}
26
+ </p>
27
+ <p
28
+ v-if="props.section.description"
29
+ :class="ui.description"
30
+ >
31
+ {{ props.section.description }}
32
+ </p>
33
+ </div>
34
+
35
+ <div
36
+ v-for="(field, index) in props.section.fields"
37
+ :key="`${field.name}-${index}`"
38
+ :class="getColumnSpanClass(field.columns)"
39
+ >
40
+ <FormField :field="field" />
41
+ </div>
42
+ </div>
43
43
  </template>
@@ -14,50 +14,50 @@ const navbarUi = computed(() => ({ left: ui.value.navbarLeft, root: ui.value.nav
14
14
  </script>
15
15
 
16
16
  <template>
17
- <UDashboardPanel :ui="panelUi">
18
- <template #header>
19
- <UDashboardNavbar :ui="navbarUi">
20
- <!--
21
- Se ocupa `#left` entero en vez de `#title`: el `#title` de la
22
- barra vive dentro de un `<h1>`, y una miga de pan ahí sería un
23
- `<nav>` dentro de un encabezado.
24
- -->
25
- <template #left>
26
- <slot name="leading" />
27
-
28
- <!-- En `neutral`: en color de marca, el último segmento se lee
29
- como un enlace a otro sitio. -->
30
- <UBreadcrumb
31
- v-if="props.breadcrumb?.length"
32
- :class="ui.breadcrumb"
33
- color="neutral"
34
- :items="props.breadcrumb"
35
- />
36
- </template>
37
-
38
- <template #right>
39
- <slot name="actions" />
40
- </template>
41
- </UDashboardNavbar>
42
- </template>
43
-
44
- <template #body>
45
- <div :class="ui.body">
46
- <!-- Un `<div>` y no un `<header>`: un `<header>` suelto es un
47
- landmark `banner`, y solo puede haber uno por página. -->
48
- <div :class="ui.heading">
49
- <h1 :class="ui.title">{{ props.title }}</h1>
50
-
51
- <p
52
- v-if="props.description"
53
- :class="ui.description"
54
- >
55
- {{ props.description }}
56
- </p>
57
- </div>
58
-
59
- <slot />
60
- </div>
61
- </template>
62
- </UDashboardPanel>
17
+ <UDashboardPanel :ui="panelUi">
18
+ <template #header>
19
+ <UDashboardNavbar :ui="navbarUi">
20
+ <!--
21
+ Se ocupa `#left` entero en vez de `#title`: el `#title` de la
22
+ barra vive dentro de un `<h1>`, y una miga de pan ahí sería un
23
+ `<nav>` dentro de un encabezado.
24
+ -->
25
+ <template #left>
26
+ <slot name="leading" />
27
+
28
+ <!-- En `neutral`: en color de marca, el último segmento se lee
29
+ como un enlace a otro sitio. -->
30
+ <UBreadcrumb
31
+ v-if="props.breadcrumb?.length"
32
+ :class="ui.breadcrumb"
33
+ color="neutral"
34
+ :items="props.breadcrumb"
35
+ />
36
+ </template>
37
+
38
+ <template #right>
39
+ <slot name="actions" />
40
+ </template>
41
+ </UDashboardNavbar>
42
+ </template>
43
+
44
+ <template #body>
45
+ <div :class="ui.body">
46
+ <!-- Un `<div>` y no un `<header>`: un `<header>` suelto es un
47
+ landmark `banner`, y solo puede haber uno por página. -->
48
+ <div :class="ui.heading">
49
+ <h1 :class="ui.title">{{ props.title }}</h1>
50
+
51
+ <p
52
+ v-if="props.description"
53
+ :class="ui.description"
54
+ >
55
+ {{ props.description }}
56
+ </p>
57
+ </div>
58
+
59
+ <slot />
60
+ </div>
61
+ </template>
62
+ </UDashboardPanel>
63
63
  </template>
@@ -13,24 +13,24 @@ const ui = useVunoTheme("state", () => props.ui);
13
13
  </script>
14
14
 
15
15
  <template>
16
- <div :class="ui.root">
17
- <UIcon
18
- class="text-dimmed"
19
- :class="ui.icon"
20
- :name="props.icon ?? icons.empty"
21
- />
22
-
23
- <p :class="ui.title">{{ props.title ?? messages.emptyTable }}</p>
24
-
25
- <p
26
- v-if="props.hint"
27
- :class="ui.hint"
28
- >
29
- {{ props.hint }}
30
- </p>
31
-
32
- <div :class="ui.actions">
33
- <slot name="actions" />
34
- </div>
35
- </div>
16
+ <div :class="ui.root">
17
+ <UIcon
18
+ class="text-dimmed"
19
+ :class="ui.icon"
20
+ :name="props.icon ?? icons.empty"
21
+ />
22
+
23
+ <p :class="ui.title">{{ props.title ?? messages.emptyTable }}</p>
24
+
25
+ <p
26
+ v-if="props.hint"
27
+ :class="ui.hint"
28
+ >
29
+ {{ props.hint }}
30
+ </p>
31
+
32
+ <div :class="ui.actions">
33
+ <slot name="actions" />
34
+ </div>
35
+ </div>
36
36
  </template>
@@ -10,25 +10,25 @@ const ui = useVunoTheme("state", () => props.ui);
10
10
  </script>
11
11
 
12
12
  <template>
13
- <div :class="ui.root">
14
- <UIcon
15
- class="text-error"
16
- :class="ui.icon"
17
- :name="icons.failure"
18
- />
19
-
20
- <p :class="ui.title">{{ props.failure.title ?? messages.failureTitle }}</p>
21
-
22
- <p :class="ui.hint">{{ props.failure.hint ?? messages.failureHint }}</p>
23
-
24
- <div :class="ui.actions">
25
- <UButton
26
- color="neutral"
27
- :icon="icons.retry"
28
- :label="messages.retry"
29
- variant="outline"
30
- @click="props.failure.retry()"
31
- />
32
- </div>
33
- </div>
13
+ <div :class="ui.root">
14
+ <UIcon
15
+ class="text-error"
16
+ :class="ui.icon"
17
+ :name="icons.failure"
18
+ />
19
+
20
+ <p :class="ui.title">{{ props.failure.title ?? messages.failureTitle }}</p>
21
+
22
+ <p :class="ui.hint">{{ props.failure.hint ?? messages.failureHint }}</p>
23
+
24
+ <div :class="ui.actions">
25
+ <UButton
26
+ color="neutral"
27
+ :icon="icons.retry"
28
+ :label="messages.retry"
29
+ variant="outline"
30
+ @click="props.failure.retry()"
31
+ />
32
+ </div>
33
+ </div>
34
34
  </template>