mirror of
https://github.com/tenrok/vue-select.git
synced 2026-05-17 02:29:37 +03:00
Add API for overwriting default components (#850)
* implement API for overwriting child components * add test coverage * update documentation for Components & Styling * update docs * refactor API, update docs * remove the service worker * fix tests
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-select
|
||||
:options="['Canada', 'United States']"
|
||||
:components="{Deselect}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
Deselect () {
|
||||
return Vue.component('Deselect', {
|
||||
render (createElement) {
|
||||
return createElement('button', 'Clear');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-select
|
||||
class="style-chooser"
|
||||
placeholder="Choose a Styling Option"
|
||||
:options="['Components', 'CSS / Variables', 'Slots']"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.style-chooser .vs__search::placeholder,
|
||||
.style-chooser .vs__dropdown-toggle,
|
||||
.style-chooser .vs__dropdown-menu {
|
||||
background: #dfe5fb;
|
||||
border: none;
|
||||
color: #394066;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.style-chooser .vs__clear,
|
||||
.style-chooser .vs__open-indicator {
|
||||
fill: #394066;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<custom-select :options="['Vue.js', 'React', 'Angular']" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import vSelect from '../../../src/components/Select';
|
||||
|
||||
const components = {
|
||||
Deselect: Vue.component('Deselect', {
|
||||
render: (createElement) => createElement('button', '❌'),
|
||||
}),
|
||||
OpenIndicator: Vue.component('OpenIndicator', {
|
||||
render: (createElement) => createElement('span', '🔽'),
|
||||
}),
|
||||
};
|
||||
|
||||
const mySelect = {...vSelect};
|
||||
|
||||
mySelect.props.components.default = () => components;
|
||||
|
||||
export default {
|
||||
components: {'custom-select': mySelect}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-select
|
||||
multiple
|
||||
v-model="selected"
|
||||
:options="['Canada', 'United States']"
|
||||
:components="{Deselect}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
selected: ['Canada']
|
||||
}),
|
||||
computed: {
|
||||
Deselect () {
|
||||
return Vue.component('Deselect', {
|
||||
render (createElement) {
|
||||
return createElement('button', 'Clear');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-select
|
||||
multiple
|
||||
v-model="selected"
|
||||
:options="['Canada', 'United States']"
|
||||
:components="{OpenIndicator}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
selected: ['Canada']
|
||||
}),
|
||||
computed: {
|
||||
OpenIndicator () {
|
||||
return Vue.component('OpenIndicator', {
|
||||
render (createElement) {
|
||||
return createElement('i', '🤘🏻');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -68,11 +68,8 @@ module.exports = {
|
||||
ga: isDeployPreview ? '' : 'UA-12818324-8',
|
||||
},
|
||||
'@vuepress/pwa': {
|
||||
serviceWorker: true,
|
||||
updatePopup: {
|
||||
message: 'New content is available.',
|
||||
buttonText: 'Refresh',
|
||||
},
|
||||
serviceWorker: false,
|
||||
updatePopup: true,
|
||||
},
|
||||
'@vuepress/plugin-register-components': {},
|
||||
'@vuepress/plugin-active-header-links': {},
|
||||
@@ -100,11 +97,19 @@ module.exports = {
|
||||
['guide/upgrading', 'Upgrading 2.x to 3.x'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Templating & Styling',
|
||||
collapsable: false,
|
||||
children: [
|
||||
['guide/components', 'Child Components'],
|
||||
['guide/css', 'CSS & Selectors'],
|
||||
['guide/slots', 'Slots'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Digging Deeper',
|
||||
collapsable: false,
|
||||
children: [
|
||||
['guide/templating', 'Templating'],
|
||||
['guide/vuex', 'Vuex'],
|
||||
['guide/ajax', 'AJAX'],
|
||||
],
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
::: tip
|
||||
VueSelect leverages scoped slots to allow for total customization of the presentation layer.
|
||||
Vue Select leverages scoped slots to allow for total customization of the presentation layer.
|
||||
Slots can be used to change the look and feel of the UI, or to simply swap out text.
|
||||
:::
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
Vue Select utilizes child components throughout, and exposes an API to overwrite these components
|
||||
with your own, using the `components` `{Object}` prop. When implementing the `components` prop in
|
||||
your code, Vue Select merge it's default components with any keys that you set in the object.
|
||||
|
||||
Your object will be merged with the object that is exported below:
|
||||
|
||||
<<< @/src/components/childComponents.js{4-7}
|
||||
|
||||
You can override the value of any of these keys with your own components.
|
||||
|
||||
## Available Components
|
||||
|
||||
### Deselect
|
||||
|
||||
You may wish to roll your own deselect button. `Deselect` is used within tags on
|
||||
`multiple` selects, and serves as the clear button for single selects. Maybe you just want to use
|
||||
a simple `<button>Clear</button>` instead.
|
||||
|
||||
```html
|
||||
<v-select :components="{Deselect}" />
|
||||
```
|
||||
|
||||
```js
|
||||
computed: {
|
||||
Deselect() {
|
||||
return Vue.component('Deselect', {
|
||||
render (createElement) {
|
||||
return createElement('button', 'Clear')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<ClearButtonOverride />
|
||||
|
||||
The same approach applies for `multiple` selects:
|
||||
|
||||
<MultipleClearButtonOverride />
|
||||
|
||||
### OpenIndicator
|
||||
|
||||
The `OpenIndicator` component is the 'caret' used within the component that adjusts orientation
|
||||
based on whether the dropdown is open or closed.
|
||||
|
||||
```html
|
||||
<v-select :components="{OpenIndicator}" />
|
||||
```
|
||||
```js
|
||||
computed: {
|
||||
OpenIndicator () {
|
||||
return Vue.component('OpenIndicator', {
|
||||
render (createElement) {
|
||||
return createElement('button', '🤘🏻');
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
<OpenIndicatorOverride />
|
||||
|
||||
## Setting at Registration
|
||||
|
||||
If you want to all instances of Vue Select to use your custom components throughout your app, while
|
||||
only having to set the implementation once, you can do so when registering Vue Select as a component.
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import vSelect from 'vue-select';
|
||||
|
||||
/**
|
||||
* Create custom components to override defaults.
|
||||
* @type {{OpenIndicator: *, Deselect: *}}
|
||||
*/
|
||||
const components = {
|
||||
Deselect: Vue.component('Deselect', {
|
||||
render: (createElement) => createElement('button', '❌'),
|
||||
}),
|
||||
OpenIndicator: Vue.component('OpenIndicator', {
|
||||
render: (createElement) => createElement('span', '🔽'),
|
||||
}),
|
||||
};
|
||||
|
||||
// Set the components prop default to return our fresh components
|
||||
vSelect.props.components.default = () => components;
|
||||
|
||||
// Register the component
|
||||
Vue.component(vSelect)
|
||||
```
|
||||
|
||||
<CustomComponentRegistration />
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
Vue Select offers many APIs for customizing the look and feel from the component. You can use
|
||||
[scoped slots](../api/slots.md), [custom child components](components.md), or modify the built in
|
||||
SCSS variables.
|
||||
|
||||
::: tip
|
||||
Support for CSS variables (custom properties) is currently on the road map for those
|
||||
that are not using sass in their projects.
|
||||
:::
|
||||
|
||||
## SCSS Variables
|
||||
|
||||
Variables are leveraged in as much of the component styles as possible. If you really want to dig
|
||||
into the SCSS, the files are located in `src/scss`. The variables listed below can be found at
|
||||
[`src/scss/global/_variables`](https://github.com/sagalbot/vue-select/blob/master/src/scss/global/_variables.scss).
|
||||
|
||||
All variables are implemented with `!default` in order to make them easier to override in your
|
||||
application.
|
||||
|
||||
<<< @/src/scss/global/_variables.scss
|
||||
|
||||
## Overriding Default Styles
|
||||
|
||||
Vue Select takes the approach of using selectors with a single level of specificity, while using
|
||||
classes that are very specific to Vue Select to avoid collisions with your app.
|
||||
|
||||
All classes within Vue Select use the `vs__` prefix, and selectors are generally a single classname
|
||||
– unless there is a state being applied to the component.
|
||||
|
||||
In order to override a default property in your app, you should add one level of specificity.
|
||||
The easiest way to do this, is to add `.v-select` before the `vs__*` selector if you want to adjust
|
||||
all instances of Vue Select, or add your own classname if you just want to affect one.
|
||||
|
||||
<CssSpecificity />
|
||||
|
||||
<<< @/docs/.vuepress/components/CssSpecificity.vue
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
::: 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
|
||||
[API Docs for Slots](../api/slots.md) in the meantime while a good guide is put together.
|
||||
:::
|
||||
|
||||
#### Scoped Slot `option`
|
||||
|
||||
vue-select provides the scoped `option` slot in order to create custom dropdown templates.
|
||||
@@ -13,3 +13,10 @@
|
||||
[context.deploy-preview]
|
||||
publish = "docs/.vuepress/dist"
|
||||
command = "yarn && yarn build:preview"
|
||||
|
||||
|
||||
# Redirects
|
||||
# @see https://www.netlify.com/docs/netlify-toml-reference/
|
||||
[[redirects]]
|
||||
from = "/guide/templating"
|
||||
to = "/guide/slots"
|
||||
|
||||
+42
-11
@@ -18,7 +18,7 @@
|
||||
{{ getOptionLabel(option) }}
|
||||
</slot>
|
||||
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="vs__deselect" aria-label="Deselect option">
|
||||
<deselect />
|
||||
<component :is="childComponents.Deselect" />
|
||||
</button>
|
||||
</span>
|
||||
</slot>
|
||||
@@ -37,10 +37,12 @@
|
||||
class="vs__clear"
|
||||
title="Clear selection"
|
||||
>
|
||||
<deselect />
|
||||
<component :is="childComponents.Deselect" />
|
||||
</button>
|
||||
|
||||
<open-indicator v-if="!noDrop" ref="openIndicator" role="presentation" class="vs__open-indicator" />
|
||||
<slot name="open-indicator-icon">
|
||||
<component :is="childComponents.OpenIndicator" v-bind="scope.openIndicator.attributes"/>
|
||||
</slot>
|
||||
|
||||
<slot name="spinner" v-bind="scope.spinner">
|
||||
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
|
||||
@@ -75,11 +77,10 @@
|
||||
import pointerScroll from '../mixins/pointerScroll'
|
||||
import typeAheadPointer from '../mixins/typeAheadPointer'
|
||||
import ajax from '../mixins/ajax'
|
||||
import Deselect from './Deselect'
|
||||
import OpenIndicator from './OpenIndicator'
|
||||
import childComponents from './childComponents';
|
||||
|
||||
export default {
|
||||
components: {Deselect, OpenIndicator},
|
||||
components: {...childComponents},
|
||||
|
||||
mixins: [pointerScroll, typeAheadPointer, ajax],
|
||||
|
||||
@@ -92,6 +93,18 @@
|
||||
*/
|
||||
value: {},
|
||||
|
||||
/**
|
||||
* An object with any custom components that you'd like to overwrite
|
||||
* the default implementation of in your app. The keys in this object
|
||||
* will be merged with the defaults.
|
||||
* @see https://vue-select.org/guide/components.html
|
||||
* @type {Function}
|
||||
*/
|
||||
components: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
/**
|
||||
* An array of strings or objects to be used as dropdown choices.
|
||||
* If you are using an array of objects, vue-select will look for
|
||||
@@ -443,10 +456,6 @@
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone props into mutable values,
|
||||
* attach any event listeners.
|
||||
*/
|
||||
created() {
|
||||
this.mutableLoading = this.loading;
|
||||
|
||||
@@ -878,7 +887,29 @@
|
||||
},
|
||||
spinner: {
|
||||
loading: this.mutableLoading
|
||||
}
|
||||
},
|
||||
openIndicator: {
|
||||
attributes: {
|
||||
'v-if': !this.noDrop,
|
||||
'ref': 'openIndicator',
|
||||
'role': 'presentation',
|
||||
'class': 'vs__open-indicator',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object containing the child components
|
||||
* that will be used throughout the component. The
|
||||
* `component` prop can be used to overwrite the defaults.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
childComponents () {
|
||||
return {
|
||||
...childComponents,
|
||||
...this.components
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import Deselect from './Deselect';
|
||||
import OpenIndicator from './OpenIndicator';
|
||||
|
||||
export default {
|
||||
Deselect,
|
||||
OpenIndicator
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Vue from 'vue';
|
||||
import { selectWithProps } from '../helpers';
|
||||
|
||||
describe('Components API', () => {
|
||||
|
||||
it('swap the Deselect component', () => {
|
||||
const Deselect = Vue.component('Deselect', {
|
||||
render (createElement) {
|
||||
return createElement('button', 'remove');
|
||||
},
|
||||
});
|
||||
|
||||
const Select = selectWithProps({components: {Deselect}});
|
||||
|
||||
expect(Select.contains(Deselect)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('swap the OpenIndicator component', () => {
|
||||
const OpenIndicator = Vue.component('OpenIndicator', {
|
||||
render (createElement) {
|
||||
return createElement('i', '^');
|
||||
},
|
||||
});
|
||||
|
||||
const Select = selectWithProps({components: {OpenIndicator}});
|
||||
|
||||
expect(Select.contains(OpenIndicator)).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe("VS - Selecting Values", () => {
|
||||
|
||||
const spy = jest.spyOn(Select.vm, "typeAheadSelect");
|
||||
|
||||
Select.find({ ref: "search" }).trigger("keyup.tab");
|
||||
Select.find({ ref: "search" }).trigger("keydown.tab");
|
||||
|
||||
expect(spy).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user