diff --git a/jsdoc/Select.js b/jsdoc/Select.js new file mode 100644 index 000000000..0e9f9a38c --- /dev/null +++ b/jsdoc/Select.js @@ -0,0 +1,181 @@ +import { ref, reactive, watch, onMounted, defineEmits, defineProps } from "vue"; +import { contentIndex } from "@utils/tabindex.js"; +import Container from "@components/Widget/Container.vue"; +import Header from "@components/Forms/Header/Field.vue"; +import ErrorField from "@components/Forms/Error/Field.vue"; + + +/** + @name Select.vue + @description This component is used to create a complete select field input with error handling and label. + We can be more precise by adding values that need to be selected to be valid. + We can also add popover to display more information. + It is mainly use in forms. + @example + { + id: 'test-input', + value: 'yes', + values : ['yes', 'no'], + name: 'test-input', + disabled: false, + required: true, + requiredValues : ['no'], // need required to be checked + label: 'Test select', + } + @param {string} id + @param {string} text - content of the button + @param {string} [type="button"] - button || submit + @param {boolean} [disabled=false] + @param {boolean} [hideText=false] - hide text to only display icon + @param {string} [color="primary"] + @param {string} [size="normal"] - sm || normal || lg || xl + @param {string} [iconName=""] - store on components/Icons/Button + @param {string} [iconColor=""] + @param {object} [eventAttr={}] - {"store" : , "default" : , "value" : , "target" : , "valueExpanded" : "expanded_value"} + @param {string|number} [tabId=""] +*/ + +const props = defineProps({ + // id && value && method + id: { + type: String, + required: true, + }, + columns: { + type: [Object, Boolean], + required: false, + default: false + }, + value: { + type: String, + required: true, + }, + values: { + type: Array, + required: true, + }, + disabled: { + type: Boolean, + required: false, + }, + required: { + type: Boolean, + required: false, + }, + requiredValues : { + type: Array, + required: false, + default : [] + }, + label: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + version: { + type: String, + required: false, + default : "" + }, + hideLabel: { + type: Boolean, + required: false, + }, + containerClass : { + type: String, + required: false, + default : "" + }, + headerClass: { + type: String, + required: false, + default : "" + }, + inpClass: { + type: String, + required: false, + default : "" + }, + tabId: { + type: [String, Number], + required: false, + default: "" + }, +}); + + +// When mounted or when props changed, we want select to display new props values +// When component value change itself, we want to switch to select.value +// To avoid component to send and stick to props values (bad behavior) +// Trick is to use select.value || props.value on template +watch(props, (newProp, oldProp) => { + if (newProp.value !== select.value) { + select.value = ""; + } +}); + +const select = reactive({ + isOpen: false, + // On mounted value is null to display props value + // Then on new select we will switch to select.value + // If we use select.value : props.value + // Component will not re-render after props.value change + value: "", + isValid: !props.required ? true : props.requiredValues.length <= 0 ? true : props.requiredValues.includes(props.value) ? true : false, +}); + +const selectBtn = ref(); +const selectWidth = ref(""); + +// EVENTS +function toggleSelect() { + select.isOpen = select.isOpen ? false : true; +} + +function closeSelect() { + select.isOpen = false; +} + +function changeValue(newValue) { + // Allow on template to switch from prop value to component own value + // Then send the new value to parent + select.value = newValue; + // Check if value is required and if it is in requiredValues + select.isValid = !props.required ? true : props.requiredValues.length <= 0 ? true : props.requiredValues.includes(newValue) ? true : false; + closeSelect(); + return newValue; +} + +// Close select dropdown when clicked outside element +watch(select, () => { + if (select.isOpen) { + document.querySelector("body").addEventListener("click", closeOutside); + } else { + document.querySelector("body").removeEventListener("click", closeOutside); + } +}); + +// Close select when clicked outside logic +function closeOutside(e) { + try { + if (e.target !== selectBtn.value) { + select.isOpen = false; + } + } catch (err) { + select.isOpen = false; + } +} + +onMounted(() => { + selectWidth.value = `${selectBtn.value.clientWidth}px`; + window.addEventListener("resize", () => { + try { + selectWidth.value = `${selectBtn.value.clientWidth}px`; + } catch (err) {} + }); +}); + +const emits = defineEmits(["inp"]); \ No newline at end of file diff --git a/jsdoc/Select.md b/jsdoc/Select.md new file mode 100644 index 000000000..876636788 Binary files /dev/null and b/jsdoc/Select.md differ diff --git a/vuejs/client/src/components/Builder.vue b/vuejs/client/src/components/Builder.vue index 8e7b15162..6ed36027c 100644 --- a/vuejs/client/src/components/Builder.vue +++ b/vuejs/client/src/components/Builder.vue @@ -3,28 +3,19 @@ import { reactive, onBeforeMount } from "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 GridLayout from "@components/Widget/GridLayout.vue"; import Grid from "@components/Widget/Grid.vue"; -/* - COMPONENT DESCRIPTION - * - * - This Builder component is used to create complete pages content with multiple components. - This is an abstract component that will be used to create any kind of page content. - You need to determine each container and each widget inside it. - * - * - PROPS ARGUMENTS - * - * - builder : array, - * - * - PROPS EXAMPLE - * - * - [{ +/** + @name Builder.vue + @description This component is a wrapper to create a complete page using containers and widgets. + We have to define each container and each widget inside it. + This is an abstract component that will be used to create any kind of page content (base dashboard elements like menu and news excluded) + @example + [ + { "type": "card", // this can be a "card", "modal", "table"... etc "containerClass": "", // tailwind css grid class (items-start, ...) "containerColumns" : {"pc": 12, "tablet": 12, "mobile": 12}, @@ -40,10 +31,9 @@ import Grid from "@components/Widget/Grid.vue"; 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',} } ] - }, + } ] - * - * + @param {array} builder - Array of containers and widgets */ const props = defineProps({ @@ -69,6 +59,8 @@ const props = defineProps({ + + diff --git a/vuejs/client/src/components/Forms/Error/Field.vue b/vuejs/client/src/components/Forms/Error/Field.vue index 5e95b551c..19d83923a 100644 --- a/vuejs/client/src/components/Forms/Error/Field.vue +++ b/vuejs/client/src/components/Forms/Error/Field.vue @@ -1,26 +1,16 @@