Skip to content
Open
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
60 changes: 59 additions & 1 deletion src/components/NcSelect/NcSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ const selectArray = [
},
},

{
props: {
inputLabel: 'With helper text',
helperText: 'Choose the option that fits best',
options: [
'foo',
'bar',
'baz',
],
},
},

{
props: {
inputLabel: 'Simple (top placement)',
Expand Down Expand Up @@ -508,6 +520,7 @@ export default {
:readonly="attributes.readonly"
:autocomplete="attributes.autocomplete"
:aria-label="attributes['aria-label']"
:aria-describedby="[attributes['aria-describedby'], helperText ? `${inputId}-helper-text` : null].filter(Boolean).join(' ') || undefined"
:aria-autocomplete="attributes['aria-autocomplete']"
:aria-controls="attributes['aria-controls']"
:aria-owns="attributes['aria-owns']"
Expand Down Expand Up @@ -552,7 +565,17 @@ export default {
<template #no-options>
{{ t('No results') }}
</template>
<template v-for="(_, name) in $slots" #[name]="data">
<!-- Helper text beneath the control, plus any consumer-provided footer slot -->
<template v-if="helperText || $slots.footer" #footer="data">
<slot name="footer" v-bind="data" />
<p
v-if="helperText"
:id="`${inputId}-helper-text`"
class="select__helper-text">
{{ helperText }}
</p>
</template>
<template v-for="name in forwardedSlots" :key="name" #[name]="data">
<!-- @slot Any combination of slots from https://vue-select.org/api/slots.html -->
<slot :name="name" v-bind="data" />
</template>
Expand Down Expand Up @@ -761,6 +784,14 @@ export default {
default: null,
},

/**
* Additional helper text shown beneath the select.
*/
helperText: {
type: String,
default: '',
},

/**
* Pass true if you are using an external label
*/
Expand Down Expand Up @@ -962,6 +993,16 @@ export default {
},

computed: {
/**
* Consumer-provided slots forwarded to vue-select, excluding `footer`
* which we render ourselves to host the helper text.
*
* @return {string[]}
*/
forwardedSlots() {
return Object.keys(this.$slots).filter((name) => name !== 'footer')
},

inputRequired() {
if (!this.required) {
return null
Expand Down Expand Up @@ -1083,6 +1124,16 @@ export default {
<style lang="scss">
@use '../../assets/input-border.scss' as border;

// Helper text beneath the control, matching NcInputField's helper text.
.nc-select.v-select.select .select__helper-text {
margin: 0;
padding-block: 4px;
padding-inline: var(--border-radius-element);
color: var(--color-text-maxcontrast);
font-size: var(--default-font-size);
overflow-wrap: anywhere;
}

.nc-select.v-select.select {
/* Custom vue-select CSS variables scoped to NcSelect */
/* Search Input */
Expand Down Expand Up @@ -1204,6 +1255,13 @@ export default {
background: transparent;
}

// Anchor the absolutely-positioned actions to the control itself, so content
// rendered below it inside .v-select (e.g. the helper text) does not shift
// the caret's vertical centering.
.vs__dropdown-toggle {
position: relative;
}

.vs__actions {
position: absolute;
z-index: 999;
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/components/NcSelect/NcSelect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,33 @@ describe('NcSelect', () => {
expect(input.attributes('aria-expanded')).toBe('false')
})
})

describe('helper text', () => {
it('renders the helper text beneath the select', () => {
const wrapper = mount(NcSelect, {
props: { inputLabel: 'Label', options, inputId: 'sel', helperText: 'Pick your favourite' },
})

const helper = wrapper.find('#sel-helper-text')
expect(helper.exists()).toBe(true)
expect(helper.text()).toBe('Pick your favourite')
})

it('renders no helper text when none is provided', () => {
const wrapper = mount(NcSelect, {
props: { inputLabel: 'Label', options, inputId: 'sel' },
})

expect(wrapper.find('#sel-helper-text').exists()).toBe(false)
})

it('links the input to the helper text via aria-describedby', () => {
const wrapper = mount(NcSelect, {
props: { inputLabel: 'Label', options, inputId: 'sel', helperText: 'Pick your favourite' },
})

const describedby = wrapper.find('input').attributes('aria-describedby')
expect(describedby).toContain('sel-helper-text')
})
})
})
Loading