2
0
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:
Jeff Sagal
2019-04-25 15:03:43 -07:00
committed by GitHub
parent d522acacfd
commit efc5093207
15 changed files with 369 additions and 19 deletions
@@ -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>
+11 -6
View File
@@ -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
View File
@@ -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.
:::
+94
View File
@@ -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 />
+37
View File
@@ -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.
+7
View File
@@ -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
View File
@@ -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
};
},
+7
View File
@@ -0,0 +1,7 @@
import Deselect from './Deselect';
import OpenIndicator from './OpenIndicator';
export default {
Deselect,
OpenIndicator
}
+30
View File
@@ -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();
});
});
+1 -1
View File
@@ -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();
});