Merged master in

This commit is contained in:
Grzegorz Leoniec 2019-01-18 14:27:00 +01:00
commit 90d7b94e7a
No known key found for this signature in database
GPG Key ID: 3AA43686D4EB1377
99 changed files with 2281 additions and 435 deletions

View File

@ -23,7 +23,7 @@ install:
- docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT --target production -t humanconnection/nitro-web .
- docker-compose -f docker-compose.yml -f docker-compose.travis.yml up -d
- git clone https://github.com/Human-Connection/Nitro-Backend.git ../Nitro-Backend
- git -C "../Nitro-Backend" checkout $BACKEND_BRANCH
- git -C "../Nitro-Backend" checkout $BACKEND_BRANCH || git -C "../Nitro-Backend" checkout master
- docker-compose -f ../Nitro-Backend/docker-compose.yml -f ../Nitro-Backend/docker-compose.travis.yml up -d
- yarn global add cypress wait-on
- yarn add cypress-cucumber-preprocessor

View File

@ -34,12 +34,6 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
background: #fff;
}
.tags {
.ds-tag {
margin-right: $space-xx-small;
}
}
blockquote {
display: block;
padding: 15px 20px 15px 45px;
@ -164,6 +158,16 @@ hr {
}
}
.ds-card .ds-section {
padding: 0;
margin-left: -$space-base;
margin-right: -$space-base;
.ds-container {
padding: $space-base;
}
}
[class$="menu-popover"] {
min-width: 130px;

View File

@ -111,7 +111,7 @@
/>
</ds-flex-item>
<ds-flex-item :width="{base: 1}">
<ds-button full-width>
<ds-button fullwidth>
<ds-icon name="user-times" />
</ds-button>
</ds-flex-item>

View File

@ -4,7 +4,7 @@
:loading="loading"
icon="plus"
primary
full-width
fullwidth
@click.prevent="follow"
>
Folgen

View File

@ -4,8 +4,8 @@
style="text-align: center"
>
<ds-button
:icon="loading ? 'spinner' : 'arrow-down'"
:disabled="loading"
:loading="loading"
icon="arrow-down"
ghost
@click="$emit('click')"
>

View File

@ -25,11 +25,11 @@
slot="popover"
slot-scope="{toggleMenu}"
class="locale-menu-popover"
:is-exact="isExact"
:matcher="matcher"
:routes="routes"
>
<ds-menu-item
slot="Navigation"
slot="menuitem"
slot-scope="item"
class="locale-menu-item"
:route="item.route"
@ -79,7 +79,7 @@ export default {
this.$i18n.set(locale)
toggleMenu()
},
isExact(locale) {
matcher(locale) {
return locale === this.$i18n.locale()
}
}

View File

@ -16,9 +16,9 @@ const setUserName = name => {
cy.get('input[id=name]')
.clear()
.type(name)
cy.contains('Save')
cy.get('[type=submit]')
.click()
.wait(200)
.not('[disabled]')
myName = name
}
@ -31,7 +31,9 @@ When('I save {string} as my location', location => {
cy.get('.ds-select-option')
.contains(location)
.click()
cy.contains('Save').click()
cy.get('[type=submit]')
.click()
.not('[disabled]')
myLocation = location
})
@ -39,7 +41,9 @@ When('I have the following self-description:', text => {
cy.get('textarea[id=bio]')
.clear()
.type(text)
cy.contains('Save').click()
cy.get('[type=submit]')
.click()
.not('[disabled]')
aboutMeText = text
})

View File

@ -59,10 +59,10 @@
<hr>
<ds-menu
:routes="routes"
:is-exact="isExact"
:matcher="matcher"
>
<ds-menu-item
slot="Navigation"
slot="menuitem"
slot-scope="item"
:route="item.route"
:parents="item.parents"
@ -156,10 +156,10 @@ export default {
}
},
methods: {
isExact(url) {
matcher(url, route) {
if (url.indexOf('/profile') === 0) {
// do only match own profile
this.$route.path === url
return this.$route.path === url
}
return this.$route.path.indexOf(url) === 0
}

View File

@ -73,7 +73,8 @@ module.exports = {
router: {
middleware: ['authenticated'],
linkActiveClass: 'router-active-link',
linkActiveClass: 'router-link-active',
linkExactActiveClass: 'router-link-exact-active',
scrollBehavior: () => {
return { x: 0, y: 0 }
}

View File

@ -47,12 +47,12 @@
"date-fns": "^2.0.0-alpha.26",
"express": "^4.16.3",
"graphql": "^14.0.2",
"graphql-tag": "^2.10.0",
"graphql-tag": "^2.10.1",
"jsonwebtoken": "^8.3.0",
"nuxt": "^2.0.0",
"nuxt-env": "^0.0.4",
"nuxt-sass-resources-loader": "^2.0.5",
"portal-vue": "^1.5.1",
"portal-vue": "~1.5.1",
"v-tooltip": "^2.0.0-rc.33",
"vue-count-to": "^1.0.13",
"vue-izitoast": "1.1.2",

View File

@ -5,7 +5,10 @@
</ds-heading>
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '200px' }">
<ds-menu :routes="routes" />
<ds-menu
:routes="routes"
:is-exact="() => true"
/>
</ds-flex-item>
<ds-flex-item>
<transition

View File

@ -17,7 +17,7 @@
<ds-flex gutter="small">
<ds-flex-item
:width="{ base: '100%', sm: '50%' }"
center
centered
>
<no-ssr>
<locale-switch
@ -28,7 +28,7 @@
<ds-space
margin-top="small"
margin-bottom="xxx-small"
center
centered
>
<img
class="login-image"
@ -39,7 +39,7 @@
</ds-flex-item>
<ds-flex-item
:width="{ base: '100%', sm: '50%' }"
center
centered
>
<ds-space margin="small">
<ds-text size="small">
@ -70,7 +70,7 @@
<ds-button
:loading="pending"
primary
full-width
fullwidth
name="submit"
type="submit"
icon="sign-in"

View File

@ -3,13 +3,13 @@
<ds-flex>
<ds-flex-item
:width="{ base: '100%' }"
center
centered
>
<ds-space
style="text-align: center;"
margin-top="large"
margin-bottom="xxx-small"
center
centered
>
<img
style="width: 200px;"
@ -21,7 +21,7 @@
style="text-align: center;"
margin-top="small"
margin-bottom="xxx-small"
center
centered
>
<ds-heading
tag="h3"

View File

@ -61,16 +61,15 @@
</template>
<ds-space margin="small" />
<!-- Comments -->
<ds-section
slot="footer"
>
<ds-section slot="footer">
<h3 style="margin-top: 0;">
<span>
<ds-icon name="comments" />
<ds-tag
v-if="post.commentsCount"
style="transform: scale(.8); margin-top: -4px; margin-left: -12px; position: absolute;"
style="margin-top: -4px; margin-left: -12px; position: absolute;"
color="primary"
size="small"
round
>
{{ post.commentsCount }}
@ -268,6 +267,7 @@ export default {
object-position: center;
}
}
.ds-card-footer {
padding: 0;

View File

@ -5,7 +5,10 @@
</ds-heading>
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '200px' }">
<ds-menu :routes="routes" />
<ds-menu
:routes="routes"
:is-exact="() => true"
/>
</ds-flex-item>
<ds-flex-item>
<transition

View File

@ -1,11 +1,15 @@
<template>
<ds-form
v-model="form"
@submit="submit"
>
<ds-card space="small">
<ds-heading tag="h3">
{{ $t('settings.data.name') }}
</ds-heading>
<ds-input
id="name"
v-model="form.name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.labelName')"
@ -13,9 +17,9 @@
<!-- eslint-disable vue/use-v-on-exact -->
<ds-select
id="city"
v-model="form.locationName"
:options="cities"
model="locationName"
icon="map-marker"
:options="cities"
:label="$t('settings.data.labelCity')"
:placeholder="$t('settings.data.labelCity')"
@input.native="handleCityInput"
@ -23,7 +27,7 @@
<!-- eslint-enable vue/use-v-on-exact -->
<ds-input
id="bio"
v-model="form.about"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
@ -33,13 +37,15 @@
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="sending"
primary
@click.prevent="submit"
>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
</ds-form>
</template>
<script>
@ -57,6 +63,7 @@ export default {
return {
axiosSource: null,
cities: [],
sending: false,
form: {
name: null,
locationName: null,
@ -83,7 +90,7 @@ export default {
},
methods: {
submit() {
console.log('SUBMIT', { ...this.form })
this.sending = true
this.$apollo
.mutate({
mutation: gql`
@ -110,7 +117,9 @@ export default {
variables: {
id: this.user.id,
name: this.form.name,
locationName: this.form.locationName,
locationName: this.form.locationName
? this.form.locationName['label'] || this.form.locationName
: null,
about: this.form.about
},
// Update the cache with the result
@ -139,13 +148,14 @@ export default {
} */
})
.then(data => {
console.log(data)
this.$toast.success('Updated user')
})
.catch(err => {
console.error(err)
this.$toast.error(err.message)
})
.finally(() => {
this.sending = false
})
},
handleCityInput(value) {
clearTimeout(timeout)

View File

@ -31,6 +31,7 @@
"babel-jest": "^23.0.1",
"babel-plugin-transform-require-context": "^0.0.3",
"cheerio": "^1.0.0-rc.2",
"clipboard-copy": "^2.0.1",
"clone-deep": "^4.0.0",
"codemirror": "^5.39.2",
"cross-env": "^5.2.0",

View File

@ -1,7 +1,8 @@
<template>
<div :class="`${iframe ? 'vuep-iframe' : ''}`">
<vuep
:template="template"
:value="template"
:options="{ theme: 'vueds' }"
:iframe="iframe" />
</div>
</template>
@ -85,6 +86,7 @@ ${code}
border: $border-size-base solid $border-color-softer;
padding: $space-base;
margin-bottom: $space-small;
overflow: visible;
.vuep-iframe & {
padding: 0;

View File

@ -95,10 +95,11 @@ export default {
})
},
docParts() {
if (!this.component.docs) {
const component = this.component.component
if (!component.__docs) {
return []
}
const parts = this.component.docs.split('```')
const parts = component.__docs.split('```')
let i = 0
const parsed = parts.reduce((result, part, index) => {
if (index % 2 === 0) {

View File

@ -2,44 +2,99 @@
<div>
<ds-space v-if="componentProps">
<ds-heading tag="h2">{{ component.name | componentName }} Props</ds-heading>
<ds-card>
<ds-table
:data="componentProps"
:fields="propFields">
<template
slot="name"
slot-scope="scope">
{{ scope.row.name }} <span v-if="scope.row.required">*</span>
slot-scope="{row}">
<ds-code inline>
{{ row.name | kebabCase }}
</ds-code>
<div v-if="row.required">
<ds-tag v-if="row.required" color="warning">required</ds-tag>
</div>
<ds-space :margin-bottom="null" margin-top="small">
<div v-if="row.options">
<ds-chip size="small" v-for="option in row.options" :key="option">
{{ option }}
</ds-chip>
</div>
<ds-text color="soft">{{ row.description }}</ds-text>
</ds-space>
</template>
<template
slot="type"
slot-scope="scope">
{{ scope.row.type.name }}
slot-scope="{row}">
<ds-chip
v-for="type in row.types"
:key="type"
inline>
{{ type }}
</ds-chip>
</template>
<template
slot="default"
slot-scope="scope">
<template v-if="scope.row.defaultValue">
<span v-if="scope.row.defaultValue.func">
Function()
</span>
<span v-else>
{{ scope.row.defaultValue.value }}
</span>
slot-scope="{row}">
<ds-chip
v-if="row.defaultValue"
color="primary">
<template v-if="row.default">
{{ row.default }}
</template>
<template v-else-if="row.defaultValue.func">
Function()
</template>
<template v-else>
{{ row.defaultValue.value }}
</template>
</ds-chip>
</template>
</ds-table>
</ds-card>
</ds-space>
<ds-space v-if="componentSlots && componentSlots.length">
<ds-heading tag="h2">{{ component.name | componentName }} Slots</ds-heading>
<ds-card>
<ds-table
:data="componentSlots"
:fields="slotFields"/>
:fields="slotFields">
<ds-code
slot="name"
slot-scope="{row}"
inline>
{{ row.name }}
</ds-code>
<ds-text
color="soft"
slot="description"
slot-scope="{row}">
{{ row.description }}
</ds-text>
</ds-table>
</ds-card>
</ds-space>
<ds-space v-if="componentEvents && componentEvents.length">
<ds-heading tag="h2">{{ component.name | componentName }} Events</ds-heading>
<ds-card>
<ds-table
:data="componentEvents"
:fields="eventFields"/>
:fields="eventFields">
<ds-code
slot="name"
slot-scope="{row}"
inline>
@{{ row.name }}
</ds-code>
<ds-text
color="soft"
slot="description"
slot-scope="{row}">
{{ row.description }}
</ds-text>
</ds-table>
</ds-card>
</ds-space>
</div>
</template>
@ -57,8 +112,7 @@ export default {
return {
propFields: {
name: {
label: 'Name',
width: '20%'
label: 'Name'
},
type: {
label: 'Type',
@ -67,8 +121,7 @@ export default {
default: {
label: 'Default',
width: '20%'
},
description: 'Description'
}
},
slotFields: {
name: {
@ -91,34 +144,68 @@ export default {
if (!this.component.props) {
return null
}
return Object.keys(this.component.props).map(name => {
return {
name,
...this.component.props[name]
}
return Object.keys(this.component.props)
.map(name => {
return this.getPropAttributes(name, this.component.props[name])
})
.sort((a, b) => {
return a.name.localeCompare(b.name)
})
},
componentSlots() {
if (!this.component.slots) {
return null
}
return Object.keys(this.component.slots).map(name => {
return Object.keys(this.component.slots)
.map(name => {
return {
name: (name.match(/[^/"\\]+/g) || []).join(''),
...this.component.slots[name]
}
})
.sort((a, b) => {
return a.name.localeCompare(b.name)
})
},
componentEvents() {
if (!this.component.events) {
return null
}
return Object.keys(this.component.events).map(name => {
return Object.keys(this.component.events)
.map(name => {
return {
name,
...this.component.events[name]
}
})
.sort((a, b) => {
return a.name.localeCompare(b.name)
})
}
},
methods: {
getPropAttributes(name, oldAttributes) {
const attributes = {
name,
...oldAttributes,
...this.getAttributesFromComment(oldAttributes.comment)
}
if (attributes.type && attributes.type.name) {
attributes.types = attributes.type.name.split('|')
}
return attributes
},
getAttributesFromComment(comment) {
const attributes = {}
const optionsMatch = comment.match(/@options[ ]+(\S[ \S]*)\n/)
if (optionsMatch) {
attributes.options = optionsMatch[1].split('|')
}
const defaultMatch = comment.match(/@default[ ]+(\S[ \S]*)\n/)
if (defaultMatch) {
attributes.default = defaultMatch[1]
}
return attributes
}
}
}

View File

@ -1,5 +1,11 @@
<template>
<div class="navigation">
<div class="navigation-search">
<ds-input
v-model="searchString"
placeholder="Filter menu ..."
icon="search" />
</div>
<ds-menu
@navigate="$emit('navigate')"
:routes="routes"
@ -12,19 +18,42 @@
<script>
export default {
name: 'Navigation',
data() {
return {
searchString: ''
}
},
computed: {
routes() {
const routes = this.$router.options.routes.filter(route => {
return route.path !== '*'
})
return routes.map(route => {
return routes
.map(route => {
const [parent, ...children] = [...route.children]
parent.children = children
parent.children = children.filter(this.fitsSearch)
return parent
})
.filter(route => {
return route.children.length || this.fitsSearch(route)
})
},
searchParts() {
return this.searchString.split(' ')
}
},
methods: {
fitsSearch(route) {
if (!this.searchString) {
return true
}
return this.searchParts.every(search => {
if (!search) {
return true
}
return route.name.toLowerCase().includes(search.toLowerCase())
})
},
nameParser(route) {
return this.$options.filters.componentName(route.name)
},
@ -44,4 +73,9 @@ export default {
.navigation {
padding: $space-base $space-x-small;
}
.navigation-search {
padding: 0 $space-small;
margin-bottom: $space-base;
}
</style>

View File

@ -69,10 +69,16 @@ function createComponentRoute(component) {
function createComponentPage(component) {
return {
// Necessary to keep reactivity (hot-reload)
data() {
return {
component
}
},
render: h =>
h(ComponentPage, {
props: {
component: component
component
}
})
}

View File

@ -36,12 +36,12 @@ import DsButton from '@@/components/navigation/Button/Button'
export default {
name: 'DsCopyField',
components: {
'ds-button': DsButton
DsButton
},
props: {
/**
* The size used for the text.
* `small, base, large`
* @options small|base|large
*/
size: {
type: String,

View File

@ -11,7 +11,7 @@ exports[`CopyField.vue matches snapshot 1`] = `
<div
class="ds-copy-field-link"
>
<ds-button-stub
<dsbutton-stub
color="soft"
ghost="true"
icon="copy"

View File

@ -1,5 +1,5 @@
## Basic usage
```
<ds-copy-field>Copy me please!</ds-copy-field>
<ds-copy-field>Copy me please!</ds-copy-field>
```

View File

@ -36,7 +36,7 @@ export default {
},
/**
* The size used for the list.
* `small, base, large, x-large`
* @options small|base|large|x-large
*/
size: {
type: String,

View File

@ -51,9 +51,7 @@ ol > .ds-list-item > .ds-list-item-prefix:before, .ds-list-item-icon {
width: $font-space-xxx-large;
height: $font-space-xxx-large + .4em;
font-size: 0.6em;
// border-radius: $border-radius-circle;
color: $text-color-soft;
// background-color: $background-color-softest;
}
ol > .ds-list-item > .ds-list-item-prefix:before {

View File

@ -155,7 +155,9 @@ export default {
},
methods: {
align(colKey) {
return this.fields && this.fields[colKey] ? this.fields[colKey].align : null
return this.fields && this.fields[colKey]
? this.fields[colKey].align
: null
},
parseLabel(label) {
return startCase(label)

View File

@ -33,7 +33,7 @@ export default {
},
/**
* The column align
* `left, center, right`
* @options left|center|right
*/
align: {
type: String,

View File

@ -43,7 +43,7 @@ export default {
},
/**
* The column align
* `left, center, right`
* @options left|center|right
*/
align: {
type: String,

View File

@ -0,0 +1,139 @@
<template>
<form
class="ds-form"
@submit.prevent="submit"
novalidate="true"
autocomplete="off">
<slot
:errors="errors"
:reset="reset" />
</form>
</template>
<script>
import Schema from 'async-validator'
import cloneDeep from 'clone-deep'
import dotProp from 'dot-prop'
/**
* Used for handling complex user input.
* @version 1.0.0
*/
export default {
name: 'DsForm',
provide() {
return {
$parentForm: this
}
},
props: {
/**
* The value of the input. Can be passed via v-model.
*/
value: {
type: Object,
required: true
},
/**
* The async-validator schema used for the form data.
*/
schema: {
type: Object,
default: () => ({})
}
},
data() {
return {
newData: null,
subscriber: [],
errors: null
}
},
watch: {
value: {
handler(value) {
this.newData = cloneDeep(value)
this.notify(value, this.errors)
},
deep: true
}
},
methods: {
submit() {
this.validate(() => {
/**
* Fires after user input.
* Receives the current form data.
*
* @event input
*/
this.$emit('input', this.newData)
/**
* Fires on form submit.
* Receives the current form data.
*
* @event submit
*/
this.$emit('submit', this.newData)
})
},
validate(cb) {
const validator = new Schema(this.schema)
// Prevent validator from printing to console
// eslint-disable-next-line
const warn = console.warn;
// eslint-disable-next-line
console.warn = () => {};
validator.validate(this.newData, errors => {
if (errors) {
this.errors = errors.reduce((errorObj, error) => {
const result = { ...errorObj }
result[error.field] = error.message
return result
}, {})
} else {
this.errors = null
}
// eslint-disable-next-line
console.warn = warn;
this.notify(this.newData, this.errors)
if (!errors && cb && typeof cb === 'function') {
cb()
}
})
},
subscribe(cb) {
if (cb && typeof cb === 'function') {
cb(cloneDeep(this.newData))
this.subscriber.push(cb)
}
},
unsubscribe(cb) {
const index = this.subscriber.findIndex(cb)
if (index > -1) {
this.subscriber.splice(index, 1)
}
},
notify(data, errors) {
this.subscriber.forEach(cb => {
cb(cloneDeep(data), errors)
})
},
async update(model, value) {
dotProp.set(this.newData, model, value)
this.validate()
},
reset() {
this.$emit('input', cloneDeep(this.value))
}
},
created() {
this.newData = cloneDeep(this.value)
}
}
</script>
<style lang="scss" src="./style.scss">
</style>
<docs src="./demo.md"></docs>

View File

@ -0,0 +1,130 @@
## Basic usage
Most commonly you want the form to handle a set of data and display appropriate input fields for each piece of data.
```html
<template>
<ds-form
v-model="formData"
@submit="handleSubmit">
<ds-input
icon="at"
model="email"
label="Email"
type="email"
placeholder="Your email address ..."></ds-input>
<ds-input
icon="lock"
model="password"
label="Password"
placeholder="Your password ..."></ds-input>
<ds-space margin-top="base">
<ds-button primary>Login</ds-button>
</ds-space>
</ds-form>
</template>
<script>
export default {
data() {
return {
formData: {
email: '',
password: ''
}
}
},
methods: {
handleSubmit(data) {
console.log('Submit form ...', data)
}
}
}
</script>
```
## Advanced usage / Validation
Use a schema to provide validation for the form inputs. Use scoped slots to get access to the forms errors and functions like reset.
```html
<template>
<ds-form
v-model="formData"
@submit="handleSubmit"
:schema="formSchema">
<template slot-scope="{ errors, reset }">
<ds-input
model="name"
label="Name"
placeholder="Your name ..."></ds-input>
<ds-input
icon="at"
model="email"
label="Email"
type="email"
placeholder="Your email address ..."></ds-input>
<ds-select
icon="user"
model="gender"
label="Gender"
:options="['male', 'female']"
placeholder="Gender ..."></ds-select>
<ds-select
icon="globe"
model="settings.languages"
label="Language"
:options="['en','de','fr','it']"
multiple></ds-select>
<ds-input
model="settings.status"
label="Status"
type="textarea"
rows="3"></ds-input>
<ds-space margin-top="large">
<ds-button @click.prevent="reset()">
Reset form
</ds-button>
<ds-button
:disabled="errors"
icon="save"
primary>
Save profile
</ds-button>
</ds-space>
</template>
</ds-form>
</template>
<script>
export default {
data() {
return {
formData: {
name: 'peter',
gender: 'male',
email: 'peter@maffay.com',
settings: {
languages: ['en'],
status: 'I spy with my little eye, a girly I can get, cause she aint get to many likes.'
}
},
// https://github.com/yiminghe/async-validator
formSchema: {
name: { required: true, message: 'Fill in a name' },
email: { type: 'email', required: true, message: 'Fill in a valid email' },
settings: {
type: 'object',
fields: {
status: { min: 20, max: 300, message: 'Write between 20 and 300 letters' }
}
}
}
}
},
methods: {
handleSubmit(data) {
console.log('Submit form ...', data)
}
}
}
</script>
```

View File

@ -6,7 +6,7 @@
:label="$parentInput.label"
:for="$parentInput.id" />
<slot/>
<ds-input-error :error="$parentInput ? $parentInput.error : null" />
<ds-input-error :error="$parentInput.error" />
</div>
</template>
@ -21,6 +21,5 @@ export default {
}
</script>
<style lang="scss">
@import 'style';
<style lang="scss" src="./style.scss">
</style>

View File

View File

View File

View File

@ -13,7 +13,7 @@
iconRight && `ds-input-has-icon-right`
]"
:id="id"
:name="name"
:name="name ? name : model"
:type="type"
:autofocus="autofocus"
:placeholder="placeholder"
@ -48,7 +48,8 @@ export default {
mixins: [inputMixin],
props: {
/**
* The type of this input `url, text, password, email, search, textarea`.
* The type of this input.
* @options url|text|password|email|search|textarea
*/
type: {
type: String,
@ -64,13 +65,6 @@ export default {
type: String,
default: null
},
/**
* The name of the field for better accessibility
*/
name: {
type: String,
default: null
},
/**
* Whether the input should be automatically focused
*/
@ -78,13 +72,6 @@ export default {
type: Boolean,
default: false
},
/**
* Whether the input should be read-only
*/
readonly: {
type: Boolean,
default: false
},
/**
* How many rows this input should have (only for type="textarea")
*/

View File

@ -1,7 +1,7 @@
## Basic usage
```
<ds-input placeholder="Name ..."/>
<ds-input placeholder="Name ..." />
```
## Usage with label
@ -10,7 +10,13 @@
<ds-input
id="name"
label="Your name"
placeholder="Name ..."/>
placeholder="Name ..." />
```
## Disabled
```
<ds-input placeholder="Name ..." disabled />
```
## Input types

View File

View File

@ -0,0 +1,123 @@
<template>
<ds-form-item>
<div
class="ds-radio"
:tabindex="tabindex"
@keydown.self.down.prevent="pointerNext"
@keydown.self.up.prevent="pointerPrev">
<component
class="ds-radio-option"
:class="[
isSelected(option) && `ds-radio-option-is-selected`
]"
v-for="option in options"
@click="handleSelect(option)"
:key="option[labelProp] || option"
:is="buttons ? 'ds-button' : 'div'"
:primary="buttons && isSelected(option)">
<span
class="ds-radio-option-mark"
v-if="!buttons"/>
<span class="ds-radio-option-label">
<!-- @slot Slot to provide custom option items -->
<slot
name="option"
:option="option">
{{ option[labelProp] || option }}
</slot>
</span>
</component>
</div>
</ds-form-item>
</template>
<script>
import inputMixin from '../shared/input'
import multiinputMixin from '../shared/multiinput'
import DsFormItem from '@@/components/data-input/FormItem/FormItem'
/**
* Used for letting the user choose one value from a set of options.
* @version 1.0.0
*/
export default {
name: 'DsRadio',
mixins: [inputMixin, multiinputMixin],
components: {
DsFormItem
},
data() {
return {
pointer: 0
}
},
props: {
/**
* Whether the input should be options should be buttons
*/
buttons: {
type: Boolean,
default: false
},
/**
* The select options.
*/
options: {
type: Array,
default() {
return []
}
},
/**
* The prop to use as the label when options are objects
*/
labelProp: {
type: String,
default: 'label'
}
},
computed: {
pointerMax() {
return this.options.length - 1
}
},
watch: {
pointerMax(max) {
if (max < this.pointer) {
this.$nextTick(() => {
this.pointer = max
})
}
}
},
methods: {
handleSelect(option) {
this.selectOption(option)
},
pointerPrev() {
if (this.pointer === 0) {
this.pointer = this.pointerMax
} else {
this.pointer--
}
this.selectPointerOption()
},
pointerNext() {
if (this.pointer === this.pointerMax) {
this.pointer = 0
} else {
this.pointer++
}
this.selectPointerOption()
},
selectPointerOption() {
this.handleSelect(this.options[this.pointer])
}
}
}
</script>
<style lang="scss" src="./style.scss">
</style>
<docs src="./demo.md"></docs>

View File

@ -0,0 +1,74 @@
## Basic usage
```
<ds-radio :options="['blue', 'red', 'green']" />
```
## Usage with label
```
<ds-radio
label="Color"
:options="['blue', 'red', 'green']" />
```
## Bind to a value
Use v-model to bind a value to the select input.
```
<template>
<div>
<ds-radio
v-model="color"
:options="['blue', 'red', 'green']"
placeholder="Color ..."></ds-radio>
<ds-text>Your color: {{ color }}</ds-text>
</div>
</template>
<script>
export default {
data() {
return {
color: 'blue'
}
}
}
</script>
```
## Style variations
```
<ds-radio
label="Color"
:options="['blue', 'red', 'green']"
buttons />
```
## 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-radio
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>
```

View File

@ -0,0 +1,56 @@
.ds-radio {
outline: none;
.ds-input-is-disabled &,
&:disabled {
color: $text-color-disabled;
opacity: $opacity-disabled;
pointer-events: none;
cursor: not-allowed;
}
}
.ds-radio-option {
&:not(.ds-button) {
@include inline-space($space-small);
}
display: inline-flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.ds-radio-option-mark {
display: inline-block;
position: relative;
width: $font-size-base;
height: $font-size-base;
border: $input-border-size solid $border-color-base;
background-color: $background-color-base;
border-radius: $border-radius-circle;
margin-right: $space-xx-small;
&:before {
position: absolute;
content: '';
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%) scale(0);
opacity: 0;
width: $font-size-x-small;
height: $font-size-x-small;
border-radius: $border-radius-circle;
background-color: $text-color-primary;
transition: all $duration-short $ease-in-sharp;
.ds-radio-option-is-selected & {
opacity: 1;
transform: translateY(-50%) translateX(-50%) scale(1);
}
}
}
.ds-radio-option-label {
font-size: $font-size-base;
cursor: pointer;
}

View File

@ -2,8 +2,12 @@
<ds-form-item>
<div
class="ds-select-wrap"
v-click-outside="handleBlur"
:class="[
isOpen && `ds-select-is-open`
]"
:tabindex="searchable ? -1 : tabindex"
v-click-outside="closeAndBlur"
@keydown.tab="closeAndBlur"
@keydown.self.down.prevent="pointerNext"
@keydown.self.up.prevent="pointerPrev"
@keypress.enter.prevent.stop.self="selectPointerOption"
@ -15,7 +19,7 @@
</div>
<div
class="ds-select"
@click="handleClick"
@click="openAndFocus"
:class="[
icon && `ds-select-has-icon`,
iconRight && `ds-select-has-icon-right`,
@ -27,7 +31,7 @@
<div
class="ds-selected-option"
v-for="(value, index) in innerValue"
:key="value">
:key="value[labelProp] || value">
<!-- @slot Slot to provide a custom selected option display -->
<slot
name="optionitem"
@ -35,68 +39,81 @@
<ds-chip
removable
@remove="deselectOption(index)"
color="primary">
{{ value }}
color="primary"
:size="size">
{{ value[labelProp] || value }}
</ds-chip>
</slot>
</div>
<input
ref="search"
class="ds-select-search"
autocomplete="off"
:id="id"
:name="model"
:name="name ? name : model"
:autofocus="autofocus"
:placeholder="placeholder"
:tabindex="tabindex"
:disabled="disabled"
:readonly="readonly"
v-model="searchString"
autocomplete="off"
@focus="handleFocus"
@focus="openAndFocus"
@keydown.tab="closeAndBlur"
@keydown.delete.stop="deselectLastOption"
@keydown.down.prevent="pointerNext"
@keydown.up.prevent="pointerPrev"
@keydown.down.prevent="handleKeyDown"
@keydown.up.prevent="handleKeyUp"
@keypress.enter.prevent.stop="selectPointerOption"
@keyup.esc="close">
</div>
<div
v-else
class="ds-select-value">
<!-- @slot Slot to provide a custom value display -->
<slot
v-if="innerValue"
name="value"
:value="innerValue">
{{ innerValue[labelProp] || innerValue }}
</slot>
<div
v-if="placeholder && !innerValue"
v-else-if="placeholder"
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"
autocomplete="off"
:id="id"
:name="model"
:name="name ? name : model"
:autofocus="autofocus"
:placeholder="placeholder"
:tabindex="tabindex"
:disabled="disabled"
:readonly="readonly"
v-model="searchString"
autocomplete="off"
@focus="handleFocus"
@focus="openAndFocus"
@keydown.tab="closeAndBlur"
@keydown.delete.stop="deselectLastOption"
@keydown.down.prevent="pointerNext"
@keydown.up.prevent="pointerPrev"
@keydown.down.prevent="handleKeyDown"
@keydown.up.prevent="handleKeyUp"
@keypress.enter.prevent.stop="selectPointerOption"
@keyup.esc="close">
</div>
<div class="ds-select-dropdown">
<ul class="ds-select-options">
<div
class="ds-select-dropdown-message"
v-if="!options || !options.length">
{{ noOptionsAvailable }}
</div>
<div
class="ds-select-dropdown-message"
v-else-if="!filteredOptions.length">
{{ noOptionsFound }} "{{ searchString }}"
</div>
<ul
class="ds-select-options"
v-else>
<li
class="ds-select-option"
:class="[
@ -106,12 +123,12 @@
v-for="(option, index) in filteredOptions"
@click="handleSelect(option)"
@mouseover="setPointer(index)"
:key="option.label || option">
:key="option[labelProp] || option">
<!-- @slot Slot to provide custom option items -->
<slot
name="option"
:option="option">
{{ option.label || option }}
{{ option[labelProp] || option }}
</slot>
</li>
</ul>
@ -134,7 +151,7 @@ import DsChip from '@@/components/typography/Chip/Chip'
import DsIcon from '@@/components/typography/Icon/Icon'
/**
* Used for handling basic user input.
* Used for letting the user choose values from a set of options.
* @version 1.0.0
*/
export default {
@ -151,7 +168,8 @@ export default {
data() {
return {
searchString: '',
pointer: 0
pointer: 0,
isOpen: false
}
},
props: {
@ -169,13 +187,6 @@ export default {
type: Boolean,
default: false
},
/**
* Whether the input should be read-only
*/
readonly: {
type: Boolean,
default: false
},
/**
* The name of the input's icon.
*/
@ -199,12 +210,33 @@ export default {
return []
}
},
/**
* The prop to use as the label when options are objects
*/
labelProp: {
type: String,
default: 'label'
},
/**
* Whether the options are searchable
*/
searchable: {
type: Boolean,
default: true
},
/**
* Message to show when no options are available
*/
noOptionsAvailable: {
type: String,
default: 'No options available.'
},
/**
* Message to show when the search result is empty
*/
noOptionsFound: {
type: String,
default: 'No options found for:'
}
},
computed: {
@ -251,13 +283,22 @@ export default {
resetSearch() {
this.searchString = ''
},
handleClick() {
openAndFocus() {
this.open()
if (!this.focus || this.multiple) {
this.$refs.search.focus()
this.handleFocus()
}
},
open() {
this.resetSearch()
this.isOpen = true
},
close() {
this.isOpen = false
},
closeAndBlur() {
this.close()
this.$refs.search.blur()
this.handleBlur()
},
@ -271,6 +312,20 @@ export default {
this.deselectOption(this.innerValue.length - 1)
}
},
handleKeyUp() {
if (!this.isOpen) {
this.open()
return
}
this.pointerPrev()
},
handleKeyDown() {
if (!this.isOpen) {
this.open()
return
}
this.pointerNext()
},
setPointer(index) {
this.pointer = index
},

View File

@ -65,7 +65,7 @@ Use the multiple prop to allow the user selecting multiple values.
## Options as objects
Options can be objects with a label and a value property.
Options can be objects. You can define which property to show as the label by defining label-prop. Defaults to "label".
```
<template>
@ -74,14 +74,21 @@ Options can be objects with a label and a value property.
v-model="color"
:options="colorOptions"
placeholder="Color ..."></ds-select>
<ds-text>Your color: {{ color }}</ds-text>
<ds-text>Selected color: {{ color }}</ds-text>
<ds-select
v-model="colors"
:options="colorOptions"
placeholder="Colors ..."
multiple></ds-select>
<ds-text>Selected colors: {{ colors }}</ds-text>
</div>
</template>
<script>
export default {
data() {
return {
color: '',
color: null,
colors: [],
colorOptions: [
{
label: 'blue',

View File

@ -239,23 +239,25 @@ describe('Select.vue', () => {
expect(wrapper.vm.pointer).toEqual(2)
})
test('should be set by key down on search input', () => {
test('should be set by key down on search input when open', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.open()
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', () => {
test('should be set by key up on search input when open', () => {
const wrapper = shallowMount(Comp, {
propsData: {
options: ['1', '2', '3']
}
})
wrapper.vm.open()
const searchInput = wrapper.find('.ds-select-search')
searchInput.trigger('keydown.up')
expect(wrapper.vm.pointer).toEqual(2)

View File

@ -4,12 +4,18 @@
.ds-select {
user-select: none;
.ds-input-has-focus & {
.ds-select-is-open & {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.ds-select-multiple {
display: flex;
align-items: center;
max-width: 100%;
}
.ds-select-search, .ds-select-value {
box-sizing: border-box;
position: absolute;
@ -70,7 +76,7 @@
color: $text-color-disabled;
}
.ds-input-has-focus & {
.ds-select-is-open & {
opacity: 1;
}
@ -87,7 +93,7 @@
.ds-select-placeholder, .ds-select-value {
pointer-events: none;
.ds-input-has-focus & {
.ds-select-is-open & {
opacity: 0;
}
}
@ -98,10 +104,13 @@
.ds-selected-options {
display: flex;
max-width: 100%;
overflow: hidden;
}
.ds-selected-option {
display: inline-flex;
align-items: center;
margin-right: $space-xx-small;
}
@ -122,12 +131,16 @@
max-height: 240px;
overflow: auto;
.ds-input-has-focus & {
.ds-select-is-open & {
visibility: visible;
opacity: 1;
}
}
.ds-select-dropdown-message {
padding: $input-padding-vertical $space-x-small;
}
.ds-select-options {
@include reset-list;
}

View File

@ -30,6 +30,13 @@ export default {
type: String,
default: null
},
/**
* Name to use on the input for accessibility
*/
name: {
type: String,
default: null
},
/**
* The label of the input.
*/
@ -51,8 +58,16 @@ export default {
type: Boolean,
default: false
},
/**
* Whether the input should be read-only
*/
readonly: {
type: Boolean,
default: false
},
/**
* The async-validator schema used for the input.
* @default null
*/
schema: {
type: Object,
@ -60,11 +75,11 @@ export default {
},
/**
* The input's size.
* `small, base, large`
* @options small|base|large
*/
size: {
type: String,
default: null,
default: 'base',
validator: value => {
return value.match(/(small|base|large)/)
}
@ -86,6 +101,7 @@ export default {
return [
this.size && `ds-input-size-${this.size}`,
this.disabled && 'ds-input-is-disabled',
this.readonly && 'ds-input-is-readonly',
this.error && 'ds-input-has-error',
this.focus && 'ds-input-has-focus'
]

View File

@ -15,6 +15,7 @@
color: $text-color-base;
background: $background-color-base;
border: $input-border-size solid $border-color-base;
border-radius: $border-radius-base;
outline: none;
@ -23,21 +24,31 @@
&::placeholder {
color: $text-color-disabled;
}
.ds-input-has-focus &,
&:focus {
border-color: $border-color-active;
background: $background-color-base;
}
.ds-input-is-disabled &,
&:disabled {
color: $text-color-disabled;
opacity: $opacity-disabled;
pointer-events: none;
cursor: not-allowed;
background-color: $background-color-disabled;
}
.ds-input-is-readonly & {
pointer-events: none;
}
.ds-input-has-error & {
border-color: $border-color-danger;
}
}
.ds-input-size-small {
font-size: $font-size-small;
@ -47,6 +58,7 @@
padding: $input-padding-vertical-small $space-x-small;
}
}
.ds-input-size-large {
font-size: $font-size-large;

View File

@ -13,11 +13,10 @@ export default {
},
methods: {
selectOption(option) {
const newValue = option.value || option
if (this.multiple) {
this.selectMultiOption(newValue)
this.selectMultiOption(option)
} else {
this.input(newValue)
this.input(option)
}
},
selectMultiOption(value) {
@ -39,11 +38,10 @@ export default {
if (!this.innerValue) {
return false
}
const value = option.value || option
if (this.multiple) {
return this.innerValue.includes(value)
return this.innerValue.includes(option)
}
return this.innerValue === value
return this.innerValue === option
}
}
}

View File

@ -29,7 +29,6 @@ context.keys().forEach(key => {
parent,
folder,
name: c.name,
docs: c.__docs,
component: c
}

View File

@ -7,13 +7,16 @@
primary && `ds-card-primary`,
secondary && `ds-card-secondary`,
centered && `ds-card-centered`,
hover && `ds-card-hover`
hover && `ds-card-hover`,
space && `ds-card-space-${space}`
]">
<div
class="ds-card-image"
v-if="image || $slots.image">
<!-- @slot Content of the card's image -->
<slot name="image">
<slot
name="image"
:image="image">
<img
:src="image"
v-if="!error"
@ -36,12 +39,7 @@
</slot>
</header>
<div class="ds-card-content">
<template v-if="space">
<ds-space :margin="space">
<slot />
</ds-space>
</template>
<template v-else>
<template>
<slot />
</template>
</div>
@ -63,22 +61,22 @@ export default {
name: 'DsCard',
props: {
/**
* The html element name used for the card.
* The outtermost html tag
*/
tag: {
type: String,
default: 'article'
},
/**
* The header for the card.
* The card's header
*/
header: {
type: String,
default: null
},
/**
* The heading type used for the header.
* `h1, h2, h3, h4, h5, h6`
* The card's header tag
* @options h1|h2|h3|h4|h5|h6
*/
headerTag: {
type: String,
@ -88,14 +86,14 @@ export default {
}
},
/**
* The image for the card.
* The card's image
*/
image: {
type: String,
default: null
},
/**
* The icon for the card.
* The card's icon
*/
icon: {
type: String,
@ -103,7 +101,6 @@ export default {
},
/**
* Highlight with primary color
* `true, false`
*/
primary: {
type: Boolean,
@ -111,7 +108,6 @@ export default {
},
/**
* Highlight with secondary color
* `true, false`
*/
secondary: {
type: Boolean,
@ -119,7 +115,6 @@ export default {
},
/**
* Center the content
* `true, false`
*/
centered: {
type: Boolean,
@ -127,7 +122,6 @@ export default {
},
/**
* Make the card hoverable
* `true, false`
*/
hover: {
type: Boolean,
@ -135,11 +129,14 @@ export default {
},
/**
* 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`
* @options small|large|x-large|xx-large
*/
space: {
type: String,
default: null
default: null,
validator: value => {
return value.match(/(small|large|x-large|xx-large)/)
}
}
},
data() {

View File

@ -209,8 +209,9 @@ Here we apply a section footer without margin
<div slot="footer" class="no-margin">
<ds-icon name="comments" />
<ds-tag
style="transform: scale(.8); margin-top: -4px; margin-left: -7px; position: absolute;"
style="margin-top: -4px; margin-left: -7px; position: absolute;"
color="primary"
size="small"
round>
2
</ds-tag>
@ -226,8 +227,9 @@ Here we apply a section footer without margin
<div slot="footer" class="no-margin">
<ds-icon name="comments" />
<ds-tag
style="transform: scale(.8); margin-top: -4px; margin-left: -7px; position: absolute;"
style="margin-top: -4px; margin-left: -7px; position: absolute;"
color="primary"
size="small"
round>
34
</ds-tag>

View File

@ -91,3 +91,20 @@ $border-radius: $border-radius-x-large;
background-color: $background-color-secondary-active;
}
}
.ds-card-space-small {
padding-top: $space-small;
padding-bottom: $space-small;
}
.ds-card-space-large {
padding-top: $space-large;
padding-bottom: $space-large;
}
.ds-card-space-x-large {
padding-top: $space-x-large;
padding-bottom: $space-x-large;
}
.ds-card-space-xx-large {
padding-top: $space-xx-large;
padding-bottom: $space-xx-large;
}

View File

@ -3,8 +3,10 @@
:is="tag"
class="ds-container"
:class="[
`ds-container-${width}`
]">
`ds-container-${width}`,
centered && `ds-container-centered`,
]"
>
<slot />
</component>
</template>
@ -18,7 +20,7 @@ export default {
name: 'DsContainer',
props: {
/**
* The html element name used for the wrapper.
* The outtermost html tag
*/
tag: {
type: String,
@ -27,7 +29,7 @@ export default {
/**
* The maximum width the container will take.
* The widths correspond to the different media breakpoints.
* `x-small, small, medium, large, x-large`
* @options x-small|small|medium|large|x-large
*/
width: {
type: String,
@ -35,6 +37,13 @@ export default {
validator: value => {
return value.match(/(x-small|small|medium|large|x-large)/)
}
},
/**
* Center the content
*/
centered: {
type: Boolean,
default: false
}
}
}

View File

@ -12,8 +12,8 @@
}
}
.ds-container .ds-container {
padding: 0;
.ds-container-centered {
text-align: center;
}
.ds-container-x-small {

View File

@ -25,29 +25,29 @@ export default {
},
props: {
/**
* The default gutter size for the columns.
* Default gutter size of columns
*/
gutter: {
type: [String, Object],
default: null
},
/**
* The default width for the columns.
* Default width of columns
*/
width: {
type: [String, Number, Object],
default: 1
},
/**
* The direction of the items.
* `row, row-reverse, column, column-reverse`
* Direction of the flex items
* @options row|row-reverse|column|column-reverse
*/
direction: {
type: [String, Object],
default: null
},
/**
* The html element name used for the wrapper.
* The outtermost html tag
*/
tag: {
type: String,

View File

@ -25,7 +25,8 @@ export default {
},
props: {
/**
* The width of the item.
* The item's width
* @default 1
*/
width: {
type: [String, Number, Object],
@ -34,7 +35,7 @@ export default {
}
},
/**
* The html element name used for the wrapper.
* The outtermost html tag
*/
tag: {
type: String,
@ -44,7 +45,7 @@ export default {
/**
* Center content vertical and horizontal
*/
center: {
centered: {
type: Boolean,
default: false
}
@ -58,7 +59,7 @@ export default {
const gutter = this.mediaQuery(this.gutter)
const widthStyle = this.parseWidth(width)
const gutterStyle = this.parseGutter(gutter)
const centerStyle = this.center
const centerStyle = this.centered
? {
'align-self': 'center',
'jusify-self': 'center'

View File

@ -57,7 +57,6 @@ export default {
props: {
/**
* Whether the layout should have a maximum width
* `true, false`
*/
contained: {
type: Boolean,

View File

@ -27,7 +27,6 @@ export default {
props: {
/**
* Whether this section should be fullheight
* `true, false`
*/
fullheight: {
type: Boolean,
@ -35,7 +34,6 @@ export default {
},
/**
* Highlight with primary color
* `true, false`
*/
primary: {
type: Boolean,
@ -43,7 +41,6 @@ export default {
},
/**
* Highlight with secondary color
* `true, false`
*/
secondary: {
type: Boolean,
@ -51,7 +48,6 @@ export default {
},
/**
* Center the content
* `true, false`
*/
centered: {
type: Boolean,

View File

@ -4,7 +4,7 @@
:style="styles"
class="ds-space"
:class="[
centered && `ds-space-centered`
centered && 'ds-space-centered'
]"
>
<slot />

View File

@ -0,0 +1,67 @@
<template>
<svg
viewBox="0 0 50 50"
class="ds-spinner"
:class="[
`ds-size-${this.size}`,
inverse && 'ds-spinner-inverse',
primary && !inverse && `ds-spinner-primary`,
secondary && !inverse && `ds-spinner-secondary`,
danger && !inverse && `ds-spinner-danger`,
]"
>
<circle class="ds-spinner-circle" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
</template>
<script>
export default {
name: 'DsSpinner',
props: {
/**
* The size used for the spinner.
* @options small|base|large
*/
size: {
type: String,
default: 'base',
validator: value => {
return value.match(/(small|base|large)/)
}
},
/**
* Set to true, if you use it on dark background
*/
inverse: {
type: Boolean,
default: false
},
/**
* Primary style
*/
primary: {
type: Boolean,
default: false
},
/**
* Secondary style
*/
secondary: {
type: Boolean,
default: false
},
/**
* Danger style
*/
danger: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="scss" src="./style.scss">
</style>
<docs src="./demo.md"></docs>

View File

@ -0,0 +1,21 @@
## Spinning loading indicator
Multiple sizes
```html
<ds-section centered>
<ds-spinner size="small"></ds-spinner>
<ds-spinner size="base"></ds-spinner>
<ds-spinner size="large"></ds-spinner>
</ds-section>
```
Inverse color
```html
<ds-section centered secondary>
<ds-spinner size="small" inverse></ds-spinner>
<ds-spinner size="base" inverse></ds-spinner>
<ds-spinner size="large" inverse></ds-spinner>
</ds-section>
```

View File

@ -0,0 +1,64 @@
$size-small: $space-base;
$size-base: $space-large;
$size-large: $space-x-large;
.ds-spinner {
animation: rotate 16s linear infinite;
position: relative;
display: inline-block;
width: $size-base;
height: $size-base;
&.ds-size-small {
width: $size-small;
height: $size-small;
}
&.ds-size-large {
width: $size-large;
height: $size-large;
}
}
.ds-spinner-circle {
stroke: $text-color-soft;
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
.ds-spinner-inverse & {
stroke: $color-primary-inverse;
}
.ds-spinner-primary & {
stroke: $text-color-primary;
}
.ds-spinner-secondary & {
stroke: $text-color-secondary;
}
.ds-spinner-danger & {
stroke: $text-color-danger;
}
}
@keyframes rotate {
100% {
transform: rotate(2160deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}

View File

@ -10,26 +10,28 @@
ghost && `ds-button-ghost`,
iconOnly && `ds-button-icon-only`,
hover && `ds-button-hover`,
fullWidth && `ds-button-full-width`,
loading && `ds-button-loading`
fullwidth && `ds-button-fullwidth`,
loading && `ds-button-loading`,
right && `ds-button-right`
]"
:name="name"
v-bind="bindings"
:is="linkTag">
<div class="ds-button-wrap">
<ds-icon
v-if="loading"
name="spinner" />
<ds-icon
v-if="icon && !loading"
:name="icon"/>
v-if="icon"
:name="icon"
/>
<span
class="ds-button-text"
v-if="$slots.default">
<slot />
</span>
<ds-icon
v-if="iconRight"
:name="iconRight"/>
</div>
<ds-spinner
v-if="loading"
:inverse="!ghost && (primary || secondary || danger)"
/>
</component>
</template>
@ -52,7 +54,7 @@ export default {
},
/**
* The size used for the text.
* `small, base, large`
* @options small|base|large
*/
size: {
type: String,
@ -63,7 +65,7 @@ export default {
},
/**
* The component / tag used for this button
* `router-link, a`
* @options router-link|a|button
*/
linkTag: {
type: String,
@ -84,7 +86,6 @@ export default {
},
/**
* Primary style
* `true, false`
*/
primary: {
type: Boolean,
@ -92,7 +93,6 @@ export default {
},
/**
* Secondary style
* `true, false`
*/
secondary: {
type: Boolean,
@ -100,7 +100,6 @@ export default {
},
/**
* Danger style
* `true, false`
*/
danger: {
type: Boolean,
@ -108,7 +107,6 @@ export default {
},
/**
* Toggle the hover state
* `true, false`
*/
hover: {
type: Boolean,
@ -116,7 +114,6 @@ export default {
},
/**
* Make the buttons background transparent
* `true, false`
*/
ghost: {
type: Boolean,
@ -130,16 +127,16 @@ export default {
default: null
},
/**
* The name of the buttons right icon.
* Put the icon to the right.
*/
iconRight: {
type: String,
default: null
right: {
type: Boolean,
default: false
},
/**
* Should the button spread to the full with of the parent?
*/
fullWidth: {
fullwidth: {
type: Boolean,
default: false
},
@ -166,7 +163,7 @@ export default {
return bindings
},
iconOnly() {
return !this.$slots.default && this.icon && !this.iconRight
return !this.$slots.default && this.icon
}
},
methods: {

View File

@ -4,7 +4,7 @@ Use a primary button to draw the users attention to important actions. Use defau
A danger button should be used only for destructive actions.
```
```html
<ds-button>Default</ds-button>
<ds-button primary>Primary</ds-button>
<ds-button secondary>Secondary</ds-button>
@ -15,7 +15,7 @@ A danger button should be used only for destructive actions.
Use a ghost button for secondary actions.
```
```html
<ds-button ghost>Default</ds-button>
<ds-button ghost primary>Primary</ds-button>
<ds-button ghost secondary>Secondary</ds-button>
@ -26,7 +26,7 @@ Use a ghost button for secondary actions.
Use different sizes to create hierarchy.
```
```html
<ds-button size="small">Small</ds-button>
<ds-button>Base</ds-button>
<ds-button size="large">Large</ds-button>
@ -35,15 +35,15 @@ Use different sizes to create hierarchy.
## Button full width
```
<ds-button full-width>Full Width</ds-button>
```html
<ds-button fullwidth primary>Full Width</ds-button>
```
## Button states
A button can take different states.
```
```html
<ds-button>Default state</ds-button>
<ds-button disabled>Disabled state</ds-button>
<ds-button hover>Hover state</ds-button>
@ -54,9 +54,9 @@ A button can take different states.
Add an icon to a button to help the user identify the button's action.
```
<ds-button icon="plus" primary>Click me</ds-button>
<ds-button icon-right="plus">Click me</ds-button>
```html
<ds-button icon="arrow-left" primary>Click me</ds-button>
<ds-button icon="arrow-right" right>Click me</ds-button>
<ds-button icon="plus" primary></ds-button>
<ds-button icon="plus" ghost></ds-button>
```
@ -65,7 +65,7 @@ Add an icon to a button to help the user identify the button's action.
Provide a path to the button. You can pass a url string or a Vue router path object.
```
```html
<ds-button path="/navigation">Click me</ds-button>
<ds-button :path="{ name: 'Navigation' }">Click me</ds-button>
```

View File

@ -37,10 +37,25 @@
border-radius: $border-radius-base;
// box-shadow: $box-shadow-inset;
opacity: 0;
visiblity: hidden;
visibility: hidden;
pointer-events: none;
}
&:after {
position: absolute;
content: '';
top: 1px;
left: 1px;
right: 1px;
bottom: 1px;
border-radius: $border-radius-base;
border: 1px dotted currentColor;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: all $duration-short $ease-out;
}
&:focus {
outline: none;
}
@ -181,7 +196,8 @@
padding: 0;
border-radius: $border-radius-rounded;
&:before {
&:before,
&:after {
border-radius: $border-radius-rounded;
}
@ -202,17 +218,49 @@
line-height: inherit;
display: inline-block;
white-space: nowrap;
margin: 0 $font-space-small;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
.ds-button-full-width {
.ds-button-fullwidth {
width: 100%;
}
.ds-button-wrap {
transition: opacity 150ms ease-in-out;
display: flex;
align-items: center;
& > * {
margin: 0 $font-space-small;
}
& > *:first-child {
margin-left: 0;
}
& > *:last-child {
margin-right: 0;
}
.ds-button-loading & {
opacity: 0.1;
}
}
.ds-button-right > .ds-button-wrap {
& > *:first-child {
margin-right: 0;
margin-left: $font-space-small;
}
& > *:last-child {
margin-right: 0;
margin-left: 0;
}
}
.ds-button-right .ds-button-wrap {
flex-flow: row-reverse;
}
.ds-button .ds-spinner {
position: absolute;
width: 60% !important;
height: 60% !important;
}

View File

@ -13,9 +13,10 @@
:route="route"
:parents="[]"
:name="route.name">
<!-- @slot Scoped slot for providing a custom menu item -->
<slot
:route="route"
name="Navigation">
name="menuitem">
<ds-menu-item
:key="route.path ? route.path : index"
:route="route" />
@ -64,7 +65,7 @@ export default {
},
/**
* The default component / tag used for the link of menu items
* `router-link, a`
* @options router-link|a
*/
linkTag: {
type: String,
@ -98,6 +99,15 @@ export default {
return route.name
}
},
/**
* Function that matches items exactly
*/
matcher: {
type: Function,
default: () => {
return false
}
},
/**
* Function that checks if the url must be matched exactly in order to activate the menu item. By default only '/' must be matched exactly.
*/

View File

@ -15,8 +15,7 @@
v-if="route"
class="ds-menu-item-link"
:class="[
isExact && 'router-active-link',
isExact && 'router-link-exact-active'
matcher && 'router-link-exact-active'
]"
v-bind="bindings"
:exact="isExact"
@ -76,7 +75,7 @@ export default {
},
/**
* The component / tag used for the link of this route
* `router-link, a`
* @options router-link|a
*/
linkTag: {
type: String,
@ -110,6 +109,9 @@ export default {
isExact() {
return this.$parentMenu.isExact(this.url)
},
matcher() {
return this.$parentMenu.matcher(this.url, this.route)
},
level() {
return this.parents.length
},

View File

@ -214,7 +214,7 @@ If you want to keep the sub menu for this menu item, be sure to use the `ds-menu
<ds-menu :routes="routes">
<ds-menu-item
@click="handleClick"
slot="Navigation"
slot="menuitem"
slot-scope="item"
:route="item.route"
:parents="item.parents">

View File

@ -46,8 +46,7 @@ ul.ds-menu-list {
transition: color $duration-short $ease-out;
border-left: 2px solid transparent;
&.router-link-active,
&.nuxt-link-active {
&.router-link-active {
color: $text-color-link-active;
}
@ -55,10 +54,9 @@ ul.ds-menu-list {
color: $text-color-link-active;
}
&.router-link-exact-active,
&.nuxt-link-exact-active {
&.router-link-exact-active {
color: $text-color-link;
// background-color: $background-color-soft;
background-color: $background-color-soft;
border-left: 2px solid $color-primary;
}
@ -117,14 +115,12 @@ ul.ds-menu-list {
&,
&:hover,
&.router-link-exact-active,
&.nuxt-link-exact-active {
&.router-link-exact-active {
background-color: transparent;
}
&:hover,
&.router-link-active,
&.nuxt-link-active {
&.router-link-active {
&:before {
opacity: 1;
}

View File

@ -13,7 +13,8 @@
<button
v-if="removable"
@click="remove"
class="ds-chip-close">
class="ds-chip-close"
tabindex="-1">
<ds-icon name="close" />
</button>
</component>
@ -30,7 +31,7 @@ export default {
props: {
/**
* The background color used for the chip.
* `medium, inverse, primary, success, warning, danger`
* @options medium|inverse|primary|success|warning|danger
*/
color: {
type: String,
@ -41,7 +42,7 @@ export default {
},
/**
* The size used for the text.
* `base, large, small`
* @options base|large|small
*/
size: {
type: String,
@ -52,7 +53,6 @@ export default {
},
/**
* Whether the chip should be removeable
* `true, false`
*/
removable: {
type: Boolean,
@ -60,7 +60,6 @@ export default {
},
/**
* Whether the chip should be rounded
* `true, false`
*/
round: {
type: Boolean,

View File

View File

@ -1,11 +1,13 @@
.ds-chip {
@include reset;
@include stack-space($space-xx-small);
@include inline-space($font-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;
padding: $font-space-xx-small $font-space-large;
padding-bottom: $font-space-xxx-small;
border-radius: $border-radius-base;
font-weight: $font-weight-bold;
color: $text-color-base;
@ -45,22 +47,30 @@
border-radius: $border-radius-rounded;
}
.ds-chip-size-base {
font-size: $font-size-small;
.ds-chip-size-small {
font-size: $font-size-xx-small;
padding: $font-space-xxx-small ($font-space-large + $font-space-xxx-small);
}
.ds-chip-size-small {
font-size: $font-size-x-small;
.ds-chip-size-base {
font-size: $font-size-small;
padding-left: $font-space-large + $font-space-xx-small;
padding-right: $font-space-large + $font-space-xx-small;
padding-top: $font-space-xxx-small;
}
.ds-chip-size-large {
font-size: $font-size-base;
padding-left: $font-space-x-large;
padding-right: $font-space-x-large;
padding-top: $font-space-xx-small;
padding-bottom: $font-space-xxx-small;
}
.ds-chip-close {
@include reset-button;
position: absolute;
right: $space-xx-small;
right: $font-space-base;
top: 50%;
transform: translateY(-50%);
display: inline-flex;

View File

@ -3,7 +3,6 @@
class="ds-code"
:is="inline ? 'code' : 'pre'"
:class="[
size && `ds-code-size-${size}`,
inline && `ds-code-inline`
]"
>
@ -21,7 +20,6 @@ export default {
props: {
/**
* Display the code inline.
* `true, false`
*/
inline: {
type: Boolean,

View File

@ -12,6 +12,7 @@
.ds-code-inline {
display: inline-block;
padding: $space-xxx-small $space-x-small;
margin-bottom: 0;
}
.ds-code-size-small {

View File

@ -26,7 +26,7 @@ export default {
props: {
/**
* The heading type used for the heading.
* `h1, h2, h3, h4, h5, h6`
* @options h1|h2|h3|h4|h5|h6
*/
tag: {
type: String,
@ -37,7 +37,7 @@ export default {
},
/**
* The size used for the heading.
* `h1, h2, h3, h4, h5, h6`
* @options h1|h2|h3|h4|h5|h6
*/
size: {
type: String,
@ -48,7 +48,6 @@ export default {
},
/**
* Primary style
* `true, false`
*/
primary: {
type: Boolean,
@ -56,7 +55,6 @@ export default {
},
/**
* Muted style
* `true, false`
*/
soft: {
type: Boolean,

View File

@ -30,7 +30,6 @@ export default {
props: {
/**
* Inverse the logo
* `true, false`
*/
inverse: {
type: Boolean,

View File

@ -23,7 +23,7 @@ export default {
props: {
/**
* The background color used for the tag.
* `medium, inverse, primary, success, warning, danger`
* @options medium|inverse|primary|success|warning|danger
*/
color: {
type: String,
@ -34,7 +34,7 @@ export default {
},
/**
* The size used for the text.
* `base, large, small`
* @options base|large|small
*/
size: {
type: String,
@ -45,7 +45,6 @@ export default {
},
/**
* Whether the tag should be round
* `true, false`
*/
round: {
type: Boolean,

View File

@ -1,10 +1,12 @@
.ds-tag {
@include reset;
@include stack-space($space-xx-small);
@include inline-space($font-space-xx-small);
display: inline-block;
font-family: $font-family-text;
line-height: $line-height-large;
line-height: $line-height-base;
padding: $font-space-xx-small $font-space-x-large;
padding-top: $font-space-x-small;
border-radius: $border-radius-base;
font-weight: $font-weight-bold;
letter-spacing: $letter-spacing-large;
@ -40,16 +42,20 @@
.ds-tag-round {
border-radius: $border-radius-rounded;
padding: 0 $font-space-base;
padding-left: $font-space-large + $font-space-xxx-small;
padding-right: $font-space-large + $font-space-xxx-small;
}
.ds-tag-size-base {
font-size: $font-size-x-small;
padding-top: $font-space-x-small;
padding-bottom: $font-space-xx-small;
}
.ds-tag-size-small {
font-size: $font-size-xx-small;
padding: $font-space-xxx-small $font-space-large;
padding-top: $font-space-x-small;
}
.ds-tag-size-large {

View File

@ -6,6 +6,7 @@
size && `ds-text-size-${size}`,
color && `ds-text-${color}`,
bold && `ds-text-bold`,
inline && `ds-text-inline`,
align && `ds-text-${align}`,
uppercase && `ds-text-uppercase`
]"
@ -36,7 +37,7 @@ export default {
props: {
/**
* The color used for the text.
* `default, soft, softer, primary, inverse, success, warning, danger`
* @options default|soft|softer|primary|inverse|success|warning|danger
*/
color: {
type: String,
@ -54,9 +55,19 @@ export default {
type: Boolean,
default: null
},
/**
* Whether the text is inline.
* @default false
*/
inline: {
type: Boolean,
default() {
return !!this.$parentText
}
},
/**
* The size used for the text.
* `small, base, large, x-large`
* @options small|base|large|x-large|xx-large|xxx-large
*/
size: {
type: String,
@ -66,12 +77,12 @@ export default {
}
},
/**
* The html element name used for the text.
* The html tag used for the text.
*/
tag: {
type: String,
default() {
return this.$parentText ? 'span' : 'p'
return this.inline ? 'span' : 'p'
}
},
/**

View File

@ -3,12 +3,17 @@
@include stack-space($font-space-x-large);
font-family: $font-family-text;
line-height: $line-height-base;
display: block;
}
.ds-text-bold {
font-weight: $font-weight-bold;
}
.ds-text-inline {
display: inline;
}
.ds-text-left {
text-align: left;
}

View File

@ -1,25 +1,11 @@
import copy from 'clipboard-copy'
export default {
install(Vue) {
Vue.mixin({
methods: {
$copyToClipboard(content) {
const el = document.createElement('textarea')
el.value = content
el.setAttribute('readonly', '')
el.style.position = 'absolute'
el.style.left = '-9999px'
document.body.appendChild(el)
const selected =
document.getSelection().rangeCount > 0
? document.getSelection().getRangeAt(0)
: false
el.select()
document.execCommand('copy')
document.body.removeChild(el)
if (selected) {
document.getSelection().removeAllRanges()
document.getSelection().addRange(selected)
}
return copy(content)
}
}
})

View File

@ -35,6 +35,7 @@ input::-ms-clear, input::-ms-reveal {
html {
font-family: sans-serif; // 2
font-size: $font-size-body;
line-height: 1.15; // 3
-webkit-text-size-adjust: 100%; // 4
-ms-text-size-adjust: 100%; // 4
@ -63,7 +64,7 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: $text-color-base;
background-color: $background-color-softer; // 2
background-color: $background-color-softer;
}
// Suppress the focus outline on elements that cannot be accessed via keyboard.

View File

@ -2,11 +2,15 @@
--------------------------------------------- */
/* 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) {

View File

@ -132,11 +132,11 @@ props:
category: text-color
- name: text-color-link
value: *color-secondary
value: *color-primary
category: text-color
- name: text-color-link-active
value: *color-secondary-active
value: *color-primary-active
category: text-color
- name: text-color-primary
@ -147,6 +147,14 @@ props:
value: *color-primary-inverse
category: text-color
- name: text-color-secondary
value: *color-secondary
category: text-color
- name: text-color-secondary-inverse
value: *color-secondary-inverse
category: text-color
- name: text-color-success
value: *color-success
category: text-color
@ -176,11 +184,15 @@ props:
value: *color-neutral-100
category: background-color
- name: background-color-soft
value: *color-neutral-95
category: background-color
- name: background-color-softer
value: *color-neutral-90
category: background-color
- name: background-color-soft
- name: background-color-softer-active
value: *color-neutral-95
category: background-color
@ -192,20 +204,20 @@ props:
value: *color-neutral-90
category: background-color
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse
value: *color-neutral-0
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
category: background-color
- name: background-color-primary
@ -220,6 +232,18 @@ props:
value: *color-primary-inverse
category: background-color
- name: background-color-secondary
value: *color-secondary
category: background-color
- name: background-color-secondary-active
value: *color-secondary-active
category: background-color
- name: background-color-secondary-inverse
value: *color-secondary-inverse
category: background-color
- name: background-color-success
value: *color-success
category: background-color
@ -270,7 +294,7 @@ props:
category: border-color
- name: border-color-active
value: *color-secondary
value: *color-primary
category: border-color
- name: border-color-primary

View File

@ -34,7 +34,7 @@ props:
#
# Color Choices
#
# Set definite color choices here.
# Set color choices here.
# Use different lightness variations of the base hues.
#
# Light theme: darkest color | Dark theme: lightest color
@ -73,33 +73,40 @@ props:
- name: color-primary-inverse
value: &color-primary-inverse "hsla({!teal}, 5%, 1)"
- name: color-secondary
value: &color-secondary "hsla({!purple}, 45%, 1)"
- name: color-secondary-active
value: &color-secondary-active "hsla({!purple}, 52%, 1)"
- name: color-secondary-inverse
value: &color-secondary-inverse "hsla({!purple}, 97%, 1)"
- name: color-success
value: &color-success "hsla({!green}, 40%, 1)"
- name: color-success-active
value: &color-success-active "hsla({!green}, 45%, 1)"
- name: color-success-inverse
value: &color-success-inverse "hsla({!green}, 5%, 1)"
value: &color-success-inverse "hsla({!green}, 97%, 1)"
- name: color-danger
value: &color-danger "hsla({!red}, 50%, 1)"
- name: color-danger-active
value: &color-danger-active "hsla({!red}, 56%, 1)"
- name: color-danger-inverse
value: &color-danger-inverse "hsla({!red}, 5%, 1)"
value: &color-danger-inverse "hsla({!red}, 97%, 1)"
- name: color-warning
value: &color-warning "hsla({!orange}, 50%, 1)"
- name: color-warning-active
value: &color-warning-active "hsla({!orange}, 56%, 1)"
- name: color-warning-inverse
value: &color-warning-inverse "hsla({!orange}, 5%, 1)"
value: &color-warning-inverse "hsla({!orange}, 97%, 1)"
- name: color-yellow
value: &color-yellow "hsla({!yellow}, 48%, 1)"
- name: color-yellow-active
value: &color-yellow-active "hsla({!yellow}, 52%, 1)"
- name: color-yellow-inverse
value: &color-yellow-inverse "hsla({!yellow}, 5%, 1)"
value: &color-yellow-inverse "hsla({!yellow}, 97%, 1)"
# Text Colors
- name: text-color-base
@ -138,6 +145,14 @@ props:
value: *color-primary-inverse
category: text-color
- name: text-color-secondary
value: *color-secondary
category: text-color
- name: text-color-secondary-inverse
value: *color-secondary-inverse
category: text-color
- name: text-color-success
value: *color-success
category: text-color
@ -167,11 +182,15 @@ props:
value: *color-neutral-100
category: background-color
- name: background-color-soft
value: *color-neutral-95
category: background-color
- name: background-color-softer
value: *color-neutral-90
category: background-color
- name: background-color-soft
- name: background-color-softer-active
value: *color-neutral-95
category: background-color
@ -180,7 +199,15 @@ props:
category: background-color
- name: background-color-softest-active
value: *color-neutral-80
value: *color-neutral-90
category: background-color
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse-softer
@ -191,14 +218,6 @@ props:
value: *color-neutral-30
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-primary
value: *color-primary
category: background-color
@ -211,6 +230,18 @@ props:
value: *color-primary-inverse
category: background-color
- name: background-color-secondary
value: *color-secondary
category: background-color
- name: background-color-secondary-active
value: *color-secondary-active
category: background-color
- name: background-color-secondary-inverse
value: *color-secondary-inverse
category: background-color
- name: background-color-success
value: *color-success
category: background-color

View File

@ -0,0 +1,24 @@
#
# BOX SHADOW TOKENS
#
# Use these tokens to set a box-shadow.
#
props:
- name: box-shadow-large
value: "0 20px 60px 0 rgba(0, 0, 0, .15)"
- name: box-shadow-base
value: "0 2px 4px rgba(3,27,78,.06)"
- name: box-shadow-small
value: "0px 8px 18px -2px rgba(0, 0, 0, .1)"
- name: box-shadow-x-small
value: "0px 0px 3px 0px rgba(0, 0, 0, .1)"
- name: box-shadow-active
value: "0 0 6px 1px rgba(20, 100, 160, 0.5)"
- name: box-shadow-inset
value: "inset 0 0 20px 1px rgba(0,0,0,.15)"
- name: box-shadow-small-inset
value: "inset 0 0 0 1px rgba(0,0,0,.05)"
global:
type: ...
category: box-shadow

View File

@ -0,0 +1,316 @@
#
# COLOR TOKENS
#
# We use HSL in order to keep consistent hues
# For reference, see http://hslpicker.com/
#
#
# Base Hues
#
# Set hues here (these don't set lightness)
#
aliases:
neutral:
value: "221, 40%"
green:
value: "100, 69%"
orange:
value: "28, 80%"
yellow:
value: "48, 100%"
blue:
value: "215, 100%"
purple:
value: "264, 88%"
pink:
value: "330, 86%"
teal:
value: "178, 100%"
red:
value: "3, 65%"
props:
#
# Color Choices
#
# Set color choices here.
# Use different lightness variations of the base hues.
#
# Light theme: darkest color | Dark theme: lightest color
- name: color-neutral-0
value: &color-neutral-0 "hsla({!neutral}, 10%, 1)"
- name: color-neutral-10
value: &color-neutral-10 "hsla({!neutral}, 16%, 1)"
- name: color-neutral-20
value: &color-neutral-20 "hsla({!neutral}, 30%, 1)"
- name: color-neutral-30
value: &color-neutral-30 "hsla({!neutral}, 40%, 1)"
- name: color-neutral-40
value: &color-neutral-40 "hsla({!neutral}, 45%, 1)"
- name: color-neutral-50
value: &color-neutral-50 "hsla({!neutral}, 60%, 1)"
- name: color-neutral-60
value: &color-neutral-60 "hsla({!neutral}, 70%, 1)"
- name: color-neutral-70
value: &color-neutral-70 "hsla({!neutral}, 80%, 1)"
- name: color-neutral-80
value: &color-neutral-80 "hsla({!neutral}, 90%, 1)"
- name: color-neutral-85
value: &color-neutral-85 "hsla({!neutral}, 94%, 1)"
- name: color-neutral-90
value: &color-neutral-90 "hsla({!neutral}, 96%, 1)"
- name: color-neutral-95
value: &color-neutral-95 "hsla({!neutral}, 98%, 1)"
# Light theme: lightest color | Dark theme: darkest color
- name: color-neutral-100
value: &color-neutral-100 "hsla({!neutral}, 100%, 1)"
- name: color-primary
value: &color-primary "hsla({!blue}, 50%, 1)"
- name: color-primary-active
value: &color-primary-active "hsla({!blue}, 54%, 1)"
- name: color-primary-inverse
value: &color-primary-inverse "hsla({!blue}, 97%, 1)"
- name: color-secondary
value: &color-secondary "hsla({!teal}, 40%, 1)"
- name: color-secondary-active
value: &color-secondary-active "hsla({!teal}, 42%, 1)"
- name: color-secondary-inverse
value: &color-secondary-inverse "hsla({!teal}, 97%, 1)"
- name: color-success
value: &color-success "hsla({!green}, 40%, 1)"
- name: color-success-active
value: &color-success-active "hsla({!green}, 45%, 1)"
- name: color-success-inverse
value: &color-success-inverse "hsla({!green}, 97%, 1)"
- name: color-danger
value: &color-danger "hsla({!red}, 50%, 1)"
- name: color-danger-active
value: &color-danger-active "hsla({!red}, 56%, 1)"
- name: color-danger-inverse
value: &color-danger-inverse "hsla({!red}, 97%, 1)"
- name: color-warning
value: &color-warning "hsla({!orange}, 50%, 1)"
- name: color-warning-active
value: &color-warning-active "hsla({!orange}, 56%, 1)"
- name: color-warning-inverse
value: &color-warning-inverse "hsla({!orange}, 97%, 1)"
- name: color-yellow
value: &color-yellow "hsla({!yellow}, 48%, 1)"
- name: color-yellow-active
value: &color-yellow-active "hsla({!yellow}, 52%, 1)"
- name: color-yellow-inverse
value: &color-yellow-inverse "hsla({!yellow}, 97%, 1)"
# Text Colors
- name: text-color-base
value: *color-neutral-20
category: text-color
- name: text-color-soft
value: *color-neutral-40
category: text-color
- name: text-color-softer
value: *color-neutral-60
category: text-color
- name: text-color-disabled
value: *color-neutral-60
category: text-color
- name: text-color-inverse
value: *color-neutral-95
category: text-color
- name: text-color-link
value: *color-primary
category: text-color
- name: text-color-link-active
value: *color-primary-active
category: text-color
- name: text-color-primary
value: *color-primary
category: text-color
- name: text-color-primary-inverse
value: *color-primary-inverse
category: text-color
- name: text-color-secondary
value: *color-secondary
category: text-color
- name: text-color-secondary-inverse
value: *color-secondary-inverse
category: text-color
- name: text-color-success
value: *color-success
category: text-color
- name: text-color-success-inverse
value: *color-success-inverse
category: text-color
- name: text-color-warning
value: *color-warning
category: text-color
- name: text-color-warning-inverse
value: *color-warning-inverse
category: text-color
- name: text-color-danger
value: *color-danger
category: text-color
- name: text-color-danger-inverse
value: *color-danger-inverse
category: text-color
# Background Colors
- name: background-color-base
value: *color-neutral-100
category: background-color
- name: background-color-soft
value: *color-neutral-95
category: background-color
- name: background-color-softer
value: *color-neutral-90
category: background-color
- name: background-color-softer-active
value: *color-neutral-95
category: background-color
- name: background-color-softest
value: *color-neutral-85
category: background-color
- name: background-color-softest-active
value: *color-neutral-90
category: background-color
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
category: background-color
- name: background-color-primary
value: *color-primary
category: background-color
- name: background-color-primary-active
value: *color-primary-active
category: background-color
- name: background-color-primary-inverse
value: *color-primary-inverse
category: background-color
- name: background-color-secondary
value: *color-secondary
category: background-color
- name: background-color-secondary-active
value: *color-secondary-active
category: background-color
- name: background-color-secondary-inverse
value: *color-secondary-inverse
category: background-color
- name: background-color-success
value: *color-success
category: background-color
- name: background-color-success-active
value: *color-success-active
category: background-color
- name: background-color-success-inverse
value: *color-success-inverse
category: background-color
- name: background-color-warning
value: *color-warning
category: background-color
- name: background-color-warning-active
value: *color-warning-active
category: background-color
- name: background-color-warning-inverse
value: *color-warning-inverse
category: background-color
- name: background-color-danger
value: *color-danger
category: background-color
- name: background-color-danger-active
value: *color-danger-active
category: background-color
- name: background-color-danger-inverse
value: *color-danger-inverse
category: background-color
# Border Colors
- name: border-color-base
value: *color-neutral-60
category: border-color
- name: border-color-soft
value: *color-neutral-70
category: border-color
- name: border-color-softer
value: *color-neutral-80
category: border-color
- name: border-color-active
value: *color-primary
category: border-color
- name: border-color-primary
value: *color-primary
category: border-color
- name: border-color-success
value: *color-success
category: border-color
- name: border-color-warning
value: *color-warning
category: border-color
- name: border-color-danger
value: *color-danger
category: border-color
global:
type: color
category: color

View File

@ -0,0 +1,25 @@
#
# FONT TOKENS
# Use these tokens to set font-weight and font-family.
#
props:
- name: font-family-heading
value: "Sailec-Bold,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,Arial,sans-serif"
category: "font-family"
- name: font-family-text
value: "Sailec-Regular,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,Arial,sans-serif"
category: "font-family"
- name: font-family-code
value: "inconsolata, monospace"
category: "font-family"
- name: font-weight-regular
value: "normal"
category: "font-weight"
- name: font-weight-bold
value: "600"
category: "font-weight"
global:
type: ...
category: font

View File

@ -0,0 +1,24 @@
#
# BOX SHADOW TOKENS
#
# Use these tokens to set a box-shadow.
#
props:
- name: box-shadow-large
value: "0 20px 60px 0 rgba(0, 0, 0, .5)"
- name: box-shadow-base
value: "0px 12px 26px -4px rgba(0, 0, 0, .5)"
- name: box-shadow-small
value: "0px 8px 18px -2px rgba(0, 0, 0, .5)"
- name: box-shadow-x-small
value: "0px 0px 3px 0px rgba(0, 0, 0, .5)"
- name: box-shadow-active
value: "0 0 6px 1px rgba(20, 100, 160, 0.5)"
- name: box-shadow-inset
value: "inset 0 0 20px 1px rgba(0,0,0,.15)"
- name: box-shadow-small-inset
value: "inset 0 0 0 1px rgba(0,0,0,.05)"
global:
type: ...
category: box-shadow

View File

@ -0,0 +1,316 @@
#
# COLOR TOKENS
#
# We use HSL in order to keep consistent hues
# For reference, see http://hslpicker.com/
#
#
# Base Hues
#
# Set hues here (these don't set lightness)
#
aliases:
neutral:
value: "240, 6%"
green:
value: "100, 69%"
orange:
value: "28, 80%"
yellow:
value: "48, 100%"
blue:
value: "200, 100%"
purple:
value: "227, 58%"
pink:
value: "330, 86%"
teal:
value: "153, 46%"
red:
value: "3, 80%"
props:
#
# Color Choices
#
# Set color choices here.
# Use different lightness variations of the base hues.
#
# Light theme: darkest color | Dark theme: lightest color
- name: color-neutral-0
value: &color-neutral-0 "hsla({!neutral}, 100%, 1)"
- name: color-neutral-10
value: &color-neutral-10 "hsla({!neutral}, 90%, 1)"
- name: color-neutral-20
value: &color-neutral-20 "hsla({!neutral}, 80%, 1)"
- name: color-neutral-30
value: &color-neutral-30 "hsla({!neutral}, 70%, 1)"
- name: color-neutral-40
value: &color-neutral-40 "hsla({!neutral}, 60%, 1)"
- name: color-neutral-50
value: &color-neutral-50 "hsla({!neutral}, 50%, 1)"
- name: color-neutral-60
value: &color-neutral-60 "hsla({!neutral}, 45%, 1)"
- name: color-neutral-70
value: &color-neutral-70 "hsla({!neutral}, 40%, 1)"
- name: color-neutral-80
value: &color-neutral-80 "hsla({!neutral}, 30%, 1)"
- name: color-neutral-85
value: &color-neutral-85 "hsla({!neutral}, 24%, 1)"
- name: color-neutral-90
value: &color-neutral-90 "hsla({!neutral}, 20%, 1)"
- name: color-neutral-95
value: &color-neutral-95 "hsla({!neutral}, 16%, 1)"
# Light theme: lightest color | Dark theme: darkest color
- name: color-neutral-100
value: &color-neutral-100 "hsla({!neutral}, 14%, 1)"
- name: color-primary
value: &color-primary "hsla({!purple}, 65%, 1)"
- name: color-primary-active
value: &color-primary-active "hsla({!purple}, 62%, 1)"
- name: color-primary-inverse
value: &color-primary-inverse "hsla({!purple}, 98%, 1)"
- name: color-secondary
value: &color-secondary "hsla({!teal}, 46%, 1)"
- name: color-secondary-active
value: &color-secondary-active "hsla({!teal}, 52%, 1)"
- name: color-secondary-inverse
value: &color-secondary-inverse "hsla({!teal}, 98%, 1)"
- name: color-success
value: &color-success "hsla({!green}, 40%, 1)"
- name: color-success-active
value: &color-success-active "hsla({!green}, 45%, 1)"
- name: color-success-inverse
value: &color-success-inverse "hsla({!green}, 98%, 1)"
- name: color-danger
value: &color-danger "hsla({!red}, 50%, 1)"
- name: color-danger-active
value: &color-danger-active "hsla({!red}, 56%, 1)"
- name: color-danger-inverse
value: &color-danger-inverse "hsla({!red}, 98%, 1)"
- name: color-warning
value: &color-warning "hsla({!orange}, 50%, 1)"
- name: color-warning-active
value: &color-warning-active "hsla({!orange}, 56%, 1)"
- name: color-warning-inverse
value: &color-warning-inverse "hsla({!orange}, 98%, 1)"
- name: color-yellow
value: &color-yellow "hsla({!yellow}, 48%, 1)"
- name: color-yellow-active
value: &color-yellow-active "hsla({!yellow}, 52%, 1)"
- name: color-yellow-inverse
value: &color-yellow-inverse "hsla({!yellow}, 98%, 1)"
# Text Colors
- name: text-color-base
value: *color-neutral-10
category: text-color
- name: text-color-soft
value: *color-neutral-30
category: text-color
- name: text-color-softer
value: *color-neutral-50
category: text-color
- name: text-color-disabled
value: *color-neutral-50
category: text-color
- name: text-color-inverse
value: *color-neutral-95
category: text-color
- name: text-color-link
value: *color-primary
category: text-color
- name: text-color-link-active
value: *color-primary-active
category: text-color
- name: text-color-primary
value: *color-primary
category: text-color
- name: text-color-primary-inverse
value: *color-primary-inverse
category: text-color
- name: text-color-secondary
value: *color-secondary
category: text-color
- name: text-color-secondary-inverse
value: *color-secondary-inverse
category: text-color
- name: text-color-success
value: *color-success
category: text-color
- name: text-color-success-inverse
value: *color-success-inverse
category: text-color
- name: text-color-warning
value: *color-warning
category: text-color
- name: text-color-warning-inverse
value: *color-warning-inverse
category: text-color
- name: text-color-danger
value: *color-danger
category: text-color
- name: text-color-danger-inverse
value: *color-danger-inverse
category: text-color
# Background Colors
- name: background-color-base
value: *color-neutral-100
category: background-color
- name: background-color-soft
value: *color-neutral-95
category: background-color
- name: background-color-softer
value: *color-neutral-90
category: background-color
- name: background-color-softer-active
value: *color-neutral-95
category: background-color
- name: background-color-softest
value: *color-neutral-85
category: background-color
- name: background-color-softest-active
value: *color-neutral-90
category: background-color
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
category: background-color
- name: background-color-primary
value: *color-primary
category: background-color
- name: background-color-primary-active
value: *color-primary-active
category: background-color
- name: background-color-primary-inverse
value: *color-primary-inverse
category: background-color
- name: background-color-secondary
value: *color-secondary
category: background-color
- name: background-color-secondary-active
value: *color-secondary-active
category: background-color
- name: background-color-secondary-inverse
value: *color-secondary-inverse
category: background-color
- name: background-color-success
value: *color-success
category: background-color
- name: background-color-success-active
value: *color-success-active
category: background-color
- name: background-color-success-inverse
value: *color-success-inverse
category: background-color
- name: background-color-warning
value: *color-warning
category: background-color
- name: background-color-warning-active
value: *color-warning-active
category: background-color
- name: background-color-warning-inverse
value: *color-warning-inverse
category: background-color
- name: background-color-danger
value: *color-danger
category: background-color
- name: background-color-danger-active
value: *color-danger-active
category: background-color
- name: background-color-danger-inverse
value: *color-danger-inverse
category: background-color
# Border Colors
- name: border-color-base
value: *color-neutral-60
category: border-color
- name: border-color-soft
value: *color-neutral-70
category: border-color
- name: border-color-softer
value: *color-neutral-80
category: border-color
- name: border-color-active
value: *color-primary
category: border-color
- name: border-color-primary
value: *color-primary
category: border-color
- name: border-color-success
value: *color-success
category: border-color
- name: border-color-warning
value: *color-warning
category: border-color
- name: border-color-danger
value: *color-danger
category: border-color
global:
type: color
category: color

View File

@ -0,0 +1,25 @@
#
# FONT TOKENS
# Use these tokens to set font-weight and font-family.
#
props:
- name: font-family-heading
value: "Whitney,Helvetica Neue,Helvetica,Arial,sans-serif"
category: "font-family"
- name: font-family-text
value: "Whitney,Helvetica Neue,Helvetica,Arial,sans-serif"
category: "font-family"
- name: font-family-code
value: "inconsolata, monospace"
category: "font-family"
- name: font-weight-regular
value: "normal"
category: "font-weight"
- name: font-weight-bold
value: "700"
category: "font-weight"
global:
type: ...
category: font

View File

@ -5,6 +5,11 @@
# For reference, see http://hslpicker.com/
#
#
# Base Hues
#
# Set hues here (these don't set lightness)
#
#
# Base Hues
#
@ -73,6 +78,13 @@ props:
- name: color-primary-inverse
value: &color-primary-inverse "hsla({!teal}, 97%, 1)"
- name: color-secondary
value: &color-secondary "hsla({!pink}, 45%, 1)"
- name: color-secondary-active
value: &color-secondary-active "hsla({!pink}, 52%, 1)"
- name: color-secondary-inverse
value: &color-secondary-inverse "hsla({!pink}, 97%, 1)"
- name: color-success
value: &color-success "hsla({!green}, 40%, 1)"
- name: color-success-active
@ -138,6 +150,14 @@ props:
value: *color-primary-inverse
category: text-color
- name: text-color-secondary
value: *color-secondary
category: text-color
- name: text-color-secondary-inverse
value: *color-secondary-inverse
category: text-color
- name: text-color-success
value: *color-success
category: text-color
@ -167,11 +187,15 @@ props:
value: *color-neutral-100
category: background-color
- name: background-color-soft
value: *color-neutral-95
category: background-color
- name: background-color-softer
value: *color-neutral-90
category: background-color
- name: background-color-soft
- name: background-color-softer-active
value: *color-neutral-95
category: background-color
@ -183,20 +207,20 @@ props:
value: *color-neutral-90
category: background-color
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
- name: background-color-inverse
value: *color-neutral-0
category: background-color
- name: background-color-inverse-soft
value: *color-neutral-10
category: background-color
- name: background-color-inverse
value: *color-neutral-0
- name: background-color-inverse-softer
value: *color-neutral-20
category: background-color
- name: background-color-inverse-softer-active
value: *color-neutral-30
category: background-color
- name: background-color-primary
@ -211,6 +235,18 @@ props:
value: *color-primary-inverse
category: background-color
- name: background-color-secondary
value: *color-secondary
category: background-color
- name: background-color-secondary-active
value: *color-secondary-active
category: background-color
- name: background-color-secondary-inverse
value: *color-secondary-inverse
category: background-color
- name: background-color-success
value: *color-success
category: background-color

View File

@ -218,6 +218,10 @@ props:
value: *color-neutral-30
category: background-color
- name: background-color-disabled
value: *color-neutral-90
category: background-color
- name: background-color-primary
value: *color-primary
category: background-color
@ -291,7 +295,7 @@ props:
value: *color-neutral-80
category: border-color
- name: border-color-light
- name: border-color-softest
value: *color-neutral-90
category: border-color

View File

@ -24,6 +24,7 @@ props:
value: "0.2em"
- name: font-space-xxx-small
value: "0.1em"
global:
type: number
category: font-spacing

View File

@ -2578,6 +2578,11 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
clipboard-copy@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-2.0.1.tgz#25214db3aabc282109cfa3429ffd885b014fc8b3"
integrity sha512-/JBr7ryeWwl2w33SRMYGfOZU5SWPVNtpB9oTxUzFp7olKKd2HM+cnhSMeETblJMnjgqtL581ncI/pcZX7o7Big==
clipboardy@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"

View File

@ -5763,10 +5763,10 @@ graphql-subscriptions@^1.0.0:
dependencies:
iterall "^1.2.1"
graphql-tag@^2.10.0, graphql-tag@^2.9.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae"
integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w==
graphql-tag@^2.10.1, graphql-tag@^2.9.2:
version "2.10.1"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02"
integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==
graphql-tools@^4.0.0:
version "4.0.3"
@ -8755,7 +8755,7 @@ popper.js@^1.12.9:
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.5.tgz#98abcce7c7c34c4ee47fcbc6b3da8af2c0a127bc"
integrity sha512-fs4Sd8bZLgEzrk8aS7Em1qh+wcawtE87kRUJQhK6+LndyV1HerX7+LURzAylVaTyWIn5NTB/lyjnWqw/AZ6Yrw==
portal-vue@^1.5.1:
portal-vue@~1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-1.5.1.tgz#6bed79ef168d9676bb79f41d43c5cd4cedf54dbc"
integrity sha512-7T0K+qyY8bnjnEpQTiLbGsUaGlFcemK9gLurVSr6x1/qzr2HkHDNCOz5i+xhuTD1CrXckf/AGeCnLzvmAHMOHw==