mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
update Button to work as tab
This commit is contained in:
parent
bb0cfb74c6
commit
5e6fa1ecf4
6 changed files with 122 additions and 44 deletions
|
|
@ -13,6 +13,8 @@ import { contentIndex } from "@utils/tabindex.js";
|
|||
import Container from "@components/Widget/Container.vue";
|
||||
import Icons from "@components/Widget/Icons.vue";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
import { useDisplayStore } from "@store/global.js";
|
||||
|
||||
/**
|
||||
* @name Widget/Button.vue
|
||||
* @description This component is a standard button.
|
||||
|
|
@ -29,12 +31,13 @@ import { useUUID } from "@utils/global.js";
|
|||
* }
|
||||
* @param {string} [id=uuidv4()] - Unique id of the button
|
||||
* @param {string} text - Content of the button. Can be a translation key or by default raw text.
|
||||
* @param {array} [display=[]] - Case display, we will update the display store with the given array. Useful when we want to use button as tabs.
|
||||
* @param {string} [type="button"] - Can be of type button || submit
|
||||
* @param {boolean} [disabled=false]
|
||||
* @param {boolean} [hideText=false] - Hide text to only display icon
|
||||
* @param {string} [color="primary"]
|
||||
* @param {string} [iconColor=""] - Color we want to apply to the icon. If falsy value, default icon color is applied.
|
||||
* @param {string} [size="normal"] - Can be of size sm || normal || lg || xl
|
||||
* @param {string} [size="normal"] - Can be of size sm || normal || lg || xl or tab
|
||||
* @param {string} [iconName=""] - Name in lowercase of icons store on /Icons. If falsy value, no icon displayed.
|
||||
* @param {Object} [attrs={}] - List of attributes to add to the button. Some attributes will conduct to additional script
|
||||
* @param {Object|boolean} [modal=false] - We can link the button to a Modal component. We need to pass the widgets inside the modal. Button click will open the modal.
|
||||
|
|
@ -54,6 +57,11 @@ const props = defineProps({
|
|||
required: true,
|
||||
default: "",
|
||||
},
|
||||
emitValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -104,6 +112,11 @@ const props = defineProps({
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
display: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: [],
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -116,19 +129,61 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const displayStore = useDisplayStore();
|
||||
|
||||
const btn = reactive({
|
||||
id: "",
|
||||
openModal: false,
|
||||
modalId: props.modal ? `${props.id}-modal` : "",
|
||||
class:
|
||||
props.color === "transparent"
|
||||
? `${props.size}`
|
||||
: `btn ${props.color} ${props.size}`,
|
||||
isActive: false,
|
||||
});
|
||||
|
||||
const btnEl = ref();
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
// transparent useful when we only want to display icon without background or shadow style
|
||||
if (props.color === "transparent") return `${props.size}`;
|
||||
return `btn ${props.color} ${props.size}`;
|
||||
});
|
||||
/**
|
||||
* @name handleClick
|
||||
* @description Wrap all the logic to handle the click event on the button.
|
||||
* This will prevent submit if not a submit button, open a modal if one is attached and update the display store if needed.
|
||||
* @param {event} e - Event object
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleClick(e) {
|
||||
if (e.target.getAttribute("type") !== "submit") e.preventDefault();
|
||||
if (typeof props.modal === "object") {
|
||||
btn.openModal = true;
|
||||
}
|
||||
if (props.display.length) {
|
||||
console.log("update", btn.isActive);
|
||||
displayStore.setDisplay(props.display[0], props.display[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a11y attributs and update when needed in case the button is related to a display group
|
||||
if (props.display.length) {
|
||||
watch(displayStore.display, (val) => {
|
||||
const isCurrent = displayStore.isCurrentDisplay(
|
||||
props.display[0],
|
||||
props.display[1]
|
||||
);
|
||||
btnEl.value.setAttribute("aria-controls", btn.modalId);
|
||||
btnEl.value.setAttribute("aria-expanded", isCurrent ? "true" : "false");
|
||||
btn.isActive = isCurrent ? true : false;
|
||||
});
|
||||
}
|
||||
|
||||
// watch openModal to add accessibility data
|
||||
watch(
|
||||
() => btn.openModal,
|
||||
(value) => {
|
||||
if (typeof props.modal === "object") {
|
||||
btnEl.value.setAttribute("aria-expanded", value ? "true" : "false");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
btn.id = useUUID(props.id);
|
||||
|
|
@ -141,28 +196,18 @@ onMounted(() => {
|
|||
btnEl.value.setAttribute("aria-controls", btn.modalId);
|
||||
btnEl.value.setAttribute(
|
||||
"aria-expanded",
|
||||
props.openModal ? "true" : "false",
|
||||
props.openModal ? "true" : "false"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// watch openModal to add accessibility data
|
||||
watch(
|
||||
() => btn.openModal,
|
||||
(value) => {
|
||||
if (typeof props.modal === "object") {
|
||||
btnEl.value.setAttribute("aria-expanded", value ? "true" : "false");
|
||||
}
|
||||
},
|
||||
);
|
||||
if (btnEl.value?.closest("[data-is='tabs']")) {
|
||||
btnEl.value.setAttribute("role", "tab");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container
|
||||
data-is="button"
|
||||
:containerClass="`${props.containerClass}`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
<Container data-is="button" :containerClass="`${props.containerClass}`">
|
||||
<button
|
||||
data-is="button"
|
||||
:type="props.type"
|
||||
|
|
@ -170,22 +215,23 @@ watch(
|
|||
:id="btn.id"
|
||||
@click="
|
||||
(e) => {
|
||||
if (e.target.getAttribute('type') !== 'submit') e.preventDefault();
|
||||
if (typeof props.modal === 'object') {
|
||||
btn.openModal = true;
|
||||
}
|
||||
handleClick(e);
|
||||
}
|
||||
"
|
||||
v-bind="props.attrs || {}"
|
||||
:tabindex="props.tabId"
|
||||
:class="[buttonClass]"
|
||||
:class="[
|
||||
btn.class,
|
||||
btn.isActive ? 'active' : '',
|
||||
props.iconName && !props.hideText ? 'icon' : 'no-icon',
|
||||
]"
|
||||
:disabled="props.disabled || false"
|
||||
:aria-labelledby="`text-${btn.id}`"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
props.hideText ? 'sr-only' : '',
|
||||
props.iconName ? 'mr-2' : '',
|
||||
props.iconName && !props.hideText ? 'mr-2' : '',
|
||||
'pointer-events-none',
|
||||
]"
|
||||
:id="`text-${btn.id}`"
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const groupEl = ref(null);
|
|||
|
||||
onMounted(() => {
|
||||
group.class =
|
||||
props.boutonGroupClass || groupEl?.value?.closest("[data-is]")
|
||||
props.boutonGroupClass || groupEl?.value.closest("[data-is]")
|
||||
? `button-group-${groupEl.value
|
||||
.closest("[data-is]")
|
||||
.getAttribute("data-is")}`
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ if (props.display.length) {
|
|||
|
||||
const containerClass = computed(() => {
|
||||
if (props.type === "card") return "layout-card";
|
||||
if (props.type === "tabs") return "layout-tabs";
|
||||
return "";
|
||||
});
|
||||
|
||||
|
|
@ -129,12 +130,7 @@ onMounted(() => {
|
|||
:id="container.id"
|
||||
:is="props.link ? 'a' : 'div'"
|
||||
:data-is="`${props.type}`"
|
||||
:class="[
|
||||
containerClass,
|
||||
gridClass,
|
||||
props.gridLayoutClass,
|
||||
'layout-grid-layout',
|
||||
]"
|
||||
:class="[containerClass, gridClass, props.gridLayoutClass]"
|
||||
>
|
||||
<slot></slot>
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Grid from "@components/Widget/Grid.vue";
|
|||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
import Tabulator from "@components/Widget/Tabulator.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import { useEqualStr } from "@utils/global.js";
|
||||
import { useTableStore } from "@store/global.js";
|
||||
import { onMounted } from "vue";
|
||||
|
|
@ -139,9 +140,27 @@ const items = [
|
|||
];
|
||||
|
||||
const builder = [
|
||||
{
|
||||
type: "tabs",
|
||||
widgets: [
|
||||
{
|
||||
type: "ButtonGroup",
|
||||
data: {
|
||||
buttons: [
|
||||
{
|
||||
text: "test",
|
||||
display: ["main", 0],
|
||||
isTab: true,
|
||||
size: "tab",
|
||||
iconName: "globe",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "card",
|
||||
gridLayoutClass: "transparent",
|
||||
widgets: [
|
||||
{
|
||||
type: "Tabulator",
|
||||
|
|
@ -167,7 +186,6 @@ const builder = [
|
|||
|
||||
<template>
|
||||
<DashboardLayout>
|
||||
<div class="col-span-12 w-full border-b borber-b-white">f</div>
|
||||
<GridLayout
|
||||
v-for="(container, index) in builder"
|
||||
:key="index"
|
||||
|
|
@ -186,6 +204,10 @@ const builder = [
|
|||
v-if="useEqualStr(widget.type, 'Tabulator')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
<ButtonGroup
|
||||
v-if="useEqualStr(widget.type, 'ButtonGroup')"
|
||||
v-bind="widget.data"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</GridLayout>
|
||||
|
|
|
|||
|
|
@ -508,16 +508,16 @@ body {
|
|||
|
||||
/* LAYOUT */
|
||||
|
||||
.layout-grid-layout {
|
||||
@apply px-6 py-4 overflow-hidden break-words grid grid-cols-12 w-full;
|
||||
}
|
||||
|
||||
.layout-grid {
|
||||
@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;
|
||||
@apply px-6 py-4 grid grid-cols-12 w-full 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-tabs {
|
||||
@apply overflow-visible relative transition dark:brightness-110 transform duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
.layout-modal-container {
|
||||
|
|
@ -1150,6 +1150,10 @@ body {
|
|||
@apply flex justify-center items-center mx-4;
|
||||
}
|
||||
|
||||
.button-group-tabs {
|
||||
@apply col-span-12 flex justify-center items-center;
|
||||
}
|
||||
|
||||
.button-group-multiple {
|
||||
@apply col-span-12 flex justify-center items-center mx-4;
|
||||
}
|
||||
|
|
@ -1391,7 +1395,17 @@ body {
|
|||
/* BTN */
|
||||
|
||||
.btn {
|
||||
@apply flex justify-center items-end leading-none tracking-wide dark:brightness-90 inline-block font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
|
||||
@apply w-full flex justify-center items-end tracking-wide dark:brightness-90 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
|
||||
}
|
||||
|
||||
.btn.active:not([disabled]) {
|
||||
@apply shadow-md brightness-90 dark:shadow-md dark:brightness-75;
|
||||
}
|
||||
|
||||
/* BTN SIZE */
|
||||
|
||||
.btn.tab {
|
||||
@apply px-3 py-2.5;
|
||||
}
|
||||
|
||||
.btn.xl {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue