start modal

* add modal base layout
* add backdrop click close modal logic
* add modal close btn logic
* update button to handle no css style and avoid svg to be clickable inside it
* update utils that allow to hide/show an element using attributs
This commit is contained in:
Jordan Blasenhauer 2024-06-21 14:28:05 +02:00
parent a7be545baa
commit a7723a2140
8 changed files with 116 additions and 38 deletions

View file

@ -490,6 +490,31 @@ body {
@apply col-span-12 grid grid-cols-12 w-full relative;
}
.layout-card {
@apply overflow-visible relative transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out;
}
.layout-modal-container {
@apply z-[9999] fixed top-0 left-0 w-full h-full;
}
.layout-backdrop {
@apply z-[10] fixed top-0 left-0 w-full h-full bg-black bg-opacity-50;
}
.layout-modal-wrap {
@apply relative z-[20] flex justify-center items-center w-full h-full;
}
.layout-modal {
@apply relative min-w-[50vw] w-full max-w-screen-xl max-h-[80vh] min-h-[200px] transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out;
}
.layout-modal-button-container {
@apply absolute right-2 top-2 z-[30];
}
.layout-settings {
@apply py-4 sm:gap-x-8 gap-y-8 col-span-12 grid grid-cols-12 w-full relative;
}
@ -1163,9 +1188,6 @@ body {
/* CARD */
.card {
@apply overflow-visible relative transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border transform duration-300 ease-in-out;
}
.card-info-text {
@apply col-span-12 mb-0 leading-normal text-sm text-gray-700 dark:text-gray-500 ml-2 mr-1.5;

File diff suppressed because one or more lines are too long

View file

@ -36,17 +36,17 @@ onMounted(() => {
<template>
<span :id="icon.id" class="sr-only">{{ $t("icons_cross_desc") }}</span>
<svg
data-svg="cross"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
:aria-labelledby="icon.id"
data-svg="cross"
role="img"
:class="['icon-svg', props.iconClass, props.iconColor]"
:aria-labelledby="icon.id"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>

View file

@ -86,6 +86,11 @@ const props = defineProps({
required: false,
default: "",
},
iconClass: {
type: String,
required: false,
default: "sm",
},
attrs: {
type: Object,
required: false,
@ -110,6 +115,7 @@ const btn = reactive({
const btnEl = ref();
const buttonClass = computed(() => {
if (props.color === "transparent") return `${props.size}`;
return `btn ${props.color} ${props.size}`;
});
@ -150,7 +156,7 @@ onMounted(() => {
<Icons
v-if="props.iconName"
:iconName="props.iconName"
:iconClass="'btn-svg'"
:iconClass="`${props.iconClass} pointer-events-none`"
:iconColor="props.iconColor"
/>
</button>

View file

@ -1,6 +1,8 @@
<script setup>
import { computed, ref, onMounted } from "vue";
import { computed, ref, onMounted, reactive } from "vue";
import Button from "@components/Widget/Button.vue";
import { contentIndex } from "@utils/tabindex.js";
import { useUUID } from "@utils/global.js";
/**
@name Widget/GridLayout.vue
@ -15,7 +17,8 @@ import { contentIndex } from "@utils/tabindex.js";
columns: { pc: 12, tablet: 12, mobile: 12},
gridLayoutClass: "items-start"
}
@param {string} [type="card"] - Type of layout component, we can have : card, table, modal and others
@param {string} [type="card"] - Type of layout component, we can have "card" or "modal"
@param {string} [id=uuidv4()] - Id of the layout component, will be used to identify the component.
@param {string} [title=""] - Title of the layout component, will be displayed at the top if exists. Type of layout component will determine the style of the title.
@param {string} [link=""] - Will transform the container tag from a div to an a tag with the link as href. Useful with card type.
@param {object} [columns={"pc": 12, "tablet": 12, "mobile": 12}] - Work with grid system { pc: 12, tablet: 12, mobile: 12}
@ -29,6 +32,11 @@ const props = defineProps({
required: false,
default: "card",
},
id: {
type: String,
required: true,
default: "",
},
title: {
type: String,
required: false,
@ -60,8 +68,13 @@ const props = defineProps({
},
});
const container = reactive({
id: props.id,
});
const containerClass = computed(() => {
if (props.type === "card") return "card";
if (props.type === "card") return "layout-card";
if (props.type === "modal") return "layout-modal";
return "";
});
@ -69,32 +82,62 @@ const gridClass = computed(() => {
return ` col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}`;
});
const gridLayoutEl = ref();
const flowEl = ref();
onMounted(() => {
container.id = useUUID(container.id);
if (!props.link) return;
gridLayoutEl.value.setAttribute("href", props.link);
gridLayoutEl.value.setAttribute("rel", "noopener");
gridLayoutEl.value.setAttribute("tabindex", props.tabId);
flowEl.value.setAttribute("href", props.link);
flowEl.value.setAttribute("rel", "noopener");
flowEl.value.setAttribute("tabindex", props.tabId);
if (!props.link.startsWith("http")) return;
gridLayoutEl.value.setAttribute("target", "_blank");
flowEl.value.setAttribute("target", "_blank");
});
</script>
<template>
<component
ref="gridLayoutEl"
:is="props.link ? 'a' : 'div'"
data-grid-layout
:class="[
containerClass,
gridClass,
props.gridLayoutClass,
'layout-grid-layout',
]"
>
<slot></slot>
</component>
<!-- modal -->
<template v-if="props.type === 'modal'">
<div class="layout-modal-container" :id="container.id">
<div class="layout-backdrop"></div>
<div class="layout-modal-wrap" :data-hide-el="container.id">
<div class="layout-modal">
<div class="layout-modal-button-container">
<Button
:attrs="{ 'data-hide-el': container.id }"
:text="'action_close_modal'"
:hideText="true"
:iconName="'cross'"
:iconColor="'dark'"
:iconClass="'lg'"
:color="'transparent'"
/>
</div>
</div>
</div>
</div>
</template>
<!-- end modal -->
<!-- card or elements on the document flow -->
<template v-if="props.type !== 'modal'">
<component
ref="flowEl"
:id="container.id"
:is="props.link ? 'a' : 'div'"
data-grid-layout
:class="[
containerClass,
gridClass,
props.gridLayoutClass,
'layout-grid-layout',
]"
>
<slot></slot>
</component>
</template>
<!-- end card or elements on the document flow -->
</template>

View file

@ -142,6 +142,7 @@
"action_save": "save {name}",
"action_add": "add {name}",
"action_close": "close {name}",
"action_close_modal": "close modal",
"action_delete": "delete {name}",
"action_link": "link",
"action_edit": "edit {name}",

View file

@ -2,6 +2,7 @@
import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import BuilderPlugins from "@components/Builder/Plugins.vue";
import { useGlobal } from "@utils/global.js";
/**
@name Page/PLugins.vue
@ -82,10 +83,14 @@ onBeforeMount(() => {
});
onMounted(() => {
useGlobal();
redirectPlugin();
deletePlugin();
});
const builder = [
{
type: "modal",
},
{
type: "card",
widgets: [

View file

@ -15,6 +15,7 @@ function useGlobal() {
window.addEventListener(
"click",
(e) => {
console.log("click", e.target);
// Update some states
useShowEl(e);
useHideEl(e);
@ -35,13 +36,13 @@ function useGlobal() {
@param {Event} e - The event object.
*/
function useShowEl(e) {
if (!e.target.closest("button").hasAttribute("data-show-el")) return;
if (!e.target.hasAttribute("data-show-el")) return;
// show
const showElId = e.target.closest("button").getAttribute("data-show-el");
const showElId = e.target.getAttribute("data-show-el");
document.getElementById(showElId).classList.remove("hidden");
// Update a11y attributes
e.target.closest("button").setAttribute("aria-controls", showElId);
e.target.closest("button").setAttribute("aria-expanded", "true");
e.target.setAttribute("aria-controls", showElId);
e.target.setAttribute("aria-expanded", "true");
}
/**
@ -56,13 +57,13 @@ function useShowEl(e) {
@param {Event} e - The event object.
*/
function useHideEl(e) {
if (!e.target.closest("button").hasAttribute("data-show-close")) return;
if (!e.target.hasAttribute("data-hide-el")) return;
// hide
const hideElId = e.target.closest("button").getAttribute("data-show-close");
const hideElId = e.target.getAttribute("data-hide-el");
document.getElementById(hideElId).classList.add("hidden");
// Update a11y attributes
e.target.closest("button").setAttribute("aria-controls", hideElId);
e.target.closest("button").setAttribute("aria-expanded", "false");
e.target.setAttribute("aria-controls", hideElId);
e.target.setAttribute("aria-expanded", "false");
}
/**