From aafd19c2d2a3e8f70f03596630a16b93d2868769 Mon Sep 17 00:00:00 2001
From: VIAUD Nathan <nathan.viaud@inria.fr>
Date: Tue, 23 Jan 2024 09:14:05 +0000
Subject: [PATCH] feat: add hints to form inputs

---
 .../forms/components/inputs/FileInput.vue     |   1 -
 .../forms/components/inputs/GenericInput.vue  | 240 +++++++++++-------
 .../forms/components/inputs/HtmlInput.vue     |   5 +-
 .../forms/components/inputs/ScoreInput.vue    |   1 -
 .../forms/components/inputs/TextAreaInput.vue |   1 -
 .../forms/components/inputs/TextInput.vue     |   1 -
 .../inputs/badges/components/IconPicker.vue   |   5 +-
 .../inputs/card/components/RadioInput.vue     |   1 -
 .../inputs/card/components/SelectInput.vue    |   1 -
 src/global.scss                               |  12 +-
 src/shared/interfaces/form/input.interface.ts |   1 +
 11 files changed, 160 insertions(+), 109 deletions(-)

diff --git a/src/features/forms/components/inputs/FileInput.vue b/src/features/forms/components/inputs/FileInput.vue
index d956af51..ffe14c0c 100644
--- a/src/features/forms/components/inputs/FileInput.vue
+++ b/src/features/forms/components/inputs/FileInput.vue
@@ -73,7 +73,6 @@ let savedState = '';
 </script>
 
 <template>
-    <label class="input-label" :for="id">{{ label }}</label>
     <div v-show="url" class="show-input">
         <div class="input-file">
             <input ref="fileInput" class="file" type="file" :accept="accept" @change="changeImage" />
diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 2492874a..bad6b0f0 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -14,7 +14,7 @@ import BadgesInput from './badges/BadgesInput.vue';
 import IconPicker from './badges/components/IconPicker.vue';
 import ConditionInput from './badges/components/ConditionInput.vue';
 import { Input, RepeatInputEvent } from '@/src/shared/interfaces';
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 
 const props = defineProps<{
     input: Input;
@@ -39,103 +39,149 @@ const inputId = computed(() => {
         return props.input.id;
     }
 });
+
+//? This is a workaround to focus the WYSIWYG editor when clicking on the label
+const htmlInput = ref(null);
+function onLabelClick(inputType: string) {
+    if(inputType !== 'html') return;
+
+    htmlInput.value.focusEditor();
+}
+
+function showLabel(inputType: string) {
+    return inputType !== 'checkbox' && inputType !== 'repeat'
+}
 </script>
 
 <template>
-    <TextInput
-        v-if="input.type === 'text'"
-        :id="inputId"
-        :label="input.label"
-        :placeholder="input.placeholder"
-        :input-value="inputValue as string"
-        :inside-card="insideCard"
-        @input="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <HtmlInput
-        v-if="input.type === 'html'"
-        :id="inputId"
-        :label="input.label"
-        :placeholder="input.placeholder"
-        :input-value="inputValue as string"
-        :inside-card="insideCard"
-        @input="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <TextAreaInput
-        v-if="input.type === 'textarea'"
-        :id="inputId"
-        :label="input.label"
-        :placeholder="input.placeholder"
-        :input-value="inputValue as string"
-        :inside-card="insideCard"
-        @input="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <FileInput
-        v-if="input.type === 'file'"
-        :id="inputId"
-        :label="input.label"
-        :accept="input.accept"
-        :input-value="inputValue as string"
-        :placeholder="input.placeholder"
-        :target-directory="input.targetDirectory"
-        @input="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <ScoreInput
-        v-if="input.type === 'score'"
-        :id="inputId"
-        :label="input.label"
-        :input-value="Number(inputValue)"
-        @input="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <CheckBoxInput
-        v-if="input.type === 'checkbox'"
-        :id="inputId"
-        :label="input.label"
-        :input-value="inputValue as boolean"
-        @change="emit('check', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <RadioInput
-        v-if="input.type === 'radio-group'"
-        :id="inputId"
-        :label="input.label"
-        :input-value="inputValue as string"
-        :pos="pos"
-        @change="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <SelectInput
-        v-if="input.type === 'select'"
-        :id="inputId"
-        :label="input.label"
-        :placeholder="input.placeholder"
-        :input-value="inputValue as string"
-        :options="input.options"
-        :linked-options="input.linkedOptions"
-        @change="emit('input', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <RepeatInput
-        v-if="input.type === 'repeat'"
-        :id="inputId"
-        :label="input.label"
-        :input-values="inputValue as string[]"
-        :inputs="input.inputs"
-        :field-index="fieldIndex"
-        :add-button="input.addButton"
-        @change="emit('repeatInput', $event)"
-        @save-given-state="emit('saveGivenState', $event)"
-    />
-    <BadgesInput v-if="input.type === 'badge'" :input-value="inputValue as string[]" />
-    <IconPicker
-        v-if="input.type === 'icon-picker'"
-        :label="input.label"
-        :input-value="inputValue as string"
-        @input="emit('input', $event)"
-    />
-    <ConditionInput v-if="input.type === 'badge-conditions'" />
+    <div class="input-group">
+        <div
+            v-if="input.label && showLabel(input.type)"
+            class="input-label"
+        >
+            <label
+                :for="inputId"
+                @click="onLabelClick(input.type)"
+            >
+                {{ input.label }}
+            </label>
+            <i
+                v-if="input.hint"
+                v-tippy="{
+                    content: input.hint,
+                    placement: 'right',
+                    arrow: true,
+                    arrowType: 'round',
+                    animation: 'fade',
+                }"
+                class="icon-help-circle"
+            />
+        </div>
+
+        <TextInput
+            v-if="input.type === 'text'"
+            :id="inputId"
+            :label="input.label"
+            :placeholder="input.placeholder"
+            :input-value="inputValue as string"
+            :inside-card="insideCard"
+            @input="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <HtmlInput
+            v-if="input.type === 'html'"
+            :id="inputId"
+            ref="htmlInput"
+            :label="input.label"
+            :placeholder="input.placeholder"
+            :input-value="inputValue as string"
+            :inside-card="insideCard"
+            @input="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <TextAreaInput
+            v-if="input.type === 'textarea'"
+            :id="inputId"
+            :label="input.label"
+            :placeholder="input.placeholder"
+            :input-value="inputValue as string"
+            :inside-card="insideCard"
+            @input="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <FileInput
+            v-if="input.type === 'file'"
+            :id="inputId"
+            :label="input.label"
+            :accept="input.accept"
+            :input-value="inputValue as string"
+            :placeholder="input.placeholder"
+            :target-directory="input.targetDirectory"
+            @input="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <ScoreInput
+            v-if="input.type === 'score'"
+            :id="inputId"
+            :label="input.label"
+            :input-value="Number(inputValue)"
+            @input="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <CheckBoxInput
+            v-if="input.type === 'checkbox'"
+            :id="inputId"
+            :label="input.label"
+            :input-value="inputValue as boolean"
+            @change="emit('check', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <RadioInput
+            v-if="input.type === 'radio-group'"
+            :id="inputId"
+            :label="input.label"
+            :input-value="inputValue as string"
+            :pos="pos"
+            @change="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <SelectInput
+            v-if="input.type === 'select'"
+            :id="inputId"
+            :label="input.label"
+            :placeholder="input.placeholder"
+            :input-value="inputValue as string"
+            :options="input.options"
+            :linked-options="input.linkedOptions"
+            @change="emit('input', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <RepeatInput
+            v-if="input.type === 'repeat'"
+            :id="inputId"
+            :label="input.label"
+            :input-values="inputValue as string[]"
+            :inputs="input.inputs"
+            :field-index="fieldIndex"
+            :add-button="input.addButton"
+            @change="emit('repeatInput', $event)"
+            @save-given-state="emit('saveGivenState', $event)"
+        />
+        <BadgesInput v-if="input.type === 'badge'" :input-value="inputValue as string[]" />
+        <IconPicker
+            v-if="input.type === 'icon-picker'"
+            :label="input.label"
+            :input-value="inputValue as string"
+            @input="emit('input', $event)"
+        />
+        <ConditionInput v-if="input.type === 'badge-conditions'" />
+    </div>
 </template>
+
+<style lang="scss" scoped >
+.input-group {
+    display: flex;
+    flex-direction: column;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/HtmlInput.vue b/src/features/forms/components/inputs/HtmlInput.vue
index 669eb355..3135506e 100644
--- a/src/features/forms/components/inputs/HtmlInput.vue
+++ b/src/features/forms/components/inputs/HtmlInput.vue
@@ -101,10 +101,13 @@ function focusEditor() {
     const editor = getTinymce().activeEditor;
     editor.focus();
 }
+
+defineExpose({
+    focusEditor
+});
 </script>
 
 <template>
-    <label @click="focusEditor">{{ label }}</label>
     <Editor
         ref="editor"
         v-model="content"
diff --git a/src/features/forms/components/inputs/ScoreInput.vue b/src/features/forms/components/inputs/ScoreInput.vue
index 12592033..989b6bbc 100644
--- a/src/features/forms/components/inputs/ScoreInput.vue
+++ b/src/features/forms/components/inputs/ScoreInput.vue
@@ -45,7 +45,6 @@ function onBlur() {
 </script>
 
 <template>
-    <label :for="id">{{ label }}</label>
     <div class="input-score">
         <button @click="minus(inputValue)"><i class="icon-minus-circle"></i></button>
         <input
diff --git a/src/features/forms/components/inputs/TextAreaInput.vue b/src/features/forms/components/inputs/TextAreaInput.vue
index 2788639e..bedc54f6 100644
--- a/src/features/forms/components/inputs/TextAreaInput.vue
+++ b/src/features/forms/components/inputs/TextAreaInput.vue
@@ -32,7 +32,6 @@ function onBlur() {
 </script>
 
 <template>
-    <label class="input-label" :for="id">{{ label }}</label>
     <textarea
         :id="id"
         class="input input-textarea"
diff --git a/src/features/forms/components/inputs/TextInput.vue b/src/features/forms/components/inputs/TextInput.vue
index f590926b..27bdfcd1 100644
--- a/src/features/forms/components/inputs/TextInput.vue
+++ b/src/features/forms/components/inputs/TextInput.vue
@@ -33,7 +33,6 @@ function onBlur() {
 </script>
 
 <template>
-    <label v-if="label !== ''" class="input-label" :for="id">{{ label }}</label>
     <input
         :id="id"
         class="input"
diff --git a/src/features/forms/components/inputs/badges/components/IconPicker.vue b/src/features/forms/components/inputs/badges/components/IconPicker.vue
index 5e7f5b7e..135fe49d 100644
--- a/src/features/forms/components/inputs/badges/components/IconPicker.vue
+++ b/src/features/forms/components/inputs/badges/components/IconPicker.vue
@@ -1,12 +1,10 @@
 <script setup lang="ts">
 import BadgeItem from '@/src/features/badge/components/BadgeItem.vue';
-import { iconsPath } from '@/src/shared/data';
 import { useEditorStore } from '@/src/shared/stores';
-import { computed } from 'vue';
 
 const editorStore = useEditorStore();
 
-const props = defineProps<{
+defineProps<{
     inputValue: string;
     label: string;
 }>();
@@ -17,7 +15,6 @@ function openIconModal() {
 </script>
 
 <template>
-    <label v-if="label !== ''" class="input-label" :for="label">{{ label }}</label>
     <div :id="label" class="container">
         <BadgeItem :icon="inputValue" :view-mode="true" :inactive="!inputValue" />
         <button class="btn btn-form" @click="openIconModal">
diff --git a/src/features/forms/components/inputs/card/components/RadioInput.vue b/src/features/forms/components/inputs/card/components/RadioInput.vue
index 171c9eed..626ca022 100644
--- a/src/features/forms/components/inputs/card/components/RadioInput.vue
+++ b/src/features/forms/components/inputs/card/components/RadioInput.vue
@@ -24,7 +24,6 @@ function onChange(value: string) {
 
 <template>
     <div class="radio">
-        <label class="group-label" :for="String(pos)">Réponse</label>
         <div :id="String(pos)" class="radio-group">
             <div class="radio-btn">
                 <input
diff --git a/src/features/forms/components/inputs/card/components/SelectInput.vue b/src/features/forms/components/inputs/card/components/SelectInput.vue
index c5731431..bdfe371c 100644
--- a/src/features/forms/components/inputs/card/components/SelectInput.vue
+++ b/src/features/forms/components/inputs/card/components/SelectInput.vue
@@ -35,7 +35,6 @@ function onChange(event: Event) {
 
 <template>
     <div class="select">
-        <label :for="id">{{ label }}</label>
         <select :id="id" :value="inputValue" class="select-box" @change="onChange">
             <option value="">Sélectionnez</option>
             <option v-for="(option, index) in getOptions()" :key="index" :value="option">{{ option }}</option>
diff --git a/src/global.scss b/src/global.scss
index 77e6ce88..7194e7ac 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -297,9 +297,19 @@ hr {
 }
 
 .input-label {
-    margin-bottom: 0.5rem;
+    display: flex;
+    gap: .3rem;
+    margin-bottom: 0.7rem;
     font-size: 1rem;
     font-family: 'Open Sans', sans-serif;
+    line-height: 1;
+
+    i {
+        display: flex;
+        align-items: center;
+        margin: 0;
+        color: var(--text-secondary);
+    }
 }
 
 .form-icon {
diff --git a/src/shared/interfaces/form/input.interface.ts b/src/shared/interfaces/form/input.interface.ts
index bbe314e0..f0a1e7a2 100644
--- a/src/shared/interfaces/form/input.interface.ts
+++ b/src/shared/interfaces/form/input.interface.ts
@@ -11,4 +11,5 @@ export interface Input {
     options?: string[];
     linkedOptions?: string;
     targetDirectory?: string;
+    hint?: string;
 }
-- 
GitLab