Bugzilla – Attachment 188878 Details for
Bug 41151
Vue Additional Fields UX Improvements
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 41151: Add optional clear button to standard input components
Bug-41151-Add-optional-clear-button-to-standard-in.patch (text/plain), 9.41 KB, created by
Martin Renvoize (ashimema)
on 2025-11-01 14:47:41 UTC
(
hide
)
Description:
Bug 41151: Add optional clear button to standard input components
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2025-11-01 14:47:41 UTC
Size:
9.41 KB
patch
obsolete
>From f1e03cec557c9d169af2a7dea35597a70bb3f65c Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Sat, 1 Nov 2025 14:24:48 +0000 >Subject: [PATCH] Bug 41151: Add optional clear button to standard input > components > >This patch enhances the core input components (InputText, InputNumber, >and TextArea) with optional clear button functionality, providing a >consistent UX across both standard fields and additional fields. > >Changes: >- Added optional 'clearable' prop (default: true) to all three components >- Implemented clear button with identical styling to additional fields >- Clear button only appears when field has content >- Can be disabled per-field with clearable: false if needed > >The clear button: >- Appears as a small 'x' icon on the right side of the input >- Only shows when the field contains a value >- Uses consistent styling and behavior across all input types >- Includes proper ARIA labels for accessibility >- Has hover and focus states for better UX > >Usage (enabled by default): > <InputText v-model="myValue" /> > <InputNumber v-model="myNumber" /> > <TextArea v-model="myText" /> > >To disable for specific fields: > <InputText :clearable="false" v-model="myValue" /> > >This provides developers with a simple, modern clear button UX by >default across all form inputs, ensuring consistency throughout the >entire application. >--- > .../vue/components/Elements/InputNumber.vue | 80 ++++++++++++++++++- > .../js/vue/components/Elements/InputText.vue | 79 +++++++++++++++++- > .../js/vue/components/Elements/TextArea.vue | 79 +++++++++++++++++- > 3 files changed, 232 insertions(+), 6 deletions(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputNumber.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputNumber.vue >index a3884942bf0..ed85c49d75e 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputNumber.vue >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputNumber.vue >@@ -1,5 +1,27 @@ > <template> >+ <span v-if="clearable" class="input-group"> >+ <input >+ :id="id" >+ inputmode="numeric" >+ v-model="model" >+ :placeholder="placeholder" >+ :required="required" >+ :size="size" >+ class="input-with-clear" >+ /> >+ <button >+ v-if="model !== null && model !== undefined && model !== ''" >+ type="button" >+ class="clear-button" >+ @click="clearInput" >+ :aria-label="$__('Clear')" >+ tabindex="-1" >+ > >+ <i class="fa fa-times"></i> >+ </button> >+ </span> > <input >+ v-else > :id="id" > inputmode="numeric" > v-model="model" >@@ -11,6 +33,7 @@ > > <script> > import { computed } from "vue"; >+import { $__ } from "@koha-vue/i18n"; > export default { > props: { > id: String, >@@ -18,6 +41,10 @@ export default { > placeholder: String, > required: Boolean, > size: Number | null, >+ clearable: { >+ type: Boolean, >+ default: true, >+ }, > }, > emits: ["update:modelValue"], > setup(props, { emit }) { >@@ -33,10 +60,59 @@ export default { > emit("update:modelValue", value); > }, > }); >- return { model }; >+ >+ const clearInput = () => { >+ model.value = ""; >+ }; >+ >+ return { model, clearInput, $__ }; > }, > name: "InputNumber", > }; > </script> > >-<style></style> >+<style scoped> >+.input-group { >+ position: relative; >+ display: inline; >+} >+ >+/* Input with clear button gets padding for the button */ >+input.input-with-clear { >+ padding-right: 2rem; >+} >+ >+.clear-button { >+ position: absolute; >+ right: 0.25rem; >+ top: 50%; >+ transform: translateY(-50%); >+ background: transparent; >+ border: none; >+ color: #999; >+ cursor: pointer; >+ padding: 0.25rem 0.5rem; >+ line-height: 1; >+ transition: color 0.15s ease-in-out; >+ z-index: 10; >+ height: 1.5rem; >+ display: flex; >+ align-items: center; >+ justify-content: center; >+ margin: 0; >+} >+ >+.clear-button:hover { >+ color: #333; >+} >+ >+.clear-button:focus { >+ outline: 2px solid #007bff; >+ outline-offset: 2px; >+ border-radius: 2px; >+} >+ >+.clear-button i { >+ font-size: 1rem; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputText.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputText.vue >index 33a6a58badb..cc04c4842b5 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputText.vue >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/InputText.vue >@@ -1,5 +1,26 @@ > <template> >+ <span v-if="clearable" class="input-group"> >+ <input >+ :id="id" >+ type="text" >+ v-model="model" >+ :placeholder="placeholder" >+ :required="required" >+ class="input-with-clear" >+ /> >+ <button >+ v-if="model" >+ type="button" >+ class="clear-button" >+ @click="clearInput" >+ :aria-label="$__('Clear')" >+ tabindex="-1" >+ > >+ <i class="fa fa-times"></i> >+ </button> >+ </span> > <input >+ v-else > :id="id" > type="text" > v-model="model" >@@ -10,12 +31,17 @@ > > <script> > import { computed } from "vue"; >+import { $__ } from "@koha-vue/i18n"; > export default { > props: { > id: String, > modelValue: String, > placeholder: String, > required: Boolean, >+ clearable: { >+ type: Boolean, >+ default: true, >+ }, > }, > emits: ["update:modelValue"], > setup(props, { emit }) { >@@ -27,10 +53,59 @@ export default { > emit("update:modelValue", value); > }, > }); >- return { model }; >+ >+ const clearInput = () => { >+ model.value = ""; >+ }; >+ >+ return { model, clearInput, $__ }; > }, > name: "InputText", > }; > </script> > >-<style></style> >+<style scoped> >+.input-group { >+ position: relative; >+ display: inline; >+} >+ >+/* Input with clear button gets padding for the button */ >+input.input-with-clear { >+ padding-right: 2rem; >+} >+ >+.clear-button { >+ position: absolute; >+ right: 0.25rem; >+ top: 50%; >+ transform: translateY(-50%); >+ background: transparent; >+ border: none; >+ color: #999; >+ cursor: pointer; >+ padding: 0.25rem 0.5rem; >+ line-height: 1; >+ transition: color 0.15s ease-in-out; >+ z-index: 10; >+ height: 1.5rem; >+ display: flex; >+ align-items: center; >+ justify-content: center; >+ margin: 0; >+} >+ >+.clear-button:hover { >+ color: #333; >+} >+ >+.clear-button:focus { >+ outline: 2px solid #007bff; >+ outline-offset: 2px; >+ border-radius: 2px; >+} >+ >+.clear-button i { >+ font-size: 1rem; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/TextArea.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/TextArea.vue >index 0d8761daf2c..4aedd8effbc 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/TextArea.vue >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Elements/TextArea.vue >@@ -1,5 +1,27 @@ > <template> >+ <span v-if="clearable" class="textarea-group"> >+ <textarea >+ :id="id" >+ v-model="model" >+ :rows="rows" >+ :cols="cols" >+ :placeholder="placeholder" >+ :required="required" >+ class="textarea-with-clear" >+ /> >+ <button >+ v-if="model" >+ type="button" >+ class="clear-button" >+ @click="clearInput" >+ :aria-label="$__('Clear')" >+ tabindex="-1" >+ > >+ <i class="fa fa-times"></i> >+ </button> >+ </span> > <textarea >+ v-else > :id="id" > v-model="model" > :rows="rows" >@@ -11,6 +33,7 @@ > > <script> > import { computed } from "vue"; >+import { $__ } from "@koha-vue/i18n"; > export default { > props: { > id: String, >@@ -25,6 +48,10 @@ export default { > type: Number, > default: 50, > }, >+ clearable: { >+ type: Boolean, >+ default: true, >+ }, > }, > emits: ["update:modelValue"], > setup(props, { emit }) { >@@ -36,10 +63,58 @@ export default { > emit("update:modelValue", value); > }, > }); >- return { model }; >+ >+ const clearInput = () => { >+ model.value = ""; >+ }; >+ >+ return { model, clearInput, $__ }; > }, > name: "TextArea", > }; > </script> > >-<style></style> >+<style scoped> >+.textarea-group { >+ position: relative; >+ display: inline-block; >+} >+ >+/* Textarea with clear button gets padding for the button */ >+textarea.textarea-with-clear { >+ padding-right: 2rem; >+} >+ >+.clear-button { >+ position: absolute; >+ right: 0.5rem; >+ top: 0.5rem; >+ background: transparent; >+ border: none; >+ color: #999; >+ cursor: pointer; >+ padding: 0.25rem 0.5rem; >+ line-height: 1; >+ transition: color 0.15s ease-in-out; >+ z-index: 10; >+ height: 1.5rem; >+ display: flex; >+ align-items: center; >+ justify-content: center; >+ margin: 0; >+} >+ >+.clear-button:hover { >+ color: #333; >+} >+ >+.clear-button:focus { >+ outline: 2px solid #007bff; >+ outline-offset: 2px; >+ border-radius: 2px; >+} >+ >+.clear-button i { >+ font-size: 1rem; >+} >+</style> >-- >2.51.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 41151
:
188814
|
188815
|
188816
|
188817
|
188818
|
188819
|
188820
|
188821
|
188822
|
188874
|
188875
|
188876
|
188877
| 188878 |
188879
|
188880