Skip to content

Commit

Permalink
feat: scope the no-options slot (#1083)
Browse files Browse the repository at this point in the history
  • Loading branch information
sagalbot committed Mar 5, 2020
1 parent a905f42 commit be44b29
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 32 deletions.
16 changes: 16 additions & 0 deletions docs/.vuepress/components/BetterNoOptions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<v-select>
<template v-slot:no-options="{ search, searching }">
<template v-if="searching">
No results found for <em>{{ search }}</em>.
</template>
<em style="opacity: 0.5;" v-else>Start typing to search for a country.</em>
</template>
</v-select>
</template>

<script>
export default {
name: 'BetterNoOptions',
};
</script>
91 changes: 69 additions & 22 deletions docs/api/slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t

### `selected-option`

#### Scope:
#### Scope:

- `option {Object}` - A selected option

```html
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
<slot
name="selected-option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
```

Expand All @@ -27,45 +30,89 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
- `multiple {Boolean}` - If the component supports the selection of multiple values

```html
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
<slot
v-for="option in valueAsArray"
name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}"
:deselect="deselect"
:multiple="multiple"
:disabled="disabled"
>
<span class="selected-tag" v-bind:key="option.index">
<slot
name="selected-option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
<button
v-if="multiple"
:disabled="disabled"
@click="deselect(option)"
type="button"
class="close"
aria-label="Remove option"
>
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
```

## Component Actions

### `spinner`

#### Scope:

- `loading {Boolean}` - if the component is in a loading state

```html
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
<slot name="spinner" v-bind="scope.spinner">
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
</slot>
```

### `no-options`
### `open-indicator`

```html
<slot name="no-options">Sorry, no matching options.</slot>
```js
attributes : {
'ref': 'openIndicator',
'role': 'presentation',
'class': 'vs__open-indicator',
}
```

```vue
<slot name="open-indicator" v-bind="scope.openIndicator">
<component :is="childComponents.OpenIndicator" v-if="!noDrop" v-bind="scope.openIndicator.attributes"/>
</slot>
```

## Dropdown

### `option`

#### Scope:

- `option {Object}` - The currently iterated option from `filteredOptions`

```html
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
<slot
name="option"
v-bind="(typeof option === 'object')?option:{[label]: option}"
>
{{ getOptionLabel(option) }}
</slot>
```

### `no-options`

The no options slot is displayed in the dropdown when `filteredOptions.length === 0`.

- `search {String}` - the current search text
- `searching {Boolean}` - if the component has search text

```vue
<slot name="no-options" v-bind="scope.noOptions">
Sorry, no matching options.
</slot>
```
26 changes: 18 additions & 8 deletions docs/guide/slots.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
::: tip 🚧
This section of the guide is a work in progress! Check back soon for an update.
Vue Select currently offers quite a few scoped slots, and you can check out the
Vue Select currently offers quite a few scoped slots, and you can check out the
[API Docs for Slots](../api/slots.md) in the meantime while a good guide is put together.
:::

#### Scoped Slot `option`
### Scoped Slot `option`

vue-select provides the scoped `option` slot in order to create custom dropdown templates.

```html
<v-select :options="options" label="title">
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```

Using the `option` slot with props `"option"` provides the current option variable to the template.

<CodePen url="NXBwYG" height="500"/>

### Improving the default `no-options` text

The `no-options` slot is displayed in the dropdown when `filteredOptions === 0`. By default, it
displays _Sorry, no matching options_. You can add more contextual information by using the slot
in your own apps.

<BetterNoOptions />

<<< @/.vuepress/components/BetterNoOptions.vue
8 changes: 6 additions & 2 deletions src/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
{{ getOptionLabel(option) }}
</slot>
</li>
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
<slot name="no-options">Sorry, no matching options.</slot>
<li v-if="filteredOptions.length === 0" class="vs__no-options" @mousedown.stop="">
<slot name="no-options" v-bind="scope.noOptions">Sorry, no matching options.</slot>
</li>
</template>
</ul>
Expand Down Expand Up @@ -1013,6 +1013,10 @@
spinner: {
loading: this.mutableLoading
},
noOptions: {
search: this.search,
searching: this.searching,
},
openIndicator: {
attributes: {
'ref': 'openIndicator',
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/Slots.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,20 @@ describe('Scoped Slots', () => {

expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree');
});

it('noOptions slot receives the current search text', async () => {
const noOptions = jest.fn();
const Select = mountDefault({}, {
scopedSlots: {'no-options': noOptions},
});

Select.vm.search = 'something not there';
Select.vm.open = true;
await Select.vm.$nextTick();

expect(noOptions).toHaveBeenCalledWith({
search: 'something not there',
searching: true,
})
});
});

0 comments on commit be44b29

Please sign in to comment.