feat(ui) : prop required + aria-required + astérisque sur Select/SelectCheckbox/Upload/RichText
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ type InputRichTextProps = {
|
|||||||
groupClass?: string
|
groupClass?: string
|
||||||
labelClass?: string
|
labelClass?: string
|
||||||
editorClass?: string
|
editorClass?: string
|
||||||
|
required?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputRichTextForTest = InputRichText as DefineComponent<InputRichTextProps>
|
const InputRichTextForTest = InputRichText as DefineComponent<InputRichTextProps>
|
||||||
@@ -162,4 +163,16 @@ describe('MalioInputRichText', () => {
|
|||||||
expect(html).toContain('Mon titre')
|
expect(html).toContain('Mon titre')
|
||||||
expect(html).toContain('Un paragraphe.')
|
expect(html).toContain('Un paragraphe.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('affiche l\'astérisque quand required est vrai', async () => {
|
||||||
|
const wrapper = await mountComponent({label: 'Champ', required: true})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('n\'affiche pas l\'astérisque par défaut', async () => {
|
||||||
|
const wrapper = await mountComponent({label: 'Champ'})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:for="editorId"
|
:for="editorId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}<MalioRequiredMark v-if="required" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- Mode lecture seule (rendu uniquement) -->
|
<!-- Mode lecture seule (rendu uniquement) -->
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
v-else
|
v-else
|
||||||
:id="editorId"
|
:id="editorId"
|
||||||
:class="mergedEditorWrapperClass"
|
:class="mergedEditorWrapperClass"
|
||||||
|
:aria-required="required || undefined"
|
||||||
@click="focusEditor"
|
@click="focusEditor"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -202,6 +203,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useId, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useId, watch } from 'vue'
|
||||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||||
|
import MalioRequiredMark from '../shared/RequiredMark.vue'
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
@@ -232,6 +234,7 @@ const props = withDefaults(
|
|||||||
groupClass?: string
|
groupClass?: string
|
||||||
labelClass?: string
|
labelClass?: string
|
||||||
editorClass?: string
|
editorClass?: string
|
||||||
|
required?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@@ -249,6 +252,7 @@ const props = withDefaults(
|
|||||||
groupClass: '',
|
groupClass: '',
|
||||||
labelClass: '',
|
labelClass: '',
|
||||||
editorClass: '',
|
editorClass: '',
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type InputUploadProps = {
|
|||||||
success?: string
|
success?: string
|
||||||
displayIcon?: boolean
|
displayIcon?: boolean
|
||||||
accept?: string
|
accept?: string
|
||||||
|
required?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputUploadForTest = InputUpload as DefineComponent<InputUploadProps>
|
const InputUploadForTest = InputUpload as DefineComponent<InputUploadProps>
|
||||||
@@ -186,4 +187,16 @@ describe('MalioInputUpload', () => {
|
|||||||
|
|
||||||
expect(wrapper.get('[data-test="icon"]').classes()).toContain('text-black')
|
expect(wrapper.get('[data-test="icon"]').classes()).toContain('text-black')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('affiche l\'astérisque quand required est vrai', () => {
|
||||||
|
const wrapper = mountComponent({label: 'Champ', required: true})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('n\'affiche pas l\'astérisque par défaut', () => {
|
||||||
|
const wrapper = mountComponent({label: 'Champ'})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
:accept="accept"
|
:accept="accept"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:required="required"
|
||||||
@change="onFileChange"
|
@change="onFileChange"
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
:for="inputId"
|
:for="inputId"
|
||||||
:class="mergedLabelClass"
|
:class="mergedLabelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}<MalioRequiredMark v-if="required" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
@@ -70,6 +71,7 @@
|
|||||||
import {computed, ref, useAttrs, useId} from 'vue'
|
import {computed, ref, useAttrs, useId} from 'vue'
|
||||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||||
import {twMerge} from 'tailwind-merge'
|
import {twMerge} from 'tailwind-merge'
|
||||||
|
import MalioRequiredMark from '../shared/RequiredMark.vue'
|
||||||
|
|
||||||
defineOptions({name: 'MalioInputUpload', inheritAttrs: false})
|
defineOptions({name: 'MalioInputUpload', inheritAttrs: false})
|
||||||
|
|
||||||
@@ -87,6 +89,7 @@ const props = withDefaults(
|
|||||||
success?: string
|
success?: string
|
||||||
displayIcon?: boolean
|
displayIcon?: boolean
|
||||||
accept?: string
|
accept?: string
|
||||||
|
required?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@@ -101,6 +104,7 @@ const props = withDefaults(
|
|||||||
success: '',
|
success: '',
|
||||||
displayIcon: true,
|
displayIcon: true,
|
||||||
accept: '',
|
accept: '',
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type SelectProps = {
|
|||||||
textLabel?: string
|
textLabel?: string
|
||||||
rounded?: string
|
rounded?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
required?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectForTest = Select as DefineComponent<SelectProps>
|
const SelectForTest = Select as DefineComponent<SelectProps>
|
||||||
@@ -260,6 +261,22 @@ describe('MalioSelect', () => {
|
|||||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-success')
|
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-success')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('affiche l\'astérisque quand required est vrai', () => {
|
||||||
|
const wrapper = mount(SelectForTest, {
|
||||||
|
props: {modelValue: null, label: 'Champ', required: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('n\'affiche pas l\'astérisque par défaut', () => {
|
||||||
|
const wrapper = mount(SelectForTest, {
|
||||||
|
props: {modelValue: null, label: 'Champ'},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps the bottom border allocation when open downward (transparent, not zero)', async () => {
|
it('keeps the bottom border allocation when open downward (transparent, not zero)', async () => {
|
||||||
const wrapper = mount(SelectForTest, {
|
const wrapper = mount(SelectForTest, {
|
||||||
props: {modelValue: null, options},
|
props: {modelValue: null, options},
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
:aria-controls="listboxId"
|
:aria-controls="listboxId"
|
||||||
:aria-invalid="hasError"
|
:aria-invalid="hasError"
|
||||||
:aria-describedby="describedBy"
|
:aria-describedby="describedBy"
|
||||||
|
:aria-required="required || undefined"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
]"
|
]"
|
||||||
:style="labelTransformStyle"
|
:style="labelTransformStyle"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}<MalioRequiredMark v-if="required" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -171,6 +172,7 @@
|
|||||||
import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
|
import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
|
||||||
import {Icon as IconifyIcon} from '@iconify/vue'
|
import {Icon as IconifyIcon} from '@iconify/vue'
|
||||||
import {twMerge} from 'tailwind-merge'
|
import {twMerge} from 'tailwind-merge'
|
||||||
|
import MalioRequiredMark from '../shared/RequiredMark.vue'
|
||||||
|
|
||||||
defineOptions({name: 'MalioSelect', inheritAttrs: false})
|
defineOptions({name: 'MalioSelect', inheritAttrs: false})
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
groupClass?: string
|
groupClass?: string
|
||||||
noOptionsText?: string
|
noOptionsText?: string
|
||||||
|
required?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
options: () => [],
|
options: () => [],
|
||||||
emptyOptionLabel: '',
|
emptyOptionLabel: '',
|
||||||
@@ -207,6 +210,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
groupClass: '',
|
groupClass: '',
|
||||||
noOptionsText: 'Aucune option disponible',
|
noOptionsText: 'Aucune option disponible',
|
||||||
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type SelectCheckboxProps = {
|
|||||||
selectAllLabel?: string
|
selectAllLabel?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
groupClass?: string
|
groupClass?: string
|
||||||
|
required?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectCheckboxForTest = SelectCheckbox as DefineComponent<SelectCheckboxProps>
|
const SelectCheckboxForTest = SelectCheckbox as DefineComponent<SelectCheckboxProps>
|
||||||
@@ -235,6 +236,22 @@ describe('MalioSelectCheckbox', () => {
|
|||||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-success')
|
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-success')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('affiche l\'astérisque quand required est vrai', () => {
|
||||||
|
const wrapper = mount(SelectCheckboxForTest, {
|
||||||
|
props: {modelValue: [], label: 'Champ', required: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('n\'affiche pas l\'astérisque par défaut', () => {
|
||||||
|
const wrapper = mount(SelectCheckboxForTest, {
|
||||||
|
props: {modelValue: [], label: 'Champ'},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps the bottom border allocation when open downward (transparent, not zero)', async () => {
|
it('keeps the bottom border allocation when open downward (transparent, not zero)', async () => {
|
||||||
const wrapper = mount(SelectCheckboxForTest, {
|
const wrapper = mount(SelectCheckboxForTest, {
|
||||||
props: {modelValue: [], options},
|
props: {modelValue: [], options},
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
:aria-controls="listboxId"
|
:aria-controls="listboxId"
|
||||||
:aria-invalid="hasError"
|
:aria-invalid="hasError"
|
||||||
:aria-describedby="describedBy"
|
:aria-describedby="describedBy"
|
||||||
|
:aria-required="required || undefined"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
]"
|
]"
|
||||||
:style="labelTransformStyle"
|
:style="labelTransformStyle"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}<MalioRequiredMark v-if="required" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -221,6 +222,7 @@ import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
|
|||||||
import {Icon as IconifyIcon} from '@iconify/vue'
|
import {Icon as IconifyIcon} from '@iconify/vue'
|
||||||
import {twMerge} from 'tailwind-merge'
|
import {twMerge} from 'tailwind-merge'
|
||||||
import Checkbox from '../checkbox/Checkbox.vue'
|
import Checkbox from '../checkbox/Checkbox.vue'
|
||||||
|
import MalioRequiredMark from '../shared/RequiredMark.vue'
|
||||||
|
|
||||||
defineOptions({name: 'MalioSelectCheckbox', inheritAttrs: false})
|
defineOptions({name: 'MalioSelectCheckbox', inheritAttrs: false})
|
||||||
|
|
||||||
@@ -246,6 +248,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
groupClass?: string
|
groupClass?: string
|
||||||
noOptionsText?: string
|
noOptionsText?: string
|
||||||
|
required?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
options: () => [],
|
options: () => [],
|
||||||
emptyOptionLabel: '',
|
emptyOptionLabel: '',
|
||||||
@@ -263,6 +266,7 @@ const props = withDefaults(defineProps<{
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
groupClass: '',
|
groupClass: '',
|
||||||
noOptionsText: 'Aucune option disponible',
|
noOptionsText: 'Aucune option disponible',
|
||||||
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
Reference in New Issue
Block a user