facilmap/frontend/src/lib/components/ui/alert.vue

155 wiersze
3.8 KiB
Vue

<script lang="ts">
import { createApp, defineComponent, h, ref, type VNode, type VNodeArrayChildren } from "vue";
import Alert from "./alert.vue";
import type { ThemeColour } from "../../utils/bootstrap";
import ModalDialog from "./modal-dialog.vue";
import ValidatedField from "./validated-form/validated-field.vue";
export type AlertProps = {
title: string;
message: string;
type?: "alert" | "confirm";
variant?: ThemeColour;
show?: boolean;
okLabel?: string;
};
export interface AlertResult {
ok: boolean;
}
async function renderAlert({ getContent, onShown, ...props }: AlertProps & {
getContent?: () => string | VNode | VNodeArrayChildren;
onShown?: () => void;
}): Promise<AlertResult> {
return await new Promise<AlertResult>((resolve) => {
const el = document.createElement('div');
el.classList.add("fm-alert-container");
document.body.appendChild(el);
const app = createApp(defineComponent({
setup() {
return () => h(Alert, {
...props,
onShown: () => {
onShown?.();
},
onHide: (result) => {
resolve(result);
},
onHidden: () => {
app.unmount();
el.remove();
}
}, getContent);
}
}));
app.mount(el);
});
}
export async function showAlert(props: Omit<AlertProps, 'type' | 'show'>): Promise<void> {
await renderAlert({ ...props, type: 'alert' });
}
export async function showConfirm(props: Omit<AlertProps, 'type' | 'show'>): Promise<boolean> {
const result = await renderAlert({ ...props, type: 'confirm' });
return result.ok;
}
export async function showPrompt({ initialValue = "", validate, ...props }: Omit<AlertProps, 'type' | 'show' | 'message'> & {
initialValue?: string;
/** Validate the value. Return an empty string or undefined to indicate validity. */
validate?: (value: string) => string | undefined;
}): Promise<string | undefined> {
const value = ref(initialValue);
const submitted = ref(false);
const touched = ref(false);
const inputRef = ref<HTMLInputElement>();
const result = await renderAlert({
...props,
message: '',
type: 'confirm',
getContent: () => h(ValidatedField, {
class: ['position-relative', touched.value ? 'was-validated' : ''],
value: value.value,
validators: validate ? [validate] as any : []
}, {
default: (slotProps: any) => [
h('input', {
type: "text",
class: "form-control",
value: value.value,
onInput: (e: InputEvent) => {
value.value = (e.target as HTMLInputElement).value;
touched.value = true;
},
onBlur: () => {
touched.value = true;
},
ref: (el) => {
slotProps.inputRef(el);
inputRef.value = el as any;
}
}),
h('div', {
class: "invalid-tooltip"
}, slotProps.validationError)
]
}),
onShown: () => {
inputRef.value!.focus();
}
});
submitted.value = true;
return result.ok ? value.value : undefined;
}
</script>
<script setup lang="ts">
const props = withDefaults(defineProps<AlertProps>(), {
type: "alert",
okLabel: "OK"
});
const emit = defineEmits<{
shown: [];
hide: [result: AlertResult];
hidden: [result: AlertResult];
}>();
const result = ref<AlertResult>({
ok: false
});
const modalRef = ref<InstanceType<typeof ModalDialog>>();
const handleSubmit = () => {
result.value.ok = true;
modalRef.value!.modal.hide();
};
</script>
<template>
<ModalDialog
:title="props.title"
:isCreate="props.type === 'confirm'"
:okLabel="props.okLabel"
:okVariant="props.variant"
@shown="emit('shown')"
@hide="emit('hide', result)"
@hidden="emit('hidden', result)"
@submit="handleSubmit"
ref="modalRef"
>
<slot>{{props.message}}</slot>
</ModalDialog>
</template>
<style lang="scss">
.fm-alert-container {
position: absolute;
}
</style>