Moved Styleguide to its own repo

This commit is contained in:
Grzegorz Leoniec 2019-02-15 19:04:41 +01:00
commit 3cdd06b252
No known key found for this signature in database
GPG Key ID: 3AA43686D4EB1377
1117 changed files with 71225 additions and 0 deletions

3
.browserslistrc Normal file
View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not ie <= 8

14
.eslintrc.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
root: true,
env: {
node: true
},
extends: ['plugin:vue/strongly-recommended', '@vue/prettier'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: 'babel-eslint'
}
}

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
# 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*

5
.postcssrc.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"bracketSpacing": true
}

57
README.md Normal file
View File

@ -0,0 +1,57 @@
# CION Vue Design System
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.cion.visualjerk.de
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)
[![Screenshot](./preview/customize.png)](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
babel.config.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
presets: ['@vue/app'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}

10
dist/demo.html vendored Normal file
View File

@ -0,0 +1,10 @@
<meta charset="utf-8">
<title>system demo</title>
<script src="./system.umd.js"></script>
<link rel="stylesheet" href="./system.css">
<script>
console.log(system)
</script>

Binary file not shown.

BIN
dist/fonts/GentiumBasic.64d1e286.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Italic.0acac383.eot vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Italic.4eb103b4.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Italic.4ffc48d0.ttf vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Italic.f28f2d64.woff vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Regular.27bd77b9.woff vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Regular.6d4e7822.ttf vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Regular.8ab18d93.eot vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Regular.bd03a2cc.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Semibold.3b0cd725.ttf vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Semibold.8b4f872c.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Semibold.8bb939ef.eot vendored Normal file

Binary file not shown.

BIN
dist/fonts/Lato-Semibold.c2b50f4a.woff vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

25
dist/report.html vendored Normal file

File diff suppressed because one or more lines are too long

644
dist/shared.scss vendored Normal file

File diff suppressed because one or more lines are too long

20503
dist/system.common.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/system.common.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/system.css vendored Normal file

File diff suppressed because one or more lines are too long

20513
dist/system.umd.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/system.umd.js.map vendored Normal file

File diff suppressed because one or more lines are too long

28
dist/system.umd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/system.umd.min.js.map vendored Normal file

File diff suppressed because one or more lines are too long

38
docs/404.html Normal file
View 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
docs/CNAME Normal file
View File

@ -0,0 +1 @@
styleguide.cion.visualjerk.de

25
docs/babel-standalone.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
docs/index.html Normal file
View File

@ -0,0 +1 @@
<!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"><link rel=icon href=/favicon.ico><script src=/babel-standalone.js></script><title>CION - Vue Design System</title><link href=/css/app.e38d5069.css rel=preload as=style><link href=/css/chunk-vendors.7a428b56.css rel=preload as=style><link href=/js/app.f428b946.js rel=preload as=script><link href=/js/chunk-vendors.0a7f54fd.js rel=preload as=script><link href=/css/chunk-vendors.7a428b56.css rel=stylesheet><link href=/css/app.e38d5069.css rel=stylesheet></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><script src=/js/chunk-vendors.0a7f54fd.js></script><script src=/js/app.f428b946.js></script></body></html>

2
docs/js/app.f428b946.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
jest.config.js Normal file
View 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/'
}

76
package.json Normal file
View File

@ -0,0 +1,76 @@
{
"name": "vue-cion-design-system",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "http-server ./docs -o -s",
"build": "yarn theo && vue-cli-service build",
"lint": "vue-cli-service lint --no-fix",
"dev": "npm-run-all --parallel theo:onchange theo servedev",
"servedev": "vue-cli-service serve --open",
"build:lib": "yarn theo && cross-env BUILD=library vue-cli-service build --target lib --name system ./src/library.js",
"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:unit": "vue-cli-service test:unit"
},
"dependencies": {
"portal-vue": "~1.5.1",
"vue": "~2.6.6"
},
"devDependencies": {
"@babel/core": "~7.2.2",
"@babel/plugin-syntax-dynamic-import": "~7.2.0",
"@babel/standalone": "~7.3.2",
"@vue/cli-plugin-babel": "~3.4.0",
"@vue/cli-plugin-eslint": "~3.4.0",
"@vue/cli-plugin-unit-jest": "~3.4.0",
"@vue/cli-service": "~3.4.0",
"@vue/test-utils": "~1.0.0-beta.29",
"async-validator": "~1.10.1",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "~24.1.0",
"babel-plugin-transform-require-context": "~0.1.1",
"cheerio": "~1.0.0-rc.2",
"clipboard-copy": "~2.0.1",
"clone-deep": "~4.0.1",
"codemirror": "~5.43.0",
"cross-env": "~5.2.0",
"dot-prop": "~4.2.0",
"lodash": "~4.17.11",
"markdown-it": "~8.4.2",
"markdown-it-abbr": "~1.0.4",
"markdown-it-deflist": "~2.0.3",
"markdown-it-emoji": "~1.4.0",
"markdown-it-footnote": "~3.0.1",
"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",
"node-sass": "~4.11.0",
"npm-run-all": "~4.1.5",
"onchange": "~5.2.0",
"raw-loader": "~1.0.0",
"sass-loader": "~7.1.0",
"theo": "~8.1.1",
"vue-click-outside": "~1.0.7",
"vue-docgen-api": "~2.6.12",
"vue-router": "~3.0.2",
"vue-svg-loader": "~0.12.0",
"vue-template-compiler": "~2.6.6",
"vuep": "git+https://github.com/visualjerk/vuep.git#fix-iframe-firefox",
"webpack-bundle-analyzer": "~3.0.4",
"webpack-merge-and-include-globally": "~2.1.14"
},
"author": "visualjerk",
"main": "./dist/system.umd.min.js",
"files": [
"dist/*",
"src/*",
"public/*",
"*.json",
"*.js"
],
"license": "MIT"
}

BIN
preview/color_tokens.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
preview/customize.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

38
public/404.html Normal file
View 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
public/CNAME Normal file
View File

@ -0,0 +1 @@
styleguide.cion.visualjerk.de

File diff suppressed because one or more lines are too long

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

18
public/index.html Normal file
View 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
src/library.js Normal file
View 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

View File

@ -0,0 +1,9 @@
module.exports = function(source, map) {
this.callback(
null,
`export default function (Component) {
Component.options.__docs = ${JSON.stringify(source)}
}`,
map
)
}

View File

@ -0,0 +1,7 @@
module.exports = function(source, map) {
this.callback(
null,
`export default function () {}`,
map
)
}

View 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
src/main.js Normal file
View File

@ -0,0 +1 @@
require('./styleguide')

32
src/styleguide/App.vue Normal file
View 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>

View 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>

View 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>&nbsp;
</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>

View 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>

View File

@ -0,0 +1,215 @@
<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>

View 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>

View 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>

View 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>

View 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`)
this.description = mdFile
} catch (err) {
this.description = null
}
}
}
</script>

View 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>

View 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>

View 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>

View 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>

View 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
src/styleguide/config.js Normal file
View 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'
}
]
}

View File

@ -0,0 +1 @@
Data Display components are used to present data in an approachable way.

View 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 />

View 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).
### 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/#/).

View File

@ -0,0 +1 @@
Layout components are used to group related content together. The also provide consistent spacing for blocks of content.

View File

@ -0,0 +1 @@
Navigation components allow the user to navigate through the application.

View 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
src/styleguide/index.js Normal file
View 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')

View 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

Some files were not shown because too many files have changed in this diff Show More