Skip to content

Commit 00200c7

Browse files
Merge pull request #7395 from nextcloud-libraries/backport/7378/stable8
[stable8] feat(NcCheckboxRadioSwitch): Add support for a description field
2 parents 0f18653 + efa51b4 commit 00200c7

File tree

3 files changed

+99
-7
lines changed

3 files changed

+99
-7
lines changed

src/components/NcCheckboxRadioSwitch/NcCheckboxContent.vue

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,17 @@
3535
</slot>
3636
</span>
3737

38-
<span v-if="$slots.default" :class="['checkbox-content__text', textClass]">
39-
<!-- @slot The checkbox/radio label -->
40-
<slot />
38+
<span class="checkbox-content__wrapper">
39+
<span v-if="$slots.default"
40+
:id="labelId"
41+
class="checkbox-content__text"
42+
:class="textClass">
43+
<!-- @slot The checkbox/radio label -->
44+
<slot />
45+
</span>
46+
<span v-if="!isButtonType && $slots.description" :id="descriptionId" class="checkbox-content__description">
47+
<slot name="description" />
48+
</span>
4149
</span>
4250
</span>
4351
</template>
@@ -140,6 +148,22 @@ export default {
140148
type: Number,
141149
default: 24,
142150
},
151+
152+
/**
153+
* Label id attribute
154+
*/
155+
labelId: {
156+
type: String,
157+
required: true,
158+
},
159+
160+
/**
161+
* Description id attribute
162+
*/
163+
descriptionId: {
164+
type: String,
165+
required: true,
166+
},
143167
},
144168
145169
computed: {
@@ -196,8 +220,11 @@ export default {
196220
// but restrict to content so plain checkboxes / radio switches do not expand
197221
max-width: fit-content;
198222
199-
&__text {
223+
&__wrapper {
200224
flex: 1 0;
225+
}
226+
227+
&__text {
201228
202229
&:empty {
203230
// hide text if empty to ensure checkbox outline is a circle instead of oval
@@ -211,12 +238,26 @@ export default {
211238
margin-block: calc((var(--default-clickable-area) - 2 * var(--default-grid-baseline) - var(--icon-height)) / 2) auto;
212239
}
213240
241+
&-checkbox:not(&--button-variant) &__icon--has-description,
242+
&-radio:not(&--button-variant) &__icon--has-description,
243+
&-switch:not(&--button-variant) &__icon--has-description {
244+
display: flex;
245+
align-items: center;
246+
margin-block-end: 0;
247+
align-self: start;
248+
}
249+
214250
&__icon > * {
215251
width: var(--icon-size);
216252
height: var(--icon-height);
217253
color: var(--color-primary-element);
218254
}
219255
256+
&__description {
257+
display: block;
258+
color: var(--color-text-maxcontrast);
259+
}
260+
220261
&--button-variant {
221262
.checkbox-content__icon:not(.checkbox-content__icon--checked) > * {
222263
color: var(--color-primary-element);

src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Note: All attributes on the element are passed to the inner input element - exce
2323
<NcCheckboxRadioSwitch v-model="sharingEnabled">
2424
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
2525
</NcCheckboxRadioSwitch>
26+
27+
<NcCheckboxRadioSwitch v-model="sharingEnabled" description="The description">
28+
Enable sharing with description
29+
</NcCheckboxRadioSwitch>
2630
<br>
2731
sharingEnabled: {{ sharingEnabled }}
2832
</div>
@@ -237,6 +241,16 @@ export default {
237241
<NcCheckboxRadioSwitch v-model="sharingEnabled" type="switch">
238242
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
239243
</NcCheckboxRadioSwitch>
244+
<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.">
245+
Enable sharing.
246+
</NcCheckboxRadioSwitch>
247+
248+
<NcCheckboxRadioSwitch v-model="sharingEnabled" type="switch">
249+
Enable sharing.
250+
<template #description>
251+
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.
252+
</template>
253+
</NcCheckboxRadioSwitch>
240254
<br>
241255
sharingEnabled: {{ sharingEnabled }}
242256
</div>
@@ -275,7 +289,8 @@ export default {
275289
v-on="isButtonType ? listeners : null">
276290
<input v-if="!isButtonType"
277291
:id="id"
278-
:aria-labelledby="!isButtonType && !ariaLabel ? `${id}-label` : null"
292+
:aria-labelledby="!isButtonType && !ariaLabel ? labelId : null"
293+
:aria-describedby="!isButtonType && (description || $slots.description) ? descriptionId : nonDataAttrs['aria-describedby']"
279294
:aria-label="ariaLabel || undefined"
280295
class="checkbox-radio-switch__input"
281296
:disabled="disabled"
@@ -296,12 +311,20 @@ export default {
296311
:button-variant="buttonVariant"
297312
:is-checked="isChecked"
298313
:loading="loading"
314+
:label-id="labelId"
315+
:description-id="descriptionId"
299316
:size="size"
300317
@click.native="onToggle">
301318
<template #icon>
302319
<!-- @slot The checkbox/radio icon, you can use it for adding an icon to the button variant -->
303320
<slot name="icon" />
304321
</template>
322+
<template v-if="$slots.description || description" #description>
323+
<!-- @slot The checkbox/radio/switch description, you can use it for adding a more complex description element as opposed to the description prop -->
324+
<slot name="description">
325+
{{ description }}
326+
</slot>
327+
</template>
305328

306329
<!-- @slot The checkbox/radio label -->
307330
<slot />
@@ -473,6 +496,16 @@ export default {
473496
type: String,
474497
default: null,
475498
},
499+
500+
/**
501+
* Description
502+
*
503+
* This is unsupported when using button has type
504+
*/
505+
description: {
506+
type: String,
507+
default: null,
508+
},
476509
},
477510
478511
emits: [
@@ -490,6 +523,8 @@ export default {
490523
const model = useModelMigration('checked', 'update:checked')
491524
return {
492525
model,
526+
labelId: GenRandomId(),
527+
descriptionId: GenRandomId(),
493528
}
494529
},
495530

tests/unit/components/NcCheckboxRadioSwitch/checkbox.spec.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ describe('NcCheckboxRadioSwitch', () => {
4949
},
5050
})
5151

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

5656
it('does not set id on button content', () => {
@@ -67,4 +67,20 @@ describe('NcCheckboxRadioSwitch', () => {
6767
expect(wrapper.find('input').exists()).toBe(false)
6868
expect(wrapper.findComponent({ name: 'NcCheckboxContent' }).attributes('id')).toBe(undefined)
6969
})
70+
71+
it('sets aria-describedby attribute correctly', () => {
72+
const wrapper = mount(NcCheckboxRadioSwitch, {
73+
propsData: {
74+
description: 'My description',
75+
},
76+
slots: {
77+
default: 'Test',
78+
},
79+
})
80+
81+
const describedById = wrapper.find('input').attributes('aria-describedby')
82+
const descriptionElement = wrapper.findComponent({ name: 'NcCheckboxContent' }).find('#' + describedById)
83+
expect(descriptionElement.exists()).toBe(true)
84+
expect(descriptionElement.text()).toContain('My description')
85+
})
7086
})

0 commit comments

Comments
 (0)