@aerogel/core 0.0.0-next.9aa7c279868edbedbcee075aef52212597d803fb → 0.0.0-next.9d1e54cc195274e9dd7d57a73fcb8a9a51927dcb
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 +93 -66
- package/dist/aerogel-core.js +1064 -920
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +2 -2
- package/src/components/contracts/AlertModal.ts +11 -0
- package/src/components/contracts/ConfirmModal.ts +3 -1
- package/src/components/contracts/DropdownMenu.ts +6 -1
- package/src/components/contracts/ErrorReportModal.ts +5 -4
- package/src/components/headless/HeadlessModal.vue +1 -1
- package/src/components/headless/HeadlessModalContent.vue +1 -1
- package/src/components/ui/AdvancedOptions.vue +1 -1
- package/src/components/ui/AlertModal.vue +5 -2
- package/src/components/ui/Button.vue +11 -9
- package/src/components/ui/ConfirmModal.vue +7 -2
- 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 +12 -6
- package/src/components/ui/Markdown.vue +16 -0
- package/src/components/ui/Modal.vue +7 -6
- package/src/components/ui/SelectOptions.vue +2 -2
- package/src/components/ui/Toast.vue +16 -14
- package/src/components/ui/index.ts +2 -0
- package/src/directives/measure.ts +11 -5
- package/src/errors/Errors.ts +17 -6
- package/src/index.css +3 -2
- package/src/ui/UI.ts +22 -12
- package/src/utils/classes.ts +9 -17
- package/src/utils/vue.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerogel/core",
|
|
3
|
-
"version": "0.0.0-next.
|
|
3
|
+
"version": "0.0.0-next.9d1e54cc195274e9dd7d57a73fcb8a9a51927dcb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@tailwindcss/forms": "^0.5.10",
|
|
27
27
|
"@tailwindcss/typography": "^0.5.16",
|
|
28
|
-
"tailwindcss": "^4.1.
|
|
28
|
+
"tailwindcss": "^4.1.4",
|
|
29
29
|
"vue": "^3.5.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
|
|
3
|
+
import { translateWithDefault } from '@aerogel/core/lang';
|
|
1
4
|
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
2
5
|
|
|
3
6
|
export interface AlertModalProps {
|
|
@@ -6,3 +9,11 @@ export interface AlertModalProps {
|
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export interface AlertModalExpose extends ModalExpose<void> {}
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
14
|
+
export function useAlertModal(props: AlertModalProps) {
|
|
15
|
+
const renderedTitle = computed(() => props.title ?? translateWithDefault('ui.alert', 'Alert'));
|
|
16
|
+
const titleHidden = computed(() => !props.title);
|
|
17
|
+
|
|
18
|
+
return { renderedTitle, titleHidden };
|
|
19
|
+
}
|
|
@@ -39,8 +39,10 @@ export function useConfirmModal(props: ConfirmModalProps) {
|
|
|
39
39
|
),
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
+
const renderedTitle = computed(() => props.title ?? translateWithDefault('ui.confirm', 'Confirm'));
|
|
43
|
+
const titleHidden = computed(() => !props.title);
|
|
42
44
|
const renderedAcceptText = computed(() => props.acceptText ?? translateWithDefault('ui.accept', 'Ok'));
|
|
43
45
|
const renderedCancelText = computed(() => props.cancelText ?? translateWithDefault('ui.cancel', 'Cancel'));
|
|
44
46
|
|
|
45
|
-
return { form, renderedAcceptText, renderedCancelText };
|
|
47
|
+
return { form, renderedTitle, titleHidden, renderedAcceptText, renderedCancelText };
|
|
46
48
|
}
|
|
@@ -4,7 +4,12 @@ import type { Falsifiable } from '@aerogel/core/utils/types';
|
|
|
4
4
|
|
|
5
5
|
export type DropdownMenuOptionData = {
|
|
6
6
|
label: string;
|
|
7
|
-
|
|
7
|
+
href?: string;
|
|
8
|
+
route?: string;
|
|
9
|
+
routeParams?: object;
|
|
10
|
+
routeQuery?: object;
|
|
11
|
+
click?: () => unknown;
|
|
12
|
+
class?: string;
|
|
8
13
|
};
|
|
9
14
|
|
|
10
15
|
export interface DropdownMenuProps {
|
|
@@ -5,6 +5,7 @@ import type { ErrorReport } from '@aerogel/core/errors';
|
|
|
5
5
|
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
6
6
|
|
|
7
7
|
export interface ErrorReportModalProps {
|
|
8
|
+
report: ErrorReport;
|
|
8
9
|
reports: ErrorReport[];
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -12,11 +13,11 @@ export interface ErrorReportModalExpose extends ModalExpose {}
|
|
|
12
13
|
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
14
15
|
export function useErrorReportModal(props: ErrorReportModalProps) {
|
|
15
|
-
const activeReportIndex = ref(0);
|
|
16
|
-
const
|
|
16
|
+
const activeReportIndex = ref(props.reports.includes(props.report) ? props.reports.indexOf(props.report) : 0);
|
|
17
|
+
const activeReport = computed(() => props.reports[activeReportIndex.value] as ErrorReport);
|
|
17
18
|
const details = computed(
|
|
18
19
|
() =>
|
|
19
|
-
|
|
20
|
+
activeReport.value.details?.trim() ||
|
|
20
21
|
translateWithDefault('errors.detailsEmpty', 'This error is missing a stacktrace.'),
|
|
21
22
|
);
|
|
22
23
|
const previousReportText = translateWithDefault('errors.previousReport', 'Show previous report');
|
|
@@ -27,6 +28,6 @@ export function useErrorReportModal(props: ErrorReportModalProps) {
|
|
|
27
28
|
details,
|
|
28
29
|
nextReportText,
|
|
29
30
|
previousReportText,
|
|
30
|
-
|
|
31
|
+
activeReport,
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -23,7 +23,7 @@ const $content = ref<Nullable<InstanceType<typeof DialogContent>>>(null);
|
|
|
23
23
|
const { modal } = injectReactiveOrFail<UIModalContext>(
|
|
24
24
|
'modal',
|
|
25
25
|
'could not obtain modal reference from <HeadlessModal>, ' +
|
|
26
|
-
'did you render this component manually? Show it using $ui.
|
|
26
|
+
'did you render this component manually? Show it using $ui.modal() instead',
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
defineProps<ModalProps>();
|
|
@@ -20,7 +20,7 @@ import type { ModalContentInstance } from '@aerogel/core/components/contracts/Mo
|
|
|
20
20
|
const { childIndex = 0 } = injectReactiveOrFail<UIModalContext>(
|
|
21
21
|
'modal',
|
|
22
22
|
'could not obtain modal reference from <HeadlessModalContent>, ' +
|
|
23
|
-
'did you render this component manually? Show it using $ui.
|
|
23
|
+
'did you render this component manually? Show it using $ui.modal() instead',
|
|
24
24
|
);
|
|
25
25
|
const $modalContentRef = injectOrFail<Ref<ModalContentInstance>>('$modalContentRef');
|
|
26
26
|
const $content = useTemplateRef('$contentRef');
|
|
@@ -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,8 +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 { useAlertModal } from '@aerogel/core/components/contracts/AlertModal';
|
|
10
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
|
+
|
|
13
16
|
defineExpose<AlertModalExpose>();
|
|
14
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'],
|
|
@@ -93,6 +95,6 @@ const renderedClasses = computedVariantClasses<Variants<Pick<ButtonProps, 'size'
|
|
|
93
95
|
disabled: false,
|
|
94
96
|
},
|
|
95
97
|
},
|
|
96
|
-
);
|
|
98
|
+
));
|
|
97
99
|
/* eslint-enable vue/max-len */
|
|
98
100
|
</script>
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- @vue-generic {import('@aerogel/core/ui/UI').ModalExposeResult<ConfirmModalExpose>} -->
|
|
3
|
-
<Modal
|
|
3
|
+
<Modal
|
|
4
|
+
v-slot="{ close }"
|
|
5
|
+
:title="renderedTitle"
|
|
6
|
+
:title-hidden="titleHidden"
|
|
7
|
+
persistent
|
|
8
|
+
>
|
|
4
9
|
<Form :form @submit="close([true, form.data()])">
|
|
5
10
|
<Markdown :text="message" :actions />
|
|
6
11
|
|
|
@@ -39,7 +44,7 @@ import { useConfirmModal } from '@aerogel/core/components/contracts/ConfirmModal
|
|
|
39
44
|
import type { ConfirmModalExpose, ConfirmModalProps } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
40
45
|
|
|
41
46
|
const { cancelVariant = 'secondary', ...props } = defineProps<ConfirmModalProps>();
|
|
42
|
-
const { form, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
47
|
+
const { form, renderedTitle, titleHidden, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
43
48
|
|
|
44
49
|
defineExpose<ConfirmModalExpose>();
|
|
45
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>
|
|
@@ -61,7 +67,7 @@ import type {
|
|
|
61
67
|
|
|
62
68
|
const props = defineProps<ErrorReportModalProps>();
|
|
63
69
|
|
|
64
|
-
const { activeReportIndex, details, nextReportText, previousReportText,
|
|
70
|
+
const { activeReportIndex, details, nextReportText, previousReportText, activeReport } = useErrorReportModal(props);
|
|
65
71
|
|
|
66
72
|
defineExpose<ErrorReportModalExpose>();
|
|
67
73
|
</script>
|
|
@@ -67,3 +67,19 @@ async function onClick(event: Event) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
</script>
|
|
70
|
+
|
|
71
|
+
<style scoped>
|
|
72
|
+
/* @apply .text-links .font-normal .no-underline .hover:underline; */
|
|
73
|
+
* :deep(a) {
|
|
74
|
+
--tw-font-weight: var(--font-weight-normal);
|
|
75
|
+
text-decoration-line: none;
|
|
76
|
+
color: var(--color-links);
|
|
77
|
+
font-weight: var(--font-weight-normal);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@media (hover: hover) {
|
|
81
|
+
* :deep(a:hover) {
|
|
82
|
+
text-decoration-line: underline;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}"
|
|
15
15
|
/>
|
|
16
16
|
<HeadlessModalContent v-bind="contentProps" :class="renderedWrapperClass">
|
|
17
|
-
<div v-if="!persistent &&
|
|
17
|
+
<div v-if="!persistent && !closeHidden" class="absolute top-0 right-0 hidden pt-3.5 pr-2.5 sm:block">
|
|
18
18
|
<button
|
|
19
19
|
type="button"
|
|
20
20
|
class="clickable z-10 rounded-full p-2.5 text-gray-400 hover:text-gray-500"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<HeadlessModalTitle
|
|
29
29
|
v-if="title"
|
|
30
|
-
class="text-base font-semibold text-gray-900"
|
|
30
|
+
class="px-4 pt-5 pb-2 text-base font-semibold text-gray-900"
|
|
31
31
|
:class="{ 'sr-only': titleHidden }"
|
|
32
32
|
>
|
|
33
33
|
<Markdown :text="title" inline />
|
|
@@ -71,18 +71,18 @@ type HeadlessModalInstance = ComponentPublicInstance & ModalExpose<T>;
|
|
|
71
71
|
|
|
72
72
|
const {
|
|
73
73
|
class: contentClass = '',
|
|
74
|
-
dismissable = true,
|
|
75
74
|
wrapperClass = '',
|
|
76
75
|
title,
|
|
77
76
|
titleHidden,
|
|
78
77
|
description,
|
|
79
78
|
persistent,
|
|
79
|
+
closeHidden,
|
|
80
80
|
...props
|
|
81
81
|
} = defineProps<
|
|
82
82
|
ModalProps & {
|
|
83
|
-
dismissable?: boolean;
|
|
84
83
|
wrapperClass?: HTMLAttributes['class'];
|
|
85
84
|
class?: HTMLAttributes['class'];
|
|
85
|
+
closeHidden?: boolean;
|
|
86
86
|
}
|
|
87
87
|
>();
|
|
88
88
|
|
|
@@ -97,11 +97,12 @@ const $modal = currentRef as Ref<Nullable<HeadlessModalInstance>>;
|
|
|
97
97
|
const context = injectReactiveOrFail<UIModalContext>('modal');
|
|
98
98
|
const inForeground = computed(() => !context.modal.closing && context.childIndex === UI.openModals.length);
|
|
99
99
|
const contentProps = computed(() => (description ? {} : { 'aria-describedby': undefined }));
|
|
100
|
-
const renderedContentClass = computed(() =>
|
|
100
|
+
const renderedContentClass = computed(() =>
|
|
101
|
+
classes('max-h-[90vh] overflow-auto px-4 pb-4', { 'pt-4': !title || titleHidden }, contentClass));
|
|
101
102
|
const renderedWrapperClass = computed(() =>
|
|
102
103
|
classes(
|
|
103
104
|
'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',
|
|
104
|
-
'overflow-hidden rounded-lg bg-white
|
|
105
|
+
'overflow-hidden rounded-lg bg-white text-left shadow-xl sm:max-w-lg',
|
|
105
106
|
'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
|
|
106
107
|
'transition-[scale,opacity] will-change-[scale,opacity] duration-300',
|
|
107
108
|
{
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<HeadlessSelectOptions :class="renderedClasses">
|
|
3
|
-
<slot v-if="select
|
|
3
|
+
<slot v-if="select.options?.length">
|
|
4
4
|
<SelectOption v-for="option of select?.options ?? []" :key="option.key" :value="option.value">
|
|
5
5
|
{{ option.label }}
|
|
6
6
|
</SelectOption>
|
|
7
7
|
</slot>
|
|
8
|
-
<slot v-else
|
|
8
|
+
<slot v-else>
|
|
9
9
|
<SelectOption disabled :value="null">
|
|
10
10
|
{{ $td('ui.selectEmpty', 'No options available') }}
|
|
11
11
|
</SelectOption>
|
|
@@ -13,32 +13,34 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script setup lang="ts">
|
|
16
|
+
import { computed } from 'vue';
|
|
16
17
|
import type { HTMLAttributes } from 'vue';
|
|
17
18
|
|
|
18
19
|
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
19
20
|
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
20
21
|
import HeadlessToast from '@aerogel/core/components/headless/HeadlessToast.vue';
|
|
21
22
|
import HeadlessToastAction from '@aerogel/core/components/headless/HeadlessToastAction.vue';
|
|
22
|
-
import {
|
|
23
|
+
import { variantClasses } from '@aerogel/core/utils/classes';
|
|
23
24
|
import type { ToastExpose, ToastProps } from '@aerogel/core/components/contracts/Toast';
|
|
24
25
|
import type { Variants } from '@aerogel/core/utils/classes';
|
|
25
26
|
|
|
26
27
|
const { class: baseClasses, variant = 'secondary' } = defineProps<ToastProps & { class?: HTMLAttributes['class'] }>();
|
|
27
|
-
const renderedClasses =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
const renderedClasses = computed(() =>
|
|
29
|
+
variantClasses<Variants<Pick<ToastProps, 'variant'>>>(
|
|
30
|
+
{ baseClasses, variant },
|
|
31
|
+
{
|
|
32
|
+
baseClasses: 'flex items-center gap-2 rounded-md p-2 ring-1 shadow-lg border-gray-200',
|
|
33
|
+
variants: {
|
|
34
|
+
variant: {
|
|
35
|
+
secondary: 'bg-gray-900 text-white ring-black',
|
|
36
|
+
danger: 'bg-red-50 text-red-900 ring-red-100',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
variant: 'secondary',
|
|
35
41
|
},
|
|
36
42
|
},
|
|
37
|
-
|
|
38
|
-
variant: 'secondary',
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
);
|
|
43
|
+
));
|
|
42
44
|
|
|
43
45
|
defineExpose<ToastExpose>();
|
|
44
46
|
</script>
|
|
@@ -7,6 +7,8 @@ export { default as DropdownMenu } from './DropdownMenu.vue';
|
|
|
7
7
|
export { default as DropdownMenuOption } from './DropdownMenuOption.vue';
|
|
8
8
|
export { default as DropdownMenuOptions } from './DropdownMenuOptions.vue';
|
|
9
9
|
export { default as EditableContent } from './EditableContent.vue';
|
|
10
|
+
export { default as ErrorLogs } from './ErrorLogs.vue';
|
|
11
|
+
export { default as ErrorLogsModal } from './ErrorLogsModal.vue';
|
|
10
12
|
export { default as ErrorMessage } from './ErrorMessage.vue';
|
|
11
13
|
export { default as ErrorReportModal } from './ErrorReportModal.vue';
|
|
12
14
|
export { default as ErrorReportModalButtons } from './ErrorReportModalButtons.vue';
|
|
@@ -3,6 +3,15 @@ import { tap } from '@noeldemartin/utils';
|
|
|
3
3
|
|
|
4
4
|
const resizeObservers: WeakMap<HTMLElement, ResizeObserver> = new WeakMap();
|
|
5
5
|
|
|
6
|
+
export type MeasureDirectiveValue =
|
|
7
|
+
| MeasureDirectiveListener
|
|
8
|
+
| {
|
|
9
|
+
css?: boolean;
|
|
10
|
+
watch?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type MeasureDirectiveModifiers = 'css' | 'watch';
|
|
14
|
+
|
|
6
15
|
export interface ElementSize {
|
|
7
16
|
width: number;
|
|
8
17
|
height: number;
|
|
@@ -10,11 +19,8 @@ export interface ElementSize {
|
|
|
10
19
|
|
|
11
20
|
export type MeasureDirectiveListener = (size: ElementSize) => unknown;
|
|
12
21
|
|
|
13
|
-
export default defineDirective({
|
|
14
|
-
mounted(element: HTMLElement, { value }) {
|
|
15
|
-
// TODO replace with argument when typed properly
|
|
16
|
-
const modifiers = { css: true, watch: true };
|
|
17
|
-
|
|
22
|
+
export default defineDirective<MeasureDirectiveValue, MeasureDirectiveModifiers>({
|
|
23
|
+
mounted(element: HTMLElement, { value, modifiers }) {
|
|
18
24
|
const listener = typeof value === 'function' ? (value as MeasureDirectiveListener) : null;
|
|
19
25
|
const update = () => {
|
|
20
26
|
const sizes = element.getBoundingClientRect();
|
package/src/errors/Errors.ts
CHANGED
|
@@ -22,16 +22,23 @@ export class ErrorsService extends Service {
|
|
|
22
22
|
this.enabled = false;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
public async inspect(error: ErrorSource | ErrorReport[]): Promise<void
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
25
|
+
public async inspect(error: ErrorSource | ErrorReport, reports?: ErrorReport[]): Promise<void>;
|
|
26
|
+
public async inspect(reports: ErrorReport[]): Promise<void>;
|
|
27
|
+
public async inspect(errorOrReports: ErrorSource | ErrorReport[], _reports?: ErrorReport[]): Promise<void> {
|
|
28
|
+
if (Array.isArray(errorOrReports) && errorOrReports.length === 0) {
|
|
29
29
|
UI.alert(translateWithDefault('errors.inspectEmpty', 'Nothing to inspect!'));
|
|
30
30
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
const report = Array.isArray(errorOrReports)
|
|
35
|
+
? (errorOrReports[0] as ErrorReport)
|
|
36
|
+
: this.isErrorReport(errorOrReports)
|
|
37
|
+
? errorOrReports
|
|
38
|
+
: await this.createErrorReport(errorOrReports);
|
|
39
|
+
const reports = Array.isArray(errorOrReports) ? (errorOrReports as ErrorReport[]) : (_reports ?? [report]);
|
|
40
|
+
|
|
41
|
+
UI.modal(UI.requireComponent('error-report-modal'), { report, reports });
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
public async report(error: ErrorSource, message?: string): Promise<void> {
|
|
@@ -75,7 +82,7 @@ export class ErrorsService extends Service {
|
|
|
75
82
|
{
|
|
76
83
|
label: translateWithDefault('errors.viewDetails', 'View details'),
|
|
77
84
|
dismiss: true,
|
|
78
|
-
click: () => UI.
|
|
85
|
+
click: () => UI.modal(UI.requireComponent('error-report-modal'), { report, reports: [report] }),
|
|
79
86
|
},
|
|
80
87
|
],
|
|
81
88
|
},
|
|
@@ -117,6 +124,10 @@ export class ErrorsService extends Service {
|
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
private isErrorReport(error: unknown): error is ErrorReport {
|
|
128
|
+
return isObject(error) && 'title' in error;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
private async createErrorReport(error: ErrorSource): Promise<ErrorReport> {
|
|
121
132
|
if (typeof error === 'string') {
|
|
122
133
|
return { title: error };
|
package/src/index.css
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
@source './';
|
|
7
7
|
|
|
8
8
|
@theme {
|
|
9
|
-
--color-background: oklch(1 0 0);
|
|
10
|
-
|
|
11
9
|
--color-primary: oklch(0.205 0 0);
|
|
12
10
|
--color-primary-50: color-mix(in oklab, var(--color-primary-600) 5%, transparent);
|
|
13
11
|
--color-primary-100: color-mix(in oklab, var(--color-primary-600) 15%, transparent);
|
|
@@ -20,6 +18,9 @@
|
|
|
20
18
|
--color-primary-800: color-mix(in oklab, var(--color-primary-600) 80%, black);
|
|
21
19
|
--color-primary-900: color-mix(in oklab, var(--color-primary-600) 70%, black);
|
|
22
20
|
--color-primary-950: color-mix(in oklab, var(--color-primary-600) 50%, black);
|
|
21
|
+
|
|
22
|
+
--color-background: oklch(1 0 0);
|
|
23
|
+
--color-links: var(--color-primary);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
.clickable {
|