mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
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:
parent
b417c83474
commit
6434f4f23b
12 changed files with 334 additions and 86 deletions
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
|||
51
vuejs/client/src/components/Stat/Icon.vue
Normal file
51
vuejs/client/src/components/Stat/Icon.vue
Normal 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>
|
||||
43
vuejs/client/src/components/Stat/Subtitle.vue
Normal file
43
vuejs/client/src/components/Stat/Subtitle.vue
Normal 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>
|
||||
33
vuejs/client/src/components/Stat/Title.vue
Normal file
33
vuejs/client/src/components/Stat/Title.vue
Normal 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>
|
||||
33
vuejs/client/src/components/Stat/Value.vue
Normal file
33
vuejs/client/src/components/Stat/Value.vue
Normal 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>
|
||||
19
vuejs/client/src/components/Title/Card.vue
Normal file
19
vuejs/client/src/components/Title/Card.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue