@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aerogel/core",
3
- "version": "0.0.0-next.9aa7c279868edbedbcee075aef52212597d803fb",
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.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
- click: () => unknown;
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 report = computed(() => props.reports[activeReportIndex.value] as ErrorReport);
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
- report.value.details?.trim() ||
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
- report,
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.openModal() instead',
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.openModal() instead',
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] cursor-pointer items-center rounded-lg py-2 pr-3 pl-1 hover:bg-gray-100 focus-visible:outline focus-visible:outline-gray-700"
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 { computedVariantClasses } from '@aerogel/core/utils/classes';
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 = computedVariantClasses<Variants<Pick<ButtonProps, 'size' | 'variant' | 'disabled'>>>(
19
+ const renderedClasses = computed(() => variantClasses<Variants<Pick<ButtonProps, 'size' | 'variant' | 'disabled'>>>(
18
20
  { baseClasses, variant, size, disabled },
19
21
  {
20
- baseClasses: 'flex items-center justify-center gap-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
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-primary-600',
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 v-slot="{ close }" :title persistent>
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 v-for="(option, key) in dropdownMenu.options" :key @select="option.click">
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 wrapper-class="p-0 sm:w-auto sm:min-w-lg sm:max-w-[80vw]">
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="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="report.description" :text="report.description" class="text-gray-600" />
44
+ <Markdown v-if="activeReport.description" :text="activeReport.description" class="text-gray-600" />
39
45
  </div>
40
- <div class="-mt-2 max-h-[80vh] overflow-auto bg-red-800/10">
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, report } = useErrorReportModal(props);
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 && dismissable" class="absolute top-0 right-0 hidden pt-3.5 pr-2.5 sm:block">
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(() => classes({ 'mt-2': title && !titleHidden }, contentClass));
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 px-4 pt-5 pb-4 text-left shadow-xl sm:max-w-lg',
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?.options?.length">
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 name="empty">
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 { computedVariantClasses } from '@aerogel/core/utils/classes';
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 = computedVariantClasses<Variants<Pick<ToastProps, 'variant'>>>(
28
- { baseClasses, variant },
29
- {
30
- baseClasses: 'flex items-center gap-2 rounded-md p-2 ring-1 shadow-lg border-gray-200',
31
- variants: {
32
- variant: {
33
- secondary: 'bg-gray-900 text-white ring-black',
34
- danger: 'bg-red-50 text-red-900 ring-red-100',
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
- defaultVariants: {
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();
@@ -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
- const reports = Array.isArray(error) ? (error as ErrorReport[]) : [await this.createErrorReport(error)];
27
-
28
- if (reports.length === 0) {
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
- UI.openModal(UI.requireComponent('error-report-modal'), { reports });
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.openModal(UI.requireComponent('error-report-modal'), { reports: [report] }),
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 {