@aerogel/core 0.0.0-next.fcfbfdc3428c34c4d1c0e781b61d244f13232fc9 → 0.1.0-next.c4b24f52d8b652bd5c14c2d12e1b38b779ab7682
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/dist/aerogel-core.css +1 -0
- package/dist/aerogel-core.d.ts +661 -919
- package/dist/aerogel-core.js +1983 -1440
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +7 -5
- package/src/components/AppLayout.vue +1 -3
- package/src/components/AppOverlays.vue +0 -27
- package/src/components/contracts/AlertModal.ts +15 -0
- package/src/components/contracts/ConfirmModal.ts +12 -5
- package/src/components/contracts/DropdownMenu.ts +8 -3
- package/src/components/contracts/ErrorReportModal.ts +8 -4
- package/src/components/contracts/Input.ts +7 -7
- package/src/components/contracts/LoadingModal.ts +6 -2
- package/src/components/contracts/Modal.ts +4 -4
- package/src/components/contracts/PromptModal.ts +5 -1
- package/src/components/contracts/Select.ts +9 -8
- package/src/components/contracts/Toast.ts +4 -2
- package/src/components/headless/HeadlessButton.vue +2 -2
- package/src/components/headless/HeadlessInputInput.vue +16 -5
- package/src/components/headless/HeadlessModal.vue +8 -43
- package/src/components/headless/HeadlessModalContent.vue +2 -2
- package/src/components/headless/HeadlessSelect.vue +10 -8
- package/src/components/headless/HeadlessSelectOptions.vue +8 -3
- package/src/components/headless/HeadlessSwitch.vue +96 -0
- package/src/components/headless/index.ts +1 -0
- package/src/components/ui/AdvancedOptions.vue +1 -1
- package/src/components/ui/AlertModal.vue +7 -3
- package/src/components/ui/Button.vue +27 -10
- package/src/components/ui/ConfirmModal.vue +11 -3
- package/src/components/ui/DropdownMenuOption.vue +12 -4
- package/src/components/ui/DropdownMenuOptions.vue +18 -1
- package/src/components/ui/ErrorLogs.vue +19 -0
- package/src/components/ui/ErrorLogsModal.vue +48 -0
- package/src/components/ui/ErrorReportModal.vue +18 -7
- package/src/components/ui/Input.vue +2 -2
- package/src/components/ui/LoadingModal.vue +3 -1
- package/src/components/ui/Markdown.vue +29 -1
- package/src/components/ui/Modal.vue +61 -21
- package/src/components/ui/ModalContext.vue +2 -1
- package/src/components/ui/PromptModal.vue +5 -2
- package/src/components/ui/Select.vue +5 -3
- package/src/components/ui/SelectLabel.vue +5 -1
- package/src/components/ui/SelectOptions.vue +6 -1
- package/src/components/ui/SelectTrigger.vue +1 -1
- package/src/components/ui/Setting.vue +31 -0
- package/src/components/ui/StartupCrash.vue +51 -6
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/Toast.vue +19 -15
- package/src/components/ui/index.ts +5 -0
- package/src/directives/measure.ts +11 -5
- package/src/errors/Errors.state.ts +1 -0
- package/src/errors/Errors.ts +45 -21
- package/src/errors/index.ts +6 -2
- package/src/errors/settings/Debug.vue +14 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/forms/FormController.test.ts +35 -9
- package/src/forms/FormController.ts +34 -24
- package/src/forms/index.ts +0 -1
- package/src/forms/utils.ts +58 -33
- package/src/forms/validation.ts +31 -0
- package/src/index.css +34 -12
- package/src/lang/index.ts +1 -1
- package/src/lang/settings/Language.vue +1 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +2 -8
- package/src/services/Service.ts +11 -6
- package/src/services/index.ts +2 -2
- package/src/testing/index.ts +4 -0
- package/src/ui/UI.state.ts +3 -13
- package/src/ui/UI.ts +103 -84
- package/src/ui/index.ts +16 -17
- package/src/utils/app.ts +7 -0
- package/src/utils/classes.ts +9 -17
- package/src/utils/composition/events.ts +2 -4
- package/src/utils/composition/forms.ts +7 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown.ts +35 -1
- package/src/utils/vue.ts +6 -1
- package/src/forms/composition.ts +0 -6
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="rootClass">
|
|
3
|
+
<label v-if="label" :for="expose.id" :class="labelClass">
|
|
4
|
+
{{ label }}
|
|
5
|
+
</label>
|
|
6
|
+
<SwitchRoot
|
|
7
|
+
:id="expose.id"
|
|
8
|
+
:name
|
|
9
|
+
:model-value="expose.value.value"
|
|
10
|
+
v-bind="$attrs"
|
|
11
|
+
:class="inputClass"
|
|
12
|
+
@update:model-value="$emit('update:modelValue', $event)"
|
|
13
|
+
>
|
|
14
|
+
<SwitchThumb :class="thumbClass" />
|
|
15
|
+
</SwitchRoot>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts" generic="T extends boolean = boolean">
|
|
20
|
+
import { SwitchRoot, SwitchThumb } from 'reka-ui';
|
|
21
|
+
import { computed, inject, readonly, watchEffect } from 'vue';
|
|
22
|
+
import { uuid } from '@noeldemartin/utils';
|
|
23
|
+
import type { HTMLAttributes } from 'vue';
|
|
24
|
+
|
|
25
|
+
import type FormController from '@aerogel/core/forms/FormController';
|
|
26
|
+
import type { FormFieldValue } from '@aerogel/core/forms/FormController';
|
|
27
|
+
import type { InputEmits, InputExpose, InputProps } from '@aerogel/core/components/contracts/Input';
|
|
28
|
+
|
|
29
|
+
defineOptions({ inheritAttrs: false });
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
name,
|
|
33
|
+
label,
|
|
34
|
+
description,
|
|
35
|
+
modelValue,
|
|
36
|
+
class: rootClass,
|
|
37
|
+
} = defineProps<
|
|
38
|
+
InputProps<T> & {
|
|
39
|
+
class?: HTMLAttributes['class'];
|
|
40
|
+
labelClass?: HTMLAttributes['class'];
|
|
41
|
+
inputClass?: HTMLAttributes['class'];
|
|
42
|
+
thumbClass?: HTMLAttributes['class'];
|
|
43
|
+
}
|
|
44
|
+
>();
|
|
45
|
+
const emit = defineEmits<InputEmits>();
|
|
46
|
+
const form = inject<FormController | null>('form', null);
|
|
47
|
+
const errors = computed(() => {
|
|
48
|
+
if (!form || !name) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return form.errors[name] ?? null;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const expose = {
|
|
56
|
+
id: `switch-${uuid()}`,
|
|
57
|
+
name: computed(() => name),
|
|
58
|
+
label: computed(() => label),
|
|
59
|
+
description: computed(() => description),
|
|
60
|
+
value: computed(() => {
|
|
61
|
+
if (form && name) {
|
|
62
|
+
return form.getFieldValue(name) as boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return modelValue;
|
|
66
|
+
}),
|
|
67
|
+
errors: readonly(errors),
|
|
68
|
+
required: computed(() => {
|
|
69
|
+
if (!name || !form) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return form.getFieldRules(name).includes('required');
|
|
74
|
+
}),
|
|
75
|
+
update(value) {
|
|
76
|
+
if (form && name) {
|
|
77
|
+
form.setFieldValue(name, value as FormFieldValue);
|
|
78
|
+
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
emit('update:modelValue', value);
|
|
83
|
+
},
|
|
84
|
+
} satisfies InputExpose;
|
|
85
|
+
|
|
86
|
+
defineExpose(expose);
|
|
87
|
+
|
|
88
|
+
watchEffect(() => {
|
|
89
|
+
if (!description && !errors.value) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.warn('Errors and description not implemented in <HeadlessSwitch>');
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
@@ -16,4 +16,5 @@ export { default as HeadlessSelectOption } from './HeadlessSelectOption.vue';
|
|
|
16
16
|
export { default as HeadlessSelectOptions } from './HeadlessSelectOptions.vue';
|
|
17
17
|
export { default as HeadlessSelectTrigger } from './HeadlessSelectTrigger.vue';
|
|
18
18
|
export { default as HeadlessSelectValue } from './HeadlessSelectValue.vue';
|
|
19
|
+
export { default as HeadlessSwitch } from './HeadlessSwitch.vue';
|
|
19
20
|
export { default as HeadlessToast } from './HeadlessToast.vue';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<details class="group">
|
|
3
3
|
<summary
|
|
4
|
-
class="-ml-2 flex w-[max-content]
|
|
4
|
+
class="-ml-2 flex w-[max-content] items-center rounded-lg py-2 pr-3 pl-1 hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700"
|
|
5
5
|
>
|
|
6
6
|
<IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
|
|
7
7
|
<span>{{ $td('ui.advancedOptions', 'Advanced options') }}</span>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Modal :title>
|
|
2
|
+
<Modal :title="renderedTitle" :title-hidden="titleHidden">
|
|
3
3
|
<Markdown :text="message" />
|
|
4
4
|
</Modal>
|
|
5
5
|
</template>
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
9
9
|
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
10
|
-
import
|
|
10
|
+
import { useAlertModal } from '@aerogel/core/components/contracts/AlertModal';
|
|
11
|
+
import type { AlertModalExpose, AlertModalProps } from '@aerogel/core/components/contracts/AlertModal';
|
|
11
12
|
|
|
12
|
-
defineProps<AlertModalProps>();
|
|
13
|
+
const props = defineProps<AlertModalProps>();
|
|
14
|
+
const { renderedTitle, titleHidden } = useAlertModal(props);
|
|
15
|
+
|
|
16
|
+
defineExpose<AlertModalExpose>();
|
|
13
17
|
</script>
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
|
+
import { computed } from 'vue';
|
|
9
|
+
|
|
8
10
|
import HeadlessButton from '@aerogel/core/components/headless/HeadlessButton.vue';
|
|
9
|
-
import {
|
|
11
|
+
import { variantClasses } from '@aerogel/core/utils/classes';
|
|
10
12
|
import type { ButtonProps } from '@aerogel/core/components/contracts/Button';
|
|
11
13
|
import type { Variants } from '@aerogel/core/utils/classes';
|
|
12
14
|
|
|
@@ -14,10 +16,10 @@ const { class: baseClasses, size, variant, disabled, ...props } = defineProps<Bu
|
|
|
14
16
|
|
|
15
17
|
/* eslint-disable vue/max-len */
|
|
16
18
|
// prettier-ignore
|
|
17
|
-
const renderedClasses =
|
|
19
|
+
const renderedClasses = computed(() => variantClasses<Variants<Pick<ButtonProps, 'size' | 'variant' | 'disabled'>>>(
|
|
18
20
|
{ baseClasses, variant, size, disabled },
|
|
19
21
|
{
|
|
20
|
-
baseClasses: '
|
|
22
|
+
baseClasses: 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
|
21
23
|
variants: {
|
|
22
24
|
variant: {
|
|
23
25
|
default: 'bg-primary-600 text-white focus-visible:outline-primary-600',
|
|
@@ -25,12 +27,12 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
|
|
|
25
27
|
danger: 'bg-red-600 text-white focus-visible:outline-red-600',
|
|
26
28
|
ghost: 'bg-transparent',
|
|
27
29
|
outline: 'bg-transparent text-primary-600 ring-primary-600',
|
|
28
|
-
link: 'text-
|
|
30
|
+
link: 'text-links',
|
|
29
31
|
},
|
|
30
32
|
size: {
|
|
31
|
-
small: 'text-xs',
|
|
32
|
-
default: 'text-sm',
|
|
33
|
-
large: 'text-base',
|
|
33
|
+
small: 'text-xs min-h-6',
|
|
34
|
+
default: 'text-sm min-h-8',
|
|
35
|
+
large: 'text-base min-h-10',
|
|
34
36
|
icon: 'rounded-full p-2.5',
|
|
35
37
|
},
|
|
36
38
|
disabled: {
|
|
@@ -41,7 +43,7 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
|
|
|
41
43
|
compoundVariants: [
|
|
42
44
|
{
|
|
43
45
|
variant: ['default', 'secondary', 'danger', 'ghost', 'outline'],
|
|
44
|
-
class: 'font-medium',
|
|
46
|
+
class: 'flex items-center justify-center gap-1 font-medium',
|
|
45
47
|
},
|
|
46
48
|
{
|
|
47
49
|
variant: ['default', 'danger'],
|
|
@@ -79,13 +81,28 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
|
|
|
79
81
|
{
|
|
80
82
|
variant: 'danger',
|
|
81
83
|
disabled: false,
|
|
82
|
-
class: 'hover:bg-red-
|
|
84
|
+
class: 'hover:bg-red-500',
|
|
83
85
|
},
|
|
84
86
|
{
|
|
85
87
|
variant: 'link',
|
|
86
88
|
disabled: false,
|
|
87
89
|
class: 'hover:underline',
|
|
88
90
|
},
|
|
91
|
+
{
|
|
92
|
+
variant: 'link',
|
|
93
|
+
size: 'small',
|
|
94
|
+
class: 'leading-6',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
variant: 'link',
|
|
98
|
+
size: 'default',
|
|
99
|
+
class: 'leading-8',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
variant: 'link',
|
|
103
|
+
size: 'large',
|
|
104
|
+
class: 'leading-10',
|
|
105
|
+
},
|
|
89
106
|
],
|
|
90
107
|
defaultVariants: {
|
|
91
108
|
variant: 'default',
|
|
@@ -93,6 +110,6 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
|
|
|
93
110
|
disabled: false,
|
|
94
111
|
},
|
|
95
112
|
},
|
|
96
|
-
);
|
|
113
|
+
));
|
|
97
114
|
/* eslint-enable vue/max-len */
|
|
98
115
|
</script>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<!-- @vue-generic {import('@aerogel/core/ui/UI').ModalExposeResult<ConfirmModalExpose>} -->
|
|
3
|
+
<Modal
|
|
4
|
+
v-slot="{ close }"
|
|
5
|
+
:title="renderedTitle"
|
|
6
|
+
:title-hidden="titleHidden"
|
|
7
|
+
persistent
|
|
8
|
+
>
|
|
3
9
|
<Form :form @submit="close([true, form.data()])">
|
|
4
10
|
<Markdown :text="message" :actions />
|
|
5
11
|
|
|
@@ -35,8 +41,10 @@ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
|
35
41
|
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
36
42
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
37
43
|
import { useConfirmModal } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
38
|
-
import type { ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
44
|
+
import type { ConfirmModalExpose, ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
39
45
|
|
|
40
46
|
const { cancelVariant = 'secondary', ...props } = defineProps<ConfirmModalProps>();
|
|
41
|
-
const { form, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
47
|
+
const { form, renderedTitle, titleHidden, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
48
|
+
|
|
49
|
+
defineExpose<ConfirmModalExpose>();
|
|
42
50
|
</script>
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<DropdownMenuItem
|
|
3
|
-
class="flex w-full items-center gap-2 rounded-lg px-2 py-2 text-sm text-gray-900 data-[highlighted]:bg-gray-100"
|
|
4
|
-
@select="$emit('select')"
|
|
5
|
-
>
|
|
2
|
+
<DropdownMenuItem :class="renderedClasses" v-bind="props" @select="$emit('select')">
|
|
6
3
|
<slot />
|
|
7
4
|
</DropdownMenuItem>
|
|
8
5
|
</template>
|
|
9
6
|
|
|
10
7
|
<script setup lang="ts">
|
|
8
|
+
import { classes } from '@aerogel/core/utils';
|
|
9
|
+
import { computed } from 'vue';
|
|
11
10
|
import { DropdownMenuItem } from 'reka-ui';
|
|
11
|
+
import type { HTMLAttributes } from 'vue';
|
|
12
|
+
import type { PrimitiveProps } from 'reka-ui';
|
|
12
13
|
|
|
13
14
|
defineEmits<{ select: [] }>();
|
|
15
|
+
|
|
16
|
+
const { class: rootClass, ...props } = defineProps<{ class?: HTMLAttributes['class'] } & PrimitiveProps>();
|
|
17
|
+
const renderedClasses = computed(() =>
|
|
18
|
+
classes(
|
|
19
|
+
'flex w-full items-center gap-2 rounded-lg px-2 py-2 text-sm text-gray-900 data-[highlighted]:bg-gray-100',
|
|
20
|
+
rootClass,
|
|
21
|
+
));
|
|
14
22
|
</script>
|
|
@@ -5,7 +5,23 @@
|
|
|
5
5
|
:side="dropdownMenu.side"
|
|
6
6
|
>
|
|
7
7
|
<slot>
|
|
8
|
-
<DropdownMenuOption
|
|
8
|
+
<DropdownMenuOption
|
|
9
|
+
v-for="(option, key) in dropdownMenu.options"
|
|
10
|
+
:key
|
|
11
|
+
:as="option.route || option.href ? HeadlessButton : undefined"
|
|
12
|
+
:class="option.class"
|
|
13
|
+
v-bind="
|
|
14
|
+
option.route || option.href
|
|
15
|
+
? {
|
|
16
|
+
href: option.href,
|
|
17
|
+
route: option.route,
|
|
18
|
+
routeParams: option.routeParams,
|
|
19
|
+
routeQuery: option.routeQuery,
|
|
20
|
+
}
|
|
21
|
+
: {}
|
|
22
|
+
"
|
|
23
|
+
@select="option.click?.()"
|
|
24
|
+
>
|
|
9
25
|
{{ option.label }}
|
|
10
26
|
</DropdownMenuOption>
|
|
11
27
|
</slot>
|
|
@@ -19,6 +35,7 @@ import { injectReactiveOrFail } from '@aerogel/core/utils';
|
|
|
19
35
|
import type { DropdownMenuExpose } from '@aerogel/core/components/contracts/DropdownMenu';
|
|
20
36
|
|
|
21
37
|
import DropdownMenuOption from './DropdownMenuOption.vue';
|
|
38
|
+
import HeadlessButton from '../headless/HeadlessButton.vue';
|
|
22
39
|
|
|
23
40
|
const dropdownMenu = injectReactiveOrFail<DropdownMenuExpose>(
|
|
24
41
|
'dropdown-menu',
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Button
|
|
3
|
+
v-if="$errors.logs.length > 0"
|
|
4
|
+
size="icon"
|
|
5
|
+
variant="ghost"
|
|
6
|
+
:title="$td('errors.viewLogs', 'View error logs')"
|
|
7
|
+
:aria-label="$td('errors.viewLogs', 'View error logs')"
|
|
8
|
+
@click="$ui.modal(ErrorLogsModal)"
|
|
9
|
+
>
|
|
10
|
+
<IconWarning class="size-6 text-red-500" />
|
|
11
|
+
</Button>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import IconWarning from '~icons/ion/warning';
|
|
16
|
+
|
|
17
|
+
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
18
|
+
import ErrorLogsModal from '@aerogel/core/components/ui/ErrorLogsModal.vue';
|
|
19
|
+
</script>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Modal :title="$td('errors.report', 'Errors ({count})', { count: $errors.logs.length })">
|
|
3
|
+
<ol>
|
|
4
|
+
<li
|
|
5
|
+
v-for="(log, index) of $errors.logs"
|
|
6
|
+
:key="index"
|
|
7
|
+
class="mb-2 flex max-w-prose min-w-56 justify-between py-2 last:mb-0"
|
|
8
|
+
>
|
|
9
|
+
<div>
|
|
10
|
+
<h3 class="font-medium">
|
|
11
|
+
{{ log.report.title }}
|
|
12
|
+
</h3>
|
|
13
|
+
<time :datetime="log.date.toISOString()" class="text-xs text-gray-700">
|
|
14
|
+
{{ log.date.toLocaleTimeString() }}
|
|
15
|
+
</time>
|
|
16
|
+
<Markdown
|
|
17
|
+
class="text-sm text-gray-500"
|
|
18
|
+
:text="log.report.description ?? getErrorMessage(log.report)"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
<Button
|
|
22
|
+
size="icon"
|
|
23
|
+
variant="ghost"
|
|
24
|
+
:aria-label="$td('errors.viewDetails', 'View details')"
|
|
25
|
+
:title="$td('errors.viewDetails', 'View details')"
|
|
26
|
+
class="self-center"
|
|
27
|
+
@click="
|
|
28
|
+
$errors.inspect(
|
|
29
|
+
log.report,
|
|
30
|
+
$errors.logs.map(({ report }) => report)
|
|
31
|
+
)
|
|
32
|
+
"
|
|
33
|
+
>
|
|
34
|
+
<IconViewShow class="size-4" aria-hidden="true" />
|
|
35
|
+
</Button>
|
|
36
|
+
</li>
|
|
37
|
+
</ol>
|
|
38
|
+
</Modal>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import IconViewShow from '~icons/zondicons/view-show';
|
|
43
|
+
|
|
44
|
+
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
45
|
+
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
46
|
+
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
47
|
+
import { getErrorMessage } from '@aerogel/core/errors';
|
|
48
|
+
</script>
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Modal
|
|
2
|
+
<Modal
|
|
3
|
+
:title="$td('errors.report', 'Error report')"
|
|
4
|
+
title-hidden
|
|
5
|
+
close-hidden
|
|
6
|
+
class="p-0"
|
|
7
|
+
wrapper-class="sm:w-auto sm:min-w-lg sm:max-w-[80vw]"
|
|
8
|
+
>
|
|
3
9
|
<div class="px-4 pt-5 pb-4">
|
|
4
10
|
<h2 class="flex justify-between gap-4">
|
|
5
11
|
<div class="flex items-center gap-2">
|
|
6
12
|
<IconExclamationSolid class="size-5 text-red-600" />
|
|
7
13
|
<ErrorReportModalTitle
|
|
8
14
|
class="text-lg leading-6 font-semibold text-gray-900"
|
|
9
|
-
:report="
|
|
15
|
+
:report="activeReport"
|
|
10
16
|
:current-report="activeReportIndex + 1"
|
|
11
17
|
:total-reports="reports.length"
|
|
12
18
|
/>
|
|
@@ -33,11 +39,11 @@
|
|
|
33
39
|
</Button>
|
|
34
40
|
</span>
|
|
35
41
|
</div>
|
|
36
|
-
<ErrorReportModalButtons :report class="gap-0.5" />
|
|
42
|
+
<ErrorReportModalButtons :report="activeReport" class="gap-0.5" />
|
|
37
43
|
</h2>
|
|
38
|
-
<Markdown v-if="
|
|
44
|
+
<Markdown v-if="activeReport.description" :text="activeReport.description" class="text-gray-600" />
|
|
39
45
|
</div>
|
|
40
|
-
<div class="-mt-2 max-h-[
|
|
46
|
+
<div class="-mt-2 max-h-[75vh] overflow-auto bg-red-800/10">
|
|
41
47
|
<pre class="p-4 text-xs text-red-800" v-text="details" />
|
|
42
48
|
</div>
|
|
43
49
|
</Modal>
|
|
@@ -54,9 +60,14 @@ import ErrorReportModalButtons from '@aerogel/core/components/ui/ErrorReportModa
|
|
|
54
60
|
import ErrorReportModalTitle from '@aerogel/core/components/ui/ErrorReportModalTitle.vue';
|
|
55
61
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
56
62
|
import { useErrorReportModal } from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
57
|
-
import type {
|
|
63
|
+
import type {
|
|
64
|
+
ErrorReportModalExpose,
|
|
65
|
+
ErrorReportModalProps,
|
|
66
|
+
} from '@aerogel/core/components/contracts/ErrorReportModal';
|
|
58
67
|
|
|
59
68
|
const props = defineProps<ErrorReportModalProps>();
|
|
60
69
|
|
|
61
|
-
const { activeReportIndex, details, nextReportText, previousReportText,
|
|
70
|
+
const { activeReportIndex, details, nextReportText, previousReportText, activeReport } = useErrorReportModal(props);
|
|
71
|
+
|
|
72
|
+
defineExpose<ErrorReportModalExpose>();
|
|
62
73
|
</script>
|
|
@@ -48,8 +48,8 @@ const renderedInputClasses = computed(() =>
|
|
|
48
48
|
'block w-full rounded-md border-0 py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6',
|
|
49
49
|
{
|
|
50
50
|
'focus:ring-primary-600': !$input.value?.errors,
|
|
51
|
-
'text-gray-900 shadow-2xs ring-gray-
|
|
52
|
-
'pr-10 text-red-900 ring-red-
|
|
51
|
+
'text-gray-900 shadow-2xs ring-gray-900/10 placeholder:text-gray-400': !$input.value?.errors,
|
|
52
|
+
'pr-10 text-red-900 ring-red-900/10 placeholder:text-red-300 focus:ring-red-500': $input.value?.errors,
|
|
53
53
|
},
|
|
54
54
|
inputClass,
|
|
55
55
|
));
|
|
@@ -25,8 +25,10 @@ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
|
25
25
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
26
26
|
import ProgressBar from '@aerogel/core/components/ui/ProgressBar.vue';
|
|
27
27
|
import { useLoadingModal } from '@aerogel/core/components/contracts/LoadingModal';
|
|
28
|
-
import type { LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
28
|
+
import type { LoadingModalExpose, LoadingModalProps } from '@aerogel/core/components/contracts/LoadingModal';
|
|
29
29
|
|
|
30
30
|
const props = defineProps<LoadingModalProps>();
|
|
31
31
|
const { renderedTitle, renderedMessage, titleHidden, showProgress } = useLoadingModal(props);
|
|
32
|
+
|
|
33
|
+
defineExpose<LoadingModalExpose>();
|
|
32
34
|
</script>
|
|
@@ -7,7 +7,7 @@ import { computed, h, useAttrs } from 'vue';
|
|
|
7
7
|
import { isInstanceOf } from '@noeldemartin/utils';
|
|
8
8
|
import type { VNode } from 'vue';
|
|
9
9
|
|
|
10
|
-
import { renderMarkdown } from '@aerogel/core/utils/markdown';
|
|
10
|
+
import { getMarkdownRouter, renderMarkdown } from '@aerogel/core/utils/markdown';
|
|
11
11
|
import { translate, translateWithDefault } from '@aerogel/core/lang';
|
|
12
12
|
import { renderVNode } from '@aerogel/core/utils/vue';
|
|
13
13
|
|
|
@@ -65,5 +65,33 @@ async function onClick(event: Event) {
|
|
|
65
65
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
if (isInstanceOf(target, HTMLAnchorElement) && target.dataset.markdownRoute) {
|
|
70
|
+
const router = getMarkdownRouter();
|
|
71
|
+
|
|
72
|
+
if (router) {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
|
|
75
|
+
router.visit(target.dataset.markdownRoute);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
</script>
|
|
82
|
+
|
|
83
|
+
<style scoped>
|
|
84
|
+
/* @apply .text-links .font-normal .no-underline .hover:underline; */
|
|
85
|
+
* :deep(a) {
|
|
86
|
+
--tw-font-weight: var(--font-weight-normal);
|
|
87
|
+
text-decoration-line: none;
|
|
88
|
+
color: var(--color-links);
|
|
89
|
+
font-weight: var(--font-weight-normal);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (hover: hover) {
|
|
93
|
+
* :deep(a:hover) {
|
|
94
|
+
text-decoration-line: underline;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<!-- @vue-generic {T} -->
|
|
2
3
|
<HeadlessModal
|
|
3
4
|
v-slot="{ close }"
|
|
4
5
|
v-bind="props"
|
|
5
6
|
:ref="($modal) => forwardRef($modal as HeadlessModalInstance)"
|
|
6
7
|
:persistent
|
|
7
8
|
>
|
|
8
|
-
<HeadlessModalOverlay
|
|
9
|
+
<HeadlessModalOverlay
|
|
10
|
+
class="fixed inset-0 animate-[fade-in_var(--tw-duration)_ease-in-out] transition-opacity duration-300 will-change-[opacity]"
|
|
11
|
+
:class="{
|
|
12
|
+
'bg-black/30': context.childIndex === 1,
|
|
13
|
+
'opacity-0': context.childIndex === 1 && context.modal.closing,
|
|
14
|
+
}"
|
|
15
|
+
/>
|
|
9
16
|
<HeadlessModalContent v-bind="contentProps" :class="renderedWrapperClass">
|
|
10
|
-
<div v-if="!persistent &&
|
|
11
|
-
<
|
|
17
|
+
<div v-if="!persistent && !closeHidden" class="absolute top-0 right-0 hidden pt-3.5 pr-2.5 sm:block">
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
class="clickable z-10 rounded-full p-2.5 text-gray-400 hover:text-gray-500"
|
|
21
|
+
@click="close()"
|
|
22
|
+
>
|
|
12
23
|
<span class="sr-only">{{ $td('ui.close', 'Close') }}</span>
|
|
13
|
-
<IconClose class="size-
|
|
14
|
-
</
|
|
24
|
+
<IconClose class="size-4" />
|
|
25
|
+
</button>
|
|
15
26
|
</div>
|
|
16
27
|
|
|
17
28
|
<HeadlessModalTitle
|
|
18
29
|
v-if="title"
|
|
19
|
-
class="text-base font-semibold text-gray-900"
|
|
20
|
-
:class="{
|
|
30
|
+
class="px-4 pt-5 text-base font-semibold text-gray-900"
|
|
31
|
+
:class="{
|
|
32
|
+
'sr-only': titleHidden,
|
|
33
|
+
'pb-0': description && !descriptionHidden,
|
|
34
|
+
'pb-2': !description || descriptionHidden,
|
|
35
|
+
}"
|
|
21
36
|
>
|
|
22
37
|
<Markdown :text="title" inline />
|
|
23
38
|
</HeadlessModalTitle>
|
|
24
39
|
|
|
25
|
-
<HeadlessModalDescription
|
|
26
|
-
|
|
40
|
+
<HeadlessModalDescription
|
|
41
|
+
v-if="description"
|
|
42
|
+
class="px-4 pt-1 pb-2"
|
|
43
|
+
:class="{ 'sr-only': descriptionHidden }"
|
|
44
|
+
>
|
|
45
|
+
<Markdown :text="description" class="text-sm leading-6 text-gray-500" />
|
|
27
46
|
</HeadlessModalDescription>
|
|
28
47
|
|
|
29
48
|
<div :class="renderedContentClass">
|
|
@@ -33,59 +52,80 @@
|
|
|
33
52
|
</HeadlessModal>
|
|
34
53
|
</template>
|
|
35
54
|
|
|
36
|
-
<script setup lang="ts">
|
|
55
|
+
<script setup lang="ts" generic="T = void">
|
|
37
56
|
import IconClose from '~icons/zondicons/close';
|
|
38
57
|
|
|
58
|
+
import { after } from '@noeldemartin/utils';
|
|
39
59
|
import { computed } from 'vue';
|
|
40
60
|
import { useForwardExpose } from 'reka-ui';
|
|
41
|
-
import type { HTMLAttributes, Ref } from 'vue';
|
|
61
|
+
import type { ComponentPublicInstance, HTMLAttributes, Ref } from 'vue';
|
|
42
62
|
import type { Nullable } from '@noeldemartin/utils';
|
|
43
63
|
|
|
44
64
|
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
45
|
-
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
46
65
|
import HeadlessModal from '@aerogel/core/components/headless/HeadlessModal.vue';
|
|
47
66
|
import HeadlessModalContent from '@aerogel/core/components/headless/HeadlessModalContent.vue';
|
|
48
67
|
import HeadlessModalDescription from '@aerogel/core/components/headless/HeadlessModalDescription.vue';
|
|
49
68
|
import HeadlessModalOverlay from '@aerogel/core/components/headless/HeadlessModalOverlay.vue';
|
|
50
69
|
import HeadlessModalTitle from '@aerogel/core/components/headless/HeadlessModalTitle.vue';
|
|
70
|
+
import UI from '@aerogel/core/ui/UI';
|
|
51
71
|
import { classes } from '@aerogel/core/utils/classes';
|
|
72
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
73
|
+
import { useEvent } from '@aerogel/core/utils/composition/events';
|
|
74
|
+
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
52
75
|
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
53
|
-
import type {
|
|
76
|
+
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
54
77
|
|
|
55
|
-
type HeadlessModalInstance =
|
|
78
|
+
type HeadlessModalInstance = ComponentPublicInstance & ModalExpose<T>;
|
|
56
79
|
|
|
57
80
|
const {
|
|
58
81
|
class: contentClass = '',
|
|
59
|
-
dismissable = true,
|
|
60
82
|
wrapperClass = '',
|
|
61
83
|
title,
|
|
62
84
|
titleHidden,
|
|
63
85
|
description,
|
|
64
86
|
persistent,
|
|
87
|
+
closeHidden,
|
|
65
88
|
...props
|
|
66
89
|
} = defineProps<
|
|
67
90
|
ModalProps & {
|
|
68
|
-
dismissable?: boolean;
|
|
69
91
|
wrapperClass?: HTMLAttributes['class'];
|
|
70
92
|
class?: HTMLAttributes['class'];
|
|
93
|
+
closeHidden?: boolean;
|
|
71
94
|
}
|
|
72
95
|
>();
|
|
73
96
|
|
|
74
|
-
|
|
97
|
+
defineSlots<ModalSlots<T>>();
|
|
98
|
+
defineExpose<AcceptRefs<ModalExpose<T>>>({
|
|
75
99
|
close: async (result) => $modal.value?.close(result),
|
|
76
100
|
$content: computed(() => $modal.value?.$content),
|
|
77
101
|
});
|
|
78
102
|
|
|
79
103
|
const { forwardRef, currentRef } = useForwardExpose<HeadlessModalInstance>();
|
|
80
104
|
const $modal = currentRef as Ref<Nullable<HeadlessModalInstance>>;
|
|
105
|
+
const context = injectReactiveOrFail<UIModalContext>('modal');
|
|
106
|
+
const inForeground = computed(() => !context.modal.closing && context.childIndex === UI.openModals.length);
|
|
81
107
|
const contentProps = computed(() => (description ? {} : { 'aria-describedby': undefined }));
|
|
82
|
-
const renderedContentClass = computed(() =>
|
|
108
|
+
const renderedContentClass = computed(() =>
|
|
109
|
+
classes('max-h-[90vh] overflow-auto px-4 pb-4', { 'pt-4': !title || titleHidden }, contentClass));
|
|
83
110
|
const renderedWrapperClass = computed(() =>
|
|
84
111
|
classes(
|
|
85
|
-
|
|
86
|
-
'
|
|
112
|
+
'isolate fixed top-1/2 left-1/2 z-50 w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2',
|
|
113
|
+
'overflow-hidden rounded-lg bg-white text-left shadow-xl sm:max-w-lg',
|
|
114
|
+
'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
|
|
115
|
+
'transition-[scale,opacity] will-change-[scale,opacity] duration-300',
|
|
116
|
+
{
|
|
117
|
+
'scale-50 opacity-0': !inForeground.value,
|
|
118
|
+
'scale-100 opacity-100': inForeground.value,
|
|
119
|
+
},
|
|
87
120
|
wrapperClass,
|
|
88
121
|
));
|
|
89
122
|
|
|
90
|
-
|
|
123
|
+
useEvent('modal-will-close', async ({ modal: { id } }) => {
|
|
124
|
+
if (id !== context.modal.id) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Wait for transitions to finish
|
|
129
|
+
await after({ ms: 300 });
|
|
130
|
+
});
|
|
91
131
|
</script>
|