diff --git a/assets/js/formkit-custom.js b/assets/js/formkit-custom.js index 514e616..adaa0bd 100644 --- a/assets/js/formkit-custom.js +++ b/assets/js/formkit-custom.js @@ -3,6 +3,7 @@ import OneTimePassword from "~/components/formkit/OneTimePassword.vue"; import MaskText from "~/components/formkit/TextMask.vue"; import FileDropzone from "~/components/formkit/FileDropzone.vue"; import Switch from "~/components/formkit/Switch.vue"; +import SearchSelect from "~/components/formkit/SearchSelect.vue"; export default { otp: createInput(OneTimePassword, { @@ -17,4 +18,7 @@ export default { switch: createInput(Switch, { props: ["value", "disabled", "name", "id"], }), + searchSelect: createInput(SearchSelect, { + props: ["options", "placeholder"], + }), }; diff --git a/assets/js/formkit-theme.js b/assets/js/formkit-theme.js index f664a52..c59489d 100644 --- a/assets/js/formkit-theme.js +++ b/assets/js/formkit-theme.js @@ -51,6 +51,13 @@ const rangeClassification = { input: "formkit-input-range", }; +const searchSelectClassification = { + label: "formkit-label-search-select", + inner: "formkit-inner-search-select", + input: "formkit-input-search-select", + message: "formkit-message-search-select", +}; + // export our definitions using our above // templates and declare one-offs and // overrides as needed. @@ -99,4 +106,5 @@ export default { inner: "formkit-inner-dropzone", dropzone: "formkit-dropzone", }, + searchSelect: searchSelectClassification, }; diff --git a/assets/style/css/component/common.css b/assets/style/css/component/common.css index b80d16a..ec1444d 100644 --- a/assets/style/css/component/common.css +++ b/assets/style/css/component/common.css @@ -4,7 +4,7 @@ body { } .w-header { - @apply z-20 fixed top-0 right-0 px-5 py-3 duration-300 shadow-md; + @apply z-[1000] fixed top-0 right-0 px-5 py-3 duration-300 shadow-md; background-color: rgb(var(--bg-2)); box-shadow: var(--box-shadow); } diff --git a/assets/style/css/component/modal.css b/assets/style/css/component/modal.css index 4d5f300..c7bfa30 100644 --- a/assets/style/css/component/modal.css +++ b/assets/style/css/component/modal.css @@ -1,6 +1,6 @@ /* RS Modal Component */ .modal { - @apply fixed top-0 left-0 w-full h-full overflow-hidden z-[1000] duration-300; + @apply fixed top-0 left-0 w-full h-full overflow-hidden z-[10000] duration-300; background: rgba(15, 23, 42, 0.5); } diff --git a/assets/style/css/form/searchSelect.css b/assets/style/css/form/searchSelect.css new file mode 100644 index 0000000..a789055 --- /dev/null +++ b/assets/style/css/form/searchSelect.css @@ -0,0 +1,129 @@ +.formkit-label-search-select{ + @apply block mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger; +} + +.formkit-label-searchSelect { + @apply block mb-2 font-semibold text-sm formkit-invalid:text-red-500 dark:formkit-invalid:text-danger; +} + +.formkit-inner-searchSelect { + @apply relative w-full; +} + +.formkit-input-searchSelect { + @apply w-full min-h-[38px] px-3 py-2 + text-sm + rounded-lg + border + border-[rgb(var(--fk-border-color))] + bg-[rgb(var(--bg-2))] + placeholder-secondary + focus:border-primary + focus:outline-none + disabled:opacity-50 + disabled:cursor-not-allowed; +} + +.formkit-message-searchSelect { + @apply formkit-invalid:text-red-500 dark:formkit-invalid:text-danger; +} + +/* Vue Select specific styling to match FormKit theme */ +.v-select { + @apply relative w-full; +} + +.v-select .vs__dropdown-toggle { + @apply flex items-center min-h-[38px] px-3 py-2 + bg-[rgb(var(--bg-2))] + border border-[rgb(var(--fk-border-color))] + rounded-lg + cursor-pointer + transition-all duration-150 ease-in-out; +} + +.v-select:not(.vs--disabled) .vs__dropdown-toggle:hover { + @apply border-[rgb(var(--fk-border-hover-color))]; +} + +.v-select.vs--open .vs__dropdown-toggle { + @apply border-primary shadow-[0_0_0_1px_rgb(var(--fk-primary-color))]; +} + +.v-select.vs--disabled .vs__dropdown-toggle { + @apply bg-[rgb(var(--fk-disabled-bg))] cursor-not-allowed opacity-50; +} + +.v-select .vs__selected-options { + @apply flex items-center flex-1 py-0 px-0 min-h-[1.5rem]; +} + +.v-select .vs__selected { + @apply text-[rgb(var(--fk-text-color))] text-sm m-0 p-0 border-none bg-transparent; +} + +.v-select .vs__search { + @apply bg-transparent border-none outline-none text-sm text-[rgb(var(--fk-text-color))] m-0 p-0 w-auto max-w-full; +} + +.v-select .vs__search::placeholder { + @apply text-[rgb(var(--fk-placeholder-color))]; +} + +.v-select .vs__actions { + @apply flex items-center px-1; +} + +.v-select .vs__open-indicator { + @apply text-[rgb(var(--fk-text-secondary-color))] cursor-pointer transition-transform duration-200 ease-in-out; + fill: currentColor; +} + +.v-select.vs--open .vs__open-indicator { + @apply rotate-180; +} + +.v-select .vs__dropdown-menu { + @apply absolute top-full left-0 right-0 z-[1050] mt-1 + bg-[rgb(var(--bg-2))] + border border-[rgb(var(--fk-border-color))] + rounded-lg + shadow-lg + max-h-[300px] + overflow-y-auto + py-1; +} + +.v-select .vs__dropdown-option { + @apply px-3 py-2 cursor-pointer text-sm text-[rgb(var(--fk-text-color))] transition-colors duration-150 ease-in-out leading-5; +} + +.v-select .vs__dropdown-option--highlight { + @apply bg-[rgb(var(--fk-hover-bg))] text-[rgb(var(--fk-text-color))]; +} + +.v-select .vs__dropdown-option--selected { + @apply bg-primary text-white font-medium; +} + +.v-select .vs__no-options { + @apply px-3 py-3 text-[rgb(var(--fk-text-secondary-color))] italic text-center text-sm; +} + +/* Hide the clear button since we set clearable to false */ +.v-select .vs__clear { + @apply hidden; +} + +/* Ensure proper spacing and alignment */ +.v-select .vs__dropdown-toggle { + @apply px-3 py-2; +} + +.v-select .vs__selected-options { + @apply p-0 m-0; +} + +.v-select .vs__actions { + @apply px-1; +} \ No newline at end of file diff --git a/components/ComponentPreview.vue b/components/ComponentPreview.vue index c011a20..bff41e7 100644 --- a/components/ComponentPreview.vue +++ b/components/ComponentPreview.vue @@ -1105,7 +1105,8 @@ const isInputType = computed(() => { 'text', 'textarea', 'number', 'email', 'password', 'date', 'time', 'datetime-local', 'url', 'tel', 'select', 'checkbox', 'radio', 'file', 'range', - 'color', 'hidden', 'mask', 'otp', 'dropzone', 'switch' + 'color', 'hidden', 'mask', 'otp', 'dropzone', 'switch', + 'searchSelect' // Add our new searchable select component ]; return inputTypes.includes(props.component.type); diff --git a/components/FormBuilderCanvas.vue b/components/FormBuilderCanvas.vue index 23ce394..d6a0bb2 100644 --- a/components/FormBuilderCanvas.vue +++ b/components/FormBuilderCanvas.vue @@ -49,7 +49,7 @@
required
- Field must be filled
- email
- Must be valid email
- url
- Must be valid URL
- number
- Must be numeric
- length:5
- Exactly 5 characters
- length:3,20
- Between 3-20 characters
- min:3
- Minimum 3 characters
- max:50
- Maximum 50 characters
- between:1,100
- Between 1 and 100
- min_value:0
- Minimum value 0
- max_value:999
- Maximum value 999
- integer
- Must be whole number
- alpha
- Only letters (A-Z, a-z)
- alpha_numeric
- Letters and numbers only
- alpha_spaces
- Letters and spaces only
- required|email
- Required email field
- required|length:3,50
- Required text, 3-50 characters
- number|between:1,100
- Number between 1-100
- required|alpha_numeric|min:5
- Required alphanumeric, min 5 chars
- - 💡 Tips: Separate rules with | (pipe) • Order doesn't matter • Leave empty for no validation -
-