diff --git a/src/plugins/util/components.js b/src/plugins/util/components.js index 15941206..969beb9f 100644 --- a/src/plugins/util/components.js +++ b/src/plugins/util/components.js @@ -246,4 +246,168 @@ const textInput = (() => { return {createTextInput}; })(); -module.exports = {switchButton, slider, textInput}; +const dropdownSelect = (() => { + /** + * Creates a custom dropdown component. + * @param {Object} options - Configuration options for the dropdown. + * @param {Array} [options.items] - The initial list of items to display in the dropdown menu. + * @param {Function} [options.onChange] - Optional callback triggered when the selected value changes. + * @param {string} [options.value] - Initial selected value. + * @returns {Object} - Dropdown component with methods to interact with it. + */ + const createDropdown = ({items = [], onChange = null, value = ''}) => { + let selectedValue = value || 'Select...'; + const dropdownDiv = document.createElement('div'); + dropdownDiv.className = 'dropdown'; + + // Create the div displaying the selected value + const selectedValueDiv = document.createElement('div'); + selectedValueDiv.className = 'dropdown-selected'; + selectedValueDiv.textContent = value || 'Select...'; + dropdownDiv.appendChild(selectedValueDiv); + + // Create the menu div where options will be appended + const dropdownMenuDiv = document.createElement('div'); + dropdownMenuDiv.className = 'dropdown-menu'; + dropdownDiv.appendChild(dropdownMenuDiv); + + /** + * Synchronizes the width of the dropdown menu to match the selected value display. + */ + const synchronizeMenuWidth = () => { + const width = selectedValueDiv.offsetWidth; + dropdownMenuDiv.style.width = `${width}px`; + }; + + /** + * Gets the current selected value from the dropdown. + * @returns {string} - The current selected value. + */ + const getValue = () => selectedValue; + + /** + * Sets the selected value in the dropdown and triggers onChange if necessary. + * @param {object} item - The new selected item. + * @param {boolean} [triggerOnChange=false] - Whether to trigger the onChange callback. + */ + const setValue = (item, triggerOnChange = false) => { + let itemValue, valueToDisplay; + if (typeof item === 'string') { + itemValue = item; + valueToDisplay = item; + } else if (typeof item === 'object' && 'value' in item) { + itemValue = item.value; + if (item.valueToDisplay) { + valueToDisplay = item.valueToDisplay; + } else { + valueToDisplay = item.value; + } + } else if (typeof item === 'object') { + itemValue = item.element.textContent; + valueToDisplay = item.element.textContent; + } + + // Only update if the new value is different from the current one + if (selectedValueDiv.innerHTML === itemValue || selectedValueDiv.innerHTML === valueToDisplay) { + return; + } + selectedValueDiv.innerHTML = valueToDisplay || itemValue; + selectedValue = itemValue; + // Trigger onChange callback if provided + if (triggerOnChange && onChange) { + onChange(selectedValue); + } + }; + + /** + * Updates the options displayed in the dropdown menu. + * Clears current options and appends the new ones. + * @param {Array} newItems - The new list of items to display in the dropdown. + */ + const updateOptions = (newItems) => { + // Clear current options before appending new ones + dropdownMenuDiv.innerHTML = ''; + + // Iterate through newItems to create and append dropdown options + newItems.forEach((item) => { + const optionDiv = document.createElement('div'); + optionDiv.className = 'dropdown-item'; + + // Check if the item is a string, object with label, or a custom element + if (typeof item === 'string') { + optionDiv.innerHTML = item; + } else if (typeof item === 'object' && item.label) { + optionDiv.innerHTML = item.label; + } else if (typeof item === 'object' && item.element && item.element instanceof HTMLElement) { + optionDiv.appendChild(item.element); + } + + // Add event listener for option click + optionDiv.addEventListener('click', () => { + setValue(item, true); + dropdownDiv.classList.remove('open'); + }); + + dropdownMenuDiv.appendChild(optionDiv); + }); + }; + + // Initialize dropdown with provided items + updateOptions(items); + + // Toggle dropdown visibility when the selected value div is clicked + selectedValueDiv.addEventListener('click', () => { + synchronizeMenuWidth(); + dropdownDiv.classList.toggle('open'); + }); + + // Close the dropdown if the user clicks outside of it + document.addEventListener('click', (event) => { + if (!dropdownDiv.contains(event.target)) { + dropdownDiv.classList.remove('open'); + } + }); + + // Return the dropdown element and helper methods for interaction + return { + element: dropdownDiv, + getValue, + setValue, + updateOptions, // Expose method to dynamically update options + }; + }; + + // Expose createDropdown method for external usage + return {createDropdown}; +})(); + +const chipTag = (() => { + let tagDiv = null; + const createChip = ({type= 'success', text = 'Success'}) => { + tagDiv = document.createElement('div'); + tagDiv.className = 'gm-tag-'+type; + + const container = document.createElement('div'); + container.className = 'gm-tag-container'; + container.textContent = text; + tagDiv.appendChild(container); + + const setType = (newType) => { + tagDiv.className = 'gm-tag-'+newType; + }; + + const setValue = (newText) => { + container.textContent = newText; + }; + + return { + element: tagDiv, + setType, + setValue, + }; + }; + + return {createChip}; +})(); + +module.exports = {switchButton, slider, textInput, dropdownSelect, chipTag}; diff --git a/src/scss/base/_variables.scss b/src/scss/base/_variables.scss index 572d8763..8201e906 100644 --- a/src/scss/base/_variables.scss +++ b/src/scss/base/_variables.scss @@ -6,6 +6,8 @@ --gm-text-color: #ffffff; --gm-primary-color: #e6195e; --gm-secondary-color: #292929; + --gm-third-color: #1a1a1a; + --gm-fourth-color: #C4C4C4; --gm-success-color: #11b920; --gm-warning-color: #ffcc00; --gm-error-color: #ff0000; @@ -23,9 +25,9 @@ --gm-btn-text-color: var(--gm-text-color); --gm-btn-bg-color: var(--gm-primary-color); --gm-btn-bg-color-hover: var(--gm-primary-color); - --gm-btn-bg-color-disabled: rgba(179, 179, 179, 0.24); + --gm-btn-bg-color-disabled: rgba(179, 179, 179, 0.24); // TODO --gm-btn-bg-color-disabled-hover: #828282; - --gm-btn-color-disabled: #c4c4c4; + --gm-btn-color-disabled: var(--gm-fourth-color); /** Input **/ --gm-input-text-color: var(--gm-text-color); diff --git a/src/scss/components/_chipTag.scss b/src/scss/components/_chipTag.scss new file mode 100644 index 00000000..7bb92d86 --- /dev/null +++ b/src/scss/components/_chipTag.scss @@ -0,0 +1,31 @@ + + .gm-tag{ + &-success{ + .gm-tag-container{ + display: flex; + align-items: center; + border-radius: 8px; + padding: 2px 6px; + color: var(--gm-success-color); + background-color: color-mix(in srgb, var(--gm-success-color), transparent 80%); + + &::after{ + padding-left: 5px; + content: ' '; + background-color: var(--gm-success-color); + mask-image: url('../assets/images/ic_check.svg'); + -webkit-mask-image: url('../assets/images/ic_check.svg'); + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + width: 13px; + height: 13px; + display: block; + margin-left: 5px; + } + } + } + } diff --git a/src/scss/components/_dropdown.scss b/src/scss/components/_dropdown.scss new file mode 100644 index 00000000..49d4a2da --- /dev/null +++ b/src/scss/components/_dropdown.scss @@ -0,0 +1,67 @@ +.dropdown { + position: relative; + font-family: Arial, sans-serif; + &.open { + .dropdown-menu { + display: block; + } + .dropdown-selected { + border-color: var(--gm-primary-color); + border-width: 2px; + &::after { + transform: rotate(-45deg); + } + + } + } + + .dropdown-selected { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0px 15px 0px; + cursor: pointer; + + transition: transform 0.2s ease; + transition: border-color 0.2s ease; + border-bottom: 1px solid var(--gm-fourth-color); + + &::after { + content: ''; + width: 8px; + height: 8px; + margin-right: 5px; + border: solid var(--gm-text-color); + border-width: 2px 2px 0 0; + transform: rotate(135deg); + transform-origin: center; + transition: transform 0.2s ease; + } + } + + .dropdown-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + z-index: 10; + margin-top: 5px; + + padding: 10px 0; + gap: 6px; + border-radius: 4px; + background-color: var(--gm-third-color); + box-shadow: 0px 1px 4px 0px var(--gm-secondary-color); + + .dropdown-item { + padding: 16px 20px; + cursor: pointer; + } + + .dropdown-item:hover { + background: var(--gm-primary-color); + } + } + +} + diff --git a/src/scss/main.scss b/src/scss/main.scss index f9a05661..7025cbdb 100644 --- a/src/scss/main.scss +++ b/src/scss/main.scss @@ -26,3 +26,5 @@ @import 'components/_slider'; @import 'components/_switchButton'; @import 'components/_textInput'; +@import 'components/_dropdown'; +@import 'components/_chipTag'; \ No newline at end of file