added styleguide as plane files

This commit is contained in:
Grzegorz Leoniec 2018-10-09 21:58:46 +02:00
parent 78256f6b43
commit 29460fa027
1040 changed files with 23903 additions and 0 deletions

View File

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

14
styleguide/.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
styleguide/.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
styleguide/.postcssrc.js Normal file
View File

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

4
styleguide/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

57
styleguide/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
```

View File

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

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

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

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
styleguide/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.

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>

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
styleguide/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/'
}

72
styleguide/package.json Normal file
View File

@ -0,0 +1,72 @@
{
"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",
"dev": "npm-run-all --parallel theo:onchange theo servedev",
"servedev": "vue-cli-service serve --open",
"build:lib": "yarn theo && 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": {
"vue": "^2.5.17"
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/standalone": "^7.0.0-beta.56",
"@vue/cli-plugin-babel": "^3.0.0-rc.12",
"@vue/cli-plugin-eslint": "^3.0.0-rc.12",
"@vue/cli-plugin-unit-jest": "^3.0.1",
"@vue/cli-service": "^3.0.0-rc.12",
"@vue/eslint-config-prettier": "^3.0.0-rc.12",
"@vue/test-utils": "^1.0.0-beta.20",
"async-validator": "^1.8.5",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^23.0.1",
"cheerio": "^1.0.0-rc.2",
"clone-deep": "^4.0.0",
"codemirror": "^5.39.2",
"dot-prop": "^4.2.0",
"lodash": "^4.17.10",
"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.9.3",
"npm-run-all": "^4.1.3",
"onchange": "^4.1.0",
"raw-loader": "^0.5.1",
"sass-loader": "^7.1.0",
"theo": "^8.0.0-beta.2",
"vue-click-outside": "^1.0.7",
"vue-docgen-api": "^2.3.13",
"vue-router": "^3.0.1",
"vue-svg-loader": "^0.8.0",
"vue-template-compiler": "^2.5.17",
"vuep": "git://github.com/visualjerk/vuep.git#iframe-preview",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-merge-and-include-globally": "^2.0.11"
},
"author": "visualjerk",
"main": "./dist/system.umd.min.js",
"files": [
"dist/*",
"src/*",
"public/*",
"*.json",
"*.js"
],
"license": "MIT"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

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
styleguide/public/CNAME Normal file
View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

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>

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

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

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,166 @@
<template>
<div :class="`${iframe ? 'vuep-iframe' : ''}`">
<vuep
:template="template"
: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;
.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,141 @@
<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() {
if (!this.component.docs) {
return []
}
const parts = this.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,128 @@
<template>
<div>
<ds-space v-if="componentProps">
<ds-heading tag="h2">{{ component.name | componentName }} Props</ds-heading>
<ds-table
:data="componentProps"
:fields="propFields">
<template
slot="name"
slot-scope="scope">
{{ scope.row.name }} <span v-if="scope.row.required">*</span>
</template>
<template
slot="type"
slot-scope="scope">
{{ scope.row.type.name }}
</template>
<template
slot="default"
slot-scope="scope">
<template v-if="scope.row.defaultValue">
<span v-if="scope.row.defaultValue.func">
Function()
</span>
<span v-else>
{{ scope.row.defaultValue.value }}
</span>
</template>
</template>
</ds-table>
</ds-space>
<ds-space v-if="componentSlots && componentSlots.length">
<ds-heading tag="h2">{{ component.name | componentName }} Slots</ds-heading>
<ds-table
:data="componentSlots"
:fields="slotFields"/>
</ds-space>
<ds-space v-if="componentEvents && componentEvents.length">
<ds-heading tag="h2">{{ component.name | componentName }} Events</ds-heading>
<ds-table
:data="componentEvents"
:fields="eventFields"/>
</ds-space>
</div>
</template>
<script>
export default {
name: 'ComponentOptionsDoc',
props: {
component: {
type: Object,
required: true
}
},
data() {
return {
propFields: {
name: {
label: 'Name',
width: '20%'
},
type: {
label: 'Type',
width: '20%'
},
default: {
label: 'Default',
width: '20%'
},
description: 'Description'
},
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 {
name,
...this.component.props[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]
}
})
},
componentEvents() {
if (!this.component.events) {
return null
}
return Object.keys(this.component.events).map(name => {
return {
name,
...this.component.events[name]
}
})
}
}
}
</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,47 @@
<template>
<div class="navigation">
<ds-menu
@navigate="$emit('navigate')"
:routes="routes"
:url-parser="urlParser"
:name-parser="nameParser"
:is-exact="isExact"/>
</div>
</template>
<script>
export default {
name: 'Navigation',
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
return parent
})
}
},
methods: {
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;
}
</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?modules!../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))
}
}

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
### Recources
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.

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,119 @@
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 {
render: h =>
h(ComponentPage, {
props: {
component: 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

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

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,35 @@
<template>
<div
class="ds-avatar"
:style="styles">
<img :src="image">
</div>
</template>
<script>
export default {
name: 'DsAvatar',
props: {
size: { type: [Number, String], default: '32px' },
image: { type: String, required: true }
},
computed: {
styles() {
let size = this.size
if (Number.isInteger(Number(size))) {
size = `${size}px`
}
return {
width: size,
height: size
}
}
}
}
</script>
<style lang="scss" src="./style.scss">
</style>
<docs src="./demo.md"></docs>

View File

@ -0,0 +1,13 @@
## Basic usage
```html
<ds-avatar image="https://s3.amazonaws.com/uifaces/faces/twitter/lisovsky/128.jpg" />
```
## Size
```html
<ds-avatar
image="https://s3.amazonaws.com/uifaces/faces/twitter/lisovsky/128.jpg"
size="60px" />
```

View File

@ -0,0 +1,29 @@
.ds-avatar {
@include reset;
border-radius: 50%;
display: inline-block;
position: relative;
margin-right: $space-xx-small;
min-height: 22px;
min-width: 22px;
&::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
border-radius: 50%;
}
img {
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
object-position: center;
}
}

View File

@ -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.
* `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>

View File

@ -0,0 +1,31 @@
// 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"
>
<ds-button
color="soft"
ghost=""
icon="copy"
/>
</div>
<div
class="ds-copy-field-message"
name="ds-copy-field-message"
style="display: none;"
>
<div
class="ds-copy-field-message-text"
/>
</div>
</div>
`;

View File

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

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