update stat component and title is now widget

* cut stat component in multiple components (title, subtitle, icon) and now we can use subcomponent or stat component in builder
* add some container style to work with absolute subcomponent
*now title is separate from the container
This commit is contained in:
Jordan Blasenhauer 2024-05-22 17:32:46 +02:00
parent b417c83474
commit 6434f4f23b
12 changed files with 334 additions and 86 deletions

View file

@ -826,7 +826,7 @@ body {
}
.stat-value {
@apply mb-1 font-bold dark:text-white/90 uppercase;
@apply my-1 font-bold dark:text-white/90 uppercase;
}
.stat-subtitle {
@ -844,7 +844,11 @@ body {
/* LAYOUT COMPONENT */
.card {
@apply transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border hover:scale-[1.01] transform duration-300 ease-in-out;
@apply relative transition dark:brightness-110 shadow-md bg-white dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border hover:scale-[1.01] transform duration-300 ease-in-out;
}
.card-title {
@apply text-2xl font-bold mb-2 col-span-12;
}
/* CARD INFO */

File diff suppressed because one or more lines are too long

View file

@ -2,12 +2,17 @@
import { reactive, onBeforeMount } from "vue";
import Grid from "@components/Widget/Grid.vue";
import GridLayout from "@components/Widget/GridLayout.vue";
import TitleCard from "@components/Title/Card.vue";
import Checkbox from "@components/Forms/Field/Checkbox.vue";
import Select from "@components/Forms/Field/Select.vue";
import Input from "@components/Forms/Field/Input.vue";
import Datepicker from "@components/Forms/Field/Datepicker.vue";
import Button from "@components/Widget/Button.vue";
import Stat from "@components/Widget/Stat.vue";
import StatTitle from "@components/Stat/Title.vue";
import StatSubtitle from "@components/Stat/Subtitle.vue";
import StatValue from "@components/Stat/Value.vue";
import StatIcon from "@components/Stat/Icon.vue";
/**
@name Builder.vue
@ -68,18 +73,20 @@ const props = defineProps({
<Grid>
<!-- widget element -->
<template v-for="(widget, index) in container.widgets" :key="index">
<Checkbox
v-if="widget.type === 'Checkbox'"
<TitleCard v-if="widget.type === 'TitleCard'" v-bind="widget.data" />
<Checkbox v-if="widget.type === 'Checkbox'" v-bind="widget.data" />
<Select v-if="widget.type === 'Select'" v-bind="widget.data" />
<Input v-if="widget.type === 'Input'" v-bind="widget.data" />
<Datepicker v-if="widget.type === 'Datepicker'" v-bind="widget.data" />
<Button v-if="widget.type === 'Button'" v-bind="widget.data" />
<Stat v-if="widget.type === 'Stat'" v-bind="widget.data" />
<StatTitle v-if="widget.type === 'StatTitle'" v-bind="widget.data" />
<StatSubtitle
v-if="widget.type === 'StatSubtitle'"
v-bind="widget.data"
></Checkbox>
<Select v-if="widget.type === 'Select'" v-bind="widget.data"></Select>
<Input v-if="widget.type === 'Input'" v-bind="widget.data"></Input>
<Datepicker
v-if="widget.type === 'Datepicker'"
v-bind="widget.data"
></Datepicker>
<Button v-if="widget.type === 'Button'" v-bind="widget.data"></Button>
<Stat v-if="widget.type === 'Stat'" v-bind="widget.data"></Stat>
/>
<StatValue v-if="widget.type === 'StatValue'" v-bind="widget.data" />
<StatIcon v-if="widget.type === 'StatIcon'" v-bind="widget.data" />
</template>
</Grid>
</GridLayout>

View file

@ -0,0 +1,51 @@
<script setup>
import Icons from "@/components/Widget/Icons.vue";
/**
@name Stat/Icon.vue
@description This component is a icon used with stats.
This can be used alone in case we don't need a complete stat widget.
In case you have a title, subtitle, value and icon to display, you can directly use Stat widget.
@example
{
iconName: "crown",
iconColor: "info",
iconClass: "text-sm"
}
@param {string} iconName - The icon name of the stat. Can be a translation key or by default raw text.
@param {string} [iconColor="info"] - The color of the icon between error, success, warning, info
@param {string} [iconClass=""] - Additional class, useful when component is used directly on a grid system.
*/
const props = defineProps({
iconName: {
type: String,
required: false,
default: "",
},
iconColor: {
type: String,
required: false,
default: "",
},
iconClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<div
v-if="props.iconName"
role="img"
aria-label="version"
:class="['stat-svg-container', props.iconColor, props.iconClass]"
>
<Icons
:iconName="props.iconName"
:iconClass="'stat-svg'"
:iconColor="'white'"
/>
</div>
</template>

View file

@ -0,0 +1,43 @@
<script setup>
/**
@name Stat/Subtitle.vue
@description This component is a subtitle used with stats.
This can be used alone in case we don't need a complete stat widget.
In case you have a title, subtitle, value and icon to display, you can directly use Stat widget.
@example
{
subtitle: "Last 30 days",
subtitleColor: "info",
subtitleClass: "text-sm"
}
@param {string} subtitle - The subtitle of the stat. Can be a translation key or by default raw text.
@param {string} [subtitleColor="info"] - The color of the subtitle between error, success, warning, info
@param {string} [subtitleClass=""] - Additional class, useful when component is used directly on a grid system
*/
const props = defineProps({
subtitle: {
type: String,
required: true,
},
subtitleColor: {
type: String,
required: false,
default: "info",
},
subtitleClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<p
v-if="props.subtitle"
:class="['stat-subtitle', props.subtitleColor, props.subtitleClass]"
>
{{ $t(props.subtitle, props.subtitle) }}
</p>
</template>

View file

@ -0,0 +1,33 @@
<script setup>
/**
@name Stat/Title.vue
@description This component is a title used with stats.
This can be used alone in case we don't need a complete stat widget.
In case you have a title, subtitle, value and icon to display, you can directly use Stat widget.
@example
{
title: "Total Users",
titleClass: "text-lg"
}
@param {string} title - The title of the stat. Can be a translation key or by default raw text.
@param {string} [titleClass=""] - Additional class, useful when component is used directly on a grid system
*/
const props = defineProps({
title: {
type: String,
required: true,
},
titleClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<p :class="['stat-title', props.titleClass]">
{{ $t(props.title, props.title) }}
</p>
</template>

View file

@ -0,0 +1,33 @@
<script setup>
/**
@name Stat/Value.vue
@description This component is a value used with stats.
This can be used alone in case we don't need a complete stat widget.
In case you have a title, subtitle, value and icon to display, you can directly use Stat widget.
@example
{
value: "100",
valueClass: "text-3xl"
}
@param {string} value - The value of the stat. Can be a translation key or by default raw text.
@param {string} [valueClass=""] - Additional class, useful when component is used directly on a grid system
*/
const props = defineProps({
value: {
type: String,
required: true,
},
valueClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<h5 :class="['stat-value', props.valueClass]">
{{ $t(props.value, props.value) }}
</h5>
</template>

View file

@ -0,0 +1,19 @@
<script setup>
const props = defineProps({
title: {
type: String,
required: true,
},
titleClass: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<h1 v-if="props.title" :class="[props.titleClass, 'card-title']">
{{ $t(props.title, props.title) }}
</h1>
</template>

View file

@ -1,5 +1,5 @@
<script setup>
import { computed } from 'vue';
import { computed } from "vue";
/**
@name Widget/Grid.vue
@ -16,17 +16,19 @@ import { computed } from 'vue';
*/
const props = defineProps({
gridClass : {
type: String,
required: false,
default: "items-start"
},
})
gridClass: {
type: String,
required: false,
default: "items-start",
},
});
</script>
<template>
<div data-grid :class="[props.gridClass, 'col-span-12 grid grid-cols-12 w-full']">
<div
data-grid
:class="[props.gridClass, 'col-span-12 grid grid-cols-12 w-full relative']"
>
<slot></slot>
</div>
</div>
</template>

View file

@ -62,11 +62,6 @@ const gridClass = computed(() => {
return `break-words grid grid-cols-12 w-full col-span-${props.columns.mobile} md:col-span-${props.columns.tablet} lg:col-span-${props.columns.pc}`;
});
const titleClass = computed(() => {
if (props.type === "card") return "text-2xl font-bold mb-2";
return "";
});
const gridLayoutEl = ref();
onMounted(() => {
@ -88,9 +83,6 @@ onMounted(() => {
data-grid-layout
:class="[containerClass, gridClass, props.gridLayoutClass, 'p-4']"
>
<h1 v-if="props.title" :class="[titleClass, 'col-span-12']">
{{ $t(props.title, props.title) }}
</h1>
<slot></slot>
</component>
</template>

View file

@ -1,10 +1,12 @@
<script setup>
import { onMounted, ref } from "vue";
import Icons from "@components/Widget/Icons.vue";
import StatTitle from "@components/Stat/Title.vue";
import StatValue from "@components/Stat/Value.vue";
import StatSubtitle from "@components/Stat/Subtitle.vue";
import StatIcon from "@components/Stat/Icon.vue";
/**
@name Widget/Stat.vue
@description This component is a basic stat element that can be used to display a title, a value and an icon.
@description This component is wrapper of all stat components.
This component has no grid system and will always get the full width of the parent.
This component is mainly use inside a blank card.
@example
@ -71,27 +73,18 @@ const props = defineProps({
props.iconName ? 'is-icon' : 'no-icon',
]"
>
<p class="stat-title">{{ $t(props.title, props.title) }}</p>
<!-- version of user -->
<h5 class="stat-value">{{ $t(props.value, props.value) }}</h5>
<p v-if="props.subtitle" :class="['stat-subtitle', props.subtitleColor]">
{{ $t(props.subtitle, props.subtitle) }}
</p>
</div>
<!-- end text -->
<!-- icon -->
<div
v-if="props.iconName"
role="img"
aria-label="version"
:class="['stat-svg-container', props.iconColor]"
>
<Icons
:iconName="props.iconName"
:iconClass="'stat-svg'"
:iconColor="'white'"
<StatTitle :title="props.title" />
<StatValue :value="props.value" />
<StatSubtitle
v-if="props.subtitle"
:subtitle="props.subtitle"
:subtitleColor="props.subtitleColor"
/>
</div>
<!-- end icon -->
<StatIcon
v-if="props.iconName"
:iconName="props.iconName"
:iconColor="props.iconColor"
/>
</div>
</template>

View file

@ -4,44 +4,115 @@ import Builder from "@components/Builder.vue";
// Define reactive properties
const data = reactive({
// Define properties here
title : 'No title'
})
// Define properties here
title: "No title",
});
// Retrieve default or flask data if available
onBeforeMount(() => {
const dataEl = document.querySelector('[data-flask]');
data.title = dataEl.getAttribute('data-flask') && dataEl.getAttribute('data-flask') !== "{{ flask_data }}" ? dataEl.getAttribute('data-flask') : dataEl.getAttribute('data-default-value');
})
const dataEl = document.querySelector("[data-flask]");
data.title =
dataEl.getAttribute("data-flask") &&
dataEl.getAttribute("data-flask") !== "{{ flask_data }}"
? dataEl.getAttribute("data-flask")
: dataEl.getAttribute("data-default-value");
});
// Example of builder
const builder = [{
// we are starting with the top level container name
// this can be a "card", "modal", "table"... etc
"type": "card",
"containerClass": "", // tailwind css grid class (items-start, ...)
"containerColumns" : {"pc": 12, "tablet": 12, "mobile": 12},
// container title
"title" : "My awesome card",
// Each widget need a name (here type) and associated data
// We need to send specific data for each widget type
widgets: [
{
type : "Checkbox",
data : {containerClass : "", columns : {"pc": 6, "tablet": 12, "mobile": 12}, id:"test-check", value: "yes", label: "Checkbox", name: "checkbox", required: true, version: "v1.0.0", hideLabel: false, headerClass: "" }
}, {
type : "Select",
data : {containerClass : "", columns : {"pc": 6, "tablet": 12, "mobile": 12}, id: 'test-select', value: 'yes', values: ['yes', 'no'], name: 'test-select', disabled: false, required: true, label: 'Test select', tabId: '1',}
}
]
},
]
const builder = [
{
// we are starting with the top level container name
// this can be a "card", "modal", "table"... etc
type: "card",
containerColumns: { pc: 12, tablet: 12, mobile: 12 },
// Each widget need a name (here type) and associated data
// We need to send specific data for each widget type
widgets: [
{
type: "TitleCard",
data: { title: "My card title" },
},
{
type: "Checkbox",
data: {
containerClass: "",
columns: { pc: 6, tablet: 12, mobile: 12 },
id: "test-check",
value: "yes",
label: "Checkbox",
name: "checkbox",
required: true,
version: "v1.0.0",
hideLabel: false,
headerClass: "",
},
},
{
type: "Select",
data: {
containerClass: "",
columns: { pc: 6, tablet: 12, mobile: 12 },
id: "test-select",
value: "yes",
values: ["yes", "no"],
name: "test-select",
disabled: false,
required: true,
label: "Test select",
tabId: "1",
},
},
],
},
{
// we are starting with the top level container name
// this can be a "card", "modal", "table"... etc
type: "card",
containerClass: "", // tailwind css grid class (items-start, ...)
containerColumns: { pc: 12, tablet: 12, mobile: 12 },
// container title
title: "My awesome card",
// Each widget need a name (here type) and associated data
// We need to send specific data for each widget type
widgets: [
{
type: "StatIcon",
data: {
iconName: "crown",
iconColor: "yellow",
iconClass: "col-span-12",
},
},
{
type: "StatTitle",
data: {
title: "stat title",
titleClass: "col-span-12",
},
},
{
type: "StatValue",
data: {
value: "20",
valueClass: "col-span-12",
},
},
{
type: "StatSubtitle",
data: {
subtitle: "some subtitle",
subtitleClass: "col-span-12",
},
},
],
},
];
</script>
<template>
<div class="bg-secondary flex flex-col items-center justify-center h-full">
<div style="width: 600px;">
<Builder :builder="builder" />
</div>
<div class="bg-secondary flex flex-col items-center justify-center h-full">
<div style="width: 600px">
<Builder :builder="builder" />
</div>
</div>
</template>