@aerogel/core 0.1.1-next.785e2c42ed379bc0f268c637d1023a51f5832d80 → 0.1.1-next.794da2a581990f8be4a2f33880a77e15fa1e40a7
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.d.ts +237 -265
- package/dist/aerogel-core.js +1358 -1324
- package/dist/aerogel-core.js.map +1 -1
- package/package.json +4 -2
- package/src/components/AppOverlays.vue +3 -2
- package/src/components/contracts/AlertModal.ts +1 -1
- package/src/components/contracts/Button.ts +1 -1
- package/src/components/contracts/ConfirmModal.ts +5 -2
- package/src/components/contracts/Modal.ts +8 -3
- package/src/components/contracts/PromptModal.ts +6 -2
- package/src/components/contracts/Toast.ts +1 -1
- package/src/components/headless/HeadlessInputInput.vue +13 -5
- package/src/components/headless/HeadlessModal.vue +6 -34
- package/src/components/headless/HeadlessModalContent.vue +5 -12
- package/src/components/index.ts +0 -1
- package/src/components/ui/AdvancedOptions.vue +4 -13
- package/src/components/ui/Button.vue +1 -0
- package/src/components/ui/ConfirmModal.vue +7 -2
- package/src/components/ui/Details.vue +23 -0
- package/src/components/ui/Form.vue +1 -1
- package/src/components/ui/Input.vue +12 -4
- package/src/components/ui/LoadingModal.vue +1 -2
- package/src/components/ui/Modal.vue +53 -33
- package/src/components/ui/ProgressBar.vue +16 -2
- package/src/components/ui/PromptModal.vue +7 -2
- package/src/components/ui/Toast.vue +1 -0
- package/src/components/ui/index.ts +1 -1
- package/src/directives/index.ts +7 -7
- package/src/errors/Errors.ts +4 -0
- package/src/forms/FormController.ts +4 -0
- package/src/index.css +9 -0
- package/src/ui/UI.state.ts +0 -11
- package/src/ui/UI.ts +42 -125
- package/src/ui/index.ts +1 -0
- package/src/ui/modals.ts +36 -0
- package/src/utils/composition/reactiveSet.test.ts +32 -0
- package/src/utils/composition/reactiveSet.ts +53 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/time.ts +2 -0
- package/src/components/AppModals.vue +0 -14
- package/src/components/ui/ModalContext.vue +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerogel/core",
|
|
3
|
-
"version": "0.1.1-next.
|
|
3
|
+
"version": "0.1.1-next.794da2a581990f8be4a2f33880a77e15fa1e40a7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
"vue": "^3.5.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@noeldemartin/utils": "
|
|
32
|
+
"@noeldemartin/utils": "0.7.1-next.5f7bc66cad4aaa2dce4a2314e99d562ff3ab993b",
|
|
33
|
+
"@noeldemartin/vue-modals": "0.0.0-next.124c6a1c5e8a2cef4ec43d6a01de0fc450f155b1",
|
|
33
34
|
"class-variance-authority": "^0.7.1",
|
|
34
35
|
"clsx": "^2.1.1",
|
|
35
36
|
"dompurify": "^3.2.4",
|
|
36
37
|
"eruda": "^3.4.1",
|
|
38
|
+
"eruda-indexeddb": "^0.1.1",
|
|
37
39
|
"marked": "^15.0.7",
|
|
38
40
|
"pinia": "^2.1.6",
|
|
39
41
|
"reka-ui": "^2.2.0",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<ModalsPortal nested />
|
|
3
3
|
<AppToasts />
|
|
4
4
|
</template>
|
|
5
5
|
|
|
6
6
|
<script setup lang="ts">
|
|
7
|
-
import
|
|
7
|
+
import { ModalsPortal } from '@aerogel/core/ui/modals';
|
|
8
|
+
|
|
8
9
|
import AppToasts from './AppToasts.vue';
|
|
9
10
|
</script>
|
|
@@ -8,7 +8,7 @@ export interface AlertModalProps {
|
|
|
8
8
|
message: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export interface AlertModalExpose extends ModalExpose
|
|
11
|
+
export interface AlertModalExpose extends ModalExpose {}
|
|
12
12
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
14
14
|
export function useAlertModal(props: AlertModalProps) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PrimitiveProps } from 'reka-ui';
|
|
2
2
|
import type { HTMLAttributes } from 'vue';
|
|
3
3
|
|
|
4
|
-
export type ButtonVariant = 'default' | 'secondary' | 'danger' | 'ghost' | 'outline' | 'link';
|
|
4
|
+
export type ButtonVariant = 'default' | 'secondary' | 'danger' | 'warning' | 'ghost' | 'outline' | 'link';
|
|
5
5
|
export type ButtonSize = 'default' | 'small' | 'large' | 'icon';
|
|
6
6
|
export interface ButtonProps extends PrimitiveProps {
|
|
7
7
|
class?: HTMLAttributes['class'];
|
|
@@ -4,10 +4,11 @@ import { translateWithDefault } from '@aerogel/core/lang';
|
|
|
4
4
|
import { useForm } from '@aerogel/core/utils/composition/forms';
|
|
5
5
|
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
6
6
|
import type { FormFieldDefinition } from '@aerogel/core/forms/FormController';
|
|
7
|
-
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
8
7
|
import type { Nullable } from '@noeldemartin/utils';
|
|
8
|
+
import type { ModalEmits, ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
9
9
|
|
|
10
10
|
export type ConfirmModalCheckboxes = Record<string, { label: string; default?: boolean; required?: boolean }>;
|
|
11
|
+
export type ConfirmModalResult = boolean | [boolean, Record<string, Nullable<boolean>>];
|
|
11
12
|
|
|
12
13
|
export interface ConfirmModalProps {
|
|
13
14
|
title?: string;
|
|
@@ -21,7 +22,9 @@ export interface ConfirmModalProps {
|
|
|
21
22
|
required?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export interface ConfirmModalExpose extends ModalExpose
|
|
25
|
+
export interface ConfirmModalExpose extends ModalExpose {}
|
|
26
|
+
|
|
27
|
+
export interface ConfirmModalEmits extends ModalEmits<ConfirmModalResult> {}
|
|
25
28
|
|
|
26
29
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
27
30
|
export function useConfirmModal(props: ConfirmModalProps) {
|
|
@@ -5,17 +5,22 @@ export type ModalContentInstance = Nullable<InstanceType<typeof DialogContent>>;
|
|
|
5
5
|
|
|
6
6
|
export interface ModalProps {
|
|
7
7
|
persistent?: boolean;
|
|
8
|
+
fullscreen?: boolean;
|
|
9
|
+
fullscreenMobile?: boolean;
|
|
8
10
|
title?: string;
|
|
9
11
|
titleHidden?: boolean;
|
|
10
12
|
description?: string;
|
|
11
13
|
descriptionHidden?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
export interface ModalSlots<Result =
|
|
16
|
+
export interface ModalSlots<Result = never> {
|
|
15
17
|
default(props: { close(result?: Result): Promise<void> }): unknown;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export interface ModalExpose
|
|
19
|
-
close(result?: Result): Promise<void>;
|
|
20
|
+
export interface ModalExpose {
|
|
20
21
|
$content: ModalContentInstance;
|
|
21
22
|
}
|
|
23
|
+
|
|
24
|
+
export interface ModalEmits<Result = never> {
|
|
25
|
+
(event: 'close', payload: Result): void;
|
|
26
|
+
}
|
|
@@ -4,7 +4,9 @@ import { useForm } from '@aerogel/core/utils/composition/forms';
|
|
|
4
4
|
import { requiredStringInput } from '@aerogel/core/forms/utils';
|
|
5
5
|
import { translateWithDefault } from '@aerogel/core/lang';
|
|
6
6
|
import type { ButtonVariant } from '@aerogel/core/components/contracts/Button';
|
|
7
|
-
import type { ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
7
|
+
import type { ModalEmits, ModalExpose } from '@aerogel/core/components/contracts/Modal';
|
|
8
|
+
|
|
9
|
+
export type PromptModalResult = string;
|
|
8
10
|
|
|
9
11
|
export interface PromptModalProps {
|
|
10
12
|
title?: string;
|
|
@@ -18,7 +20,9 @@ export interface PromptModalProps {
|
|
|
18
20
|
cancelVariant?: ButtonVariant;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export interface PromptModalExpose extends ModalExpose
|
|
23
|
+
export interface PromptModalExpose extends ModalExpose {}
|
|
24
|
+
|
|
25
|
+
export interface PromptModalEmits extends ModalEmits<PromptModalResult> {}
|
|
22
26
|
|
|
23
27
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
24
28
|
export function usePromptModal(props: PromptModalProps) {
|
|
@@ -19,6 +19,7 @@ import { computed, inject, useTemplateRef, watchEffect } from 'vue';
|
|
|
19
19
|
|
|
20
20
|
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
21
21
|
import { onFormFocus } from '@aerogel/core/utils/composition/forms';
|
|
22
|
+
import { LOCAL_TIMEZONE_OFFSET } from '@aerogel/core/utils';
|
|
22
23
|
import type FormController from '@aerogel/core/forms/FormController';
|
|
23
24
|
import type { FormFieldValue } from '@aerogel/core/forms/FormController';
|
|
24
25
|
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
@@ -39,7 +40,7 @@ const renderedType = computed(() => {
|
|
|
39
40
|
return ['text', 'email', 'number', 'tel', 'url'].includes(fieldType) ? fieldType : 'text';
|
|
40
41
|
});
|
|
41
42
|
const checked = computed(() => {
|
|
42
|
-
if (
|
|
43
|
+
if (renderedType.value !== 'checkbox') {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -59,11 +60,15 @@ function getValue(): FormFieldValue | null {
|
|
|
59
60
|
return null;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
switch (
|
|
63
|
+
switch (renderedType.value) {
|
|
63
64
|
case 'checkbox':
|
|
64
65
|
return $input.value.checked;
|
|
65
66
|
case 'date':
|
|
66
|
-
|
|
67
|
+
case 'time':
|
|
68
|
+
case 'datetime-local':
|
|
69
|
+
return new Date(Math.round($input.value.valueAsNumber / 60000) * 60000 + LOCAL_TIMEZONE_OFFSET);
|
|
70
|
+
case 'number':
|
|
71
|
+
return $input.value.valueAsNumber;
|
|
67
72
|
default:
|
|
68
73
|
return $input.value.value;
|
|
69
74
|
}
|
|
@@ -75,8 +80,11 @@ watchEffect(() => {
|
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
83
|
+
if (['date', 'time', 'datetime-local'].includes(renderedType.value) && value.value instanceof Date) {
|
|
84
|
+
const roundedValue = Math.round(value.value.getTime() / 60000) * 60000;
|
|
85
|
+
|
|
86
|
+
$input.value.valueAsNumber = roundedValue - LOCAL_TIMEZONE_OFFSET;
|
|
87
|
+
input.update(new Date(roundedValue));
|
|
80
88
|
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
@@ -1,57 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<DialogRoot :ref="forwardRef" open @update:open="persistent || close()">
|
|
2
|
+
<DialogRoot :ref="forwardRef" open @update:open="persistent || $event || close()">
|
|
3
3
|
<DialogPortal>
|
|
4
|
-
<slot :close
|
|
4
|
+
<slot :close />
|
|
5
5
|
</DialogPortal>
|
|
6
6
|
</DialogRoot>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts" generic="T = void">
|
|
10
|
-
import { provide, ref } from 'vue';
|
|
11
10
|
import { DialogPortal, DialogRoot, useForwardExpose } from 'reka-ui';
|
|
11
|
+
import { provide, ref } from 'vue';
|
|
12
12
|
import type { DialogContent } from 'reka-ui';
|
|
13
13
|
import type { Nullable } from '@noeldemartin/utils';
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import { useEvent } from '@aerogel/core/utils/composition/events';
|
|
17
|
-
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
15
|
+
import { useModal } from '@aerogel/core/ui/modals';
|
|
18
16
|
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
19
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
20
17
|
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
21
18
|
|
|
22
19
|
const $content = ref<Nullable<InstanceType<typeof DialogContent>>>(null);
|
|
23
|
-
const {
|
|
24
|
-
'modal',
|
|
25
|
-
'could not obtain modal reference from <HeadlessModal>, ' +
|
|
26
|
-
'did you render this component manually? Show it using $ui.modal() instead',
|
|
27
|
-
);
|
|
20
|
+
const { close } = useModal<T>();
|
|
28
21
|
|
|
29
22
|
defineProps<ModalProps>();
|
|
30
23
|
defineSlots<ModalSlots<T>>();
|
|
31
|
-
defineExpose<AcceptRefs<ModalExpose
|
|
24
|
+
defineExpose<AcceptRefs<ModalExpose>>({ $content });
|
|
32
25
|
|
|
33
26
|
const { forwardRef } = useForwardExpose();
|
|
34
|
-
const closed = ref(false);
|
|
35
27
|
|
|
36
28
|
provide('$modalContentRef', $content);
|
|
37
|
-
|
|
38
|
-
useEvent('close-modal', async ({ id, result }) => {
|
|
39
|
-
if (id !== modal.id) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await close(result);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
async function close(result?: unknown) {
|
|
47
|
-
if (closed.value) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await Events.emit('modal-will-close', { modal, result });
|
|
52
|
-
|
|
53
|
-
closed.value = true;
|
|
54
|
-
|
|
55
|
-
await Events.emit('modal-has-closed', { modal, result });
|
|
56
|
-
}
|
|
57
29
|
</script>
|
|
@@ -2,29 +2,22 @@
|
|
|
2
2
|
<DialogContent ref="$contentRef">
|
|
3
3
|
<slot />
|
|
4
4
|
|
|
5
|
-
<
|
|
5
|
+
<ModalComponent :is="child" v-if="child" />
|
|
6
6
|
</DialogContent>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
import {
|
|
10
|
+
import { useTemplateRef, watchEffect } from 'vue';
|
|
11
11
|
import { DialogContent } from 'reka-ui';
|
|
12
12
|
import type { Ref } from 'vue';
|
|
13
13
|
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import { injectOrFail, injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
17
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
14
|
+
import { ModalComponent, useModal } from '@aerogel/core/ui/modals';
|
|
15
|
+
import { injectOrFail } from '@aerogel/core/utils/vue';
|
|
18
16
|
import type { ModalContentInstance } from '@aerogel/core/components/contracts/Modal';
|
|
19
17
|
|
|
20
|
-
const {
|
|
21
|
-
'modal',
|
|
22
|
-
'could not obtain modal reference from <HeadlessModalContent>, ' +
|
|
23
|
-
'did you render this component manually? Show it using $ui.modal() instead',
|
|
24
|
-
);
|
|
18
|
+
const { child } = useModal();
|
|
25
19
|
const $modalContentRef = injectOrFail<Ref<ModalContentInstance>>('$modalContentRef');
|
|
26
20
|
const $content = useTemplateRef('$contentRef');
|
|
27
|
-
const childModal = computed(() => UI.modals[childIndex] ?? null);
|
|
28
21
|
|
|
29
22
|
watchEffect(() => ($modalContentRef.value = $content.value));
|
|
30
23
|
</script>
|
package/src/components/index.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
>
|
|
6
|
-
<IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
|
|
7
|
-
<span>{{ $td('ui.advancedOptions', 'Advanced options') }}</span>
|
|
8
|
-
</summary>
|
|
9
|
-
|
|
10
|
-
<div class="pt-2 pl-4">
|
|
11
|
-
<slot />
|
|
12
|
-
</div>
|
|
13
|
-
</details>
|
|
2
|
+
<Details :label="$td('ui.advancedOptions', 'Advanced options')">
|
|
3
|
+
<slot />
|
|
4
|
+
</Details>
|
|
14
5
|
</template>
|
|
15
6
|
|
|
16
7
|
<script setup lang="ts">
|
|
17
|
-
import
|
|
8
|
+
import Details from './Details.vue';
|
|
18
9
|
</script>
|
|
@@ -25,6 +25,7 @@ const renderedClasses = computed(() => variantClasses<Variants<Pick<ButtonProps,
|
|
|
25
25
|
default: 'bg-primary-600 text-white focus-visible:outline-primary-600',
|
|
26
26
|
secondary: 'bg-background text-gray-900 ring-gray-300',
|
|
27
27
|
danger: 'bg-red-600 text-white focus-visible:outline-red-600',
|
|
28
|
+
warning: 'bg-yellow-600 text-white focus-visible:outline-yellow-600',
|
|
28
29
|
ghost: 'bg-transparent',
|
|
29
30
|
outline: 'bg-transparent text-primary-600 ring-primary-600',
|
|
30
31
|
link: 'text-links',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- @vue-generic {import('@aerogel/core/
|
|
2
|
+
<!-- @vue-generic {import('@aerogel/core/components/contracts/ConfirmModal').ConfirmModalResult} -->
|
|
3
3
|
<Modal
|
|
4
4
|
v-slot="{ close }"
|
|
5
5
|
:title="renderedTitle"
|
|
@@ -41,10 +41,15 @@ import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
|
41
41
|
import Button from '@aerogel/core/components/ui/Button.vue';
|
|
42
42
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
43
43
|
import { useConfirmModal } from '@aerogel/core/components/contracts/ConfirmModal';
|
|
44
|
-
import type {
|
|
44
|
+
import type {
|
|
45
|
+
ConfirmModalEmits,
|
|
46
|
+
ConfirmModalExpose,
|
|
47
|
+
ConfirmModalProps,
|
|
48
|
+
} from '@aerogel/core/components/contracts/ConfirmModal';
|
|
45
49
|
|
|
46
50
|
const { cancelVariant = 'secondary', ...props } = defineProps<ConfirmModalProps>();
|
|
47
51
|
const { form, renderedTitle, titleHidden, renderedAcceptText, renderedCancelText } = useConfirmModal(props);
|
|
48
52
|
|
|
53
|
+
defineEmits<ConfirmModalEmits>();
|
|
49
54
|
defineExpose<ConfirmModalExpose>();
|
|
50
55
|
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<details class="group">
|
|
3
|
+
<summary
|
|
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
|
+
>
|
|
6
|
+
<IconCheveronRight class="size-6 transition-transform group-open:rotate-90" />
|
|
7
|
+
<span>{{ label }}</span>
|
|
8
|
+
</summary>
|
|
9
|
+
|
|
10
|
+
<div :class="renderedContentClasses">
|
|
11
|
+
<slot />
|
|
12
|
+
</div>
|
|
13
|
+
</details>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { classes } from '@aerogel/core/utils';
|
|
18
|
+
import { type HTMLAttributes, computed } from 'vue';
|
|
19
|
+
import IconCheveronRight from '~icons/zondicons/cheveron-right';
|
|
20
|
+
|
|
21
|
+
const { label, contentClass } = defineProps<{ label: string; contentClass?: HTMLAttributes['class'] }>();
|
|
22
|
+
const renderedContentClasses = computed(() => classes('pt-2 pl-4', contentClass));
|
|
23
|
+
</script>
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
<IconExclamationSolid class="size-5 text-red-500" />
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
|
-
<HeadlessInputDescription class="
|
|
17
|
-
<HeadlessInputError class="
|
|
16
|
+
<HeadlessInputDescription :class="renderedDescriptionClasses" />
|
|
17
|
+
<HeadlessInputError :class="renderedErrorClasses" />
|
|
18
18
|
</HeadlessInput>
|
|
19
19
|
</template>
|
|
20
20
|
|
|
@@ -35,13 +35,21 @@ import type { InputEmits, InputProps } from '@aerogel/core/components/contracts/
|
|
|
35
35
|
|
|
36
36
|
defineOptions({ inheritAttrs: false });
|
|
37
37
|
defineEmits<InputEmits>();
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
const { label, inputClass, wrapperClass, descriptionClass, errorClass, ...props } = defineProps<
|
|
40
|
+
InputProps & {
|
|
41
|
+
inputClass?: HTMLAttributes['class'];
|
|
42
|
+
wrapperClass?: HTMLAttributes['class'];
|
|
43
|
+
descriptionClass?: HTMLAttributes['class'];
|
|
44
|
+
errorClass?: HTMLAttributes['class'];
|
|
45
|
+
}
|
|
40
46
|
>();
|
|
41
47
|
const $input = useTemplateRef('$inputRef');
|
|
42
48
|
const [inputAttrs, rootClasses] = useInputAttrs();
|
|
43
49
|
const renderedWrapperClasses = computed(() =>
|
|
44
50
|
classes('relative rounded-md shadow-2xs', { 'mt-1': label }, wrapperClass));
|
|
51
|
+
const renderedDescriptionClasses = computed(() => classes('mt-2 text-sm text-gray-600', descriptionClass));
|
|
52
|
+
const renderedErrorClasses = computed(() => classes('mt-2 text-sm text-red-600', errorClass));
|
|
45
53
|
const renderedInputClasses = computed(() =>
|
|
46
54
|
classes(
|
|
47
55
|
// eslint-disable-next-line vue/max-len
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Modal
|
|
3
3
|
persistent
|
|
4
|
-
class="flex"
|
|
5
4
|
wrapper-class="w-auto"
|
|
6
5
|
:title="renderedTitle"
|
|
7
6
|
:title-hidden
|
|
8
|
-
:class="{ 'flex-col-reverse': showProgress, 'items-center justify-center gap-2': !showProgress }"
|
|
7
|
+
:class="{ 'flex-col-reverse': showProgress, 'flex-row items-center justify-center gap-2': !showProgress }"
|
|
9
8
|
>
|
|
10
9
|
<ProgressBar
|
|
11
10
|
v-if="showProgress"
|
|
@@ -7,14 +7,20 @@
|
|
|
7
7
|
:persistent
|
|
8
8
|
>
|
|
9
9
|
<HeadlessModalOverlay
|
|
10
|
-
class="fixed inset-0
|
|
10
|
+
class="fixed inset-0 transition-opacity duration-300 will-change-[opacity]"
|
|
11
11
|
:class="{
|
|
12
|
-
'
|
|
13
|
-
'
|
|
12
|
+
'animate-[fade-in_var(--tw-duration)_ease-in-out]': !hasRenderedModals,
|
|
13
|
+
'bg-black/30': firstVisibleModal?.id === id || (!firstVisibleModal && modals[0]?.id === id),
|
|
14
|
+
'opacity-0': !firstVisibleModal,
|
|
15
|
+
hidden: renderFullscreen,
|
|
14
16
|
}"
|
|
15
17
|
/>
|
|
16
18
|
<HeadlessModalContent v-bind="contentProps" :class="renderedWrapperClass">
|
|
17
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
v-if="!persistent && !closeHidden"
|
|
21
|
+
class="absolute top-0 right-0 pt-3.5 pr-2.5"
|
|
22
|
+
:class="{ 'hidden sm:block': !renderFullscreen }"
|
|
23
|
+
>
|
|
18
24
|
<button
|
|
19
25
|
type="button"
|
|
20
26
|
class="clickable z-10 rounded-full p-2.5 text-gray-400 hover:text-gray-500"
|
|
@@ -52,14 +58,13 @@
|
|
|
52
58
|
</HeadlessModal>
|
|
53
59
|
</template>
|
|
54
60
|
|
|
55
|
-
<script
|
|
61
|
+
<script lang="ts">
|
|
56
62
|
import IconClose from '~icons/zondicons/close';
|
|
57
63
|
|
|
58
|
-
import { after } from '@noeldemartin/utils';
|
|
59
|
-
import { computed } from 'vue';
|
|
60
64
|
import { useForwardExpose } from 'reka-ui';
|
|
65
|
+
import { computed, onMounted } from 'vue';
|
|
61
66
|
import type { ComponentPublicInstance, HTMLAttributes, Ref } from 'vue';
|
|
62
|
-
import type
|
|
67
|
+
import { type Nullable, after } from '@noeldemartin/utils';
|
|
63
68
|
|
|
64
69
|
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
65
70
|
import HeadlessModal from '@aerogel/core/components/headless/HeadlessModal.vue';
|
|
@@ -69,13 +74,17 @@ import HeadlessModalOverlay from '@aerogel/core/components/headless/HeadlessModa
|
|
|
69
74
|
import HeadlessModalTitle from '@aerogel/core/components/headless/HeadlessModalTitle.vue';
|
|
70
75
|
import UI from '@aerogel/core/ui/UI';
|
|
71
76
|
import { classes } from '@aerogel/core/utils/classes';
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
77
|
+
import { reactiveSet } from '@aerogel/core/utils';
|
|
78
|
+
import { injectModal, modals, useModal } from '@aerogel/core/ui/modals';
|
|
79
|
+
import type { ModalController } from '@aerogel/core/ui/modals';
|
|
74
80
|
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
75
81
|
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
76
|
-
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
const renderedModals = reactiveSet<ModalController>();
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<script setup lang="ts" generic="T = void">
|
|
87
|
+
type HeadlessModalInstance = ComponentPublicInstance & ModalExpose;
|
|
79
88
|
|
|
80
89
|
const {
|
|
81
90
|
class: contentClass = '',
|
|
@@ -85,6 +94,8 @@ const {
|
|
|
85
94
|
description,
|
|
86
95
|
persistent,
|
|
87
96
|
closeHidden,
|
|
97
|
+
fullscreen,
|
|
98
|
+
fullscreenMobile,
|
|
88
99
|
...props
|
|
89
100
|
} = defineProps<
|
|
90
101
|
ModalProps & {
|
|
@@ -95,37 +106,46 @@ const {
|
|
|
95
106
|
>();
|
|
96
107
|
|
|
97
108
|
defineSlots<ModalSlots<T>>();
|
|
98
|
-
defineExpose<AcceptRefs<ModalExpose
|
|
99
|
-
close: async (result) => $modal.value?.close(result),
|
|
109
|
+
defineExpose<AcceptRefs<ModalExpose>>({
|
|
100
110
|
$content: computed(() => $modal.value?.$content),
|
|
101
111
|
});
|
|
102
112
|
|
|
103
113
|
const { forwardRef, currentRef } = useForwardExpose<HeadlessModalInstance>();
|
|
114
|
+
const { id, visible } = useModal();
|
|
104
115
|
const $modal = currentRef as Ref<Nullable<HeadlessModalInstance>>;
|
|
105
|
-
const
|
|
106
|
-
const inForeground = computed(
|
|
116
|
+
const modal = injectModal();
|
|
117
|
+
const inForeground = computed(
|
|
118
|
+
() => visible.value && modals.value.toReversed().find((modal) => modal.visible.value)?.id === id.value,
|
|
119
|
+
);
|
|
120
|
+
const firstVisibleModal = computed(() => modals.value.find((modal) => modal.visible.value));
|
|
121
|
+
const hasRenderedModals = computed(() => modals.value.some((modal) => renderedModals.has(modal)));
|
|
107
122
|
const contentProps = computed(() => (description ? {} : { 'aria-describedby': undefined }));
|
|
108
123
|
const renderedContentClass = computed(() =>
|
|
109
|
-
classes(
|
|
124
|
+
classes(
|
|
125
|
+
'overflow-auto px-4 pb-4 flex flex-col flex-1',
|
|
126
|
+
{ 'pt-4': !title || titleHidden, 'max-h-[90vh]': !renderFullscreen.value },
|
|
127
|
+
contentClass,
|
|
128
|
+
));
|
|
129
|
+
const renderFullscreen = computed(() => fullscreen || (fullscreenMobile && UI.mobile));
|
|
110
130
|
const renderedWrapperClass = computed(() =>
|
|
111
131
|
classes(
|
|
112
|
-
'isolate fixed
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
'isolate fixed z-50 flex flex-col overflow-hidden bg-white text-left duration-300',
|
|
133
|
+
renderFullscreen.value
|
|
134
|
+
? [
|
|
135
|
+
'inset-0 transition-[transform,translate] will-change-[transform,translate]',
|
|
136
|
+
renderedModals.has(modal.value) || 'animate-[slide-in_var(--tw-duration)_ease-in-out]',
|
|
137
|
+
inForeground.value ? 'translate-y-0' : 'translate-y-full',
|
|
138
|
+
]
|
|
139
|
+
: [
|
|
140
|
+
'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full',
|
|
141
|
+
'max-w-[calc(100%-2rem)] rounded-lg shadow-xl sm:max-w-lg',
|
|
142
|
+
'transition-[scale,opacity] will-change-[scale,opacity]',
|
|
143
|
+
renderedModals.has(modal.value) ||
|
|
144
|
+
'animate-[fade-in_var(--tw-duration)_ease-in-out,grow_var(--tw-duration)_ease-in-out]',
|
|
145
|
+
inForeground.value ? 'scale-100 opacity-100' : 'scale-50 opacity-0',
|
|
146
|
+
],
|
|
120
147
|
wrapperClass,
|
|
121
148
|
));
|
|
122
149
|
|
|
123
|
-
|
|
124
|
-
if (id !== context.modal.id) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Wait for transitions to finish
|
|
129
|
-
await after({ ms: 300 });
|
|
130
|
-
});
|
|
150
|
+
onMounted(() => after(500).then(() => renderedModals.add(modal.value)));
|
|
131
151
|
</script>
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="mt-1 h-2 w-full overflow-hidden rounded-full bg-gray-200">
|
|
2
|
+
<div class="relative mt-1 h-2 w-full overflow-hidden rounded-full bg-gray-200">
|
|
3
3
|
<div :class="filledClasses" :style="`transform:translateX(-${(1 - renderedProgress) * 100}%)`" />
|
|
4
|
+
<div
|
|
5
|
+
v-if="overflowWidthPercentage"
|
|
6
|
+
:class="overflowClasses"
|
|
7
|
+
:style="{ width: `${overflowWidthPercentage}%` }"
|
|
8
|
+
/>
|
|
4
9
|
<span class="sr-only">
|
|
5
10
|
{{
|
|
6
11
|
$td('ui.progress', '{progress}% complete', {
|
|
@@ -18,8 +23,9 @@ import { classes } from '@aerogel/core/utils/classes';
|
|
|
18
23
|
import type Job from '@aerogel/core/jobs/Job';
|
|
19
24
|
import type { Falsifiable } from '@aerogel/core/utils';
|
|
20
25
|
|
|
21
|
-
const { filledClass, progress, job } = defineProps<{
|
|
26
|
+
const { filledClass, overflowClass, progress, job } = defineProps<{
|
|
22
27
|
filledClass?: string;
|
|
28
|
+
overflowClass?: string;
|
|
23
29
|
progress?: number;
|
|
24
30
|
job?: Falsifiable<Job>;
|
|
25
31
|
}>();
|
|
@@ -28,6 +34,12 @@ let cleanup: Falsifiable<Function>;
|
|
|
28
34
|
const jobProgress = ref(0);
|
|
29
35
|
const filledClasses = computed(() =>
|
|
30
36
|
classes('size-full transition-transform duration-500 rounded-r-full ease-linear bg-primary-600', filledClass));
|
|
37
|
+
const overflowClasses = computed(() =>
|
|
38
|
+
classes(
|
|
39
|
+
'absolute inset-y-0 right-0 size-full rounded-r-full border-l-3 border-gray-200',
|
|
40
|
+
'bg-primary-900 transition-[width] duration-500 ease-linear',
|
|
41
|
+
overflowClass,
|
|
42
|
+
));
|
|
31
43
|
const renderedProgress = computed(() => {
|
|
32
44
|
if (typeof progress === 'number') {
|
|
33
45
|
return progress;
|
|
@@ -35,6 +47,8 @@ const renderedProgress = computed(() => {
|
|
|
35
47
|
|
|
36
48
|
return jobProgress.value;
|
|
37
49
|
});
|
|
50
|
+
const overflowWidthPercentage = computed(() =>
|
|
51
|
+
renderedProgress.value > 1 ? 100 * ((renderedProgress.value - 1) / renderedProgress.value) : null);
|
|
38
52
|
|
|
39
53
|
watch(
|
|
40
54
|
() => job,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- @vue-generic {import('@aerogel/core/
|
|
2
|
+
<!-- @vue-generic {import('@aerogel/core/components/contracts/PromptModal').PromptModalResult} -->
|
|
3
3
|
<Modal v-slot="{ close }" :title="renderedTitle" persistent>
|
|
4
4
|
<Form :form @submit="close(form.draft)">
|
|
5
5
|
<Markdown v-if="renderedMessage" :text="renderedMessage" />
|
|
@@ -29,10 +29,15 @@ import Form from '@aerogel/core/components/ui/Form.vue';
|
|
|
29
29
|
import Input from '@aerogel/core/components/ui/Input.vue';
|
|
30
30
|
import Modal from '@aerogel/core/components/ui/Modal.vue';
|
|
31
31
|
import { usePromptModal } from '@aerogel/core/components/contracts/PromptModal';
|
|
32
|
-
import type {
|
|
32
|
+
import type {
|
|
33
|
+
PromptModalEmits,
|
|
34
|
+
PromptModalExpose,
|
|
35
|
+
PromptModalProps,
|
|
36
|
+
} from '@aerogel/core/components/contracts/PromptModal';
|
|
33
37
|
|
|
34
38
|
const { cancelVariant = 'secondary', ...props } = defineProps<PromptModalProps>();
|
|
35
39
|
const { form, renderedTitle, renderedMessage, renderedAcceptText, renderedCancelText } = usePromptModal(props);
|
|
36
40
|
|
|
41
|
+
defineEmits<PromptModalEmits>();
|
|
37
42
|
defineExpose<PromptModalExpose>();
|
|
38
43
|
</script>
|