feat(lite): rework modal system (#6994)
This commit is contained in:
parent
79d48f3b56
commit
b11f11f4db
@ -14,66 +14,66 @@
|
|||||||
</UiActionButton>
|
</UiActionButton>
|
||||||
</UiFilterGroup>
|
</UiFilterGroup>
|
||||||
|
|
||||||
<UiModal
|
<UiModal v-model="isOpen">
|
||||||
v-if="isOpen"
|
<ConfirmModalLayout @submit.prevent="handleSubmit">
|
||||||
:icon="faFilter"
|
<template #default>
|
||||||
@submit.prevent="handleSubmit"
|
<div class="rows">
|
||||||
@close="handleCancel"
|
<CollectionFilterRow
|
||||||
>
|
v-for="(newFilter, index) in newFilters"
|
||||||
<div class="rows">
|
:key="newFilter.id"
|
||||||
<CollectionFilterRow
|
v-model="newFilters[index]"
|
||||||
v-for="(newFilter, index) in newFilters"
|
:available-filters="availableFilters"
|
||||||
:key="newFilter.id"
|
@remove="removeNewFilter"
|
||||||
v-model="newFilters[index]"
|
/>
|
||||||
:available-filters="availableFilters"
|
</div>
|
||||||
@remove="removeNewFilter"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="newFilters.some((filter) => filter.isAdvanced)"
|
v-if="newFilters.some((filter) => filter.isAdvanced)"
|
||||||
class="available-properties"
|
class="available-properties"
|
||||||
>
|
|
||||||
{{ $t("available-properties-for-advanced-filter") }}
|
|
||||||
<div class="properties">
|
|
||||||
<UiBadge
|
|
||||||
v-for="(filter, property) in availableFilters"
|
|
||||||
:key="property"
|
|
||||||
:icon="getFilterIcon(filter)"
|
|
||||||
>
|
>
|
||||||
{{ property }}
|
{{ $t("available-properties-for-advanced-filter") }}
|
||||||
</UiBadge>
|
<div class="properties">
|
||||||
</div>
|
<UiBadge
|
||||||
</div>
|
v-for="(filter, property) in availableFilters"
|
||||||
|
:key="property"
|
||||||
|
:icon="getFilterIcon(filter)"
|
||||||
|
>
|
||||||
|
{{ property }}
|
||||||
|
</UiBadge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<UiButton transparent @click="addNewFilter">
|
<UiButton transparent @click="addNewFilter">
|
||||||
{{ $t("add-or") }}
|
{{ $t("add-or") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
<UiButton :disabled="!isFilterValid" type="submit">
|
<UiButton :disabled="!isFilterValid" type="submit">
|
||||||
{{ $t(editedFilter ? "update" : "add") }}
|
{{ $t(editedFilter ? "update" : "add") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
<UiButton outlined @click="handleCancel">
|
<UiButton outlined @click="close">
|
||||||
{{ $t("cancel") }}
|
{{ $t("cancel") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</template>
|
</template>
|
||||||
|
</ConfirmModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Or, parse } from "complex-matcher";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import type { Filters, NewFilter } from "@/types/filter";
|
|
||||||
import { faFilter, faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import CollectionFilterRow from "@/components/CollectionFilterRow.vue";
|
import CollectionFilterRow from "@/components/CollectionFilterRow.vue";
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
||||||
import UiBadge from "@/components/ui/UiBadge.vue";
|
import UiBadge from "@/components/ui/UiBadge.vue";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiFilter from "@/components/ui/UiFilter.vue";
|
import UiFilter from "@/components/ui/UiFilter.vue";
|
||||||
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
|
||||||
import useModal from "@/composables/modal.composable";
|
import useModal from "@/composables/modal.composable";
|
||||||
import { getFilterIcon } from "@/libs/utils";
|
import { getFilterIcon } from "@/libs/utils";
|
||||||
|
import type { Filters, NewFilter } from "@/types/filter";
|
||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { Or, parse } from "complex-matcher";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
activeFilters: string[];
|
activeFilters: string[];
|
||||||
@ -85,7 +85,7 @@ const emit = defineEmits<{
|
|||||||
(event: "removeFilter", filter: string): void;
|
(event: "removeFilter", filter: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { isOpen, open, close } = useModal();
|
const { isOpen, open, close } = useModal({ onClose: () => reset() });
|
||||||
const newFilters = ref<NewFilter[]>([]);
|
const newFilters = ref<NewFilter[]>([]);
|
||||||
let newFilterId = 0;
|
let newFilterId = 0;
|
||||||
|
|
||||||
@ -156,11 +156,6 @@ const handleSubmit = () => {
|
|||||||
reset();
|
reset();
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
reset();
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
@ -190,4 +185,10 @@ const handleCancel = () => {
|
|||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -219,7 +219,6 @@ const valueInputAfter = computed(() =>
|
|||||||
.collection-filter-row {
|
.collection-filter-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem 0;
|
|
||||||
border-bottom: 1px solid var(--background-color-secondary);
|
border-bottom: 1px solid var(--background-color-secondary);
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
@ -242,4 +241,8 @@ const valueInputAfter = computed(() =>
|
|||||||
.form-widget-advanced {
|
.form-widget-advanced {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-action-button:first-of-type {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -17,56 +17,56 @@
|
|||||||
</UiActionButton>
|
</UiActionButton>
|
||||||
</UiFilterGroup>
|
</UiFilterGroup>
|
||||||
|
|
||||||
<UiModal
|
<UiModal v-model="isOpen">
|
||||||
v-if="isOpen"
|
<ConfirmModalLayout @submit.prevent="handleSubmit">
|
||||||
:icon="faSort"
|
<template #default>
|
||||||
@submit.prevent="handleSubmit"
|
<div class="form-widgets">
|
||||||
@close="handleCancel"
|
<FormWidget :label="$t('sort-by')">
|
||||||
>
|
<select v-model="newSortProperty">
|
||||||
<div class="form-widgets">
|
<option v-if="!newSortProperty"></option>
|
||||||
<FormWidget :label="$t('sort-by')">
|
<option
|
||||||
<select v-model="newSortProperty">
|
v-for="(sort, property) in availableSorts"
|
||||||
<option v-if="!newSortProperty"></option>
|
:key="property"
|
||||||
<option
|
:value="property"
|
||||||
v-for="(sort, property) in availableSorts"
|
>
|
||||||
:key="property"
|
{{ sort.label ?? property }}
|
||||||
:value="property"
|
</option>
|
||||||
>
|
</select>
|
||||||
{{ sort.label ?? property }}
|
</FormWidget>
|
||||||
</option>
|
<FormWidget>
|
||||||
</select>
|
<select v-model="newSortIsAscending">
|
||||||
</FormWidget>
|
<option :value="true">{{ $t("ascending") }}</option>
|
||||||
<FormWidget>
|
<option :value="false">{{ $t("descending") }}</option>
|
||||||
<select v-model="newSortIsAscending">
|
</select>
|
||||||
<option :value="true">{{ $t("ascending") }}</option>
|
</FormWidget>
|
||||||
<option :value="false">{{ $t("descending") }}</option>
|
</div>
|
||||||
</select>
|
</template>
|
||||||
</FormWidget>
|
|
||||||
</div>
|
<template #buttons>
|
||||||
<template #buttons>
|
<UiButton type="submit">{{ $t("add") }}</UiButton>
|
||||||
<UiButton type="submit">{{ $t("add") }}</UiButton>
|
<UiButton outlined @click="close">
|
||||||
<UiButton outlined @click="handleCancel">
|
{{ $t("cancel") }}
|
||||||
{{ $t("cancel") }}
|
</UiButton>
|
||||||
</UiButton>
|
</template>
|
||||||
</template>
|
</ConfirmModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import FormWidget from "@/components/FormWidget.vue";
|
import FormWidget from "@/components/FormWidget.vue";
|
||||||
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
import UiActionButton from "@/components/ui/UiActionButton.vue";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiFilter from "@/components/ui/UiFilter.vue";
|
import UiFilter from "@/components/ui/UiFilter.vue";
|
||||||
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
import UiFilterGroup from "@/components/ui/UiFilterGroup.vue";
|
||||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
|
||||||
import useModal from "@/composables/modal.composable";
|
import useModal from "@/composables/modal.composable";
|
||||||
import type { ActiveSorts, Sorts } from "@/types/sort";
|
import type { ActiveSorts, Sorts } from "@/types/sort";
|
||||||
import {
|
import {
|
||||||
faCaretDown,
|
faCaretDown,
|
||||||
faCaretUp,
|
faCaretUp,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSort,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ const emit = defineEmits<{
|
|||||||
(event: "removeSort", property: string): void;
|
(event: "removeSort", property: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { isOpen, open, close } = useModal();
|
const { isOpen, open, close } = useModal({ onClose: () => reset() });
|
||||||
|
|
||||||
const newSortProperty = ref();
|
const newSortProperty = ref();
|
||||||
const newSortIsAscending = ref<boolean>(true);
|
const newSortIsAscending = ref<boolean>(true);
|
||||||
@ -96,11 +96,6 @@ const handleSubmit = () => {
|
|||||||
reset();
|
reset();
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
reset();
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
|
@ -1,45 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiModal
|
<UiModal v-model="isSslModalOpen" color="error">
|
||||||
v-if="isSslModalOpen"
|
<ConfirmModalLayout :icon="faServer">
|
||||||
:icon="faServer"
|
<template #title>{{ $t("unreachable-hosts") }}</template>
|
||||||
color="error"
|
|
||||||
@close="clearUnreachableHostsUrls"
|
<template #default>
|
||||||
>
|
<div class="description">
|
||||||
<template #title>{{ $t("unreachable-hosts") }}</template>
|
<p>{{ $t("following-hosts-unreachable") }}</p>
|
||||||
<div class="description">
|
<p>{{ $t("allow-self-signed-ssl") }}</p>
|
||||||
<p>{{ $t("following-hosts-unreachable") }}</p>
|
<ul>
|
||||||
<p>{{ $t("allow-self-signed-ssl") }}</p>
|
<li v-for="url in unreachableHostsUrls" :key="url">
|
||||||
<ul>
|
<a :href="url" class="link" rel="noopener" target="_blank">{{
|
||||||
<li v-for="url in unreachableHostsUrls" :key="url">
|
url
|
||||||
<a :href="url" class="link" rel="noopener" target="_blank">{{
|
}}</a>
|
||||||
url
|
</li>
|
||||||
}}</a>
|
</ul>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
</template>
|
||||||
</div>
|
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<UiButton color="success" @click="reload">
|
<UiButton color="success" @click="reload">
|
||||||
{{ $t("unreachable-hosts-reload-page") }}
|
{{ $t("unreachable-hosts-reload-page") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
<UiButton @click="clearUnreachableHostsUrls">{{ $t("cancel") }}</UiButton>
|
<UiButton @click="closeSslModal">{{ $t("cancel") }}</UiButton>
|
||||||
</template>
|
</template>
|
||||||
|
</ConfirmModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
|
import useModal from "@/composables/modal.composable";
|
||||||
import { useHostCollection } from "@/stores/xen-api/host.store";
|
import { useHostCollection } from "@/stores/xen-api/host.store";
|
||||||
import { faServer } from "@fortawesome/free-solid-svg-icons";
|
import { faServer } from "@fortawesome/free-solid-svg-icons";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
|
||||||
import { computed, ref, watch } from "vue";
|
|
||||||
import { difference } from "lodash-es";
|
import { difference } from "lodash-es";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
|
||||||
const { records: hosts } = useHostCollection();
|
const { records: hosts } = useHostCollection();
|
||||||
const unreachableHostsUrls = ref<Set<string>>(new Set());
|
const unreachableHostsUrls = ref<Set<string>>(new Set());
|
||||||
const clearUnreachableHostsUrls = () => unreachableHostsUrls.value.clear();
|
|
||||||
const isSslModalOpen = computed(() => unreachableHostsUrls.value.size > 0);
|
|
||||||
const reload = () => window.location.reload();
|
const reload = () => window.location.reload();
|
||||||
|
|
||||||
|
const { isOpen: isSslModalOpen, close: closeSslModal } = useModal({
|
||||||
|
onClose: () => unreachableHostsUrls.value.clear(),
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unreachableHostsUrls.value.size,
|
||||||
|
(size) => {
|
||||||
|
isSslModalOpen.value = size > 0;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
watch(hosts, (nextHosts, previousHosts) => {
|
watch(hosts, (nextHosts, previousHosts) => {
|
||||||
difference(nextHosts, previousHosts).forEach((host) => {
|
difference(nextHosts, previousHosts).forEach((host) => {
|
||||||
const url = new URL("http://localhost");
|
const url = new URL("http://localhost");
|
||||||
@ -53,7 +66,11 @@ watch(hosts, (nextHosts, previousHosts) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
.description p {
|
.description {
|
||||||
margin: 1rem 0;
|
text-align: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -58,7 +58,7 @@ const getDefaultOpenedDirectories = (): Set<string> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openedDirectories = new Set<string>();
|
const openedDirectories = new Set<string>();
|
||||||
const parts = currentRoute.path.split("/");
|
const parts = currentRoute.path.split("/").slice(2);
|
||||||
let currentPath = "";
|
let currentPath = "";
|
||||||
|
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiModal v-if="isRawValueModalOpen" @close="closeRawValueModal">
|
<UiModal v-model="isRawValueModalOpen">
|
||||||
<CodeHighlight :code="rawValueModalPayload" />
|
<BasicModalLayout>
|
||||||
|
<CodeHighlight :code="rawValueModalPayload" />
|
||||||
|
</BasicModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
<StoryParamsTable>
|
<StoryParamsTable>
|
||||||
<thead>
|
<thead>
|
||||||
@ -99,7 +101,8 @@ import CodeHighlight from "@/components/CodeHighlight.vue";
|
|||||||
import StoryParamsTable from "@/components/component-story/StoryParamsTable.vue";
|
import StoryParamsTable from "@/components/component-story/StoryParamsTable.vue";
|
||||||
import StoryWidget from "@/components/component-story/StoryWidget.vue";
|
import StoryWidget from "@/components/component-story/StoryWidget.vue";
|
||||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
import BasicModalLayout from "@/components/ui/modals/layouts/BasicModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
import useModal from "@/composables/modal.composable";
|
import useModal from "@/composables/modal.composable";
|
||||||
import useSortedCollection from "@/composables/sorted-collection.composable";
|
import useSortedCollection from "@/composables/sorted-collection.composable";
|
||||||
import { vTooltip } from "@/directives/tooltip.directive";
|
import { vTooltip } from "@/directives/tooltip.directive";
|
||||||
@ -130,7 +133,6 @@ const model = useVModel(props, "modelValue", emit);
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
open: openRawValueModal,
|
open: openRawValueModal,
|
||||||
close: closeRawValueModal,
|
|
||||||
isOpen: isRawValueModalOpen,
|
isOpen: isRawValueModalOpen,
|
||||||
payload: rawValueModalPayload,
|
payload: rawValueModalPayload,
|
||||||
} = useModal<string>();
|
} = useModal<string>();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
v-if="label !== undefined || learnMoreUrl !== undefined"
|
v-if="label !== undefined || learnMoreUrl !== undefined"
|
||||||
class="label-container"
|
class="label-container"
|
||||||
>
|
>
|
||||||
<label :for="id" class="label">
|
<label :class="{ light }" :for="id" class="label">
|
||||||
<UiIcon :icon="icon" />
|
<UiIcon :icon="icon" />
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
@ -58,6 +58,7 @@ const props = withDefaults(
|
|||||||
error?: string;
|
error?: string;
|
||||||
help?: string;
|
help?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
light?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{ disabled: undefined }
|
{ disabled: undefined }
|
||||||
);
|
);
|
||||||
@ -95,14 +96,24 @@ useContext(DisabledContext, () => props.disabled);
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-blue-scale-100);
|
|
||||||
font-size: 1.4rem;
|
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
|
||||||
|
&.light {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: var(--color-blue-scale-300);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.light) {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-blue-scale-100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container {
|
.messages-container {
|
||||||
|
@ -1,20 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiModal
|
<UiModal
|
||||||
@submit.prevent="saveJson"
|
v-model="isCodeModalOpen"
|
||||||
:color="isJsonValid ? 'success' : 'error'"
|
:color="isJsonValid ? 'success' : 'error'"
|
||||||
v-if="isCodeModalOpen"
|
closable
|
||||||
:icon="faCode"
|
|
||||||
@close="closeCodeModal"
|
|
||||||
>
|
>
|
||||||
<FormTextarea class="modal-textarea" v-model="editedJson" />
|
<FormModalLayout @submit.prevent="saveJson" :icon="faCode">
|
||||||
<template #buttons>
|
<template #default>
|
||||||
<UiButton transparent @click="formatJson">{{ $t("reformat") }}</UiButton>
|
<FormTextarea class="modal-textarea" v-model="editedJson" />
|
||||||
<UiButton outlined @click="closeCodeModal">{{ $t("cancel") }}</UiButton>
|
</template>
|
||||||
<UiButton :disabled="!isJsonValid" type="submit"
|
|
||||||
>{{ $t("save") }}
|
<template #buttons>
|
||||||
</UiButton>
|
<UiButton transparent @click="formatJson">
|
||||||
</template>
|
{{ $t("reformat") }}
|
||||||
|
</UiButton>
|
||||||
|
<UiButton outlined @click="closeCodeModal">
|
||||||
|
{{ $t("cancel") }}
|
||||||
|
</UiButton>
|
||||||
|
<UiButton :disabled="!isJsonValid" type="submit">
|
||||||
|
{{ $t("save") }}
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
</FormModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
|
|
||||||
<FormInput
|
<FormInput
|
||||||
@click="openCodeModal"
|
@click="openCodeModal"
|
||||||
:model-value="jsonValue"
|
:model-value="jsonValue"
|
||||||
@ -26,8 +34,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import FormInput from "@/components/form/FormInput.vue";
|
import FormInput from "@/components/form/FormInput.vue";
|
||||||
import FormTextarea from "@/components/form/FormTextarea.vue";
|
import FormTextarea from "@/components/form/FormTextarea.vue";
|
||||||
|
import FormModalLayout from "@/components/ui/modals/layouts/FormModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
|
||||||
import useModal from "@/composables/modal.composable";
|
import useModal from "@/composables/modal.composable";
|
||||||
import { faCode } from "@fortawesome/free-solid-svg-icons";
|
import { faCode } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useVModel, whenever } from "@vueuse/core";
|
import { useVModel, whenever } from "@vueuse/core";
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<form
|
|
||||||
:class="className"
|
|
||||||
class="ui-modal"
|
|
||||||
v-bind="$attrs"
|
|
||||||
@click.self="emit('close')"
|
|
||||||
>
|
|
||||||
<div class="container">
|
|
||||||
<span v-if="onClose" class="close-icon" @click="emit('close')">
|
|
||||||
<UiIcon :icon="faXmark" />
|
|
||||||
</span>
|
|
||||||
<div v-if="icon || $slots.icon" class="modal-icon">
|
|
||||||
<slot name="icon">
|
|
||||||
<UiIcon :icon="icon" />
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
<UiTitle v-if="$slots.title" type="h4">
|
|
||||||
<slot name="title" />
|
|
||||||
</UiTitle>
|
|
||||||
<div v-if="$slots.subtitle" class="subtitle">
|
|
||||||
<slot name="subtitle" />
|
|
||||||
</div>
|
|
||||||
<div v-if="$slots.default" class="content">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<UiButtonGroup :color="color">
|
|
||||||
<slot name="buttons" />
|
|
||||||
</UiButtonGroup>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
|
||||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
|
||||||
import UiTitle from "@/components/ui/UiTitle.vue";
|
|
||||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
|
||||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
icon?: IconDefinition;
|
|
||||||
color?: "info" | "warning" | "error" | "success";
|
|
||||||
onClose?: () => void;
|
|
||||||
}>(),
|
|
||||||
{ color: "info" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "close"): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { escape } = useMagicKeys();
|
|
||||||
whenever(escape, () => emit("close"));
|
|
||||||
|
|
||||||
const className = computed(() => {
|
|
||||||
return [`color-${props.color}`, { "has-icon": props.icon !== undefined }];
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
|
||||||
.ui-modal {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 2;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
overflow: auto;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #00000080;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-success {
|
|
||||||
--modal-color: var(--color-green-infra-base);
|
|
||||||
--modal-background-color: var(--background-color-green-infra);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-info {
|
|
||||||
--modal-color: var(--color-extra-blue-base);
|
|
||||||
--modal-background-color: var(--background-color-extra-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-warning {
|
|
||||||
--modal-color: var(--color-orange-world-base);
|
|
||||||
--modal-background-color: var(--background-color-orange-world);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-error {
|
|
||||||
--modal-color: var(--color-red-vates-base);
|
|
||||||
--modal-background-color: var(--background-color-red-vates);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 40rem;
|
|
||||||
padding: 4.2rem;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 1rem;
|
|
||||||
background-color: var(--modal-background-color);
|
|
||||||
box-shadow: var(--shadow-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-icon {
|
|
||||||
font-size: 2rem;
|
|
||||||
position: absolute;
|
|
||||||
top: 1.5rem;
|
|
||||||
right: 2rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--modal-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container :deep(.accent) {
|
|
||||||
color: var(--modal-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-icon {
|
|
||||||
font-size: 4.8rem;
|
|
||||||
margin: 2rem 0;
|
|
||||||
color: var(--modal-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-title {
|
|
||||||
margin-top: 4rem;
|
|
||||||
|
|
||||||
.has-icon & {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--color-blue-scale-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
overflow: auto;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
max-height: calc(100vh - 40rem);
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-button-group {
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<UiIcon
|
||||||
|
:class="textClass"
|
||||||
|
:icon="faXmark"
|
||||||
|
class="modal-close-icon"
|
||||||
|
@click="close"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
|
import { useContext } from "@/composables/context.composable";
|
||||||
|
import { ColorContext } from "@/context";
|
||||||
|
import { IK_MODAL_CLOSE } from "@/types/injection-keys";
|
||||||
|
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { inject } from "vue";
|
||||||
|
|
||||||
|
const { textClass } = useContext(ColorContext);
|
||||||
|
|
||||||
|
const close = inject(IK_MODAL_CLOSE, undefined);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.modal-close-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="tag"
|
||||||
|
:class="[backgroundClass, { nested: isNested }]"
|
||||||
|
class="modal-container"
|
||||||
|
>
|
||||||
|
<header v-if="$slots.header" class="modal-header">
|
||||||
|
<slot name="header" />
|
||||||
|
</header>
|
||||||
|
<main v-if="$slots.default" class="modal-content">
|
||||||
|
<slot name="default" />
|
||||||
|
</main>
|
||||||
|
<footer v-if="$slots.footer" class="modal-footer">
|
||||||
|
<slot name="footer" />
|
||||||
|
</footer>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useContext } from "@/composables/context.composable";
|
||||||
|
import { ColorContext } from "@/context";
|
||||||
|
import type { Color } from "@/types";
|
||||||
|
import { IK_MODAL_NESTED } from "@/types/injection-keys";
|
||||||
|
import { inject, provide } from "vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
tag?: string;
|
||||||
|
color?: Color;
|
||||||
|
}>(),
|
||||||
|
{ tag: "div" }
|
||||||
|
);
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
header: () => any;
|
||||||
|
default: () => any;
|
||||||
|
footer: () => any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { backgroundClass } = useContext(ColorContext, () => props.color);
|
||||||
|
|
||||||
|
const isNested = inject(IK_MODAL_NESTED, false);
|
||||||
|
provide(IK_MODAL_NESTED, true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.modal-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto 1fr;
|
||||||
|
max-width: calc(100vw - 2rem);
|
||||||
|
max-height: calc(100vh - 20rem);
|
||||||
|
padding: 2rem;
|
||||||
|
gap: 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
|
||||||
|
&:not(.nested) {
|
||||||
|
min-width: 40rem;
|
||||||
|
box-shadow: var(--shadow-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nested {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
text-align: center;
|
||||||
|
grid-row: 2;
|
||||||
|
padding: 2rem;
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
grid-row: 3;
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
</style>
|
57
@xen-orchestra/lite/src/components/ui/modals/UiModal.vue
Normal file
57
@xen-orchestra/lite/src/components/ui/modals/UiModal.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="isOpen" class="ui-modal" @click.self="close">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useContext } from "@/composables/context.composable";
|
||||||
|
import { ColorContext } from "@/context";
|
||||||
|
import type { Color } from "@/types";
|
||||||
|
import { IK_MODAL_CLOSE } from "@/types/injection-keys";
|
||||||
|
import { useMagicKeys, useVModel, whenever } from "@vueuse/core/index";
|
||||||
|
import { provide } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean;
|
||||||
|
color?: Color;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "update:modelValue", value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isOpen = useVModel(props, "modelValue", emit);
|
||||||
|
|
||||||
|
const close = () => (isOpen.value = false);
|
||||||
|
|
||||||
|
provide(IK_MODAL_CLOSE, close);
|
||||||
|
|
||||||
|
useContext(ColorContext, () => props.color);
|
||||||
|
|
||||||
|
const { escape } = useMagicKeys();
|
||||||
|
|
||||||
|
whenever(escape, () => close());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.ui-modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(26, 27, 56, 0.25);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<ModalContainer>
|
||||||
|
<template #header>
|
||||||
|
<ModalCloseIcon class="close-icon" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
</ModalContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||||
|
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
default: () => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.close-icon {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<ModalContainer tag="form">
|
||||||
|
<template #header>
|
||||||
|
<div class="close-bar">
|
||||||
|
<ModalCloseIcon />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
<UiIcon :class="textClass" :icon="icon" class="main-icon" />
|
||||||
|
<div v-if="$slots.title || $slots.subtitle" class="titles">
|
||||||
|
<UiTitle v-if="$slots.title" type="h4">
|
||||||
|
<slot name="title" />
|
||||||
|
</UiTitle>
|
||||||
|
<div v-if="$slots.subtitle" class="subtitle">
|
||||||
|
<slot name="subtitle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="$slots.default">
|
||||||
|
<slot name="default" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UiButtonGroup>
|
||||||
|
<slot name="buttons" />
|
||||||
|
</UiButtonGroup>
|
||||||
|
</template>
|
||||||
|
</ModalContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
|
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||||
|
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||||
|
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||||
|
import UiTitle from "@/components/ui/UiTitle.vue";
|
||||||
|
import { useContext } from "@/composables/context.composable";
|
||||||
|
import { ColorContext } from "@/context";
|
||||||
|
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
icon?: IconDefinition;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { textClass } = useContext(ColorContext);
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
title: () => void;
|
||||||
|
subtitle: () => void;
|
||||||
|
default: () => void;
|
||||||
|
buttons: () => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.close-bar {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-icon {
|
||||||
|
font-size: 4.8rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titles {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-blue-scale-200);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<ModalContainer tag="form">
|
||||||
|
<template #header>
|
||||||
|
<div :class="borderClass" class="title-bar">
|
||||||
|
<UiIcon :class="textClass" :icon="icon" />
|
||||||
|
<slot name="title" />
|
||||||
|
<ModalCloseIcon class="close-icon" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UiButtonGroup class="footer-buttons">
|
||||||
|
<slot name="buttons" />
|
||||||
|
</UiButtonGroup>
|
||||||
|
</template>
|
||||||
|
</ModalContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||||
|
import ModalCloseIcon from "@/components/ui/modals/ModalCloseIcon.vue";
|
||||||
|
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||||
|
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||||
|
import { useContext } from "@/composables/context.composable";
|
||||||
|
import { ColorContext, DisabledContext } from "@/context";
|
||||||
|
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
icon?: IconDefinition;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ disabled: undefined }
|
||||||
|
);
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
title: () => void;
|
||||||
|
default: () => void;
|
||||||
|
buttons: () => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { textClass, borderClass } = useContext(ColorContext);
|
||||||
|
|
||||||
|
useContext(DisabledContext, () => props.disabled);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.title-bar {
|
||||||
|
display: flex;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,42 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
v-tooltip="areSomeVmsInExecution && $t('selected-vms-in-execution')"
|
v-tooltip="areSomeVmsInExecution && $t('selected-vms-in-execution')"
|
||||||
:disabled="areSomeVmsInExecution"
|
:disabled="isDisabled"
|
||||||
:icon="faTrashCan"
|
:icon="faTrashCan"
|
||||||
@click="openDeleteModal"
|
@click="openDeleteModal"
|
||||||
>
|
>
|
||||||
{{ $t("delete") }}
|
{{ $t("delete") }}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<UiModal
|
<UiModal v-model="isDeleteModalOpen">
|
||||||
v-if="isDeleteModalOpen"
|
<ConfirmModalLayout :icon="faSatellite">
|
||||||
:icon="faSatellite"
|
<template #title>
|
||||||
@close="closeDeleteModal"
|
<i18n-t keypath="confirm-delete" scope="global" tag="div">
|
||||||
>
|
<span :class="textClass">
|
||||||
<template #title>
|
{{ $t("n-vms", { n: vmRefs.length }) }}
|
||||||
<i18n-t keypath="confirm-delete" scope="global" tag="div">
|
</span>
|
||||||
<span :class="textClass">
|
</i18n-t>
|
||||||
{{ $t("n-vms", { n: vmRefs.length }) }}
|
</template>
|
||||||
</span>
|
|
||||||
</i18n-t>
|
<template #subtitle>
|
||||||
</template>
|
{{ $t("please-confirm") }}
|
||||||
<template #subtitle>
|
</template>
|
||||||
{{ $t("please-confirm") }}
|
|
||||||
</template>
|
<template #buttons>
|
||||||
<template #buttons>
|
<UiButton outlined @click="closeDeleteModal">
|
||||||
<UiButton outlined @click="closeDeleteModal">
|
{{ $t("go-back") }}
|
||||||
{{ $t("go-back") }}
|
</UiButton>
|
||||||
</UiButton>
|
<UiButton @click="deleteVms">
|
||||||
<UiButton @click="deleteVms">
|
{{ $t("delete-vms", { n: vmRefs.length }) }}
|
||||||
{{ $t("delete-vms", { n: vmRefs.length }) }}
|
</UiButton>
|
||||||
</UiButton>
|
</template>
|
||||||
</template>
|
</ConfirmModalLayout>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MenuItem from "@/components/menu/MenuItem.vue";
|
import MenuItem from "@/components/menu/MenuItem.vue";
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
|
||||||
import { useContext } from "@/composables/context.composable";
|
import { useContext } from "@/composables/context.composable";
|
||||||
import useModal from "@/composables/modal.composable";
|
import useModal from "@/composables/modal.composable";
|
||||||
import { ColorContext } from "@/context";
|
import { ColorContext } from "@/context";
|
||||||
@ -68,6 +69,10 @@ const areSomeVmsInExecution = computed(() =>
|
|||||||
vms.value.some((vm) => vm.power_state !== VM_POWER_STATE.HALTED)
|
vms.value.some((vm) => vm.power_state !== VM_POWER_STATE.HALTED)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isDisabled = computed(
|
||||||
|
() => vms.value.length === 0 || areSomeVmsInExecution.value
|
||||||
|
);
|
||||||
|
|
||||||
const deleteVms = async () => {
|
const deleteVms = async () => {
|
||||||
await xenApi.vm.delete(props.vmRefs);
|
await xenApi.vm.delete(props.vmRefs);
|
||||||
closeDeleteModal();
|
closeDeleteModal();
|
||||||
|
@ -1,16 +1,57 @@
|
|||||||
# useModal composable
|
# useModal composable
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### API
|
||||||
|
|
||||||
|
`useModal<T>(options: ModalOptions)`
|
||||||
|
|
||||||
|
Type parameter:
|
||||||
|
|
||||||
|
- `T`: The type for the modal's payload.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
- `options`: An optional object of type `ModalOptions`.
|
||||||
|
|
||||||
|
Returns an object with:
|
||||||
|
|
||||||
|
- `payload: ReadOnly<Ref<T | undefined>>`: The payload data of the modal. Mainly used if a single modal is used for
|
||||||
|
multiple items (typically with `v-for`)
|
||||||
|
- `isOpen: WritableComputedRef<boolean>`: A writable computed indicating if the modal is open or not.
|
||||||
|
- `open(currentPayload?: T)`: A function to open the modal and optionally set its payload.
|
||||||
|
- `close(force = false)`: A function to close the modal. If force is set to `true`, the modal will be closed without
|
||||||
|
calling the `confirmClose` callback.
|
||||||
|
|
||||||
|
#### Types
|
||||||
|
|
||||||
|
`ModalOptions`
|
||||||
|
|
||||||
|
An object type that accepts:
|
||||||
|
|
||||||
|
- `confirmClose?: () => boolean`: An optional callback that is called before the modal is closed. If this function
|
||||||
|
returns `false`, the modal will not be closed.
|
||||||
|
- `onClose?: () => void`: An optional callback that is called after the modal is closed.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div v-for="item in items">
|
<div v-for="item in items">
|
||||||
{{ item.name }} <button @click="openRemoveModal(item)">Delete</button>
|
{{ item.name }}
|
||||||
|
<button @click="openRemoveModal(item)">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UiModal v-if="isRemoveModalOpen">
|
<UiModal v-model="isRemoveModalOpen">
|
||||||
Are you sure you want to delete {{ removeModalPayload.name }}
|
<ModalContainer>
|
||||||
|
<template #header>
|
||||||
<button @click="handleRemove">Yes</button>
|
Are you sure you want to delete {{ removeModalPayload.name }}?
|
||||||
<button @click="closeRemoveModal">No</button>
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<button @click="handleRemove">Yes</button>
|
||||||
|
<button @click="closeRemoveModal">No</button>
|
||||||
|
</template>
|
||||||
|
</ModalContainer>
|
||||||
</UiModal>
|
</UiModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -22,7 +63,11 @@ const {
|
|||||||
isOpen: isRemoveModalOpen,
|
isOpen: isRemoveModalOpen,
|
||||||
open: openRemoveModal,
|
open: openRemoveModal,
|
||||||
close: closeRemoveModal,
|
close: closeRemoveModal,
|
||||||
} = useModal();
|
} = useModal({
|
||||||
|
confirmClose: () =>
|
||||||
|
window.confirm("Are you sure you want to close this modal?"),
|
||||||
|
onClose: () => console.log("Modal closed"),
|
||||||
|
});
|
||||||
|
|
||||||
async function handleRemove() {
|
async function handleRemove() {
|
||||||
await removeItem(removeModalPayload.id);
|
await removeItem(removeModalPayload.id);
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { ref } from "vue";
|
import { computed, readonly, ref } from "vue";
|
||||||
|
|
||||||
export default function useModal<T>() {
|
type ModalOptions = {
|
||||||
|
confirmClose?: () => boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function useModal<T>(options: ModalOptions = {}) {
|
||||||
const $payload = ref<T>();
|
const $payload = ref<T>();
|
||||||
const $isOpen = ref(false);
|
const $isOpen = ref(false);
|
||||||
|
|
||||||
@ -8,15 +13,35 @@ export default function useModal<T>() {
|
|||||||
$isOpen.value = true;
|
$isOpen.value = true;
|
||||||
$payload.value = payload;
|
$payload.value = payload;
|
||||||
};
|
};
|
||||||
|
const close = (force = false) => {
|
||||||
|
if (!force && options.confirmClose?.() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.onClose) {
|
||||||
|
options.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
$isOpen.value = false;
|
$isOpen.value = false;
|
||||||
$payload.value = undefined;
|
$payload.value = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isOpen = computed({
|
||||||
|
get() {
|
||||||
|
return $isOpen.value;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
if (value) {
|
||||||
|
open();
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
payload: $payload,
|
payload: readonly($payload),
|
||||||
isOpen: $isOpen,
|
isOpen,
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
prop('error').type('string').widget(),
|
prop('error').type('string').widget(),
|
||||||
prop('help').type('string').widget().preset('256 by default'),
|
prop('help').type('string').widget().preset('256 by default'),
|
||||||
prop('disabled').type('boolean').widget().ctx(),
|
prop('disabled').type('boolean').widget().ctx(),
|
||||||
|
prop('light').bool().widget(),
|
||||||
slot().help('Contains the input'),
|
slot().help('Contains the input'),
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
```vue-template
|
||||||
|
<UiModal v-model="isOpen">
|
||||||
|
<BasicModalLayout>
|
||||||
|
Here is a basic modal...
|
||||||
|
</BasicModalLayout>
|
||||||
|
</UiModal>
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue-script
|
||||||
|
const { isOpen } = useModal();
|
||||||
|
```
|
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentStory
|
||||||
|
v-slot="{ settings }"
|
||||||
|
:params="[
|
||||||
|
slot(),
|
||||||
|
setting('defaultSlotContent').preset('Modal content').widget(text()),
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<BasicModalLayout>
|
||||||
|
{{ settings.defaultSlotContent }}
|
||||||
|
</BasicModalLayout>
|
||||||
|
</ComponentStory>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
||||||
|
import BasicModalLayout from "@/components/ui/modals/layouts/BasicModalLayout.vue";
|
||||||
|
import { setting, slot } from "@/libs/story/story-param";
|
||||||
|
import { text } from "@/libs/story/story-widget";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped></style>
|
@ -0,0 +1,21 @@
|
|||||||
|
```vue-template
|
||||||
|
<UiModal v-model="isOpen">
|
||||||
|
<ConfirmModalLayout :icon="faShip">
|
||||||
|
<template #title>Do you confirm?</template>
|
||||||
|
<template #subtitle>You should be sure about this</template>
|
||||||
|
<template #buttons>
|
||||||
|
<UiButton outlined @click="close">I prefer not</UiButton>
|
||||||
|
<UiButton @click="accept">Yes, I'm sure!</UiButton>
|
||||||
|
</template>
|
||||||
|
</ConfirmModalLayout>
|
||||||
|
</UiModal>
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue-script
|
||||||
|
const { isOpen, close } = useModal();
|
||||||
|
|
||||||
|
const accept = async () => {
|
||||||
|
// do something
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
```
|
@ -1,45 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<ComponentStory
|
<ComponentStory
|
||||||
|
v-slot="{ properties, settings }"
|
||||||
:params="[
|
:params="[
|
||||||
colorProp(),
|
|
||||||
iconProp(),
|
iconProp(),
|
||||||
event('close').preset(close),
|
|
||||||
slot('default'),
|
|
||||||
slot('title'),
|
slot('title'),
|
||||||
slot('subtitle'),
|
slot('subtitle'),
|
||||||
slot('icon'),
|
slot('default'),
|
||||||
slot('buttons').help('Meant to receive UiButton components'),
|
slot('buttons').help('Meant to receive UiButton components'),
|
||||||
setting('title').preset('Modal Title').widget(),
|
setting('title').preset('Modal Title').widget(),
|
||||||
setting('subtitle').preset('Modal Subtitle').widget(),
|
setting('subtitle').preset('Modal Subtitle').widget(),
|
||||||
]"
|
]"
|
||||||
v-slot="{ properties, settings }"
|
|
||||||
>
|
>
|
||||||
<UiButton type="button" @click="open">Open Modal</UiButton>
|
<ConfirmModalLayout v-bind="properties">
|
||||||
|
|
||||||
<UiModal v-bind="properties" v-if="isOpen">
|
|
||||||
<template #title>{{ settings.title }}</template>
|
<template #title>{{ settings.title }}</template>
|
||||||
<template #subtitle>{{ settings.subtitle }}</template>
|
<template #subtitle>{{ settings.subtitle }}</template>
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<UiButton @click="close">Discard</UiButton>
|
<UiButton outlined>Discard</UiButton>
|
||||||
|
<UiButton>Go</UiButton>
|
||||||
</template>
|
</template>
|
||||||
</UiModal>
|
</ConfirmModalLayout>
|
||||||
</ComponentStory>
|
</ComponentStory>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
||||||
|
import ConfirmModalLayout from "@/components/ui/modals/layouts/ConfirmModalLayout.vue";
|
||||||
import UiButton from "@/components/ui/UiButton.vue";
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
import UiModal from "@/components/ui/UiModal.vue";
|
import { iconProp, setting, slot } from "@/libs/story/story-param";
|
||||||
import useModal from "@/composables/modal.composable";
|
|
||||||
import {
|
|
||||||
colorProp,
|
|
||||||
event,
|
|
||||||
iconProp,
|
|
||||||
setting,
|
|
||||||
slot,
|
|
||||||
} from "@/libs/story/story-param";
|
|
||||||
|
|
||||||
const { open, close, isOpen } = useModal();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped></style>
|
<style lang="postcss" scoped></style>
|
@ -0,0 +1,25 @@
|
|||||||
|
```vue-template
|
||||||
|
<UiModal v-model="isOpen">
|
||||||
|
<FormModalLayout :icon="faShip" @submit.prevent="handleSubmit">
|
||||||
|
<template #title>Migrate 3 VMs/template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
<!-- Form content goes here... -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #buttons>
|
||||||
|
<UiButton outlined @click="close">Cancel</UiButton>
|
||||||
|
<UiButton type="submit">Migrate 3 VMs</UiButton>
|
||||||
|
</template>
|
||||||
|
</ConfirmModalLayout>
|
||||||
|
</UiModal>
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue-script
|
||||||
|
const { isOpen, close } = useModal();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// Handling form submission...
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentStory
|
||||||
|
v-slot="{ properties }"
|
||||||
|
:params="[iconProp(), slot('title'), slot('default'), slot('buttons')]"
|
||||||
|
>
|
||||||
|
<FormModalLayout :icon="faRoute" v-bind="properties">
|
||||||
|
<template #title>Migrate 3 VMs</template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormInputWrapper
|
||||||
|
label="Select a destination host"
|
||||||
|
learn-more-url="http://..."
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<FormInput />
|
||||||
|
</FormInputWrapper>
|
||||||
|
|
||||||
|
<FormInputWrapper
|
||||||
|
label="Select a migration network (optional)"
|
||||||
|
learn-more-url="http://..."
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<FormInput />
|
||||||
|
</FormInputWrapper>
|
||||||
|
|
||||||
|
<FormInputWrapper
|
||||||
|
help="Individual selection for each VDI is not available on multiple VMs migration."
|
||||||
|
label="Select a destination SR"
|
||||||
|
learn-more-url="http://..."
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<FormInput />
|
||||||
|
</FormInputWrapper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #buttons>
|
||||||
|
<UiButton outlined>Cancel</UiButton>
|
||||||
|
<UiButton>Migrate 3 VMs</UiButton>
|
||||||
|
</template>
|
||||||
|
</FormModalLayout>
|
||||||
|
</ComponentStory>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
||||||
|
import FormInput from "@/components/form/FormInput.vue";
|
||||||
|
import FormInputWrapper from "@/components/form/FormInputWrapper.vue";
|
||||||
|
import FormModalLayout from "@/components/ui/modals/layouts/FormModalLayout.vue";
|
||||||
|
import UiButton from "@/components/ui/UiButton.vue";
|
||||||
|
import { iconProp, slot } from "@/libs/story/story-param";
|
||||||
|
import { faRoute } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped></style>
|
@ -0,0 +1,19 @@
|
|||||||
|
A basic modal container containing 3 slots: `header`, `default` and `footer`.
|
||||||
|
|
||||||
|
Tag will be `div` by default but can be changed with the `tag` prop.
|
||||||
|
|
||||||
|
Color can be changed with the `color` prop.
|
||||||
|
|
||||||
|
To keep the content centered vertically, header and footer will always have the same height.
|
||||||
|
|
||||||
|
Modal content has an max height + overflow to prevent the modal growing out of the screen.
|
||||||
|
|
||||||
|
Modal containers can be nested.
|
||||||
|
|
||||||
|
```vue-template
|
||||||
|
<ModalContainer>
|
||||||
|
<template #header>Header</template>
|
||||||
|
<template #default>Content</template>
|
||||||
|
<template #header>Footer</template>
|
||||||
|
</ModalContainer>
|
||||||
|
```
|
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentStory
|
||||||
|
v-slot="{ properties, settings }"
|
||||||
|
:params="[
|
||||||
|
prop('tag').str().default('div').widget(),
|
||||||
|
colorProp(),
|
||||||
|
slot('header'),
|
||||||
|
slot(),
|
||||||
|
slot('footer'),
|
||||||
|
setting('headerSlotContent')
|
||||||
|
.preset('Header')
|
||||||
|
.widget(text())
|
||||||
|
.help('Content for default slot'),
|
||||||
|
setting('defaultSlotContent')
|
||||||
|
.preset('Content')
|
||||||
|
.widget(text())
|
||||||
|
.help('Content for default slot'),
|
||||||
|
setting('footerSlotContent')
|
||||||
|
.preset('Footer')
|
||||||
|
.widget(text())
|
||||||
|
.help('Content for default slot'),
|
||||||
|
setting('showNested')
|
||||||
|
.preset(false)
|
||||||
|
.widget(boolean())
|
||||||
|
.help('Show nested modal'),
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<ModalContainer v-bind="properties">
|
||||||
|
<template #header>
|
||||||
|
{{ settings.headerSlotContent }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default>
|
||||||
|
{{ settings.defaultSlotContent }}
|
||||||
|
<ModalContainer v-if="settings.showNested" color="error">
|
||||||
|
Nested modal
|
||||||
|
</ModalContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
{{ settings.footerSlotContent }}
|
||||||
|
</template>
|
||||||
|
</ModalContainer>
|
||||||
|
</ComponentStory>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
||||||
|
import ModalContainer from "@/components/ui/modals/ModalContainer.vue";
|
||||||
|
import { colorProp, prop, setting, slot } from "@/libs/story/story-param";
|
||||||
|
import { boolean, text } from "@/libs/story/story-widget";
|
||||||
|
</script>
|
21
@xen-orchestra/lite/src/stories/modals/ui-modal.story.md
Normal file
21
@xen-orchestra/lite/src/stories/modals/ui-modal.story.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
This component only handle the modal backdrop and content positioning.
|
||||||
|
|
||||||
|
You can use any pre-made layouts, create your own or use the `ModalContainer` component.
|
||||||
|
|
||||||
|
It is meant to be used with `useModal` composable.
|
||||||
|
|
||||||
|
```vue-template
|
||||||
|
<button @click="open">Delete all items</button>
|
||||||
|
|
||||||
|
<UiModal v-model="isOpen">
|
||||||
|
<ModalContainer...>
|
||||||
|
<!-- <ConfirmModalLayout ...> (Or you can use a pre-made layout) -->
|
||||||
|
</UiModal>
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue-script
|
||||||
|
import { faRemove } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { useModal } from "@composable/modal.composable";
|
||||||
|
|
||||||
|
const { open, close, isOpen } = useModal().
|
||||||
|
```
|
24
@xen-orchestra/lite/src/stories/modals/ui-modal.story.vue
Normal file
24
@xen-orchestra/lite/src/stories/modals/ui-modal.story.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<ComponentStory
|
||||||
|
:params="[
|
||||||
|
model()
|
||||||
|
.required()
|
||||||
|
.type('boolean')
|
||||||
|
.help('Whether the modal is opened or not'),
|
||||||
|
colorProp().ctx(),
|
||||||
|
slot().help('Place your ModalContainer here'),
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<button type="button" @click="open">Open modal</button>
|
||||||
|
<UiModal v-model="isOpen" />
|
||||||
|
</ComponentStory>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComponentStory from "@/components/component-story/ComponentStory.vue";
|
||||||
|
import UiModal from "@/components/ui/modals/UiModal.vue";
|
||||||
|
import useModal from "@/composables/modal.composable";
|
||||||
|
import { colorProp, model, slot } from "@/libs/story/story-param";
|
||||||
|
|
||||||
|
const { isOpen, open } = useModal();
|
||||||
|
</script>
|
@ -1,19 +0,0 @@
|
|||||||
```vue-template
|
|
||||||
<button @click="open">Delete all items</button>
|
|
||||||
|
|
||||||
<UiModal v-if="isOpen" @close="close" :icon="faRemove">
|
|
||||||
<template #title>You are about to delete 12 items</template>
|
|
||||||
<template #subtitle>They'll be gone forever</template>
|
|
||||||
<template #buttons>
|
|
||||||
<UiButton @click="delete" color="error">Yes, delete</UiButton>
|
|
||||||
<UiButton @click="close">Cancel</UiButton>
|
|
||||||
</template>
|
|
||||||
</UiModal>
|
|
||||||
```
|
|
||||||
|
|
||||||
```vue-script
|
|
||||||
import { faRemove } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { useModal } from "@composable/modal.composable";
|
|
||||||
|
|
||||||
const { open, close, isOpen } = useModal().
|
|
||||||
```
|
|
@ -47,3 +47,7 @@ export const IK_BUTTON_GROUP_TRANSPARENT = Symbol() as InjectionKey<
|
|||||||
export const IK_CARD_GROUP_VERTICAL = Symbol() as InjectionKey<boolean>;
|
export const IK_CARD_GROUP_VERTICAL = Symbol() as InjectionKey<boolean>;
|
||||||
|
|
||||||
export const IK_INPUT_ID = Symbol() as InjectionKey<ComputedRef<string>>;
|
export const IK_INPUT_ID = Symbol() as InjectionKey<ComputedRef<string>>;
|
||||||
|
|
||||||
|
export const IK_MODAL_CLOSE = Symbol() as InjectionKey<() => void>;
|
||||||
|
|
||||||
|
export const IK_MODAL_NESTED = Symbol() as InjectionKey<boolean>;
|
||||||
|
Loading…
Reference in New Issue
Block a user