refactor(other): include styleguide repo including all commits (#8932)
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
||||
[submodule "styleguide"]
|
||||
path = styleguide
|
||||
url = https://github.com/Human-Connection/Nitro-Styleguide.git
|
||||
[submodule "deployment/configurations/stage.ocelot.social"]
|
||||
path = deployment/configurations/stage.ocelot.social
|
||||
url = git@github.com:Ocelot-Social-Community/stage.ocelot.social.git
|
||||
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 7ef83405006b016fe45b476ed6e34ec189d7d283
|
||||
7
styleguide/.browserslistrc
Normal file
@ -0,0 +1,7 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
||||
chrome >= 59
|
||||
firefox >= 55
|
||||
safari >= 10
|
||||
ios_saf >= 9
|
||||
18
styleguide/.eslintrc.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: ['plugin:vue/strongly-recommended'],
|
||||
plugins: [
|
||||
'vue'
|
||||
],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
||||
24
styleguide/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
docs
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
/src/system/tokens/generated
|
||||
/src/system/icons/generated
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
2
styleguide/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
src/system/icons/_all/
|
||||
src/system/tokens/_examples/
|
||||
5
styleguide/.postcssrc.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
6
styleguide/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
31
styleguide/.travis.yml
Normal file
@ -0,0 +1,31 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_module
|
||||
|
||||
install:
|
||||
- yarn install --production=false --frozen-lockfile --non-interactive
|
||||
- yarn build
|
||||
- yarn build:lib
|
||||
|
||||
before_deploy:
|
||||
- rm -Rf ./node_modules .docs ./preview ./tests *.log*
|
||||
- rm -Rf ./src/system/icons/_all
|
||||
- rm -Rf ./src/system/tokens/_examples
|
||||
|
||||
deploy:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email: $NPM_EMAIL
|
||||
api_key: $NPM_TOKEN
|
||||
on:
|
||||
condition: ./deployment_condition.sh
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable
|
||||
keep_history: true
|
||||
on:
|
||||
condition: ./deployment_condition.sh
|
||||
59
styleguide/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Human-Connection Styleguide
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Styleguide)
|
||||
[](https://www.npmjs.com/package/@human-connection/styleguide)
|
||||
|
||||
CION is a Design System build primary for Vue applications. You can use it as a starting point for building your own Design System.
|
||||
|
||||
The system utilizes design tokens, a living styleguide with integrated code playgrounds and reusable components for common UI tasks.
|
||||
|
||||
Living styleguide demo: https://styleguide.human-connection.org
|
||||
|
||||
Landing page demo: https://cion.visualjerk.de
|
||||
|
||||
Integrate it in your application: [Quick Start](https://github.com/visualjerk/vue-cion-design-system/wiki/Quick-Start)
|
||||
|
||||
[](https://github.com/visualjerk/vue-cion-design-system/raw/master/preview/customize.png)
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Compiles and hot-reloads living styleguide
|
||||
|
||||
```
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Living styleguide
|
||||
|
||||
|
||||
Compiles living styleguide to `./docs`
|
||||
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Library
|
||||
|
||||
Compiles design system as a library to `./dist`
|
||||
|
||||
```
|
||||
yarn build:lib
|
||||
```
|
||||
|
||||
## Helper
|
||||
|
||||
### Serve living styleguide locally
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
4
styleguide/babel.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/app'],
|
||||
plugins: ['@babel/plugin-syntax-dynamic-import']
|
||||
}
|
||||
17
styleguide/deployment_condition.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
if [ "$BRANCH" != "master" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_VERSION=$(cat package.json | jq -r .version)
|
||||
PUBLISHED_VERSION=$(yarn info @human-connection/styleguide version --silent)
|
||||
|
||||
if [ "$CURRENT_VERSION" == "$PUBLISHED_VERSION" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
exit 0
|
||||
24
styleguide/jest.config.js
Normal file
@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'vue'
|
||||
],
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
'^.+\\.jsx?$': 'babel-jest'
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^@@/(.*)$': '<rootDir>/src/system/$1'
|
||||
},
|
||||
snapshotSerializers: [
|
||||
'jest-serializer-vue'
|
||||
],
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)|**/spec.(js|jsx|ts|tsx)'
|
||||
],
|
||||
testURL: 'http://localhost/'
|
||||
}
|
||||
87
styleguide/package.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@human-connection/styleguide",
|
||||
"version": "0.5.22",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"serve": "http-server ./docs -o -s",
|
||||
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider yarn theo && vue-cli-service build --no-fix",
|
||||
"lint": "vue-cli-service lint --no-fix-warning",
|
||||
"dev": "npm-run-all --parallel theo:onchange theo servedev",
|
||||
"servedev": "vue-cli-service serve --open --no-fix",
|
||||
"build:lib": "yarn theo && cross-env BUILD=library vue-cli-service build --target lib --name system ./src/library.js --no-fix",
|
||||
"theo": "theo ./src/system/tokens/tokens.yml --transform web --format map.scss,scss,raw.json,json --dest ./src/system/tokens/generated",
|
||||
"theo:onchange": "onchange \"./src/system/tokens/*.yml\" -- npm run theo",
|
||||
"test": "echo 'TODO: FIX & ADD TESTING!'",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^2.6.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.6.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "~7.2.0",
|
||||
"@babel/standalone": "~7.6.0",
|
||||
"@vue/cli-plugin-babel": "~3.11.0",
|
||||
"@vue/cli-plugin-eslint": "~3.11.0",
|
||||
"@vue/cli-plugin-unit-jest": "~3.11.0",
|
||||
"@vue/cli-service": "~3.11.0",
|
||||
"@vue/eslint-config-prettier": "~5.0.0",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
"async-validator": "~3.0.4",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-jest": "~24.9.0",
|
||||
"babel-plugin-transform-require-context": "~0.1.1",
|
||||
"cheerio": "~1.0.0-rc.2",
|
||||
"clipboard-copy": "~3.1.0",
|
||||
"clone-deep": "~4.0.1",
|
||||
"codemirror": "~5.48.4",
|
||||
"cross-env": "~5.2.1",
|
||||
"dot-prop": "~5.1.0",
|
||||
"http-server": "^0.11.1",
|
||||
"lodash": "~4.17.15",
|
||||
"markdown-it": "~9.1.0",
|
||||
"markdown-it-abbr": "~1.0.4",
|
||||
"markdown-it-deflist": "~2.0.3",
|
||||
"markdown-it-emoji": "~1.4.0",
|
||||
"markdown-it-footnote": "~3.0.2",
|
||||
"markdown-it-ins": "~2.0.0",
|
||||
"markdown-it-katex": "~2.0.3",
|
||||
"markdown-it-mark": "~2.0.0",
|
||||
"markdown-it-sub": "~1.0.0",
|
||||
"markdown-it-sup": "~1.0.0",
|
||||
"markdown-it-task-lists": "~2.1.1",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"onchange": "~6.0.0",
|
||||
"portal-vue": "~2.1.6",
|
||||
"raw-loader": "~3.1.0",
|
||||
"sass": "1.77.6",
|
||||
"sass-loader": "~10.1.1",
|
||||
"theo": "~8.1.4",
|
||||
"vue-click-outside": "~1.0.7",
|
||||
"vue-docgen-api": "^3.22.0",
|
||||
"vue-router": "~3.1.3",
|
||||
"vue-svg-loader": "~0.12.0",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuep": "git+https://github.com/visualjerk/vuep.git#fix-iframe-firefox",
|
||||
"webpack-bundle-analyzer": "~3.4.1",
|
||||
"webpack-merge-and-include-globally": "~2.1.16"
|
||||
},
|
||||
"authors": [
|
||||
"appinteractive",
|
||||
"roschaefer",
|
||||
"visualjerk",
|
||||
"ulfgebhardt"
|
||||
],
|
||||
"main": "./dist/system.umd.min.js",
|
||||
"files": [
|
||||
"dist/*",
|
||||
"src/*",
|
||||
"public/*",
|
||||
"*.json",
|
||||
"*.js"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
BIN
styleguide/preview/color_tokens.gif
Normal file
|
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 4.7 MiB |
BIN
styleguide/preview/customize.png
Normal file
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
38
styleguide/public/404.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Single Page Apps for GitHub Pages</title>
|
||||
<script type="text/javascript">
|
||||
// Single Page Apps for GitHub Pages
|
||||
// https://github.com/rafrex/spa-github-pages
|
||||
// Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
|
||||
// ----------------------------------------------------------------------
|
||||
// This script takes the current url and converts the path and query
|
||||
// string into just a query string, and then redirects the browser
|
||||
// to the new url with only a query string and hash fragment,
|
||||
// e.g. http://www.foo.tld/one/two?a=b&c=d#qwe, becomes
|
||||
// http://www.foo.tld/?p=/one/two&q=a=b~and~c=d#qwe
|
||||
// Note: this 404.html file must be at least 512 bytes for it to work
|
||||
// with Internet Explorer (it is currently > 512 bytes)
|
||||
// If you're creating a Project Pages site and NOT using a custom domain,
|
||||
// then set segmentCount to 1 (enterprise users may need to set it to > 1).
|
||||
// This way the code will only replace the route part of the path, and not
|
||||
// the real directory in which the app resides, for example:
|
||||
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
|
||||
// https://username.github.io/repo-name/?p=/one/two&q=a=b~and~c=d#qwe
|
||||
// Otherwise, leave segmentCount as 0.
|
||||
var segmentCount = 0;
|
||||
var l = window.location;
|
||||
l.replace(
|
||||
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
|
||||
l.pathname.split('/').slice(0, 1 + segmentCount).join('/') + '/?p=/' +
|
||||
l.pathname.slice(1).split('/').slice(segmentCount).join('/').replace(/&/g, '~and~') +
|
||||
(l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
|
||||
l.hash
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
1
styleguide/public/CNAME
Normal file
@ -0,0 +1 @@
|
||||
styleguide.human-connection.org
|
||||
25
styleguide/public/babel-standalone.js
Normal file
BIN
styleguide/public/favicon.ico
Normal file
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
18
styleguide/public/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<script src="/babel-standalone.js"></script>
|
||||
<title>Human Connection - Styleguide</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but this CION doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
8
styleguide/src/library.js
Normal file
@ -0,0 +1,8 @@
|
||||
import system from './system'
|
||||
import { tokens } from './system/tokens'
|
||||
import * as utils from './system/utils'
|
||||
import * as mixins from './system/mixins'
|
||||
|
||||
export { tokens, utils, mixins }
|
||||
|
||||
export default system
|
||||
9
styleguide/src/loader/docs-loader.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function(source, map) {
|
||||
this.callback(
|
||||
null,
|
||||
`export default function (Component) {
|
||||
Component.options.__docs = ${JSON.stringify(source)}
|
||||
}`,
|
||||
map
|
||||
)
|
||||
}
|
||||
7
styleguide/src/loader/docs-trim-loader.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function(source, map) {
|
||||
this.callback(
|
||||
null,
|
||||
`export default function () {}`,
|
||||
map
|
||||
)
|
||||
}
|
||||
13
styleguide/src/loader/jsdoc-loader.js
Normal file
@ -0,0 +1,13 @@
|
||||
// Get jsdocs meta from component with
|
||||
// @url: https://github.com/vue-styleguidist/vue-docgen-api
|
||||
const parseSource = require('vue-docgen-api').parseSource
|
||||
|
||||
module.exports = function(source) {
|
||||
const callback = this.async()
|
||||
const content = JSON.stringify(source)
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029')
|
||||
.replace(/\\n/g, '\n')
|
||||
const component = parseSource(content, this.resourcePath)
|
||||
callback(null, `module.exports = ${JSON.stringify(component)}`)
|
||||
}
|
||||
1
styleguide/src/main.js
Normal file
@ -0,0 +1 @@
|
||||
require('./styleguide')
|
||||
32
styleguide/src/styleguide/App.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<ds-page ref="page">
|
||||
<template slot="brand">
|
||||
<router-link to="/">
|
||||
<ds-logo/>
|
||||
</router-link>
|
||||
</template>
|
||||
<navigation
|
||||
@navigate="$refs.page.closeDrawer()"
|
||||
slot="sidebar"/>
|
||||
<navigation
|
||||
@navigate="$refs.page.closeDrawer()"
|
||||
slot="drawer"/>
|
||||
<router-view/>
|
||||
</ds-page>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Navigation from './components/Navigation'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Navigation
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
168
styleguide/src/styleguide/components/CodeExample.vue
Executable file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div :class="`${iframe ? 'vuep-iframe' : ''}`">
|
||||
<vuep
|
||||
:value="template"
|
||||
:options="{ theme: 'vueds' }"
|
||||
:iframe="iframe" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vuep from 'vuep'
|
||||
import 'vuep/dist/vuep.css'
|
||||
|
||||
export default {
|
||||
name: 'CodeExample',
|
||||
data() {
|
||||
return {
|
||||
iframe: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Vuep
|
||||
},
|
||||
computed: {
|
||||
template() {
|
||||
return this.getCode()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCode() {
|
||||
const codeLines = this.code.split('\n')
|
||||
const codeTypeMatch = codeLines[0].trim().match(/^[A-Za-z]+$/g)
|
||||
if (codeTypeMatch) {
|
||||
codeLines.shift()
|
||||
const codeType = codeTypeMatch[0]
|
||||
if (codeType === 'iframe') {
|
||||
this.iframe = true
|
||||
}
|
||||
}
|
||||
while (codeLines[0].trim() === '') {
|
||||
codeLines.shift()
|
||||
}
|
||||
while (codeLines[codeLines.length - 1].trim() === '') {
|
||||
codeLines.pop()
|
||||
}
|
||||
if (codeLines[0].trim() === '<template>') {
|
||||
return codeLines.join('\n')
|
||||
}
|
||||
const code = codeLines.map(line => ' ' + line).join('\n')
|
||||
/* eslint-disable */
|
||||
return `<template>
|
||||
<div>
|
||||
${code}
|
||||
</div>
|
||||
</template>
|
||||
<script><\/script>`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vuep {
|
||||
display: flex;
|
||||
height: auto;
|
||||
font-family: inherit;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.vuep-editor {
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.vuep-preview {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
border: $border-size-base solid $border-color-softer;
|
||||
padding: $space-base;
|
||||
margin-bottom: $space-small;
|
||||
overflow: visible;
|
||||
|
||||
.vuep-iframe & {
|
||||
padding: 0;
|
||||
min-height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
// Codemirror Theme
|
||||
$codemirror-background: $background-color-soft;
|
||||
$codemirror-primary: $color-primary;
|
||||
|
||||
.cm-s-vueds {
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
font-family: $font-family-code;
|
||||
letter-spacing: 0.3px;
|
||||
word-spacing: 1px;
|
||||
background: $codemirror-background;
|
||||
color: $text-color-soft;
|
||||
border: $border-size-base solid $border-color-softer;
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
background-color: $codemirror-background;
|
||||
border: none;
|
||||
border-right: $border-size-base solid $border-color-softer;
|
||||
padding-right: $space-x-small;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
div.CodeMirror-cursor {
|
||||
border-left: 2px solid $text-color-base;
|
||||
}
|
||||
|
||||
.CodeMirror-activeline-background {
|
||||
background: rgba($codemirror-primary, 0.1);
|
||||
}
|
||||
.CodeMirror-selected {
|
||||
background: rgba($codemirror-primary, 0.1);
|
||||
}
|
||||
.cm-comment {
|
||||
font-style: italic;
|
||||
color: $text-color-softer;
|
||||
}
|
||||
.cm-tag {
|
||||
color: $codemirror-primary;
|
||||
}
|
||||
.cm-attribute {
|
||||
color: $text-color-warning;
|
||||
}
|
||||
.cm-keyword {
|
||||
color: $text-color-danger;
|
||||
}
|
||||
.cm-string {
|
||||
color: $text-color-success;
|
||||
}
|
||||
.cm-property {
|
||||
color: $text-color-warning;
|
||||
}
|
||||
.cm-variable-2 {
|
||||
color: $text-color-danger;
|
||||
}
|
||||
.cm-atom {
|
||||
color: $text-color-success;
|
||||
}
|
||||
.cm-number {
|
||||
color: $text-color-danger;
|
||||
}
|
||||
.cm-operator {
|
||||
color: $codemirror-primary;
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: $text-color-softer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
142
styleguide/src/styleguide/components/ComponentDoc.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-page-title :heading="component.name | componentName" />
|
||||
<ds-container>
|
||||
<ds-space
|
||||
v-if="component.tags"
|
||||
margin-top="base">
|
||||
<template
|
||||
v-for="(tagGroup, name) in component.tags">
|
||||
<ds-tag
|
||||
v-for="(tag, index) in tagGroup"
|
||||
:color="tagColor(tag)"
|
||||
:key="`${name}${index}`">
|
||||
{{ tagDescription(tag) }}
|
||||
</ds-tag>
|
||||
</template>
|
||||
</ds-space>
|
||||
<ds-space margin-bottom="xx-large">
|
||||
<ds-text size="x-large">{{ component.description }}</ds-text>
|
||||
</ds-space>
|
||||
<ds-space
|
||||
v-for="(part, index) in docParts"
|
||||
margin-bottom="xx-large"
|
||||
:key="index">
|
||||
<ds-space>
|
||||
<markdown :content="part.description"/>
|
||||
</ds-space>
|
||||
<code-example
|
||||
:code="part.example"
|
||||
v-if="part.example"/>
|
||||
</ds-space>
|
||||
<ds-space margin-bottom="xx-large">
|
||||
<component-options-doc :component="component" />
|
||||
</ds-space>
|
||||
<ds-space
|
||||
margin-bottom="xx-large"
|
||||
v-if="component.children"
|
||||
v-for="child in component.children"
|
||||
:key="child.name">
|
||||
<component-options-doc :component="child" />
|
||||
</ds-space>
|
||||
</ds-container></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeExample from './CodeExample'
|
||||
import ComponentOptionsDoc from './ComponentOptionsDoc'
|
||||
|
||||
export default {
|
||||
name: 'ComponentDoc',
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CodeExample,
|
||||
ComponentOptionsDoc
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
propFields: {
|
||||
name: {
|
||||
label: 'Prop Name',
|
||||
width: '20%'
|
||||
},
|
||||
type: {
|
||||
label: 'Type',
|
||||
width: '20%'
|
||||
},
|
||||
default: {
|
||||
label: 'Default',
|
||||
width: '20%'
|
||||
},
|
||||
description: 'Description'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
componentProps() {
|
||||
return Object.keys(this.component.props).map(name => {
|
||||
return {
|
||||
name,
|
||||
...this.component.props[name]
|
||||
}
|
||||
})
|
||||
},
|
||||
componentSlots() {
|
||||
return Object.keys(this.component.slots).map(name => {
|
||||
return {
|
||||
name,
|
||||
...this.component.slots[name]
|
||||
}
|
||||
})
|
||||
},
|
||||
docParts() {
|
||||
const component = this.component.component
|
||||
if (!component.__docs) {
|
||||
return []
|
||||
}
|
||||
const parts = component.__docs.split('```')
|
||||
let i = 0
|
||||
const parsed = parts.reduce((result, part, index) => {
|
||||
if (index % 2 === 0) {
|
||||
result[i] = {
|
||||
description: part
|
||||
}
|
||||
} else {
|
||||
result[i].example = part
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}, [])
|
||||
return parsed
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tagColor(tag) {
|
||||
if (tag.title === 'deprecated') {
|
||||
return 'warning'
|
||||
}
|
||||
if (tag.title === 'see') {
|
||||
return 'primary'
|
||||
}
|
||||
return 'inverse'
|
||||
},
|
||||
tagDescription(tag) {
|
||||
if (tag.description === true) {
|
||||
return tag.title
|
||||
}
|
||||
if (tag.title === 'see') {
|
||||
return `Child of ${tag.description}`
|
||||
}
|
||||
return `${tag.title} ${tag.description}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
32
styleguide/src/styleguide/components/ComponentItem.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<ds-flex-item>
|
||||
<ds-card
|
||||
:header="name">
|
||||
{{ component.description }}
|
||||
<template slot="footer">
|
||||
<ds-button
|
||||
:path="{ name: component.name }"
|
||||
primary>
|
||||
{{ name }} Details
|
||||
</ds-button>
|
||||
</template>
|
||||
</ds-card>
|
||||
</ds-flex-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ComponentItem',
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.$options.filters.componentName(this.component.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
222
styleguide/src/styleguide/components/ComponentOptionsDoc.vue
Executable file
@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<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="{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="{row}">
|
||||
<ds-chip
|
||||
v-for="type in row.types"
|
||||
:key="type"
|
||||
inline>
|
||||
{{ type }}
|
||||
</ds-chip>
|
||||
</template>
|
||||
<template
|
||||
slot="default"
|
||||
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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ComponentOptionsDoc',
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
propFields: {
|
||||
name: {
|
||||
label: 'Name'
|
||||
},
|
||||
type: {
|
||||
label: 'Type',
|
||||
width: '20%'
|
||||
},
|
||||
default: {
|
||||
label: 'Default',
|
||||
width: '20%'
|
||||
}
|
||||
},
|
||||
slotFields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
width: '20%'
|
||||
},
|
||||
description: 'Description'
|
||||
},
|
||||
eventFields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
width: '20%'
|
||||
},
|
||||
description: 'Description'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
componentProps() {
|
||||
if (!this.component.props) {
|
||||
return null
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
22
styleguide/src/styleguide/components/ComponentPage.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<component-doc :component="component" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ComponentDoc from './ComponentDoc'
|
||||
|
||||
export default {
|
||||
name: 'ComponentPage',
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ComponentDoc
|
||||
}
|
||||
}
|
||||
</script>
|
||||
81
styleguide/src/styleguide/components/Navigation.vue
Executable file
@ -0,0 +1,81 @@
|
||||
<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"
|
||||
:url-parser="urlParser"
|
||||
:name-parser="nameParser"
|
||||
:is-exact="isExact"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 => {
|
||||
const [parent, ...children] = [...route.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)
|
||||
},
|
||||
urlParser(route) {
|
||||
return {
|
||||
name: route.name
|
||||
}
|
||||
},
|
||||
isExact(url) {
|
||||
return url.name === 'Introduction'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navigation {
|
||||
padding: $space-base $space-x-small;
|
||||
}
|
||||
|
||||
.navigation-search {
|
||||
padding: 0 $space-small;
|
||||
margin-bottom: $space-base;
|
||||
}
|
||||
</style>
|
||||
21
styleguide/src/styleguide/components/PageWrapper.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageWrapper',
|
||||
props: {
|
||||
section: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
components: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
71
styleguide/src/styleguide/components/SectionPage.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-page-title :heading="section.name" />
|
||||
<ds-container>
|
||||
<ds-space margin-top="x-large">
|
||||
<markdown
|
||||
:content="description"
|
||||
:components="requiredComponents"
|
||||
v-if="description"/>
|
||||
</ds-space>
|
||||
<ds-flex
|
||||
gutter="base"
|
||||
:width="{ base: '100%', sm: '50%' }">
|
||||
<component-item
|
||||
v-if="components"
|
||||
v-for="component in components"
|
||||
:key="component.name"
|
||||
:component="component" />
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ComponentItem from './ComponentItem'
|
||||
|
||||
export default {
|
||||
name: 'SectionPage',
|
||||
props: {
|
||||
section: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
components: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ComponentItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
description: null,
|
||||
requiredComponents: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const name = this.section.name.replace(' ', '')
|
||||
|
||||
if (this.section.requiredComponents) {
|
||||
this.section.requiredComponents.forEach(component => {
|
||||
try {
|
||||
const cFile = require(`./${component}`).default
|
||||
this.requiredComponents[cFile.name] = cFile
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('could not get required component', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const mdFile = require(`raw-loader!../docs/${name}.md`).default
|
||||
this.description = mdFile
|
||||
} catch (err) {
|
||||
this.description = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
70
styleguide/src/styleguide/components/global/DesignTokens.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<ds-space margin-top="x-large">
|
||||
<ds-flex
|
||||
gutter="large"
|
||||
:direction="{ lg: 'row-reverse' }">
|
||||
<ds-flex-item :width="{ base: '100%', lg: '250px' }">
|
||||
<div class="sticky">
|
||||
<ds-heading tag="h4">Categories</ds-heading>
|
||||
<ds-menu :routes="tokenMenu" />
|
||||
</div>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<ds-space
|
||||
v-for="(group, key) in tokenMap"
|
||||
:key="key">
|
||||
<ds-heading
|
||||
tag="h3"
|
||||
:id="key">{{ key | startCase }}</ds-heading>
|
||||
<ds-table
|
||||
:data="group"
|
||||
:fields="{token: { width: '300px' }, example: 'Example'}">
|
||||
<template
|
||||
slot="token"
|
||||
slot-scope="data">
|
||||
<ds-copy-field>
|
||||
{{ data.row.scss }}
|
||||
</ds-copy-field>
|
||||
<ds-copy-field>
|
||||
tokens.{{ data.row.name | camelCase }}
|
||||
</ds-copy-field>
|
||||
</template>
|
||||
<template
|
||||
slot="example"
|
||||
slot-scope="data">
|
||||
<token-item :token="data.row" />
|
||||
</template>
|
||||
</ds-table>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { tokenMap } from '@@/tokens'
|
||||
|
||||
export default {
|
||||
name: 'DesignTokens',
|
||||
computed: {
|
||||
tokenMap() {
|
||||
return tokenMap
|
||||
},
|
||||
tokenMenu() {
|
||||
return Object.keys(this.tokenMap).map(key => {
|
||||
return {
|
||||
name: this.$options.filters.startCase(key),
|
||||
path: `#${key}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
}
|
||||
</style>
|
||||
70
styleguide/src/styleguide/components/global/IconList.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<ds-space margin-top="x-large">
|
||||
<ds-space>
|
||||
<ds-input
|
||||
v-model="searchString"
|
||||
icon="search"
|
||||
placeholder="Search icon ..."/>
|
||||
</ds-space>
|
||||
<ds-flex
|
||||
gutter="large"
|
||||
:width="{ base: '100%', xs: '50%', lg: '33.3332%'}">
|
||||
<ds-flex-item
|
||||
v-for="icon in filteredIcons"
|
||||
:key="icon">
|
||||
<ds-card>
|
||||
<div class="icon-preview">
|
||||
<ds-text size="x-large">
|
||||
<ds-icon :name="icon"/>
|
||||
</ds-text>
|
||||
</div>
|
||||
<ds-copy-field>{{ icon }}</ds-copy-field>
|
||||
<template slot="footer">
|
||||
<ds-button
|
||||
@click="copy(icon)"
|
||||
primary>Copy Code</ds-button>
|
||||
</template>
|
||||
</ds-card>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-space>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { iconNames } from '@@/icons'
|
||||
|
||||
export default {
|
||||
name: 'IconList',
|
||||
data() {
|
||||
return {
|
||||
searchString: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icons() {
|
||||
return iconNames
|
||||
},
|
||||
filteredIcons() {
|
||||
if (!this.searchString) {
|
||||
return this.icons
|
||||
}
|
||||
return this.icons.filter(icon => {
|
||||
return icon.toLowerCase().indexOf(this.searchString.toLowerCase()) > -1
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copy(icon) {
|
||||
const code = `<ds-icon name="${icon}" />`
|
||||
this.$copyToClipboard(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-preview {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
143
styleguide/src/styleguide/components/global/Markdown.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<component
|
||||
v-if="parsedComponent"
|
||||
:is="parsedComponent"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import cheerio from 'cheerio'
|
||||
import Vue from 'vue/dist/vue.common'
|
||||
|
||||
import markdownIt from 'markdown-it'
|
||||
import emoji from 'markdown-it-emoji'
|
||||
import subscript from 'markdown-it-sub'
|
||||
import superscript from 'markdown-it-sup'
|
||||
import footnote from 'markdown-it-footnote'
|
||||
import deflist from 'markdown-it-deflist'
|
||||
import abbreviation from 'markdown-it-abbr'
|
||||
import insert from 'markdown-it-ins'
|
||||
import mark from 'markdown-it-mark'
|
||||
import katex from 'markdown-it-katex'
|
||||
import tasklists from 'markdown-it-task-lists'
|
||||
|
||||
export default {
|
||||
name: 'Markdown',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
components: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
md: new markdownIt()
|
||||
.use(emoji)
|
||||
.use(subscript)
|
||||
.use(superscript)
|
||||
.use(footnote)
|
||||
.use(deflist)
|
||||
.use(abbreviation)
|
||||
.use(insert)
|
||||
.use(mark)
|
||||
.use(katex, { throwOnError: false, errorColor: ' #cc0000' })
|
||||
.use(tasklists)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parsedComponent() {
|
||||
if (!this.content) {
|
||||
return false
|
||||
}
|
||||
return this.parseComponent()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseComponent() {
|
||||
if (!this.content) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.md.set({
|
||||
html: true,
|
||||
xhtmlOut: true,
|
||||
linkify: true
|
||||
})
|
||||
|
||||
const html = this.md.render(this.content) || ''
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
// Replace h-tags
|
||||
for (let l = 1; l <= 6; l++) {
|
||||
$(`h${l}`).each((i, item) => {
|
||||
$(item).replaceWith(
|
||||
$(
|
||||
'<ds-heading tag="' +
|
||||
`h${l}` +
|
||||
'">' +
|
||||
$(item).html() +
|
||||
'</ds-heading>'
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Replace p-tags
|
||||
$('p').each((i, item) => {
|
||||
// Handle component tags
|
||||
if (
|
||||
$(item).children().length &&
|
||||
Array.from($(item).children()).some(c => c.name.indexOf('-') > -1)
|
||||
) {
|
||||
$(item).replaceWith($('<div>' + $(item).html() + '</div>'))
|
||||
} else {
|
||||
$(item).replaceWith(
|
||||
$('<ds-text size="large">' + $(item).html() + '</ds-text>')
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Replace ul-tags
|
||||
$('ul').each((i, item) => {
|
||||
$(item).replaceWith(
|
||||
$('<ds-list size="large">' + $(item).html() + '</ds-list>')
|
||||
)
|
||||
})
|
||||
|
||||
// Replace ol-tags
|
||||
$('ol').each((i, item) => {
|
||||
$(item).replaceWith(
|
||||
$('<ds-list size="large" ordered>' + $(item).html() + '</ds-list>')
|
||||
)
|
||||
})
|
||||
|
||||
// Replace ol-tags
|
||||
$('li').each((i, item) => {
|
||||
$(item).replaceWith(
|
||||
$('<ds-list-item>' + $(item).html() + '</ds-list-item>')
|
||||
)
|
||||
})
|
||||
|
||||
const componentHtml = $('body').html()
|
||||
const template = `<div>${componentHtml}</div>`
|
||||
const component = Vue.compile(template)
|
||||
|
||||
if (this.components) {
|
||||
component.components = this.components
|
||||
}
|
||||
|
||||
this.$options.components['parsed-markdown-component'] = {
|
||||
...component
|
||||
}
|
||||
|
||||
return 'parsed-markdown-component'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
126
styleguide/src/styleguide/components/global/TokenItem.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!hideTokenGroups.includes(this.token.category)"
|
||||
:style="styles"
|
||||
:class="`token-item${timeGroups.includes(this.token.category) ? ' hover' : ''}`">
|
||||
<span v-if="textGroups.includes(this.token.category)">
|
||||
Aa
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<code>{{ token.value }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { tokens } from '@@/tokens'
|
||||
|
||||
export default {
|
||||
name: 'TokenItem',
|
||||
props: {
|
||||
token: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
textGroups: [
|
||||
'text-color',
|
||||
'font-size',
|
||||
'font-family',
|
||||
'font-weight',
|
||||
'font-spacing',
|
||||
'letter-spacing',
|
||||
'line-height'
|
||||
],
|
||||
timeGroups: ['time', 'ease'],
|
||||
hideTokenGroups: ['space-size', 'media-size', 'z-index', 'media-query']
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
const styles = {}
|
||||
switch (this.token.category) {
|
||||
case 'text-color':
|
||||
styles.color = this.token.value
|
||||
break
|
||||
case 'border-color':
|
||||
styles.borderColor = this.token.value
|
||||
break
|
||||
case 'color':
|
||||
case 'background-color':
|
||||
styles.backgroundColor = this.token.value
|
||||
styles.borderColor = tokens.borderColorSofter
|
||||
break
|
||||
case 'font-size':
|
||||
styles.fontSize = this.token.value
|
||||
break
|
||||
case 'font-spacing':
|
||||
styles.paddingTop = this.token.value
|
||||
styles.paddingBottom = this.token.value
|
||||
styles.borderColor = tokens.borderColorBase
|
||||
styles.height = 'auto'
|
||||
break
|
||||
case 'font-family':
|
||||
styles.fontFamily = this.token.value
|
||||
break
|
||||
case 'font-weight':
|
||||
styles.fontWeight = this.token.value
|
||||
break
|
||||
case 'opacity':
|
||||
styles.opacity = this.token.value
|
||||
styles.backgroundColor = tokens.colorBlack
|
||||
break
|
||||
case 'size':
|
||||
case 'space':
|
||||
styles.height = this.token.value
|
||||
styles.borderColor = tokens.borderColorBase
|
||||
break
|
||||
case 'border-size':
|
||||
styles.borderWidth = this.token.value
|
||||
styles.borderColor = tokens.borderColorBase
|
||||
break
|
||||
case 'border-radius':
|
||||
styles.borderRadius = this.token.value
|
||||
styles.width = tokens.sizeHeightBase
|
||||
styles.borderColor = tokens.borderColorBase
|
||||
break
|
||||
case 'box-shadow':
|
||||
styles.boxShadow = this.token.value
|
||||
styles.marginBottom = tokens.spaceBase
|
||||
break
|
||||
case 'time':
|
||||
styles.transitionDuration = this.token.value
|
||||
styles.backgroundColor = tokens.backgroundColorInverseSofter
|
||||
break
|
||||
case 'ease':
|
||||
styles.transitionTimingFunction = this.token.value
|
||||
styles.transitionDuration = tokens.durationLong
|
||||
styles.backgroundColor = tokens.backgroundColorInverseSofter
|
||||
break
|
||||
}
|
||||
return styles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.token-item {
|
||||
height: $size-height-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: $border-size-base solid transparent;
|
||||
line-height: $line-height-base;
|
||||
}
|
||||
|
||||
.hover {
|
||||
transition: opacity;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
14
styleguide/src/styleguide/components/global/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
// Get components
|
||||
const context = require.context('.', true, /\.vue$/)
|
||||
|
||||
const components = []
|
||||
context.keys().forEach(key => {
|
||||
const c = context(key).default
|
||||
components.push(c)
|
||||
})
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
components.forEach(c => Vue.component(c.name, c))
|
||||
}
|
||||
}
|
||||
26
styleguide/src/styleguide/config.js
Normal file
@ -0,0 +1,26 @@
|
||||
export default {
|
||||
sections: [
|
||||
{
|
||||
name: 'Introduction',
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
name: 'Design Tokens'
|
||||
},
|
||||
{
|
||||
name: 'Layout'
|
||||
},
|
||||
{
|
||||
name: 'Typography'
|
||||
},
|
||||
{
|
||||
name: 'Data Input'
|
||||
},
|
||||
{
|
||||
name: 'Data Display'
|
||||
},
|
||||
{
|
||||
name: 'Navigation'
|
||||
}
|
||||
]
|
||||
}
|
||||
1
styleguide/src/styleguide/docs/DataDisplay.md
Normal file
@ -0,0 +1 @@
|
||||
Data Display components are used to present data in an approachable way.
|
||||
8
styleguide/src/styleguide/docs/DesignTokens.md
Normal file
@ -0,0 +1,8 @@
|
||||
Design tokens are the visual design atoms of the design system — specifically, they are named entities that store visual design attributes. We use them in place of hard-coded values (such as hex values for color or pixel values for spacing) in order to maintain a scalable and consistent visual system for UI development.
|
||||
|
||||
You can use tokens as SCSS variables or import them in your JS:
|
||||
```
|
||||
import { tokens } from 'system'
|
||||
```
|
||||
|
||||
<design-tokens />
|
||||
29
styleguide/src/styleguide/docs/Introduction.md
Executable file
@ -0,0 +1,29 @@
|
||||
## Human Connection - Styleguide
|
||||
|
||||
This Design System provides our team with essential components and guidelines to achieve a consistent user experience accross our applications.
|
||||
|
||||
### Documentation
|
||||
|
||||
If you are searching for more technical information for the UI & API, you can find it in the [HC Documentation](https://docs.human-connection.org/nitro).
|
||||
|
||||
### How to use it
|
||||
|
||||
Use the system's components to solve common UI problems like layout, typography, displaying data or data input.
|
||||
|
||||
When no component fits your case, either extend an existing one or create a new one.
|
||||
|
||||
### Extending existing components
|
||||
|
||||
When extending components, make sure not to break existing features. If you really need to, it might be better to create a new one and mark the existing component as `deprecated`
|
||||
|
||||
### Creating new components
|
||||
|
||||
Keep a few things in mind when creating a new component:
|
||||
* Keep it simple
|
||||
* Keep it extendable
|
||||
* Use Design Tokens wherever needed
|
||||
* Document it
|
||||
|
||||
### Resources
|
||||
|
||||
If you would like to become a component superhero feel free to dive into the [Vuejs Component Style Guide](https://pablohpsilva.github.io/vuejs-component-style-guide/#/).
|
||||
1
styleguide/src/styleguide/docs/Layout.md
Normal file
@ -0,0 +1 @@
|
||||
Layout components are used to group related content together. The also provide consistent spacing for blocks of content.
|
||||
1
styleguide/src/styleguide/docs/Navigation.md
Normal file
@ -0,0 +1 @@
|
||||
Navigation components allow the user to navigate through the application.
|
||||
1
styleguide/src/styleguide/docs/Typography.md
Normal file
@ -0,0 +1 @@
|
||||
Typographic components are used to give text a semantic meaning. They also create hierarchy and provide styling through size and color.
|
||||
21
styleguide/src/styleguide/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
import System from '@@'
|
||||
import Components from './components/global'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import startCase from 'lodash/startCase'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(System)
|
||||
Vue.use(Components)
|
||||
|
||||
Vue.filter('componentName', value => {
|
||||
return startCase(value.replace(/^Ds/, ''))
|
||||
})
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
125
styleguide/src/styleguide/router/index.js
Normal file
@ -0,0 +1,125 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import kebabCase from 'lodash/kebabCase'
|
||||
|
||||
import { componentsMap } from '@@/components'
|
||||
import config from '../config'
|
||||
import PageWrapper from '../components/PageWrapper'
|
||||
import SectionPage from '../components/SectionPage'
|
||||
import ComponentPage from '../components/ComponentPage'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
function createRoute(section) {
|
||||
const components = componentsMap[kebabCase(section.name)]
|
||||
const filteredComponents = components
|
||||
? components.filter(c => !c.parent)
|
||||
: null
|
||||
|
||||
const route = {
|
||||
path: section.path ? section.path : createUrl(section.name),
|
||||
component: createPageWrapper(section, filteredComponents),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: section.name,
|
||||
component: createSectionPage(section, filteredComponents)
|
||||
}
|
||||
]
|
||||
}
|
||||
if (filteredComponents) {
|
||||
route.children = route.children.concat(
|
||||
filteredComponents.map(createComponentRoute)
|
||||
)
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
function createPageWrapper(section, components) {
|
||||
return {
|
||||
render: h =>
|
||||
h(PageWrapper, {
|
||||
props: {
|
||||
section: section,
|
||||
components: components
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createSectionPage(section, components) {
|
||||
return {
|
||||
render: h =>
|
||||
h(SectionPage, {
|
||||
props: {
|
||||
section: section,
|
||||
components: components
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createComponentRoute(component) {
|
||||
return {
|
||||
path: createUrl(component.name, false),
|
||||
name: component.name,
|
||||
component: createComponentPage(component)
|
||||
}
|
||||
}
|
||||
|
||||
function createComponentPage(component) {
|
||||
return {
|
||||
// Necessary to keep reactivity (hot-reload)
|
||||
data() {
|
||||
return {
|
||||
component
|
||||
}
|
||||
},
|
||||
render: h =>
|
||||
h(ComponentPage, {
|
||||
props: {
|
||||
component
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createUrl(name, root = true) {
|
||||
const parts = Array.isArray(name) ? name : [name]
|
||||
const url = root ? '/' : ''
|
||||
return url + parts.map(sanitize).join('/')
|
||||
}
|
||||
|
||||
function sanitize(name) {
|
||||
const sanitized = name.toLowerCase().replace(' ', '-')
|
||||
return encodeURIComponent(sanitized)
|
||||
}
|
||||
|
||||
const routes = config.sections.map(createRoute)
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (to.hash) {
|
||||
return {
|
||||
selector: to.hash
|
||||
}
|
||||
}
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
return { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Redirect for github pages
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.query && to.query.p) {
|
||||
next({ path: to.query.p, query: null })
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
BIN
styleguide/src/system/assets/fonts/gentium-basic/GentiumBasic-Italic.woff
Executable file
BIN
styleguide/src/system/assets/fonts/gentium-basic/GentiumBasic-Italic.woff2
Executable file
BIN
styleguide/src/system/assets/fonts/gentium-basic/GentiumBasic.woff
Executable file
BIN
styleguide/src/system/assets/fonts/gentium-basic/GentiumBasic.woff2
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Bold.woff
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Bold.woff2
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-BoldItalic.woff
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-BoldItalic.woff2
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Italic.woff
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Italic.woff2
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Regular.woff
Executable file
BIN
styleguide/src/system/assets/fonts/lato/Lato-Regular.woff2
Executable file
BIN
styleguide/src/system/assets/img/Logo-Horizontal-Dark.png
Normal file
48
styleguide/src/system/assets/img/Logo-Horizontal-Dark.svg
Normal file
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
BIN
styleguide/src/system/assets/img/Logo-Horizontal-Dark@2x.png
Normal file
BIN
styleguide/src/system/assets/img/Logo-Horizontal.png
Normal file
48
styleguide/src/system/assets/img/Logo-Horizontal.svg
Normal file
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
BIN
styleguide/src/system/assets/img/Logo-Horizontal@2x.png
Normal file
8
styleguide/src/system/assets/img/logo_cion.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 239 59" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29.5 14.449L43.5774 38.8316H15.4227L29.5 14.449Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5 55C43.5833 55 55 43.5833 55 29.5C55 15.4167 43.5833 4 29.5 4C15.4167 4 4 15.4167 4 29.5C4 43.5833 15.4167 55 29.5 55ZM29.5 59C45.7924 59 59 45.7924 59 29.5C59 13.2076 45.7924 0 29.5 0C13.2076 0 0 13.2076 0 29.5C0 45.7924 13.2076 59 29.5 59Z" fill="currentColor"/>
|
||||
<path d="M118.83 44.291C116.877 45.9707 114.689 47.2598 112.268 48.1582C109.846 49.0566 107.336 49.5059 104.738 49.5059C102.746 49.5059 100.822 49.2422 98.9668 48.7148C97.1309 48.207 95.4023 47.4844 93.7812 46.5469C92.1797 45.5898 90.7148 44.4473 89.3867 43.1191C88.0586 41.791 86.916 40.3262 85.959 38.7246C85.0215 37.1035 84.2891 35.375 83.7617 33.5391C83.2539 31.6836 83 29.7598 83 27.7676C83 25.7754 83.2539 23.8516 83.7617 21.9961C84.2891 20.1406 85.0215 18.4121 85.959 16.8105C86.916 15.1895 88.0586 13.7148 89.3867 12.3867C90.7148 11.0586 92.1797 9.92578 93.7812 8.98828C95.4023 8.03125 97.1309 7.29883 98.9668 6.79102C100.822 6.26367 102.746 6 104.738 6C107.336 6 109.846 6.44922 112.268 7.34766C114.689 8.22656 116.877 9.51562 118.83 11.2148L114.377 18.5391C113.146 17.2109 111.691 16.1953 110.012 15.4922C108.332 14.7695 106.574 14.4082 104.738 14.4082C102.883 14.4082 101.145 14.7598 99.5234 15.4629C97.9023 16.166 96.4863 17.123 95.2754 18.334C94.0645 19.5254 93.1074 20.9414 92.4043 22.582C91.7012 24.2031 91.3496 25.9316 91.3496 27.7676C91.3496 29.6035 91.7012 31.332 92.4043 32.9531C93.1074 34.5547 94.0645 35.9609 95.2754 37.1719C96.4863 38.3828 97.9023 39.3398 99.5234 40.043C101.145 40.7461 102.883 41.0977 104.738 41.0977C106.574 41.0977 108.332 40.7461 110.012 40.043C111.691 39.3203 113.146 38.2949 114.377 36.9668L118.83 44.291Z" fill="currentColor"/>
|
||||
<path d="M138.131 48.5977H129.723V6.58594H138.131V48.5977Z" fill="currentColor"/>
|
||||
<path d="M193.115 27.7676C193.115 29.7598 192.852 31.6836 192.324 33.5391C191.816 35.375 191.094 37.1035 190.156 38.7246C189.219 40.3262 188.086 41.791 186.758 43.1191C185.43 44.4473 183.965 45.5898 182.363 46.5469C180.762 47.4844 179.033 48.207 177.178 48.7148C175.322 49.2422 173.398 49.5059 171.406 49.5059C169.414 49.5059 167.49 49.2422 165.635 48.7148C163.799 48.207 162.07 47.4844 160.449 46.5469C158.848 45.5898 157.383 44.4473 156.055 43.1191C154.727 41.791 153.584 40.3262 152.627 38.7246C151.689 37.1035 150.957 35.375 150.43 33.5391C149.922 31.6836 149.668 29.7598 149.668 27.7676C149.668 25.7754 149.922 23.8516 150.43 21.9961C150.957 20.1406 151.689 18.4121 152.627 16.8105C153.584 15.209 154.727 13.7441 156.055 12.416C157.383 11.0879 158.848 9.95508 160.449 9.01758C162.07 8.08008 163.799 7.35742 165.635 6.84961C167.49 6.32227 169.414 6.05859 171.406 6.05859C173.398 6.05859 175.322 6.32227 177.178 6.84961C179.033 7.35742 180.762 8.08008 182.363 9.01758C183.965 9.95508 185.43 11.0879 186.758 12.416C188.086 13.7441 189.219 15.209 190.156 16.8105C191.094 18.4121 191.816 20.1406 192.324 21.9961C192.852 23.8516 193.115 25.7754 193.115 27.7676ZM184.766 27.7676C184.766 25.9316 184.414 24.2031 183.711 22.582C183.008 20.9414 182.051 19.5254 180.84 18.334C179.648 17.123 178.232 16.166 176.592 15.4629C174.971 14.7598 173.242 14.4082 171.406 14.4082C169.551 14.4082 167.812 14.7598 166.191 15.4629C164.57 16.166 163.154 17.123 161.943 18.334C160.732 19.5254 159.775 20.9414 159.072 22.582C158.369 24.2031 158.018 25.9316 158.018 27.7676C158.018 29.6035 158.369 31.332 159.072 32.9531C159.775 34.5547 160.732 35.9609 161.943 37.1719C163.154 38.3828 164.57 39.3398 166.191 40.043C167.812 40.7461 169.551 41.0977 171.406 41.0977C173.242 41.0977 174.971 40.7461 176.592 40.043C178.232 39.3398 179.648 38.3828 180.84 37.1719C182.051 35.9609 183.008 34.5547 183.711 32.9531C184.414 31.332 184.766 29.6035 184.766 27.7676Z" fill="currentColor"/>
|
||||
<path d="M238.256 48.5977H229.262L213.061 20.9414V48.5977H204.652V6.58594H213.646L229.848 34.2715V6.58594H238.256V48.5977Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
123
styleguide/src/system/components/data-display/Avatar/Avatar.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div
|
||||
class="ds-avatar"
|
||||
:class="[
|
||||
`ds-size-${this.size}`,
|
||||
online && 'is-online'
|
||||
]"
|
||||
:style="styles"
|
||||
>
|
||||
<ds-flex
|
||||
v-if="!hasImage || error"
|
||||
style="height: 100%">
|
||||
<ds-flex-item centered>
|
||||
<template v-if="isAnonymus">
|
||||
<ds-icon name="eye-slash" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ userInitials }}
|
||||
</template>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<img
|
||||
v-if="image && !error"
|
||||
:src="image"
|
||||
@error="onError"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import helpers from './lib/helpers.js'
|
||||
import { tokens } from '@@/tokens'
|
||||
import camelCase from 'lodash/camelCase'
|
||||
import upperFirst from 'lodash/upperFirst'
|
||||
|
||||
export default {
|
||||
name: 'DsAvatar',
|
||||
props: {
|
||||
backgroundColor: { type: String, default: null },
|
||||
name: { type: String, default: 'Anonymus' },
|
||||
/**
|
||||
* The size used for the avatar.
|
||||
* @options small|base|large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: 'base',
|
||||
validator: value => {
|
||||
return value.match(/(small|base|large|x-large)/)
|
||||
}
|
||||
},
|
||||
image: { type: String, default: null },
|
||||
online: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAnonymus() {
|
||||
return !this.name || this.name.toLowerCase() === 'anonymus'
|
||||
},
|
||||
styles() {
|
||||
let size = this.sizeValue
|
||||
if (Number.isInteger(Number(size))) {
|
||||
size = `${size}px`
|
||||
}
|
||||
const nummericSize = Number.parseInt(size)
|
||||
return {
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: this.hasImage ? 'white' : this.background,
|
||||
fontSize: Math.floor(nummericSize / 2.5) + 'px',
|
||||
fontWeight: 'bold',
|
||||
color: this.fontColor
|
||||
}
|
||||
},
|
||||
sizeValue() {
|
||||
return tokens[`sizeAvatar${upperFirst(camelCase(this.size))}`]
|
||||
},
|
||||
hasImage() {
|
||||
return Boolean(this.image) && !this.error
|
||||
},
|
||||
userInitials() {
|
||||
return helpers.initials(this.name)
|
||||
},
|
||||
background() {
|
||||
return (
|
||||
this.backgroundColor ||
|
||||
helpers.randomBackgroundColor(
|
||||
this.name.length,
|
||||
helpers.backgroundColors
|
||||
)
|
||||
)
|
||||
},
|
||||
fontColor() {
|
||||
return this.color || helpers.lightenColor(this.background, 200)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onError() {
|
||||
this.error = true
|
||||
},
|
||||
updateSize() {
|
||||
if (this.hasImage) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.size = this.$refs.avatar.getBoundingClientRect().width
|
||||
} catch (err) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
47
styleguide/src/system/components/data-display/Avatar/demo.md
Normal file
@ -0,0 +1,47 @@
|
||||
## Basic usage
|
||||
|
||||
```html
|
||||
<ds-avatar
|
||||
name="Hans Alba"
|
||||
image="https://s3.amazonaws.com/uifaces/faces/twitter/lisovsky/128.jpg" />
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
```html
|
||||
<ds-avatar online size="small" name="Hans Peter"></ds-avatar>
|
||||
<ds-avatar online size="base" name="Hans Peter"></ds-avatar>
|
||||
<ds-avatar online size="large" name="Hans Peter"></ds-avatar>
|
||||
<ds-avatar online size="x-large" name="Hans Peter"></ds-avatar>
|
||||
<ds-avatar online size="x-large"></ds-avatar>
|
||||
<ds-avatar online size="large"></ds-avatar>
|
||||
<ds-avatar online size="base"></ds-avatar>
|
||||
<ds-avatar online size="small"></ds-avatar>
|
||||
```
|
||||
|
||||
## Broken Image
|
||||
|
||||
```html
|
||||
<ds-avatar
|
||||
name="Peter Sommerfield"
|
||||
image="http://not-valid-image-link.org/image-does-not-exist.jpg"
|
||||
/>
|
||||
```
|
||||
|
||||
## Anonymus
|
||||
|
||||
```html
|
||||
<ds-avatar
|
||||
image="http://not-valid-image-link##.org/image-does-not-exist.jpg"
|
||||
/>
|
||||
```
|
||||
|
||||
## Online Status
|
||||
|
||||
```html
|
||||
<ds-avatar
|
||||
name="Hans Alba"
|
||||
image="https://s3.amazonaws.com/uifaces/faces/twitter/lisovsky/128.jpg"
|
||||
online
|
||||
/>
|
||||
```
|
||||
@ -0,0 +1,65 @@
|
||||
export default {
|
||||
backgroundColors: [
|
||||
'#295E87',
|
||||
'#007D93',
|
||||
'#933D86',
|
||||
'#005093',
|
||||
'#4A5580',
|
||||
'#0067A5',
|
||||
'#007D93',
|
||||
'#007A70',
|
||||
'#008F6D',
|
||||
'#008255',
|
||||
'#43913A',
|
||||
'#C61A6A',
|
||||
'#15A748',
|
||||
'#007FBA',
|
||||
'#008AC4',
|
||||
'#E1B424',
|
||||
'#00753C',
|
||||
'#B42554',
|
||||
'#4F3B68',
|
||||
'#EB8B2D',
|
||||
'#DC3E2A',
|
||||
'#A7BE33',
|
||||
'#DF542A',
|
||||
'#00A3DA',
|
||||
'#84BF41'
|
||||
],
|
||||
|
||||
initials(name) {
|
||||
let un = name || 'Anonymus'
|
||||
let parts = un.split(/[ -]/)
|
||||
let initials = ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
initials += parts[i].charAt(0)
|
||||
}
|
||||
if (initials.length > 3 && initials.search(/[A-Z]/) !== -1) {
|
||||
initials = initials.replace(/[a-z]+/g, '')
|
||||
}
|
||||
initials = initials.substr(0, 3).toUpperCase()
|
||||
return initials
|
||||
},
|
||||
randomBackgroundColor(seed, colors) {
|
||||
return colors[seed % colors.length]
|
||||
},
|
||||
lightenColor(hex, amt) {
|
||||
// From https://css-tricks.com/snippets/javascript/lighten-darken-color/
|
||||
var usePound = false
|
||||
if (hex[0] === '#') {
|
||||
hex = hex.slice(1)
|
||||
usePound = true
|
||||
}
|
||||
var num = parseInt(hex, 16)
|
||||
var r = (num >> 16) + amt
|
||||
if (r > 255) r = 255
|
||||
else if (r < 0) r = 0
|
||||
var b = ((num >> 8) & 0x00ff) + amt
|
||||
if (b > 255) b = 255
|
||||
else if (b < 0) b = 0
|
||||
var g = (num & 0x0000ff) + amt
|
||||
if (g > 255) g = 255
|
||||
else if (g < 0) g = 0
|
||||
return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
.ds-avatar {
|
||||
@include reset;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-right: $space-xx-small;
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
text-align: center;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
box-shadow: inset 0 0 0 1px rgba($color-neutral-0, .1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.is-online::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 33%;
|
||||
height: 33%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border: 2px solid $background-color-base;
|
||||
border-radius: 100%;
|
||||
background-color: $color-success;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.ds-icon {
|
||||
margin-top: -0.1em;
|
||||
}
|
||||
|
||||
&.ds-size-small {
|
||||
width: $size-avatar-small;
|
||||
height: $size-avatar-small;
|
||||
|
||||
&.is-online::before {
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
&.ds-size-base {
|
||||
border-width: 1px;
|
||||
width: $size-avatar-base;
|
||||
height: $size-avatar-base;
|
||||
|
||||
}
|
||||
&.ds-size-large {
|
||||
width: $size-avatar-large;
|
||||
height: $size-avatar-large;
|
||||
}
|
||||
&.ds-size-x-large {
|
||||
width: $size-avatar-x-large;
|
||||
height: $size-avatar-x-large;
|
||||
|
||||
&.is-online::before {
|
||||
height: $space-large;
|
||||
width: $space-large;
|
||||
border-width: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
class="ds-copy-field"
|
||||
:class="`ds-copy-field-${size}`">
|
||||
<div ref="text">
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
class="ds-copy-field-link">
|
||||
<ds-button
|
||||
@click="copy"
|
||||
icon="copy"
|
||||
color="soft"
|
||||
ghost/>
|
||||
</div>
|
||||
<transition name="ds-copy-field-message">
|
||||
<div
|
||||
v-show="showMessage"
|
||||
class="ds-copy-field-message">
|
||||
<div
|
||||
class="ds-copy-field-message-text"
|
||||
ref="messageText"/>
|
||||
</div>
|
||||
</transition>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DsButton from '@@/components/navigation/Button/Button'
|
||||
/**
|
||||
* A copy field is used to present text that can easily
|
||||
* be copied to the users clipboard by clicking on it.
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export default {
|
||||
name: 'DsCopyField',
|
||||
components: {
|
||||
DsButton
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* The size used for the text.
|
||||
* @options small|base|large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: 'base',
|
||||
validator: value => {
|
||||
return value.match(/(small|base|large)/)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The html element name used for the copy field.
|
||||
*/
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMessage: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copy() {
|
||||
const content = this.$refs.text.innerText
|
||||
this.$refs.messageText.innerText = content
|
||||
this.$copyToClipboard(content)
|
||||
this.showMessage = true
|
||||
this.$nextTick(() => {
|
||||
this.showMessage = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CopyField.vue matches snapshot 1`] = `
|
||||
<div
|
||||
class="ds-copy-field ds-copy-field-base"
|
||||
>
|
||||
<div>
|
||||
Test
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="ds-copy-field-link"
|
||||
>
|
||||
<dsbutton-stub
|
||||
color="soft"
|
||||
ghost="true"
|
||||
icon="copy"
|
||||
linktag="button"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="ds-copy-field-message"
|
||||
name="ds-copy-field-message"
|
||||
style="display: none;"
|
||||
>
|
||||
<div
|
||||
class="ds-copy-field-message-text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -0,0 +1,5 @@
|
||||
## Basic usage
|
||||
|
||||
```
|
||||
<ds-copy-field>Copy me please!</ds-copy-field>
|
||||
```
|
||||
@ -0,0 +1,26 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import Comp from './CopyField.vue'
|
||||
|
||||
describe('CopyField.vue', () => {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(Comp, {
|
||||
slots: {
|
||||
default: 'Test'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('defaults to div', () => {
|
||||
expect(wrapper.props().tag).toEqual('div')
|
||||
})
|
||||
|
||||
it('displays text', () => {
|
||||
expect(wrapper.text()).toEqual('Test')
|
||||
})
|
||||
|
||||
it('matches snapshot', () => {
|
||||
expect(wrapper.element).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,60 @@
|
||||
.ds-copy-field {
|
||||
@include reset;
|
||||
@include stack-space($space-xx-small);
|
||||
position: relative;
|
||||
font-family: $font-family-text;
|
||||
line-height: $line-height-base;
|
||||
padding: $space-x-small $space-small;
|
||||
border-radius: $border-radius-base;
|
||||
letter-spacing: $letter-spacing-small;
|
||||
background-color: $background-color-softer;
|
||||
}
|
||||
|
||||
.ds-copy-field-small {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.ds-copy-field-large {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
|
||||
.ds-copy-field-link {
|
||||
@include reset;
|
||||
position: absolute;
|
||||
right: $space-xx-small;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ds-copy-field-message {
|
||||
@include reset;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
user-select: none;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: all $duration-x-long $ease-out;
|
||||
}
|
||||
|
||||
.ds-copy-field-message-text {
|
||||
@include reset;
|
||||
padding: $space-x-small $space-small;
|
||||
transition: all $duration-x-long $ease-out;
|
||||
transform: scale(1);
|
||||
transform-origin: 0 50%;
|
||||
}
|
||||
|
||||
.ds-copy-field-message-enter,
|
||||
.ds-copy-field-message-leave-to {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
.ds-copy-field-message-text {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
62
styleguide/src/system/components/data-display/List/List.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<component
|
||||
:is="ordered ? 'ol' : 'ul'"
|
||||
class="ds-list"
|
||||
:class="[
|
||||
size && `ds-list-size-${size}`
|
||||
]">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Used in combination with the list item component to display lists of data.
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export default {
|
||||
name: 'DsList',
|
||||
provide() {
|
||||
return {
|
||||
$parentList: this
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
$parentList: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* Whether or not the list is ordered.
|
||||
*/
|
||||
ordered: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* The size used for the list.
|
||||
* @options small|base|large|x-large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: value => {
|
||||
return value.match(/(small|base|large|x-large)/)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The name of the list icon.
|
||||
*/
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'angle-right'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<li class="ds-list-item">
|
||||
<span class="ds-list-item-prefix">
|
||||
<span
|
||||
v-if="!$parentList.ordered"
|
||||
class="ds-list-item-icon">
|
||||
<ds-icon :name="icon"/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="ds-list-item-content">
|
||||
<slot />
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @version 1.0.0
|
||||
* @see DsList
|
||||
*/
|
||||
export default {
|
||||
name: 'DsListItem',
|
||||
inject: {
|
||||
$parentList: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* The name of the list icon.
|
||||
*/
|
||||
icon: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$parentList ? this.$parentList.icon : 'angle-right'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
60
styleguide/src/system/components/data-display/List/demo.md
Normal file
@ -0,0 +1,60 @@
|
||||
## Basic usage
|
||||
|
||||
```
|
||||
<ds-list>
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
```
|
||||
|
||||
## Ordered list
|
||||
|
||||
Use an ordered list to display a sorted set of items.
|
||||
|
||||
```
|
||||
<ds-list ordered>
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
```
|
||||
|
||||
## Custom icon
|
||||
|
||||
Provide a custom icon for the list items.
|
||||
|
||||
```
|
||||
<ds-list icon="user">
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
Use different sizes to create hierarchy.
|
||||
|
||||
```
|
||||
<ds-list size="x-large">
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
<ds-list size="large">
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
<ds-list size="base">
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
<ds-list size="small">
|
||||
<ds-list-item>Layout components</ds-list-item>
|
||||
<ds-list-item>Typography components</ds-list-item>
|
||||
<ds-list-item>Navigation components</ds-list-item>
|
||||
</ds-list>
|
||||
```
|
||||
@ -0,0 +1,60 @@
|
||||
.ds-list {
|
||||
@include reset;
|
||||
@include stack-space($font-space-x-large);
|
||||
font-family: $font-family-text;
|
||||
line-height: $line-height-base;
|
||||
list-style-type: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ol.ds-list {
|
||||
counter-reset: list-counter;
|
||||
}
|
||||
|
||||
.ds-list-size-small {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.ds-list-size-base {
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
.ds-list-size-large {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
|
||||
.ds-list-size-x-large {
|
||||
font-size: $font-size-x-large;
|
||||
}
|
||||
|
||||
.ds-list-item {
|
||||
@include reset;
|
||||
@include stack-space($font-space-x-large);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ds-list-item-prefix {
|
||||
flex: 0 0 $font-space-xxx-large;
|
||||
color: $text-color-primary;
|
||||
}
|
||||
|
||||
.ds-list-item-content {
|
||||
@include layout-flex-fix;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
ol > .ds-list-item > .ds-list-item-prefix:before, .ds-list-item-icon {
|
||||
display: flex;
|
||||
transform: translateY(0.2em);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $font-space-xxx-large;
|
||||
height: $font-space-xxx-large + .4em;
|
||||
font-size: 0.6em;
|
||||
color: $text-color-soft;
|
||||
}
|
||||
|
||||
ol > .ds-list-item > .ds-list-item-prefix:before {
|
||||
content: counter(list-counter);
|
||||
counter-increment: list-counter;
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div
|
||||
class="ds-number"
|
||||
:class="[
|
||||
size && `ds-number-size-${size}`
|
||||
]"
|
||||
>
|
||||
<ds-text
|
||||
:size="size"
|
||||
class="ds-number-count"
|
||||
style="margin-bottom: 0">
|
||||
<slot name="count">{{ count }}</slot>
|
||||
</ds-text>
|
||||
<ds-text
|
||||
:uppercase="uppercase"
|
||||
:size="labelSize"
|
||||
class="ds-number-label"
|
||||
color="soft">
|
||||
{{ label }}
|
||||
</ds-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DsNumber',
|
||||
props: {
|
||||
size: { type: String, default: 'x-large' },
|
||||
labelSize: { type: String, default: 'small' },
|
||||
count: { type: [Number, String], default: 0 },
|
||||
label: { type: String, default: null },
|
||||
uppercase: { type: Boolean, default: false }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
33
styleguide/src/system/components/data-display/Number/demo.md
Normal file
@ -0,0 +1,33 @@
|
||||
## Basic usage
|
||||
|
||||
```html
|
||||
<ds-number
|
||||
count="125"
|
||||
label="Users Online" />
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
```html
|
||||
<ds-number
|
||||
size="small"
|
||||
count="1734"
|
||||
label="Contributions" />
|
||||
```
|
||||
```html
|
||||
<ds-number
|
||||
size="xx-large"
|
||||
label-size="large"
|
||||
count="1734"
|
||||
label="Contributions" />
|
||||
```
|
||||
|
||||
## Slot
|
||||
|
||||
```html
|
||||
<ds-number
|
||||
size="large"
|
||||
label="collected donations from users">
|
||||
<span slot="count">1.123.124,03 EUR</span>
|
||||
<ds-number>
|
||||
```
|
||||
@ -0,0 +1,15 @@
|
||||
.ds-number {
|
||||
text-align: center;
|
||||
}
|
||||
.ds-number-count {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ds-number-label {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
// .ds-number-size-x-large,
|
||||
// .ds-number-size-xx-large {
|
||||
// .ds-number-label {
|
||||
// font-size: $font-size-base;
|
||||
// }
|
||||
// }
|
||||
172
styleguide/src/system/components/data-display/Table/Table.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div
|
||||
class="ds-table-wrap"
|
||||
v-if="dataArray">
|
||||
<table
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="ds-table"
|
||||
:class="[
|
||||
condensed && 'ds-table-condensed',
|
||||
bordered && 'ds-table-bordered'
|
||||
]">
|
||||
<colgroup>
|
||||
<col
|
||||
v-for="header in headers"
|
||||
:key="header.key"
|
||||
:width="header.width">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<ds-table-head-col
|
||||
v-for="header in headers"
|
||||
:key="header.key"
|
||||
:align="align(header.key)">
|
||||
{{ header.label }}
|
||||
</ds-table-head-col>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, index) in rows"
|
||||
:key="row.key || index">
|
||||
<ds-table-col
|
||||
v-for="col in row"
|
||||
:key="col.key"
|
||||
:align="align(col.key)">
|
||||
<!-- @slot Slots are named by fields -->
|
||||
<slot
|
||||
:name="col.key"
|
||||
:row="dataArray[index] ? dataArray[index] : null"
|
||||
:col="col"
|
||||
:index="index">
|
||||
{{ col.value }}
|
||||
</slot>
|
||||
</ds-table-col>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import startCase from 'lodash/startCase'
|
||||
|
||||
/**
|
||||
* Used in combination with the table row to create data tables.
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export default {
|
||||
name: 'DsTable',
|
||||
provide() {
|
||||
return {
|
||||
$parentTable: this
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* The table's data
|
||||
*/
|
||||
data: {
|
||||
type: [Array, Object],
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The table's header config
|
||||
*/
|
||||
fields: {
|
||||
type: [Array, Object],
|
||||
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: {
|
||||
dataArray() {
|
||||
if (Array.isArray(this.data)) {
|
||||
return this.data
|
||||
}
|
||||
if (typeof this.data === 'object') {
|
||||
return Object.keys(this.data).map(key => this.data[key])
|
||||
}
|
||||
return []
|
||||
},
|
||||
headers() {
|
||||
let keys = this.dataArray[0] ? Object.keys(this.dataArray[0]) : []
|
||||
let headerObj = {}
|
||||
if (this.fields) {
|
||||
if (Array.isArray(this.fields)) {
|
||||
keys = this.fields
|
||||
} else if (typeof this.fields === 'object') {
|
||||
keys = Object.keys(this.fields)
|
||||
headerObj = this.fields
|
||||
}
|
||||
}
|
||||
|
||||
return keys.map(key => {
|
||||
let header = {
|
||||
key,
|
||||
label: this.parseLabel(key),
|
||||
width: ''
|
||||
}
|
||||
if (headerObj[key]) {
|
||||
const headerMerge =
|
||||
typeof headerObj[key] === 'string'
|
||||
? { label: headerObj[key] }
|
||||
: headerObj[key]
|
||||
header = Object.assign(header, headerMerge)
|
||||
}
|
||||
return header
|
||||
})
|
||||
},
|
||||
rows() {
|
||||
let keys = this.dataArray[0] ? Object.keys(this.dataArray[0]) : []
|
||||
return this.dataArray.map(row => {
|
||||
if (this.fields) {
|
||||
keys = Array.isArray(this.fields)
|
||||
? this.fields
|
||||
: Object.keys(this.fields)
|
||||
}
|
||||
|
||||
return keys.map(key => {
|
||||
return {
|
||||
key,
|
||||
value: row[key]
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
align(colKey) {
|
||||
return this.fields && this.fields[colKey]
|
||||
? this.fields[colKey].align
|
||||
: null
|
||||
},
|
||||
parseLabel(label) {
|
||||
return startCase(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<!-- eslint-disable -->
|
||||
<td
|
||||
class="ds-table-col"
|
||||
:class="[
|
||||
align && `ds-table-col-${align}`
|
||||
]">
|
||||
<slot/>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Used in combination with the table component to create data tables.
|
||||
* @version 1.0.0
|
||||
* @see DsTable
|
||||
* @private
|
||||
*/
|
||||
export default {
|
||||
name: 'DsTableCol',
|
||||
inject: {
|
||||
$parentTable: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* The column width
|
||||
*/
|
||||
width: {
|
||||
type: [String, Number, Object],
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* The column align
|
||||
* @options left|center|right
|
||||
*/
|
||||
align: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: value => {
|
||||
return value.match(/(left|center|right)/)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<th
|
||||
class="ds-table-head-col"
|
||||
:class="[
|
||||
align && `ds-table-head-col-${align}`
|
||||
]">
|
||||
<slot>
|
||||
{{ label }}
|
||||
</slot>
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Used in combination with the table component to create data tables.
|
||||
* @version 1.0.0
|
||||
* @see DsTable
|
||||
* @private
|
||||
*/
|
||||
export default {
|
||||
name: 'DsTableHeadCol',
|
||||
inject: {
|
||||
$parentTable: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* The column value
|
||||
*/
|
||||
label: {
|
||||
type: [Number, String, Array, Object],
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The column width
|
||||
*/
|
||||
width: {
|
||||
type: [String, Number, Object],
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* The column align
|
||||
* @options left|center|right
|
||||
*/
|
||||
align: {
|
||||
type: String,
|
||||
default: null,
|
||||
validator: value => {
|
||||
return value.match(/(left|center|right)/)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {}
|
||||
}
|
||||
</script>
|
||||
195
styleguide/src/system/components/data-display/Table/demo.md
Executable file
@ -0,0 +1,195 @@
|
||||
## Basic usage
|
||||
|
||||
Display an array of data objects.
|
||||
```
|
||||
<template>
|
||||
<ds-table :data="tableData">
|
||||
</ds-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableData: [
|
||||
{
|
||||
name: 'Rengar',
|
||||
type: 'Jungler',
|
||||
loves: 'Hide and seek'
|
||||
},
|
||||
{
|
||||
name: 'Renekton',
|
||||
type: 'Toplaner',
|
||||
loves: 'Slice and dice'
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
type: 'ADC',
|
||||
loves: 'Spray and pray'
|
||||
},
|
||||
{
|
||||
name: 'Blitz',
|
||||
type: 'Support',
|
||||
loves: 'Hook you up'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Specify fields
|
||||
|
||||
You can specify which fields to display
|
||||
```
|
||||
<template>
|
||||
<ds-table :data="tableData" :fields="tableFields">
|
||||
</ds-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableFields: ['name', 'type'],
|
||||
tableData: [
|
||||
{
|
||||
name: 'Rengar',
|
||||
type: 'Jungler',
|
||||
loves: 'Hide and seek'
|
||||
},
|
||||
{
|
||||
name: 'Renekton',
|
||||
type: 'Toplaner',
|
||||
loves: 'Slice and dice'
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
type: 'ADC',
|
||||
loves: 'Spray and pray'
|
||||
},
|
||||
{
|
||||
name: 'Blitz',
|
||||
type: 'Support',
|
||||
loves: 'Hook you up'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Customize header
|
||||
|
||||
You can customize the header by setting fields as an object.
|
||||
|
||||
The value can be a string representing the fields label or an object with options.
|
||||
```
|
||||
<template>
|
||||
<ds-table :data="tableData" :fields="tableFields">
|
||||
</ds-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableFields: {
|
||||
name: 'Hero',
|
||||
type: {
|
||||
label: 'Job',
|
||||
width: '300px',
|
||||
align: 'right'
|
||||
}
|
||||
},
|
||||
tableData: [
|
||||
{
|
||||
name: 'Rengar',
|
||||
type: 'Jungler',
|
||||
loves: 'Hide and seek'
|
||||
},
|
||||
{
|
||||
name: 'Renekton',
|
||||
type: 'Toplaner',
|
||||
loves: 'Slice and dice'
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
type: 'ADC',
|
||||
loves: 'Spray and pray'
|
||||
},
|
||||
{
|
||||
name: 'Blitz',
|
||||
type: 'Support',
|
||||
loves: 'Hook you up'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Custom columns
|
||||
|
||||
You can define custom templates for columns and create columns that do not have a corresponding data attribute.
|
||||
|
||||
Via scoped slots you have access to the columns `row`, `index` and `col`.
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<ds-table :data="tableData" :fields="tableFields">
|
||||
<template slot="loves" slot-scope="scope">
|
||||
{{ scope.row.name }} loves {{ scope.row.loves }}
|
||||
</template>
|
||||
<template slot="edit" slot-scope="scope">
|
||||
<ds-button
|
||||
size="small"
|
||||
@click="deleteRow(scope.row)">delete</ds-button>
|
||||
</template>
|
||||
</ds-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableFields: ['name', 'type', 'loves', 'edit'],
|
||||
tableData: [
|
||||
{
|
||||
name: 'Rengar',
|
||||
type: 'Jungler',
|
||||
loves: 'Hide and seek'
|
||||
},
|
||||
{
|
||||
name: 'Renekton',
|
||||
type: 'Toplaner',
|
||||
loves: 'Slice and dice'
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
type: 'ADC',
|
||||
loves: 'Spray and pray'
|
||||
},
|
||||
{
|
||||
name: 'Blitz',
|
||||
type: 'Support',
|
||||
loves: 'Hook you up'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteRow(row) {
|
||||
const index = this.tableData.indexOf(row)
|
||||
if (index > -1) {
|
||||
this.tableData.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
65
styleguide/src/system/components/data-display/Table/style.scss
Executable file
@ -0,0 +1,65 @@
|
||||
.ds-table-wrap {
|
||||
@include reset;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ds-table {
|
||||
@include reset;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ds-table-col {
|
||||
@include reset;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.ds-table-col,
|
||||
.ds-table-head-col {
|
||||
&.ds-table-col-left,
|
||||
&.ds-table-head-col-left {
|
||||
text-align: left;
|
||||
}
|
||||
&.ds-table-col-center,
|
||||
&.ds-table-head-col-center {
|
||||
text-align: center;
|
||||
}
|
||||
&.ds-table-col-right,
|
||||
&.ds-table-head-col-right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
152
styleguide/src/system/components/data-input/Form/Form.vue
Executable file
@ -0,0 +1,152 @@
|
||||
<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'
|
||||
// Disable warnings to console
|
||||
Schema.warning = function() {}
|
||||
|
||||
/**
|
||||
* 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 on form submit.
|
||||
* Receives the current form data.
|
||||
*
|
||||
* @event submit
|
||||
*/
|
||||
this.$emit('submit', this.newData)
|
||||
})
|
||||
},
|
||||
validate(cb) {
|
||||
const validator = new Schema(this.schema)
|
||||
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
|
||||
}
|
||||
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)
|
||||
/**
|
||||
* Fires after user input.
|
||||
* Receives the current form data.
|
||||
* The form data is not validated and can be invalid.
|
||||
* This event is fired before the input-valid event.
|
||||
*
|
||||
* @event input
|
||||
*/
|
||||
await this.$emit('input', cloneDeep(this.newData))
|
||||
this.validate(() => {
|
||||
/**
|
||||
* Fires after user input.
|
||||
* Receives the current form data.
|
||||
* This is only called if the form data is successfully validated.
|
||||
*
|
||||
* @event input-valid
|
||||
*/
|
||||
this.$emit('input-valid', cloneDeep(this.newData))
|
||||
})
|
||||
},
|
||||
reset() {
|
||||
/**
|
||||
* Fires after reset() was called.
|
||||
* Receives the current form data.
|
||||
* Reset has to be handled manually.
|
||||
*
|
||||
* @event reset
|
||||
*/
|
||||
this.$emit('reset', cloneDeep(this.value))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.newData = cloneDeep(this.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
164
styleguide/src/system/components/data-input/Form/demo.md
Executable file
@ -0,0 +1,164 @@
|
||||
## 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"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
@reset="handleReset"
|
||||
: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-input
|
||||
icon="at"
|
||||
model="emailConfirm"
|
||||
label="Confirm Email"
|
||||
type="email"
|
||||
placeholder="Confirm 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="disabled"
|
||||
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' },
|
||||
emailConfirm: [
|
||||
{ validator: this.matchEmail },
|
||||
// the last entry is called first ¯\_(ツ)_/¯
|
||||
{ type: 'email', required: true, message: 'Confirm your email'}
|
||||
],
|
||||
settings: {
|
||||
type: 'object',
|
||||
fields: {
|
||||
status: { min: 20, max: 300, message: 'Write between 20 and 300 letters' }
|
||||
}
|
||||
}
|
||||
},
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit(data) {
|
||||
console.log('Submit form ...', data)
|
||||
},
|
||||
handleInput(data) {
|
||||
console.log('Input form ...', data)
|
||||
this.disabled = true
|
||||
},
|
||||
handleInputValid(data) {
|
||||
this.disabled = false
|
||||
console.log('Input-valid form ...', data)
|
||||
},
|
||||
handleReset(data) {
|
||||
console.log('Reset form ...', data)
|
||||
},
|
||||
|
||||
matchEmail(rule, value, callback, source, options) {
|
||||
var errors = [];
|
||||
if(this.formData.email !== value) {
|
||||
errors.push(new Error('EMail missmatch'));
|
||||
}
|
||||
callback(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
0
styleguide/src/system/components/data-input/Form/style.scss
Executable file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div
|
||||
class="ds-form-item"
|
||||
:class="$parentInput.stateClasses">
|
||||
<ds-input-label
|
||||
:label="$parentInput.label"
|
||||
:for="$parentInput.id" />
|
||||
<slot/>
|
||||
<ds-input-error :error="$parentInput.error" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @version 1.0.0
|
||||
* @private
|
||||
*/
|
||||
export default {
|
||||
name: 'DsFormItem',
|
||||
inject: ['$parentInput']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<transition name="ds-input-error">
|
||||
<div
|
||||
class="ds-input-error"
|
||||
v-show="!!error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @version 1.0.0
|
||||
* @private
|
||||
*/
|
||||
export default {
|
||||
name: 'DsInputError',
|
||||
props: {
|
||||
error: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<label
|
||||
class="ds-input-label"
|
||||
v-show="!!label">
|
||||
{{ label }}
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @version 1.0.0
|
||||
* @private
|
||||
*/
|
||||
export default {
|
||||
name: 'DsInputLabel',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,29 @@
|
||||
.ds-form-item {
|
||||
position: relative;
|
||||
@include stack-space($space-small);
|
||||
}
|
||||
|
||||
.ds-input-error {
|
||||
color: $color-danger;
|
||||
font-size: $font-size-x-small;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.ds-input-error-enter-active {
|
||||
transition: opacity $duration-base $ease-out,
|
||||
transform $duration-base $ease-out;
|
||||
}
|
||||
|
||||
.ds-input-error-enter,
|
||||
.ds-input-error-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.ds-input-label {
|
||||
padding-bottom: $space-xx-small;
|
||||
color: $text-color-soft;
|
||||
font-size: $font-size-base;
|
||||
display: block;
|
||||
}
|
||||
111
styleguide/src/system/components/data-input/Input/Input.vue
Executable file
@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<ds-form-item>
|
||||
<div class="ds-input-wrap">
|
||||
<div
|
||||
v-if="icon"
|
||||
class="ds-input-icon">
|
||||
<ds-icon :name="icon"/>
|
||||
</div>
|
||||
<component
|
||||
class="ds-input"
|
||||
:class="[
|
||||
icon && `ds-input-has-icon`,
|
||||
iconRight && `ds-input-has-icon-right`
|
||||
]"
|
||||
:id="id"
|
||||
:name="name ? name : model"
|
||||
:type="type"
|
||||
:autofocus="autofocus"
|
||||
:placeholder="placeholder"
|
||||
:tabindex="tabindex"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:is="tag"
|
||||
:value.prop="innerValue"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
:rows="type === 'textarea' ? rows : null"
|
||||
v-html="type === 'textarea' ? innerValue : null"/>
|
||||
<div
|
||||
v-if="iconRight"
|
||||
class="ds-input-icon-right">
|
||||
<ds-icon :name="iconRight"/>
|
||||
</div>
|
||||
</div>
|
||||
</ds-form-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import inputMixin from '../shared/input'
|
||||
|
||||
/**
|
||||
* Used for handling basic user input.
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export default {
|
||||
name: 'DsInput',
|
||||
mixins: [inputMixin],
|
||||
props: {
|
||||
/**
|
||||
* The type of this input.
|
||||
* @options url|text|password|email|search|textarea
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
validator: value => {
|
||||
return value.match(/(url|text|password|email|search|textarea)/)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The placeholder shown when value is empty.
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* Whether the input should be automatically focused
|
||||
*/
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* How many rows this input should have (only for type="textarea")
|
||||
*/
|
||||
rows: {
|
||||
type: [String, Number],
|
||||
default: 1
|
||||
},
|
||||
/**
|
||||
* The name of the input's icon.
|
||||
*/
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* The name of the input's right icon.
|
||||
*/
|
||||
iconRight: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tag() {
|
||||
if (this.type === 'textarea') {
|
||||
return 'textarea'
|
||||
}
|
||||
return 'input'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="./style.scss">
|
||||
</style>
|
||||
|
||||
<docs src="./demo.md"></docs>
|
||||
113
styleguide/src/system/components/data-input/Input/demo.md
Normal file
@ -0,0 +1,113 @@
|
||||
## Basic usage
|
||||
|
||||
```
|
||||
<ds-input placeholder="Name ..." />
|
||||
```
|
||||
|
||||
## Usage with label
|
||||
|
||||
```
|
||||
<ds-input
|
||||
id="name"
|
||||
label="Your name"
|
||||
placeholder="Name ..." />
|
||||
```
|
||||
|
||||
## Disabled
|
||||
|
||||
```
|
||||
<ds-input placeholder="Name ..." disabled />
|
||||
```
|
||||
|
||||
## Input types
|
||||
|
||||
You can use an input for different types of input.
|
||||
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<ds-input v-model="text"></ds-input>
|
||||
<ds-input v-model="text" type="password"></ds-input>
|
||||
<ds-input v-model="text" type="textarea" rows="2"></ds-input>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
text: 'Default text'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Bind to a value
|
||||
|
||||
Use v-model to bind a value to the input.
|
||||
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<ds-input
|
||||
v-model="name"
|
||||
placeholder="Name ..."></ds-input>
|
||||
<ds-text>Your name: {{ name }}</ds-text>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</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-input
|
||||
v-model="name"
|
||||
:schema="{type: 'string', min: 6, message: 'Name must be longer' }"
|
||||
placeholder="Name ..." />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Input sizes
|
||||
|
||||
```
|
||||
<ds-input placeholder="Small ..." size="small"></ds-input>
|
||||
<ds-input placeholder="Base ..."></ds-input>
|
||||
<ds-input placeholder="Large ..." size="large"></ds-input>
|
||||
```
|
||||
|
||||
## Input icons
|
||||
|
||||
Add an icon to help the user identify the input type.
|
||||
|
||||
```
|
||||
<ds-input placeholder="Search ..." icon="search"></ds-input>
|
||||
<ds-input placeholder="Time ..." icon="clock"></ds-input>
|
||||
<ds-input placeholder="Search ..." icon-right="search"></ds-input>
|
||||
<ds-input placeholder="Search ..." icon="search" size="small"></ds-input>
|
||||
<ds-input placeholder="Search ..." icon="search" size="large"></ds-input>
|
||||
```
|
||||
17
styleguide/src/system/components/data-input/Input/style.scss
Normal file
@ -0,0 +1,17 @@
|
||||
@import '../shared/input.scss';
|
||||
|
||||
@include input(ds-input);
|
||||
|
||||
textarea.ds-input {
|
||||
height: auto;
|
||||
min-height: $input-height;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea.ds-input-size-small {
|
||||
min-height: $input-height-small;
|
||||
}
|
||||
|
||||
textarea.ds-input-size-large {
|
||||
min-height: $input-height-large;
|
||||
}
|
||||
123
styleguide/src/system/components/data-input/Radio/Radio.vue
Executable 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>
|
||||