upgrade styleguide the second

This commit is contained in:
Grzegorz Leoniec 2018-12-07 16:09:23 +01:00
parent 0440d06e54
commit 2ef13980a6
No known key found for this signature in database
GPG Key ID: 3AA43686D4EB1377
28 changed files with 1376 additions and 96 deletions

View File

@ -19,7 +19,7 @@
height: 100%;
top: 0px;
left: 0px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0 0 1px rgba($color-neutral-0, .1);
border-radius: 50%;
}

View File

@ -5,7 +5,11 @@
<table
cellpadding="0"
cellspacing="0"
class="ds-table">
class="ds-table"
:class="[
condensed && 'ds-table-condensed',
bordered && 'ds-table-bordered'
]">
<colgroup>
<col
v-for="header in headers"
@ -24,7 +28,7 @@
<tbody>
<tr
v-for="(row, index) in rows"
:key="index">
:key="row.key || index">
<ds-table-col
v-for="col in row"
:key="col.key">
@ -75,6 +79,20 @@ export default {
default() {
return null
}
},
/**
* Should the table be more condense?
*/
condensed: {
type: Boolean,
default: false
},
/**
* Should the table have borders?
*/
bordered: {
type: Boolean,
default: true
}
},
computed: {

View File

@ -11,19 +11,34 @@
.ds-table-col {
@include reset;
border-bottom: $border-color-softer solid $border-size-base;
vertical-align: top;
padding: $space-small $space-xx-small;
&:last-child {
padding-right: 0;
}
}
.ds-table-head-col {
@include reset;
border-bottom: $border-color-softer solid $border-size-base;
padding: $space-small $space-xx-small;
text-align: left;
font-weight: $font-weight-bold;
}
}
// bordered
.ds-table-bordered {
.ds-table-col,
.ds-table-head-col {
border-bottom: $border-color-softer dotted $border-size-base;
}
tr:last-child .ds-table-col {
border-bottom: none;
}
}
// condensed
.ds-table-condensed {
.ds-table-col,
.ds-table-head-col {
padding-top: $space-x-small;
padding-bottom: $space-x-small;
}
}

View File

@ -1,10 +1,9 @@
<template>
<div
<div
class="ds-form-item"
:class="$parentInput ? $parentInput.stateClasses : ''">
<ds-input-label
v-if="$parentInput"
:label="$parentInput.label"
:class="$parentInput.stateClasses">
<ds-input-label
:label="$parentInput.label"
:for="$parentInput.id" />
<slot/>
<ds-input-error :error="$parentInput ? $parentInput.error : null" />

View File

View File

View File

View File

@ -17,11 +17,12 @@
:type="type"
:autofocus="autofocus"
:placeholder="placeholder"
:tabindex="tabindex"
:disabled="disabled"
:readonly="readonly"
:is="tag"
:value.prop="innerValue"
@input="input"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
:rows="type === 'textarea' ? rows : null"

View File

View File

View File

@ -1,33 +1,119 @@
<template>
<ds-form-item>
<div class="ds-select-wrap">
<div
class="ds-select-wrap"
v-click-outside="handleBlur"
:tabindex="searchable ? -1 : tabindex"
@keydown.self.down.prevent="pointerNext"
@keydown.self.up.prevent="pointerPrev"
@keypress.enter.prevent.stop.self="selectPointerOption"
@keyup.esc="close">
<div
v-if="icon"
class="ds-select-icon">
<ds-icon :name="icon"/>
</div>
<select
<div
class="ds-select"
@click="handleClick"
:class="[
icon && `ds-select-has-icon`,
iconRight && `ds-select-has-icon-right`
]"
:id="id"
:name="model"
:autofocus="autofocus"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:value.prop="innerValue"
@input="input"
@focus="handleFocus"
@blur="handleBlur">
<option
v-for="option in options"
:key="option.label || option">
{{ option.label || option }}
</option>
</select>
iconRight && `ds-select-has-icon-right`,
multiple && `ds-select-multiple`
]">
<div
v-if="multiple"
class="ds-selected-options">
<div
class="ds-selected-option"
v-for="(value, index) in innerValue"
:key="value">
<!-- @slot Slot to provide a custom selected option display -->
<slot
name="optionitem"
:value="value">
<ds-chip
removable
@remove="deselectOption(index)"
color="primary">
{{ value }}
</ds-chip>
</slot>
</div>
<input
ref="search"
class="ds-select-search"
:id="id"
:name="model"
:autofocus="autofocus"
:placeholder="placeholder"
:tabindex="tabindex"
:disabled="disabled"
:readonly="readonly"
v-model="searchString"
@focus="handleFocus"
@keydown.delete.stop="deselectLastOption"
@keydown.down.prevent="pointerNext"
@keydown.up.prevent="pointerPrev"
@keypress.enter.prevent.stop="selectPointerOption"
@keyup.esc="close">
</div>
<div
v-else
class="ds-select-value">
<div
v-if="placeholder && !innerValue"
class="ds-select-placeholder">
{{ placeholder }}
</div>
<!-- @slot Slot to provide a custom value display -->
<slot
v-else
name="value"
:value="innerValue">
{{ innerValue }}
</slot>
</div>
<input
v-if="!multiple"
ref="search"
class="ds-select-search"
:id="id"
:name="model"
:autofocus="autofocus"
:placeholder="placeholder"
:tabindex="tabindex"
:disabled="disabled"
:readonly="readonly"
v-model="searchString"
@focus="handleFocus"
@keydown.delete.stop="deselectLastOption"
@keydown.down.prevent="pointerNext"
@keydown.up.prevent="pointerPrev"
@keypress.enter.prevent.stop="selectPointerOption"
@keyup.esc="close">
</div>
<div class="ds-select-dropdown">
<ul class="ds-select-options">
<li
class="ds-select-option"
:class="[
isSelected(option) && `ds-select-option-is-selected`,
pointer === index && `ds-select-option-hover`
]"
v-for="(option, index) in filteredOptions"
@click="handleSelect(option)"
@mouseover="setPointer(index)"
:key="option.label || option">
<!-- @slot Slot to provide custom option items -->
<slot
name="option"
:option="option">
{{ option.label || option }}
</slot>
</li>
</ul>
</div>
<div
v-if="iconRight"
class="ds-select-icon-right">
@ -39,6 +125,11 @@
<script>
import inputMixin from '../shared/input'
import multiinputMixin from '../shared/multiinput'
import ClickOutside from 'vue-click-outside'
import DsFormItem from '@@/components/data-input/FormItem/FormItem'
import DsChip from '@@/components/typography/Chip/Chip'
import DsIcon from '@@/components/typography/Icon/Icon'
/**
* Used for handling basic user input.
@ -46,7 +137,21 @@ import inputMixin from '../shared/input'
*/
export default {
name: 'DsSelect',
mixins: [inputMixin],
mixins: [inputMixin, multiinputMixin],
components: {
DsFormItem,
DsChip,
DsIcon
},
directives: {
ClickOutside
},
data() {
return {
searchString: '',
pointer: 0
}
},
props: {
/**
* The placeholder shown when value is empty.
@ -69,13 +174,6 @@ export default {
type: Boolean,
default: false
},
/**
* Whether the user can select multiple items
*/
multiple: {
type: Boolean,
default: false
},
/**
* The name of the input's icon.
*/
@ -88,7 +186,7 @@ export default {
*/
iconRight: {
type: String,
default: null
default: 'angle-down'
},
/**
* The select options.
@ -98,6 +196,98 @@ export default {
default() {
return []
}
},
/**
* Whether the options are searchable
*/
searchable: {
type: Boolean,
default: true
}
},
computed: {
filteredOptions() {
if (!this.searchString) {
return this.options
}
const searchParts = this.searchString.split(' ')
return this.options.filter(option => {
const value = option.value || option
return searchParts.every(part => {
if (!part) {
return true
}
return value.toLowerCase().includes(part.toLowerCase())
})
})
},
pointerMax() {
return this.filteredOptions.length - 1
}
},
watch: {
pointerMax(max) {
if (max < this.pointer) {
this.$nextTick(() => {
this.pointer = max
})
}
}
},
methods: {
handleSelect(options) {
this.selectOption(options)
this.resetSearch()
if (this.multiple) {
this.$refs.search.focus()
this.handleFocus()
} else {
this.close()
}
},
resetSearch() {
this.searchString = ''
},
handleClick() {
if (!this.focus || this.multiple) {
this.$refs.search.focus()
this.handleFocus()
}
},
close() {
this.$refs.search.blur()
this.handleBlur()
},
deselectLastOption() {
if (
this.multiple &&
this.innerValue &&
this.innerValue.length &&
!this.searchString.length
) {
this.deselectOption(this.innerValue.length - 1)
}
},
setPointer(index) {
this.pointer = index
},
pointerPrev() {
if (this.pointer === 0) {
this.pointer = this.pointerMax
} else {
this.pointer--
}
},
pointerNext() {
if (this.pointer === this.pointerMax) {
this.pointer = 0
} else {
this.pointer++
}
},
selectPointerOption() {
this.handleSelect(this.filteredOptions[this.pointer])
}
}
}

View File

@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Select.vue matches snapshot 1`] = `
<dsformitem-stub>
<div
class="ds-select-wrap"
tabindex="-1"
>
<!---->
<div
class="ds-select ds-select-has-icon-right"
>
<div
class="ds-select-value"
>
1
</div>
<input
class="ds-select-search"
tabindex="0"
/>
</div>
<div
class="ds-select-dropdown"
>
<ul
class="ds-select-options"
>
<li
class="ds-select-option ds-select-option-is-selected ds-select-option-hover"
>
1
</li>
<li
class="ds-select-option"
>
2
</li>
<li
class="ds-select-option"
>
3
</li>
</ul>
</div>
<div
class="ds-select-icon-right"
>
<dsicon-stub
arialabel="icon"
name="angle-down"
tag="span"
/>
</div>
</div>
</dsformitem-stub>
`;

View File

@ -2,4 +2,158 @@
```
<ds-select :options="['blue', 'red', 'green']" />
```
## Usage with label
```
<ds-select
label="Color"
:options="['blue', 'red', 'green']" />
```
## Bind to a value
Use v-model to bind a value to the select input.
```
<template>
<div>
<ds-select
v-model="color"
:options="['blue', 'red', 'green']"
placeholder="Color ..."></ds-select>
<ds-text>Your color: {{ color }}</ds-text>
</div>
</template>
<script>
export default {
data() {
return {
color: 'blue'
}
}
}
</script>
```
## Multiselect
Use the multiple prop to allow the user selecting multiple values.
```
<template>
<div>
<ds-select
v-model="color"
:options="['blue', 'red', 'green']"
placeholder="Color ..."
multiple></ds-select>
<ds-text>Your colors: {{ color }}</ds-text>
</div>
</template>
<script>
export default {
data() {
return {
color: ['blue', 'red']
}
}
}
</script>
```
## Options as objects
Options can be objects with a label and a value property.
```
<template>
<div>
<ds-select
v-model="color"
:options="colorOptions"
placeholder="Color ..."></ds-select>
<ds-text>Your color: {{ color }}</ds-text>
</div>
</template>
<script>
export default {
data() {
return {
color: '',
colorOptions: [
{
label: 'blue',
value: '#0e17d8'
},
{
label: 'red',
value: '#d80e3f'
},
{
label: 'green',
value: '#0ed853'
}
]
}
}
}
</script>
```
## Validation
We use <a href="https://github.com/yiminghe/async-validator" targe="_blank">async-validator schemas</a> for validation.
If you need to validate more than one field it is better to use the form component.
```
<template>
<div>
<ds-select
v-model="color"
:options="['blue', 'red', 'green']"
:schema="{type: 'enum', enum: ['green'], message: 'Please choose green :)' }"
placeholder="Color ..." />
</div>
</template>
<script>
export default {
data() {
return {
color: ''
}
}
}
</script>
```
## Select sizes
```
<ds-select placeholder="Small ..." size="small"></ds-select>
<ds-select placeholder="Base ..."></ds-select>
<ds-select placeholder="Large ..." size="large"></ds-select>
```
## Select icons
Add an icon to help the user identify the select fields usage.
```
<ds-select
placeholder="User ..."
icon="user"></ds-select>
<ds-select
placeholder="Day ..."
icon="clock"></ds-select>
<ds-select
placeholder="User ..."
size="small"
icon="user"></ds-select>
<ds-select
placeholder="User ..."
size="large"
icon="user"></ds-select>
```

View File

@ -0,0 +1,309 @@
import { shallowMount } from '@vue/test-utils'
import Comp from './Select.vue'
describe('Select.vue', () => {
describe('Events emitting', () => {
describe('@input', () => {
test('should be called when the value is changed passing the new value', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '3',
options: ['1', '2', '3']
}
})
wrapper.vm.selectOption(wrapper.vm.options[0])
expect(wrapper.emitted().input[0]).toEqual(['1'])
})
test('should be called when an option is clicked passing the options value', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '3',
options: ['1', '2', '3']
}
})
wrapper.find('.ds-select-option').trigger('click')
expect(wrapper.emitted().input[0]).toEqual(['1'])
})
})
})
describe('innerValue', () => {
test('should contain a single selected value by default', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '1',
options: ['1', '2', '3']
}
})
expect(wrapper.vm.innerValue).toEqual('1')
})
test('should contain an array of values when multiple: true', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: ['1'],
options: ['1', '2', '3'],
multiple: true
}
})
expect(wrapper.vm.innerValue).toEqual(['1'])
})
})
describe('options', () => {
test('should highlight the selected value', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '1',
options: ['1', '2', '3']
}
})
const option = wrapper.find('.ds-select-option')
expect(option.classes()).toContain('ds-select-option-is-selected')
})
test('should highlight all selected values when multiple: true', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: ['1', '2'],
options: ['1', '2', '3'],
multiple: true
}
})
const option = wrapper.findAll('.ds-select-option')
expect(option.at(0).classes()).toContain('ds-select-option-is-selected')
expect(option.at(1).classes()).toContain('ds-select-option-is-selected')
})
})
describe('selectOption', () => {
test('should set innerValue to selected value', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '3',
options: ['1', '2', '3']
}
})
wrapper.vm.selectOption(wrapper.vm.options[0])
expect(wrapper.vm.innerValue).toEqual('1')
})
test('should add selected value to innerValue when multiple: true', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: ['3'],
options: ['1', '2', '3'],
multiple: true
}
})
wrapper.vm.selectOption(wrapper.vm.options[0])
expect(wrapper.vm.innerValue).toEqual(['3', '1'])
})
test('should toggle selected value in innerValue when multiple: true', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: ['3', '1'],
options: ['1', '2', '3'],
multiple: true
}
})
wrapper.vm.selectOption(wrapper.vm.options[0])
expect(wrapper.vm.innerValue).toEqual(['3'])
})
})
describe('search', () => {
test('should filter options by search string', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['cat', 'duck', 'dog']
}
})
wrapper.vm.searchString = 'do'
expect(wrapper.vm.filteredOptions).toEqual(['dog'])
})
test('should be case insensitive', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['cat', 'duck', 'dog']
}
})
wrapper.vm.searchString = 'DO'
expect(wrapper.vm.filteredOptions).toEqual(['dog'])
})
test('should ignore spaces', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['cat', 'duck', 'dog']
}
})
wrapper.vm.searchString = 'd o'
expect(wrapper.vm.filteredOptions).toEqual(['dog'])
})
test('should display filtered options', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['cat', 'duck', 'dog']
}
})
wrapper.vm.searchString = 'do'
const filteredOptions = wrapper.findAll('.ds-select-option')
expect(filteredOptions.length).toEqual(1)
})
test('should work when using search input', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['cat', 'duck', 'dog']
}
})
const searchInput = wrapper.find('.ds-select-search')
searchInput.setValue('do')
expect(wrapper.vm.filteredOptions).toEqual(['dog'])
})
})
describe('pointer', () => {
test('should be set by mouse over option', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
const options = wrapper.findAll('.ds-select-option')
options.at(2).trigger('mouseover')
expect(wrapper.vm.pointer).toEqual(2)
})
test('should be set by pointerNext', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointerNext()
expect(wrapper.vm.pointer).toEqual(1)
})
test('should be set to 0 by pointerNext when on last entry', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointer = 2
wrapper.vm.pointerNext()
expect(wrapper.vm.pointer).toEqual(0)
})
test('should be set by pointerPrev', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointer = 1
wrapper.vm.pointerPrev()
expect(wrapper.vm.pointer).toEqual(0)
})
test('should be set to last entry by pointerPrev when 0', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointerPrev()
expect(wrapper.vm.pointer).toEqual(2)
})
test('should be set by key down on wrap', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
const wrap = wrapper.find('.ds-select-wrap')
wrap.trigger('keydown.down')
expect(wrapper.vm.pointer).toEqual(1)
})
test('should be set by key up on wrap', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
const wrap = wrapper.find('.ds-select-wrap')
wrap.trigger('keydown.up')
expect(wrapper.vm.pointer).toEqual(2)
})
test('should be set by key down on search input', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
const searchInput = wrapper.find('.ds-select-search')
searchInput.trigger('keydown.down')
expect(wrapper.vm.pointer).toEqual(1)
})
test('should be set by key up on search input', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
const searchInput = wrapper.find('.ds-select-search')
searchInput.trigger('keydown.up')
expect(wrapper.vm.pointer).toEqual(2)
})
test('should select option by pointer value', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointer = 1
wrapper.vm.selectPointerOption()
expect(wrapper.vm.innerValue).toEqual('2')
})
test('should select option by enter key on wrap', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointer = 1
const wrap = wrapper.find('.ds-select-wrap')
wrap.trigger('keypress.enter')
expect(wrapper.vm.innerValue).toEqual('2')
})
test('should select option by enter key on search input', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.pointer = 1
const searchInput = wrapper.find('.ds-select-search')
searchInput.trigger('keypress.enter')
expect(wrapper.vm.innerValue).toEqual('2')
})
})
it('matches snapshot', () => {
const wrapper = shallowMount(Comp, {
propsData: {
value: '1',
options: ['1', '2', '3']
}
})
expect(wrapper.element).toMatchSnapshot()
})
})

View File

@ -1,3 +1,149 @@
@import '../shared/input.scss';
@include input(ds-select);
@include input(ds-select);
.ds-select {
user-select: none;
.ds-input-has-focus & {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.ds-select-search, .ds-select-value {
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: $input-border-size solid transparent;
padding: $input-padding-vertical $space-x-small;
line-height: $line-height-base;
.ds-input-size-small & {
padding: $input-padding-vertical-small $space-x-small;
}
.ds-input-size-large & {
padding: $input-padding-vertical-large $space-x-small;
}
.ds-select-has-icon & {
padding-left: $input-height;
.ds-input-size-small & {
padding-left: $input-height-small;
}
.ds-input-size-large & {
padding-left: $input-height-large;
}
}
.ds-select-has-icon-right & {
padding-right: $input-height;
.ds-input-size-small & {
padding-right: $input-height-small;
}
.ds-input-size-large & {
padding-right: $input-height-large;
}
}
}
.ds-select-search {
appearance: none;
font-size: inherit;
font-family: $font-family-text;
width: 100%;
background: transparent;
color: $text-color-base;
outline: none;
user-select: text;
opacity: 0;
&::placeholder {
color: $text-color-disabled;
}
.ds-input-has-focus & {
opacity: 1;
}
.ds-select-multiple & {
position: relative;
display: inline-flex;
width: auto;
height: auto;
padding: 0;
opacity: 1;
}
}
.ds-select-placeholder, .ds-select-value {
pointer-events: none;
.ds-input-has-focus & {
opacity: 0;
}
}
.ds-select-placeholder {
color: $text-color-disabled;
}
.ds-selected-options {
display: flex;
}
.ds-selected-option {
display: inline-flex;
margin-right: $space-xx-small;
}
.ds-select-dropdown {
position: absolute;
z-index: $z-index-dropdown;
top: 100%;
left: 0;
width: 100%;
background-color: $background-color-base;
border: $input-border-size solid $border-color-active;
border-top: 0;
border-bottom-left-radius: $border-radius-base;
border-bottom-right-radius: $border-radius-base;
visibility: hidden;
opacity: 0;
transition: all $duration-short $ease-out;
max-height: 240px;
overflow: auto;
.ds-input-has-focus & {
visibility: visible;
opacity: 1;
}
}
.ds-select-options {
@include reset-list;
}
.ds-select-option {
padding: $input-padding-vertical $space-x-small;
cursor: pointer;
transition: all $duration-short $ease-out;
&.ds-select-option-hover {
background-color: $background-color-primary;
color: $text-color-primary-inverse;
}
}
.ds-select-option-is-selected {
background-color: $background-color-soft;
color: $text-color-primary;
}

View File

@ -20,7 +20,7 @@ export default {
* The value of the input. Can be passed via v-model.
*/
value: {
type: [String, Object, Number],
type: [String, Object, Number, Array],
default: null
},
/**
@ -56,7 +56,7 @@ export default {
*/
schema: {
type: Object,
default: () => ({})
default: () => null
},
/**
* The input's size.
@ -68,6 +68,10 @@ export default {
validator: value => {
return value.match(/(small|base|large)/)
}
},
tabindex: {
type: Number,
default: 0
}
},
data() {
@ -107,9 +111,13 @@ export default {
}
},
methods: {
input(event) {
handleInput(event) {
this.input(event.target.value)
},
input(value) {
this.innerValue = value
if (this.$parentForm) {
this.$parentForm.update(this.model, event.target.value)
this.$parentForm.update(this.model, value)
} else {
/**
* Fires after user input.
@ -117,8 +125,8 @@ export default {
*
* @event input
*/
this.$emit('input', event.target.value)
this.validate(event.target.value)
this.$emit('input', value)
this.validate(value)
}
},
handleFormUpdate(data, errors) {
@ -126,6 +134,9 @@ export default {
this.error = errors ? errors[this.model] : null
},
validate(value) {
if (!this.schema) {
return
}
const validator = new Schema({ input: this.schema })
// Prevent validator from printing to console
// eslint-disable-next-line

View File

@ -2,63 +2,67 @@
.#{$class}-wrap {
position: relative;
}
.#{$class} {
appearance: none;
box-sizing: border-box;
font-size: $font-size-base;
font-size: $input-font-size-base;
line-height: $line-height-base;
font-family: $font-family-text;
width: 100%;
padding: $input-padding-vertical $space-x-small;
height: $input-height;
color: $text-color-base;
background: $background-color-base;
border: $input-border-size solid $border-color-soft;
border: $input-border-size solid $border-color-base;
border-radius: $border-radius-base;
outline: none;
transition: all $duration-short $ease-out;
&::placeholder {
color: $text-color-disabled;
}
.ds-input-has-focus &,
&:focus {
border-color: $border-color-active;
background: $background-color-base;
}
.#{$class}-is-disabled &,
.ds-input-is-disabled &,
&:disabled {
color: $text-color-disabled;
opacity: $opacity-disabled;
cursor: not-allowed;
}
.#{$class}-has-error & {
.ds-input-has-error & {
border-color: $border-color-danger;
}
}
.#{$class}-size-small {
.ds-input-size-small {
font-size: $font-size-small;
.#{$class} {
font-size: $input-font-size-small;
height: $input-height-small;
padding: $input-padding-vertical-small $space-x-small;
}
}
.#{$class}-size-large {
.ds-input-size-large {
font-size: $font-size-large;
.#{$class} {
font-size: $input-font-size-large;
height: $input-height-large;
padding: $input-padding-vertical-large $space-x-small;
}
}
.#{$class}-icon,
.#{$class}-icon-right {
position: absolute;
@ -71,32 +75,47 @@
width: $input-height;
color: $text-color-softer;
transition: color $duration-short $ease-out;
.#{$class}-has-focus & {
pointer-events: none;
.ds-input-has-focus & {
color: $text-color-base;
}
}
.ds-input-size-small & {
width: $input-height-small;
}
.ds-input-size-large & {
width: $input-height-large;
}
}
.#{$class}-icon-right {
right: 0;
left: auto;
}
.#{$class}-has-icon {
padding-left: $input-height;
.ds-input-size-small & {
padding-left: $input-height-small;
}
.#{$class}-size-small &,
.#{$class}-size-large & {
padding-left: $input-height;
.ds-input-size-large & {
padding-left: $input-height-large;
}
}
.#{$class}-has-icon-right {
padding-right: $input-height;
.#{$class}-size-small &,
.#{$class}-size-large & {
padding-right: $input-height;
.ds-input-size-small & {
padding-right: $input-height-small;
}
}
.ds-input-size-large & {
padding-right: $input-height-large;
}
}
}

View File

@ -0,0 +1,49 @@
/**
* @mixin
*/
export default {
props: {
/**
* Whether the user can select multiple items
*/
multiple: {
type: Boolean,
default: false
}
},
methods: {
selectOption(option) {
const newValue = option.value || option
if (this.multiple) {
this.selectMultiOption(newValue)
} else {
this.input(newValue)
}
},
selectMultiOption(value) {
if (!this.innerValue) {
return this.input([value])
}
const index = this.innerValue.indexOf(value)
if (index < 0) {
return this.input([...this.innerValue, value])
}
this.deselectOption(index)
},
deselectOption(index) {
const newArray = [...this.innerValue]
newArray.splice(index, 1)
this.input(newArray)
},
isSelected(option) {
if (!this.innerValue) {
return false
}
const value = option.value || option
if (this.multiple) {
return this.innerValue.includes(value)
}
return this.innerValue === value
}
}
}

View File

@ -14,9 +14,9 @@
v-if="image || $slots.image">
<!-- @slot Content of the card's image -->
<slot name="image">
<img
:src="image"
v-if="!error"
<img
:src="image"
v-if="!error"
@error="onError" >
</slot>
</div>
@ -36,7 +36,14 @@
</slot>
</header>
<div class="ds-card-content">
<slot />
<template v-if="space">
<ds-space :margin="space">
<slot />
</ds-space>
</template>
<template v-else>
<slot />
</template>
</div>
<footer
class="ds-card-footer"
@ -125,6 +132,14 @@ export default {
hover: {
type: Boolean,
default: false
},
/**
* If you need some spacing you can provide it here like for ds-space
* `xxx-small, xx-small, x-small, small, large, x-large, xx-large, xxx-large`
*/
space: {
type: String,
default: null
}
},
data() {

View File

@ -21,6 +21,23 @@ Set a header and image for the card and provide some content.
</ds-flex>
```
## Space
Need more or less space top and bottom of the card?
Specify with with the `space` prop
```html
<ds-card space="xx-small">
xx-small
</ds-card>
```
```html
<ds-card space="xx-large">
xx-large
</ds-card>
```
## Cards with footer
Most commonly the footer will contain some actions connected to the content.

View File

@ -0,0 +1,93 @@
<template>
<component
:is="tag"
class="ds-chip"
:class="[
`ds-chip-size-${size}`,
`ds-chip-${color}`,
removable && 'ds-chip-removable',
round && 'ds-chip-round'
]"
>
<slot />
<button
v-if="removable"
@click="remove"
class="ds-chip-close">
<ds-icon name="close" />
</button>
</component>
</template>
<script>
/**
* Chips are used to represent small blocks of information.
* Their most common usage is for displaying contacts or tags.
* @version 1.0.0
*/
export default {
name: 'DsChip',
props: {
/**
* The background color used for the chip.
* `medium, inverse, primary, success, warning, danger`
*/
color: {
type: String,
default: 'medium',
validator: value => {
return value.match(/(medium|inverse|primary|success|warning|danger)/)
}
},
/**
* The size used for the text.
* `base, large, small`
*/
size: {
type: String,
default: 'base',
validator: value => {
return value.match(/(base|large|small)/)
}
},
/**
* Whether the chip should be removeable
* `true, false`
*/
removable: {
type: Boolean,
default: false
},
/**
* Whether the chip should be rounded
* `true, false`
*/
round: {
type: Boolean,
default: true
},
/**
* The html element name used for the text.
*/
tag: {
type: String,
default: 'span'
}
},
methods: {
remove() {
/**
* Fires after user clicked the remove button.
*
* @event remove
*/
this.$emit('remove')
}
}
}
</script>
<style lang="scss" src="./style.scss">
</style>
<docs src="./demo.md"></docs>

View File

@ -0,0 +1,62 @@
## Chip colors
Use different colors to emphasize or provide meaning.
```
<ds-chip>Medium</ds-chip>
<ds-chip color="inverse">Inverse</ds-chip>
<ds-chip color="primary">Primary</ds-chip>
<ds-chip color="success">Success</ds-chip>
<ds-chip color="warning">Warning</ds-chip>
<ds-chip color="danger">Danger</ds-chip>
```
## Chip sizes
Use different sizes to create hierarchy (defaults to `base`).
```
<ds-chip size="small">Small</ds-chip>
<ds-chip size="base">Base</ds-chip>
<ds-chip size="large">Large</ds-chip>
```
## Deletable
A chip can be deletable.
```
<template>
<div>
<ds-chip
v-for="(tag, index) in tags"
@remove="removeTag(index)"
removable
:key="tag">
{{ tag }}
</ds-chip>
<ds-chip
v-for="(tag, index) in tags"
@remove="removeTag(index)"
removable
color="primary"
:key="tag">
{{ tag }}
</ds-chip>
</div>
</template>
<script>
export default {
data() {
return {
tags: ['Dog', 'Cat', 'Duck']
}
},
methods: {
removeTag (index) {
this.tags.splice(index, 1)
}
}
}
</script>
```

View File

@ -0,0 +1,81 @@
.ds-chip {
@include reset;
@include stack-space($space-xx-small);
position: relative;
display: inline-block;
font-family: $font-family-text;
line-height: $line-height-base;
padding: $space-xx-small $space-x-small;
border-radius: $border-radius-base;
font-weight: $font-weight-bold;
color: $text-color-base;
background-color: $background-color-softest;
&.ds-chip-removable {
padding-right: $space-x-small + $space-small;
}
}
.ds-chip-inverse {
color: $text-color-inverse;
background-color: $background-color-inverse-softer;
}
.ds-chip-primary {
color: $text-color-primary-inverse;
background-color: $background-color-primary;
}
.ds-chip-success {
color: $text-color-success-inverse;
background-color: $background-color-success;
}
.ds-chip-warning {
color: $text-color-warning-inverse;
background-color: $background-color-warning;
}
.ds-chip-danger {
color: $text-color-danger-inverse;
background-color: $background-color-danger;
}
.ds-chip-round {
border-radius: $border-radius-rounded;
}
.ds-chip-size-base {
font-size: $font-size-small;
}
.ds-chip-size-small {
font-size: $font-size-x-small;
}
.ds-chip-size-large {
font-size: $font-size-base;
}
.ds-chip-close {
@include reset-button;
position: absolute;
right: $space-xx-small;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: $font-size-x-small;
width: $space-small;
height: $space-small;
border-radius: $border-radius-circle;
//background-color: $background-color-base;
opacity: $opacity-soft;
cursor: pointer;
transition: all $duration-short $ease-out-sharp;
&:hover {
opacity: 1;
}
}

View File

@ -1,23 +1,27 @@
/* FORM VARIABLES / MIXINS
--------------------------------------------- */
$input-font-size-base: $font-size-base;
$input-border-size: $border-size-base;
$input-padding-vertical: $space-x-small;
$input-height: calc(
#{$font-size-base * $line-height-base} + #{($input-padding-vertical + $input-border-size) * 2}
);
$input-padding-vertical-small: $space-xx-small;
$input-font-size-small: $font-size-base;
$input-padding-vertical-small: $space-xxx-small;
$input-height-small: calc(
#{$font-size-small * $line-height-base} + #{($input-padding-vertical-small + $input-border-size) * 2}
#{$font-size-base * $line-height-base} + #{($input-padding-vertical-small + $input-border-size) * 2}
);
$input-font-size-large: $font-size-large;
$input-padding-vertical-large: $space-x-small;
$input-height-large: calc(
#{$font-size-large * $line-height-base} + #{($input-padding-vertical-large + $input-border-size) * 2}
#{$input-font-size-large * $line-height-base} + #{($input-padding-vertical-large + $input-border-size) * 2}
);
// $input-font-size-large: $font-size-x-large;
$input-padding-vertical-x-large: $space-small;
$input-height-x-large: calc(
#{$font-size-x-large * $line-height-base} + #{($input-padding-vertical-x-large + $input-border-size) * 2}
#{$input-font-size-large * $line-height-base} + #{($input-padding-vertical-x-large + $input-border-size) * 2}
);

View File

@ -2,15 +2,11 @@
--------------------------------------------- */
/* AUTO SCALING FOR TYPE WITH MIN/MAX SIZES
@param {Number} $responsive - Viewport-based size
@param {Number} $min - Minimum font size (px)
@param {Number} $max - Maximum font size (px) (optional)
@param {Number} $fallback - Fallback for viewport-based units (optional)
@example SCSS - 5vw size, 35px min & 150px max size + 50px fallback:
@include responsive-font(5vw, 35px, 150px, 50px);
*/
@mixin responsive-font($responsive, $min, $max: false, $fallback: false) {
@ -44,6 +40,32 @@
margin: 0;
}
@mixin reset-list {
@include reset;
list-style: none;
}
@mixin reset-button {
@include reset;
border: 0;
width: auto;
overflow: visible;
background: transparent;
color: inherit;
font: inherit;
line-height: normal;
outline: none;
font-smoothing: inherit;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
&::-moz-focus-inner {
border: 0;
padding: 0;
}
}
@mixin clearfix {
&:before, &:after {
display: table;
@ -104,4 +126,4 @@
border-bottom-left-radius: $size;
border-bottom-right-radius: $size;
}
}
}

View File

@ -291,6 +291,10 @@ props:
value: *color-neutral-80
category: border-color
- name: border-color-light
value: *color-neutral-90
category: border-color
- name: border-color-active
value: *color-primary
category: border-color

View File

@ -17,7 +17,7 @@ props:
- name: font-size-base
value: "1rem"
- name: font-size-body
value: "16px"
value: "15px"
- name: font-size-small
value: "0.8rem"
- name: font-size-x-small

View File

@ -6,6 +6,8 @@
props:
- name: z-index-modal
value: "9999"
- name: z-index-dropdown
value: "8888"
- name: z-index-page-submenu
value: "2500"
- name: z-index-page-header