mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
start input list with add and delete actions
This commit is contained in:
parent
9f1dbef6e3
commit
9400784fe3
8 changed files with 583 additions and 23 deletions
|
|
@ -179,6 +179,11 @@ body {
|
|||
@apply font-bold text-red-500 absolute;
|
||||
}
|
||||
|
||||
|
||||
.input-error-dropdown-msg {
|
||||
@apply text-red-500 text-sm mb-0 font-semibold leading-normal tracking-normal;
|
||||
}
|
||||
|
||||
.input-error-msg {
|
||||
@apply absolute text-red-500 text-[0.75rem] font-semibold mb-0 mt-0.5;
|
||||
}
|
||||
|
|
@ -211,6 +216,14 @@ body {
|
|||
@apply pointer-events-none text-green-500 -z-10 opacity-0;
|
||||
}
|
||||
|
||||
.input-list-add {
|
||||
@apply transition absolute top-1 right-9 cursor-pointer transition-transform h-6 w-6 disabled:cursor-not-allowed disabled:opacity-50 disabled:dark:opacity-75;
|
||||
}
|
||||
|
||||
.input-list-svg {
|
||||
@apply absolute top-1.5 right-2 pointer-events-none transition-transform h-6 w-6 fill-gray-600 dark:fill-gray-500;
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
@apply relative z-10 mt-1;
|
||||
}
|
||||
|
|
@ -256,10 +269,6 @@ body {
|
|||
@apply flex flex-col max-h-[200px] overflow-x-hidden overflow-y-auto;
|
||||
}
|
||||
|
||||
.combobox-no-match {
|
||||
@apply text-[0.75rem] font-semibold mb-0 mt-0.5;
|
||||
}
|
||||
|
||||
.select-dropdown-container {
|
||||
@apply flex max-h-[200px] overflow-x-hidden overflow-y-auto flex-col w-fit mt-2;
|
||||
}
|
||||
|
|
@ -277,7 +286,7 @@ body {
|
|||
}
|
||||
|
||||
.select-dropdown-btn {
|
||||
@apply outline-offset-[-4px] border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-6 py-2 text-center align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-normal dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300;
|
||||
@apply outline-offset-[-4px] border-b border-l border-r border-gray-300 hover:brightness-90 bg-white text-gray-700 my-0 relative px-2 py-2 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-normal dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.active.select-dropdown-btn {
|
||||
|
|
@ -558,13 +567,17 @@ body {
|
|||
}
|
||||
|
||||
.icon-container {
|
||||
@apply block w-fit pointer-events-none;
|
||||
@apply block w-fit pointer-events-none;
|
||||
}
|
||||
|
||||
.stick.icon-container {
|
||||
@apply absolute top-0 right-0 min-w-12 dark:brightness-90 flex justify-center items-center w-12 h-12 text-center rounded-circle;
|
||||
}
|
||||
|
||||
.icon-input {
|
||||
@apply h-6 w-6 relative pointer-events-none;
|
||||
}
|
||||
|
||||
.icon-social {
|
||||
@apply hover:opacity-80 pointer-events-none;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
63
src/client/vite/src/components/Forms/Error/Dropdown.vue
Normal file
63
src/client/vite/src/components/Forms/Error/Dropdown.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<script setup>
|
||||
/**
|
||||
@name Forms/Error/Dropdown.vue
|
||||
@description This component is used to display a feedback message on a dropdown field.
|
||||
It is used with /Forms/Field components.
|
||||
@example
|
||||
{
|
||||
isValid: false,
|
||||
isValue: false,
|
||||
}
|
||||
@param {boolean} [isValid=false] - Check if the field is valid
|
||||
@param {boolean} [isValue=false] - Check if the field has a value, display a different message if the field is empty or not
|
||||
@param {boolean} [isValueTaken=false] - Check if input is already taken. Use with list input.
|
||||
@param {string} [errorClass=""] - Additional class
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
isValid: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
isValue: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
isValueTaken: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
errorClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:aria-hidden="props.isValid ? 'true' : 'false'"
|
||||
:class="[
|
||||
props.isValid ? 'hidden' : '',
|
||||
'select-dropdown-btn last first',
|
||||
props.errorClass,
|
||||
]"
|
||||
role="alert"
|
||||
>
|
||||
<p class="input-error-dropdown-msg">
|
||||
{{
|
||||
props.isValid
|
||||
? $t("inp_input_valid")
|
||||
: props.isNoMatch
|
||||
? $t("inp_input_no_match")
|
||||
: props.isValueTaken
|
||||
? $t("inp_input_error_taken")
|
||||
: !props.isValue
|
||||
? $t("inp_input_error_required")
|
||||
: $t("inp_input_error")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
}
|
||||
@param {boolean} [isValid=false] - Check if the field is valid
|
||||
@param {boolean} [isValue=false] - Check if the field has a value, display a different message if the field is empty or not
|
||||
@param {boolean} [isValueTaken=false] - Check if input is already taken. Use with list input.
|
||||
@param {string} [errorClass=""] - Additional class
|
||||
*/
|
||||
|
||||
|
|
@ -22,6 +23,11 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
isValueTaken: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
errorClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -39,6 +45,10 @@ const props = defineProps({
|
|||
{{
|
||||
props.isValid
|
||||
? $t("inp_input_valid")
|
||||
: props.isNoMatch
|
||||
? $t("inp_input_no_match")
|
||||
: props.isValueTaken
|
||||
? $t("inp_input_error_taken")
|
||||
: !props.isValue
|
||||
? $t("inp_input_error_required")
|
||||
: $t("inp_input_error")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ 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";
|
||||
import ErrorDropdown from "@components/Forms/Error/Dropdown.vue";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
|
|
@ -431,18 +432,14 @@ const emits = defineEmits(["inp"]);
|
|||
:value="inp.value"
|
||||
:type="'text'"
|
||||
/>
|
||||
<div
|
||||
class="select-dropdown-btn"
|
||||
<ErrorDropdown
|
||||
v-if="!inp.isMatching"
|
||||
:aria-hidden="!inp.isMatching ? 'true' : 'false'"
|
||||
role="alert"
|
||||
>
|
||||
<p class="combobox-no-match">
|
||||
{{ $t("inp_combobox_no_match") }}
|
||||
</p>
|
||||
</div>
|
||||
:isNoMatch="true"
|
||||
:isValid="false"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="inp.isMatching"
|
||||
data-select-dropdown-items
|
||||
:id="`${inp.id}-list`"
|
||||
:aria-hidden="select.isOpen ? 'false' : 'true'"
|
||||
|
|
|
|||
463
src/client/vite/src/components/Forms/Field/List.vue
Normal file
463
src/client/vite/src/components/Forms/Field/List.vue
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
watch,
|
||||
onMounted,
|
||||
defineEmits,
|
||||
defineProps,
|
||||
computed,
|
||||
onBeforeMount,
|
||||
} 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";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
import Icons from "@components/Widget/Icons.vue";
|
||||
import ErrorDropdown from "@components/Forms/Error/Dropdown.vue";
|
||||
|
||||
/**
|
||||
@name Forms/Field/List.vue
|
||||
@description This component is used display list of values in a dropdown, remove or add an item in an easy way.
|
||||
We can also add popover to display more information.
|
||||
@example
|
||||
{
|
||||
id: 'test-input',
|
||||
value: 'yes no maybe',
|
||||
name: 'test-list',
|
||||
label: 'Test list',
|
||||
inpType: "list",
|
||||
popovers : [
|
||||
{
|
||||
text: "This is a popover text",
|
||||
iconName: "info",
|
||||
},]
|
||||
}
|
||||
@param {string} [id=uuidv4()] - Unique id
|
||||
@param {string} label - The label of the field. Can be a translation key or by default raw text.
|
||||
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
|
||||
@param {string} value
|
||||
@param {string} [separator=" "] - Separator to split the value, by default it is a space
|
||||
@param {string} [maxBtnChars=""] - Max char to display in the dropdown button handler.
|
||||
@param {array} [popovers] - List of popovers to display more information
|
||||
@param {string} [inpType="list"] - The type of the field, useful when we have multiple fields in the same container to display the right field
|
||||
@param {boolean} [disabled=false]
|
||||
@param {boolean} [required=false]
|
||||
@param {object} [columns={"pc": "12", "tablet": "12", "mobile": "12}] - Field has a grid system. This allow to get multiple field in the same row if needed.
|
||||
@param {boolean} [hideLabel=false]
|
||||
@param {boolean} [onlyDown=false] - If the dropdown should stay down
|
||||
@param {boolean} [overflowAttrEl=""] - Attribute the element has to check for overflow
|
||||
@param {string} [containerClass=""]
|
||||
@param {string} [inpClass=""]
|
||||
@param {string} [headerClass=""]
|
||||
@param {string|number} [tabId=contentIndex] - The tabindex of the field, by default it is the contentIndex
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
// id && value && method
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: " ",
|
||||
},
|
||||
maxBtnChars: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
inpType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "select",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
popovers: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: [],
|
||||
},
|
||||
hideLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
onlyDown: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
overflowAttrEl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
inpClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
tabId: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: contentIndex,
|
||||
},
|
||||
});
|
||||
|
||||
const inp = reactive({
|
||||
isOpen: false,
|
||||
id: "",
|
||||
value: props.value,
|
||||
values: computed(() => {
|
||||
return inp.value.split(props.separator);
|
||||
}),
|
||||
isValid: computed(() => {
|
||||
// Case enter value start or en by separator
|
||||
if (inp.value.startsWith(props.separator)) return false;
|
||||
if (inp.value.endsWith(props.separator)) return false;
|
||||
if (!inp.value && props.required) return false;
|
||||
if (!props.pattern) return true;
|
||||
// Check if value is valid related to pattern
|
||||
return inp.value.match(new RegExp(props.pattern)) ? true : false;
|
||||
}),
|
||||
enterValue: "",
|
||||
// Check if enter value is already a value
|
||||
isEnterMatching: computed(() => {
|
||||
if (!inp.enterValue) return false;
|
||||
if (!props.value.split(props.separator)) return false;
|
||||
return props.value
|
||||
.split(props.separator)
|
||||
.some((str) => str.toLowerCase() === inp.enterValue.toLowerCase());
|
||||
}),
|
||||
// Check that the current inp.value with the current enter value is valid related to pattern
|
||||
isEnterValid: computed(() => {
|
||||
// Case enter value start or en by separator
|
||||
if (inp.enterValue.startsWith(props.separator)) return false;
|
||||
if (inp.enterValue.endsWith(props.separator)) return false;
|
||||
if (!inp.enterValue) return true;
|
||||
if (!props.required) return true;
|
||||
if (!props.pattern) return true;
|
||||
|
||||
const newValue = inp.enterValue
|
||||
? `${inp.value} ${inp.enterValue}`
|
||||
: inp.value;
|
||||
return newValue.match(new RegExp(props.pattern)) ? true : false;
|
||||
}),
|
||||
});
|
||||
|
||||
const inputEl = ref();
|
||||
const selectWidth = ref("");
|
||||
const selectDropdown = ref();
|
||||
|
||||
// EVENTS
|
||||
function openSelect() {
|
||||
inp.isOpen = true;
|
||||
// Reset input value
|
||||
setTimeout(() => {
|
||||
// Get field container rect
|
||||
const fieldContainer = inputEl.value.closest("[data-field-container]");
|
||||
const parent = props.overflowAttrEl
|
||||
? fieldContainer.closest(`[${props.overflowAttrEl}]`)
|
||||
: fieldContainer.parentElement;
|
||||
// Update position only if parent has overflow
|
||||
const isOverflow = parent.scrollHeight > parent.clientHeight ? true : false;
|
||||
if (!isOverflow) return;
|
||||
|
||||
// Get all rect
|
||||
const selectBtnRect = inputEl.value.getBoundingClientRect();
|
||||
const fieldContainerRect = fieldContainer.getBoundingClientRect();
|
||||
const selectDropRect = selectDropdown.value.getBoundingClientRect();
|
||||
|
||||
const parentRect = parent.getBoundingClientRect();
|
||||
|
||||
const canBeDown = props.onlyDown
|
||||
? true
|
||||
: fieldContainerRect.bottom + selectDropRect.height < parentRect.bottom
|
||||
? true
|
||||
: false;
|
||||
|
||||
if (!canBeDown) {
|
||||
selectDropdown.value.style.top = `-${
|
||||
selectDropRect.height + selectBtnRect.height - 16
|
||||
}px`;
|
||||
}
|
||||
|
||||
if (canBeDown) {
|
||||
selectDropdown.value.style.top = `${selectBtnRect.height}px`;
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function closeSelect() {
|
||||
inp.isOpen = false;
|
||||
}
|
||||
|
||||
// Close select when clicked outside logic
|
||||
function closeOutside(e) {
|
||||
try {
|
||||
if (e.target !== inputEl.value && e.target !== inputEl.value) {
|
||||
inp.isOpen = false;
|
||||
}
|
||||
} catch (err) {
|
||||
inp.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
function closeScroll(e) {
|
||||
if (!e.target) return;
|
||||
// Case not a DOM element (like the document itself)
|
||||
if (e.target.nodeType !== 1) return (inp.isOpen = false);
|
||||
// Case DOM, check if it is the select dropdown
|
||||
if (
|
||||
e.target.hasAttribute("data-select-dropdown") ||
|
||||
e.target.hasAttribute("data-select-dropdown-items")
|
||||
)
|
||||
return;
|
||||
inp.isOpen = false;
|
||||
}
|
||||
|
||||
// Check after a key is pressed if the current active element is the select button
|
||||
// If not close the select
|
||||
function closeTab(e) {
|
||||
if (e.key !== "Tab" && e.key !== "Shift-Tab") return;
|
||||
setTimeout(() => {
|
||||
const activeEl = document.activeElement;
|
||||
if (activeEl.closest("[data-select-dropdown]") !== selectDropdown.value)
|
||||
return (inp.isOpen = false);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// Case the entry is focus and value is valid, add it to the list
|
||||
function addEntry(e) {
|
||||
// check if keyboard event
|
||||
if (e.key && e.key !== "Enter") return;
|
||||
if (!inp.isEnterValid || inp.isEnterMatching) return;
|
||||
if (
|
||||
document.activeElement !== inputEl.value &&
|
||||
!e.target.hasAttribute("data-add-entry")
|
||||
)
|
||||
return;
|
||||
|
||||
inp.value = `${inp.enterValue}${props.separator}${inp.value}`;
|
||||
console.log(inp.value);
|
||||
inp.enterValue = "";
|
||||
}
|
||||
|
||||
// Case the entry is focus and value is valid, add it to the list
|
||||
function deleteValue(value) {
|
||||
inp.value = inp.value
|
||||
.split(props.separator)
|
||||
.filter((val) => val !== value)
|
||||
.join(props.separator);
|
||||
}
|
||||
|
||||
// Close select dropdown when clicked outside element
|
||||
watch(inp, () => {
|
||||
if (inp.isOpen) {
|
||||
window.addEventListener("click", closeOutside);
|
||||
window.addEventListener("scroll", closeScroll, true);
|
||||
window.addEventListener("keydown", closeTab);
|
||||
window.addEventListener("keydown", addEntry);
|
||||
} else {
|
||||
window.removeEventListener("click", closeOutside);
|
||||
window.removeEventListener("scroll", closeScroll, true);
|
||||
window.removeEventListener("keydown", closeTab);
|
||||
window.removeEventListener("keydown", addEntry);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
inp.id = useUUID(props.id);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
selectWidth.value = `${inputEl.value.clientWidth}px`;
|
||||
window.addEventListener("resize", () => {
|
||||
try {
|
||||
selectWidth.value = `${inputEl.value.clientWidth}px`;
|
||||
} catch (err) {}
|
||||
});
|
||||
});
|
||||
|
||||
const emits = defineEmits(["inp"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container
|
||||
data-field-container
|
||||
:class="[inp.isOpen ? 'z-[100]' : '']"
|
||||
:containerClass="`${props.containerClass}`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
<Header
|
||||
:popovers="props.popovers"
|
||||
:required="props.required"
|
||||
:name="props.name"
|
||||
:id="inp.id"
|
||||
:label="props.label"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
||||
<!--custom-->
|
||||
<div class="relative">
|
||||
<div class="input-regular-container">
|
||||
<input
|
||||
data-toggle-dropdown
|
||||
:aria-controls="`${inp.id}-custom`"
|
||||
:aria-expanded="inp.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('inp_list_input_desc')"
|
||||
:tabindex="props.tabId"
|
||||
ref="inputEl"
|
||||
@input="
|
||||
(e) => {
|
||||
inp.enterValue = e.target.value;
|
||||
$emit('inp', inp.value);
|
||||
}
|
||||
"
|
||||
:id="inp.id"
|
||||
:class="[
|
||||
'input-regular',
|
||||
inp.isValid && !inp.isEnterMatching && inp.isEnterValid
|
||||
? 'valid'
|
||||
: 'invalid',
|
||||
props.inpClass,
|
||||
]"
|
||||
@focusin="openSelect()"
|
||||
:required="props.required || false"
|
||||
:readonly="props.readonly || false"
|
||||
:disabled="props.disabled || false"
|
||||
:placeholder="
|
||||
props.placeholder
|
||||
? $t(
|
||||
props.placeholder,
|
||||
$t('dashboard_placeholder', props.placeholder)
|
||||
)
|
||||
: ''
|
||||
"
|
||||
:name="props.name"
|
||||
:value="inp.enterValue"
|
||||
type="text"
|
||||
/>
|
||||
<button
|
||||
data-add-entry
|
||||
@click.prevent="(e) => addEntry(e)"
|
||||
:disabled="
|
||||
inp.isValid &&
|
||||
!inp.isEnterMatching &&
|
||||
inp.isEnterValid &&
|
||||
inp.enterValue
|
||||
? false
|
||||
: true
|
||||
"
|
||||
:data-is="'input'"
|
||||
class="input-list-add"
|
||||
>
|
||||
<Icons :iconName="'plus'" />
|
||||
</button>
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
:class="[inp.isOpen ? '-rotate-180' : '']"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="input-list-svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- dropdown-->
|
||||
<div
|
||||
data-select-dropdown
|
||||
:aria-hidden="inp.isOpen ? 'false' : 'true'"
|
||||
:aria-expanded="inp.isOpen ? 'true' : 'false'"
|
||||
ref="selectDropdown"
|
||||
role="listbox"
|
||||
:style="{ width: selectWidth }"
|
||||
:id="`${inp.id}-custom`"
|
||||
:class="[inp.isOpen ? 'open' : 'close']"
|
||||
class="select-dropdown-container"
|
||||
:aria-description="$t('inp_select_dropdown_desc')"
|
||||
>
|
||||
<ErrorDropdown
|
||||
v-if="!inp.isMatching || !inp.isEnterValid || !inp.isValid"
|
||||
:isValid="inp.isValid && !inp.isEnterMatching && inp.isEnterValid"
|
||||
:isValue="!!inp.value"
|
||||
:isValueTaken="inp.isEnterMatching"
|
||||
/>
|
||||
<button
|
||||
@click="deleteValue(value)"
|
||||
v-if="inp.isValid && !inp.isEnterMatching && inp.isEnterValid"
|
||||
role="option"
|
||||
:tabindex="inp.isOpen ? props.tabId : '-1'"
|
||||
v-for="(value, id) in inp.values"
|
||||
:class="[
|
||||
id === 0 ? 'first' : '',
|
||||
id === inp.values.length - 1 ? 'last' : '',
|
||||
'select-dropdown-btn',
|
||||
]"
|
||||
data-select-item
|
||||
:data-setting-id="inp.id"
|
||||
:data-setting-value="value"
|
||||
:aria-controls="`${inp.id}-text`"
|
||||
>
|
||||
{{ value }}
|
||||
</button>
|
||||
</div>
|
||||
<ErrorField
|
||||
v-if="!inp.isOpen"
|
||||
:errorClass="'input'"
|
||||
:isValid="inp.isValid && !inp.isEnterMatching && inp.isEnterValid"
|
||||
:isValue="!!inp.value"
|
||||
:isValueTaken="inp.isEnterMatching"
|
||||
/>
|
||||
<!-- end dropdown-->
|
||||
</div>
|
||||
<!-- end custom-->
|
||||
</Container>
|
||||
</template>
|
||||
|
|
@ -102,13 +102,14 @@
|
|||
"dashboard_no_match_filter" : "No match found with filter",
|
||||
"dashboard_something_wrong": "Something is wrong",
|
||||
"inp_input_valid": "input valid",
|
||||
"inp_input_error_no_match": "No match found",
|
||||
"inp_input_error_required": "input is required",
|
||||
"inp_input_error": "input is invalid",
|
||||
"inp_input_error_taken": "value already taken",
|
||||
"inp_popover_multisite": "This setting is multisite.",
|
||||
"inp_popover_global": "This setting is global.",
|
||||
"inp_popover_method_disabled": "This setting method (scheduler, autoconf...) unable value change.",
|
||||
"inp_combobox": "Combobox input for relate select radio group.",
|
||||
"inp_combobox_no_match": "No match found",
|
||||
"inp_select_dropdown_button_desc": "Toggle hide/show radio group (dropdown) to change value.",
|
||||
"inp_select_dropdown_desc": "Radio group (dropdown) to change value.",
|
||||
"inp_input_password_desc": "Toggle hide/show password.",
|
||||
|
|
@ -126,7 +127,9 @@
|
|||
"inp_editor_desc": "Editor input behaving like a code editor.",
|
||||
"inp_input_clipboard_copied": "copied to clipboard",
|
||||
"inp_input_clipboard_desc": "Copy to clipboard on click.",
|
||||
"inp_templates_desc": "Choose a template. This will override de",
|
||||
"inp_templates_desc": "Choose a template. Switching will reset none save settings update.",
|
||||
"inp_list_enter_match": "This value already match existing list item",
|
||||
"inp_list_invalid_entry" : "This value is invalid for list",
|
||||
"icons_cross_desc": "Cross icon representing a close, delete, error or cancel state.",
|
||||
"icons_check_desc": "Check icon representing a success, valid or active state.",
|
||||
"icons_core_desc": "Core icon representing a core setting or plugin.",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
<script setup>
|
||||
import { reactive, onBeforeMount, triggerRef } from "vue";
|
||||
import Icons from "@components/Widget/Icons.vue";
|
||||
import InputList from "@components/Forms/Field/List.vue";
|
||||
import GridLayout from "@components/Widget/GridLayout.vue";
|
||||
import DashboardLayout from "@components/Dashboard/Layout.vue";
|
||||
|
||||
const icon = {
|
||||
iconName: "wire",
|
||||
const list = {
|
||||
id: "test-input",
|
||||
value: "yes no maybe",
|
||||
name: "test-list",
|
||||
label: "Test list",
|
||||
inpType: "list",
|
||||
onlyDown: true,
|
||||
popovers: [
|
||||
{
|
||||
text: "This is a popover text",
|
||||
iconName: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 12, tablet: 12, mobile: 12 },
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -13,9 +26,7 @@ const icon = {
|
|||
<DashboardLayout>
|
||||
<GridLayout :columns="{ pc: 12, tablet: 12, mobile: 12 }">
|
||||
<!-- widget grid -->
|
||||
<div data-is="menu">
|
||||
<Icons v-bind="icon" />
|
||||
</div>
|
||||
<InputList v-bind="list" />
|
||||
</GridLayout>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in a new issue