Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions src/components/NcCheckboxRadioSwitch/NcCheckboxContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@
</slot>
</span>

<span v-if="$slots.default" :class="['checkbox-content__text', textClass]">
<!-- @slot The checkbox/radio label -->
<slot />
<span class="checkbox-content__wrapper">
<span v-if="$slots.default"
:id="labelId"
class="checkbox-content__text"
:class="textClass">
<!-- @slot The checkbox/radio label -->
<slot />
</span>
<span v-if="!isButtonType && $slots.description" :id="descriptionId" class="checkbox-content__description">
<slot name="description" />
</span>
</span>
</span>
</template>
Expand Down Expand Up @@ -140,6 +148,22 @@ export default {
type: Number,
default: 24,
},

/**
* Label id attribute
*/
labelId: {
type: String,
required: true,
},

/**
* Description id attribute
*/
descriptionId: {
type: String,
required: true,
},
},

computed: {
Expand Down Expand Up @@ -196,8 +220,11 @@ export default {
// but restrict to content so plain checkboxes / radio switches do not expand
max-width: fit-content;

&__text {
&__wrapper {
flex: 1 0;
}

&__text {

&:empty {
// hide text if empty to ensure checkbox outline is a circle instead of oval
Expand All @@ -211,12 +238,26 @@ export default {
margin-block: calc((var(--default-clickable-area) - 2 * var(--default-grid-baseline) - var(--icon-height)) / 2) auto;
}

&-checkbox:not(&--button-variant) &__icon--has-description,
&-radio:not(&--button-variant) &__icon--has-description,
&-switch:not(&--button-variant) &__icon--has-description {
display: flex;
align-items: center;
margin-block-end: 0;
align-self: start;
}

&__icon > * {
width: var(--icon-size);
height: var(--icon-height);
color: var(--color-primary-element);
}

&__description {
display: block;
color: var(--color-text-maxcontrast);
}

&--button-variant {
.checkbox-content__icon:not(.checkbox-content__icon--checked) > * {
color: var(--color-primary-element);
Expand Down
37 changes: 36 additions & 1 deletion src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Note: All attributes on the element are passed to the inner input element - exce
<NcCheckboxRadioSwitch v-model="sharingEnabled">
Enable sharing. This can contain a long multiline text, that will be wrapped in a second row. It is generally not advised to have such long text inside of an element
</NcCheckboxRadioSwitch>

<NcCheckboxRadioSwitch v-model="sharingEnabled" description="The description">
Enable sharing with description
</NcCheckboxRadioSwitch>
<br>
sharingEnabled: {{ sharingEnabled }}
</div>
Expand Down Expand Up @@ -237,6 +241,16 @@ export default {
<NcCheckboxRadioSwitch v-model="sharingEnabled" type="switch">
Enable sharing. This can contain a long multiline text, that will be wrapped in a second row. It is generally not advised to have such long text inside of an element
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-model="sharingEnabled" type="switch" description="Instead you can use a description as a prop which can also be a long multiline text, that will be wrapped in a second row.">
Enable sharing.
</NcCheckboxRadioSwitch>

<NcCheckboxRadioSwitch v-model="sharingEnabled" type="switch">
Enable sharing.
<template #description>
Or you can use a description as slot which can also be a <strong>long multiline text</strong>, that will be wrapped in a second row.
</template>
</NcCheckboxRadioSwitch>
<br>
sharingEnabled: {{ sharingEnabled }}
</div>
Expand Down Expand Up @@ -275,7 +289,8 @@ export default {
v-on="isButtonType ? listeners : null">
<input v-if="!isButtonType"
:id="id"
:aria-labelledby="!isButtonType && !ariaLabel ? `${id}-label` : null"
:aria-labelledby="!isButtonType && !ariaLabel ? labelId : null"
:aria-describedby="!isButtonType && (description || $slots.description) ? descriptionId : nonDataAttrs['aria-describedby']"
:aria-label="ariaLabel || undefined"
class="checkbox-radio-switch__input"
:disabled="disabled"
Expand All @@ -296,12 +311,20 @@ export default {
:button-variant="buttonVariant"
:is-checked="isChecked"
:loading="loading"
:label-id="labelId"
:description-id="descriptionId"
:size="size"
@click.native="onToggle">
<template #icon>
<!-- @slot The checkbox/radio icon, you can use it for adding an icon to the button variant -->
<slot name="icon" />
</template>
<template v-if="$slots.description || description" #description>
<!-- @slot The checkbox/radio/switch description, you can use it for adding a more complex description element as opposed to the description prop -->
<slot name="description">
{{ description }}
</slot>
</template>

<!-- @slot The checkbox/radio label -->
<slot />
Expand Down Expand Up @@ -473,6 +496,16 @@ export default {
type: String,
default: null,
},

/**
* Description
*
* This is unsupported when using button has type
*/
description: {
type: String,
default: null,
},
},

emits: [
Expand All @@ -490,6 +523,8 @@ export default {
const model = useModelMigration('checked', 'update:checked')
return {
model,
labelId: GenRandomId(),
descriptionId: GenRandomId(),
}
},

Expand Down
20 changes: 18 additions & 2 deletions tests/unit/components/NcCheckboxRadioSwitch/checkbox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ describe('NcCheckboxRadioSwitch', () => {
},
})

expect(wrapper.find('input').attributes('aria-labelledby')).toBe('test-id-label')
expect(wrapper.findComponent({ name: 'NcCheckboxContent' }).attributes('id')).toBe('test-id-label')
const labelById = wrapper.find('input').attributes('aria-labelledby')
expect(wrapper.findComponent({ name: 'NcCheckboxContent' }).find('#' + labelById).exists()).toBe(true)
})

it('does not set id on button content', () => {
Expand All @@ -67,4 +67,20 @@ describe('NcCheckboxRadioSwitch', () => {
expect(wrapper.find('input').exists()).toBe(false)
expect(wrapper.findComponent({ name: 'NcCheckboxContent' }).attributes('id')).toBe(undefined)
})

it('sets aria-describedby attribute correctly', () => {
const wrapper = mount(NcCheckboxRadioSwitch, {
propsData: {
description: 'My description',
},
slots: {
default: 'Test',
},
})

const describedById = wrapper.find('input').attributes('aria-describedby')
const descriptionElement = wrapper.findComponent({ name: 'NcCheckboxContent' }).find('#' + describedById)
expect(descriptionElement.exists()).toBe(true)
expect(descriptionElement.text()).toContain('My description')
})
})
Loading