├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── index.html ├── package.json ├── src ├── App.vue ├── api │ ├── colours.json │ ├── generate-data │ │ ├── colours.js │ │ ├── css-obj.js │ │ ├── index.js │ │ └── scales.js │ ├── index.js │ ├── scales.json │ ├── styles.js │ └── styles.json ├── class-names │ ├── class-names-item.component.vue │ ├── class-names-list.component.vue │ ├── class-names.module.js │ ├── class-names.view.vue │ ├── index.js │ └── terminal.component.vue ├── components │ ├── animated-line.component.vue │ ├── app-header.component.vue │ ├── clipboard-button.component.vue │ ├── field-label.component.vue │ ├── index.js │ ├── nav-link.component.vue │ ├── navigation.component.vue │ ├── section-menu-item.component.vue │ ├── section-menu.component.vue │ ├── src-link.component.vue │ └── text-field.component.vue ├── index.css ├── main.js ├── palette │ ├── colour-block.component.vue │ ├── colour-chip.component.vue │ ├── index.js │ └── palette.view.vue ├── router │ ├── foo.component.vue │ └── index.js ├── scales │ ├── border-section.component.vue │ ├── box-scale-grid.component.vue │ ├── heights-section.component.vue │ ├── index.js │ ├── opacity-section │ │ ├── index.js │ │ ├── opacity-scale.component.vue │ │ └── opacity-section.component.vue │ ├── scales.module.js │ ├── scales.view.vue │ ├── shadow-section.component.vue │ ├── spacing-section │ │ ├── index.js │ │ ├── spacing-scale.component.vue │ │ └── spacing-section.component.vue │ ├── typography-section │ │ ├── index.js │ │ ├── type-scale-grid.component.vue │ │ └── typography-section.component.vue │ └── widths-section │ │ ├── index.js │ │ ├── width-scale-grid.component.vue │ │ └── widths-section.component.vue ├── store │ ├── index.js │ └── modules │ │ └── tachyons.js ├── tools │ ├── css-src.component.vue │ ├── index.js │ ├── media-queryfy │ │ ├── index.js │ │ ├── media-queryfy.component.vue │ │ └── skin-css.component.vue │ ├── skin-generator │ │ ├── index.js │ │ └── skin-generator.component.vue │ ├── tools.module.js │ ├── tools.view.vue │ └── utils.js └── utils │ └── index.js ├── static ├── .gitkeep ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── manifest.json ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png └── safari-pinned-tab.svg └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime", "ramda"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | extends: 'airbnb-base', 13 | // required to lint *.vue files 14 | plugins: [ 15 | 'html' 16 | ], 17 | // check if imports actually resolve 18 | 'settings': { 19 | 'import/resolver': { 20 | 'webpack': { 21 | 'config': 'build/webpack.base.conf.js' 22 | } 23 | } 24 | }, 25 | // add your custom rules here 26 | 'rules': { 27 | // don't require .vue extension when importing 28 | 'import/extensions': ['error', 'always', { 29 | 'js': 'never', 30 | 'vue': 'never' 31 | }], 32 | // allow optionalDependencies 33 | 'import/no-extraneous-dependencies': ['error', { 34 | 'optionalDependencies': ['test/unit/index.js'] 35 | }], 36 | // allow debugger during development 37 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tachyons-tldr 2 | 3 | > quick lookup for tachyon classes, scales and colour palette. 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, function (err, stats) { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | function exec (cmd) { 7 | return require('child_process').execSync(cmd).toString().trim() 8 | } 9 | 10 | const versionRequirements = [ 11 | { 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | } 16 | ] 17 | 18 | if (shell.which('npm')) { 19 | versionRequirements.push({ 20 | name: 'npm', 21 | currentVersion: exec('npm --version'), 22 | versionRequirement: packageConfig.engines.npm 23 | }) 24 | } 25 | 26 | module.exports = function () { 27 | const warnings = [] 28 | for (let i = 0; i < versionRequirements.length; i++) { 29 | const mod = versionRequirements[i] 30 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 31 | warnings.push(mod.name + ': ' + 32 | chalk.red(mod.currentVersion) + ' should be ' + 33 | chalk.green(mod.versionRequirement) 34 | ) 35 | } 36 | } 37 | 38 | if (warnings.length) { 39 | console.log('') 40 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 41 | console.log() 42 | for (let i = 0; i < warnings.length; i++) { 43 | const warning = warnings[i] 44 | console.log(' ' + warning) 45 | } 46 | console.log() 47 | process.exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict' 3 | require('eventsource-polyfill') 4 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 5 | 6 | hotClient.subscribe(function (event) { 7 | if (event.action === 'reload') { 8 | window.location.reload() 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | const config = require('../config') 5 | if (!process.env.NODE_ENV) { 6 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 7 | } 8 | 9 | const opn = require('opn') 10 | const path = require('path') 11 | const express = require('express') 12 | const webpack = require('webpack') 13 | const proxyMiddleware = require('http-proxy-middleware') 14 | const webpackConfig = (process.env.NODE_ENV === 'testing' || process.env.NODE_ENV === 'production') 15 | ? require('./webpack.prod.conf') 16 | : require('./webpack.dev.conf') 17 | 18 | // default port where dev server listens for incoming traffic 19 | const port = process.env.PORT || config.dev.port 20 | // automatically open browser, if not set will be false 21 | const autoOpenBrowser = !!config.dev.autoOpenBrowser 22 | // Define HTTP proxies to your custom API backend 23 | // https://github.com/chimurai/http-proxy-middleware 24 | const proxyTable = config.dev.proxyTable 25 | 26 | const app = express() 27 | const compiler = webpack(webpackConfig) 28 | 29 | const devMiddleware = require('webpack-dev-middleware')(compiler, { 30 | publicPath: webpackConfig.output.publicPath, 31 | quiet: true 32 | }) 33 | 34 | const hotMiddleware = require('webpack-hot-middleware')(compiler, { 35 | log: false, 36 | heartbeat: 2000 37 | }) 38 | // force page reload when html-webpack-plugin template changes 39 | // currently disabled until this is resolved: 40 | // https://github.com/jantimon/html-webpack-plugin/issues/680 41 | // compiler.plugin('compilation', function (compilation) { 42 | // compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 43 | // hotMiddleware.publish({ action: 'reload' }) 44 | // cb() 45 | // }) 46 | // }) 47 | 48 | // enable hot-reload and state-preserving 49 | // compilation error display 50 | app.use(hotMiddleware) 51 | 52 | // proxy api requests 53 | Object.keys(proxyTable).forEach(function (context) { 54 | let options = proxyTable[context] 55 | if (typeof options === 'string') { 56 | options = { target: options } 57 | } 58 | app.use(proxyMiddleware(options.filter || context, options)) 59 | }) 60 | 61 | // handle fallback for HTML5 history API 62 | app.use(require('connect-history-api-fallback')()) 63 | 64 | // serve webpack bundle output 65 | app.use(devMiddleware) 66 | 67 | // serve pure static assets 68 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 69 | app.use(staticPath, express.static('./static')) 70 | 71 | const uri = 'http://localhost:' + port 72 | 73 | var _resolve 74 | var _reject 75 | var readyPromise = new Promise((resolve, reject) => { 76 | _resolve = resolve 77 | _reject = reject 78 | }) 79 | 80 | var server 81 | var portfinder = require('portfinder') 82 | portfinder.basePort = port 83 | 84 | console.log('> Starting dev server...') 85 | devMiddleware.waitUntilValid(() => { 86 | portfinder.getPort((err, port) => { 87 | if (err) { 88 | _reject(err) 89 | } 90 | process.env.PORT = port 91 | var uri = 'http://localhost:' + port 92 | console.log('> Listening at ' + uri + '\n') 93 | // when env is testing, don't need open it 94 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 95 | opn(uri) 96 | } 97 | server = app.listen(port) 98 | _resolve() 99 | }) 100 | }) 101 | 102 | module.exports = { 103 | ready: readyPromise, 104 | close: () => { 105 | server.close() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | 6 | exports.assetsPath = function (_path) { 7 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 8 | ? config.build.assetsSubDirectory 9 | : config.dev.assetsSubDirectory 10 | return path.posix.join(assetsSubDirectory, _path) 11 | } 12 | 13 | exports.cssLoaders = function (options) { 14 | options = options || {} 15 | 16 | const cssLoader = { 17 | loader: 'css-loader', 18 | options: { 19 | minimize: process.env.NODE_ENV === 'production', 20 | sourceMap: options.sourceMap 21 | } 22 | } 23 | 24 | // generate loader string to be used with extract text plugin 25 | function generateLoaders (loader, loaderOptions) { 26 | const loaders = [cssLoader] 27 | if (loader) { 28 | loaders.push({ 29 | loader: loader + '-loader', 30 | options: Object.assign({}, loaderOptions, { 31 | sourceMap: options.sourceMap 32 | }) 33 | }) 34 | } 35 | 36 | // Extract CSS when that option is specified 37 | // (which is the case during production build) 38 | if (options.extract) { 39 | return ExtractTextPlugin.extract({ 40 | use: loaders, 41 | fallback: 'vue-style-loader' 42 | }) 43 | } else { 44 | return ['vue-style-loader'].concat(loaders) 45 | } 46 | } 47 | 48 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 49 | return { 50 | css: generateLoaders(), 51 | postcss: generateLoaders(), 52 | less: generateLoaders('less'), 53 | sass: generateLoaders('sass', { indentedSyntax: true }), 54 | scss: generateLoaders('sass'), 55 | stylus: generateLoaders('stylus'), 56 | styl: generateLoaders('stylus') 57 | } 58 | } 59 | 60 | // Generate loaders for standalone style files (outside of .vue) 61 | exports.styleLoaders = function (options) { 62 | const output = [] 63 | const loaders = exports.cssLoaders(options) 64 | for (const extension in loaders) { 65 | const loader = loaders[extension] 66 | output.push({ 67 | test: new RegExp('\\.' + extension + '$'), 68 | use: loader 69 | }) 70 | } 71 | return output 72 | } 73 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | 6 | module.exports = { 7 | loaders: utils.cssLoaders({ 8 | sourceMap: isProduction 9 | ? config.build.productionSourceMap 10 | : config.dev.cssSourceMap, 11 | extract: isProduction 12 | }), 13 | transformToRequire: { 14 | video: 'src', 15 | source: 'src', 16 | img: 'src', 17 | image: 'xlink:href' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | entry: { 13 | app: './src/main.js' 14 | }, 15 | node: { fs: 'empty' }, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: '[name].js', 19 | chunkFilename: '[name].js', 20 | publicPath: process.env.NODE_ENV === 'production' 21 | ? config.build.assetsPublicPath 22 | : config.dev.assetsPublicPath 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.vue', '.json'], 26 | alias: { 27 | 'vue$': 'vue/dist/vue.esm.js', 28 | '@': resolve('src'), 29 | } 30 | }, 31 | module: { 32 | rules: [ 33 | // { 34 | // test: /\.(js|vue)$/, 35 | // loader: 'eslint-loader', 36 | // enforce: 'pre', 37 | // include: [resolve('src'), resolve('test')], 38 | // options: { 39 | // formatter: require('eslint-friendly-formatter') 40 | // } 41 | // }, 42 | { 43 | test: /\.vue$/, 44 | loader: 'vue-loader', 45 | options: vueLoaderConfig 46 | }, 47 | { 48 | test: /\.js$/, 49 | loader: 'babel-loader', 50 | include: [resolve('src'), resolve('test')] 51 | }, 52 | { 53 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 58 | } 59 | }, 60 | { 61 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 66 | } 67 | }, 68 | { 69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 70 | loader: 'url-loader', 71 | options: { 72 | limit: 10000, 73 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 74 | } 75 | } 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 18 | }, 19 | // cheap-module-eval-source-map is faster for development 20 | devtool: '#cheap-module-eval-source-map', 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': config.dev.env 24 | }), 25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 26 | new webpack.HotModuleReplacementPlugin(), 27 | new webpack.NoEmitOnErrorsPlugin(), 28 | // https://github.com/ampedandwired/html-webpack-plugin 29 | new HtmlWebpackPlugin({ 30 | filename: 'index.html', 31 | template: 'index.html', 32 | inject: true 33 | }), 34 | new FriendlyErrorsPlugin() 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | 13 | const env = process.env.NODE_ENV === 'testing' 14 | ? require('../config/test.env') 15 | : config.build.env 16 | 17 | const webpackConfig = merge(baseWebpackConfig, { 18 | module: { 19 | rules: utils.styleLoaders({ 20 | sourceMap: config.build.productionSourceMap, 21 | extract: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? '#source-map' : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[name].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify 36 | new webpack.optimize.UglifyJsPlugin({ 37 | compress: { 38 | warnings: false 39 | }, 40 | sourceMap: true 41 | }), 42 | // extract css into its own file 43 | new ExtractTextPlugin({ 44 | filename: utils.assetsPath('css/[name].[contenthash].css') 45 | }), 46 | // Compress extracted CSS. We are using this plugin so that possible 47 | // duplicated CSS from different components can be deduped. 48 | new OptimizeCSSPlugin({ 49 | cssProcessorOptions: { 50 | safe: true 51 | } 52 | }), 53 | // generate dist index.html with correct asset hash for caching. 54 | // you can customize output by editing /index.html 55 | // see https://github.com/ampedandwired/html-webpack-plugin 56 | new HtmlWebpackPlugin({ 57 | filename: process.env.NODE_ENV === 'testing' 58 | ? 'index.html' 59 | : config.build.index, 60 | template: 'index.html', 61 | inject: true, 62 | minify: { 63 | removeComments: true, 64 | collapseWhitespace: true, 65 | removeAttributeQuotes: true 66 | // more options: 67 | // https://github.com/kangax/html-minifier#options-quick-reference 68 | }, 69 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 70 | chunksSortMode: 'dependency' 71 | }), 72 | // keep module.id stable when vender modules does not change 73 | new webpack.HashedModuleIdsPlugin(), 74 | new webpack.optimize.CommonsChunkPlugin({ 75 | children: true, 76 | minChunks: 2, 77 | async: true, 78 | }), 79 | // split vendor js into its own file 80 | new webpack.optimize.CommonsChunkPlugin({ 81 | name: 'vendor', 82 | minChunks: function (module) { 83 | // any required modules inside node_modules are extracted to vendor 84 | return ( 85 | module.resource && 86 | /\.js$/.test(module.resource) && 87 | module.resource.indexOf( 88 | path.join(__dirname, '../node_modules') 89 | ) === 0 90 | ) 91 | } 92 | }), 93 | // extract webpack runtime and module manifest to its own file in order to 94 | // prevent vendor hash from being updated whenever app bundle is updated 95 | new webpack.optimize.CommonsChunkPlugin({ 96 | name: 'manifest', 97 | chunks: ['vendor'] 98 | }), 99 | // copy custom static assets 100 | new CopyWebpackPlugin([ 101 | { 102 | from: path.resolve(__dirname, '../static'), 103 | to: config.build.assetsSubDirectory, 104 | ignore: ['.*'] 105 | } 106 | ]) 107 | ] 108 | }) 109 | 110 | if (config.build.productionGzip) { 111 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 112 | 113 | webpackConfig.plugins.push( 114 | new CompressionWebpackPlugin({ 115 | asset: '[path].gz[query]', 116 | algorithm: 'gzip', 117 | test: new RegExp( 118 | '\\.(' + 119 | config.build.productionGzipExtensions.join('|') + 120 | ')$' 121 | ), 122 | threshold: 10240, 123 | minRatio: 0.8 124 | }) 125 | ) 126 | } 127 | 128 | if (config.build.bundleAnalyzerReport) { 129 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 130 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 131 | } 132 | 133 | module.exports = webpackConfig 134 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is the webpack config used for unit tests. 3 | 4 | const utils = require('./utils') 5 | const webpack = require('webpack') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | 9 | const webpackConfig = merge(baseWebpackConfig, { 10 | // use inline sourcemap for karma-sourcemap-loader 11 | module: { 12 | rules: utils.styleLoaders() 13 | }, 14 | devtool: '#inline-source-map', 15 | resolveLoader: { 16 | alias: { 17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 19 | 'scss-loader': 'sass-loader' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': require('../config/test.env') 25 | }) 26 | ] 27 | }) 28 | 29 | // no need for app entry during tests 30 | delete webpackConfig.entry 31 | 32 | module.exports = webpackConfig 33 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | // Template version: 1.1.3 4 | // see http://vuejs-templates.github.io/webpack for documentation. 5 | 6 | const path = require('path') 7 | 8 | module.exports = { 9 | build: { 10 | env: require('./prod.env'), 11 | index: path.resolve(__dirname, '../dist/index.html'), 12 | assetsRoot: path.resolve(__dirname, '../dist'), 13 | assetsSubDirectory: '', 14 | assetsPublicPath: '/', 15 | productionSourceMap: false, 16 | // Gzip off by default as many popular static hosts such as 17 | // Surge or Netlify already gzip all static assets for you. 18 | // Before setting to `true`, make sure to: 19 | // npm install --save-dev compression-webpack-plugin 20 | productionGzip: false, 21 | productionGzipExtensions: ['js', 'css'], 22 | // Run the build command with an extra argument to 23 | // View the bundle analyzer report after build finishes: 24 | // `npm run build --report` 25 | // Set to `true` or `false` to always turn it on or off 26 | bundleAnalyzerReport: process.env.npm_config_report 27 | }, 28 | dev: { 29 | env: require('./dev.env'), 30 | port: process.env.PORT || 8080, 31 | autoOpenBrowser: true, 32 | assetsSubDirectory: '', 33 | assetsPublicPath: '/', 34 | proxyTable: {}, 35 | // CSS Sourcemaps off by default because relative paths are "buggy" 36 | // with this option, according to the CSS-Loader README 37 | // (https://github.com/webpack/css-loader#sourcemaps) 38 | // In our experience, they generally work as expected, 39 | // just be aware of this issue when enabling this option. 40 | cssSourceMap: false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tachyons tldr 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tachyons-decrypt", 3 | "version": "1.1.0", 4 | "description": "quick lookup for tachyon classes, scales and colour palette", 5 | "author": "Varun Vachhar ", 6 | "private": true, 7 | "scripts": { 8 | "predev": "npm run api", 9 | "dev": "node build/dev-server.js", 10 | "start": "npm run dev", 11 | "prebuild": "npm run api && rm -rf dist", 12 | "build": "node build/build.js", 13 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 14 | "test": "npm run unit", 15 | "lint": "eslint --ext .js,.vue src test/unit/specs", 16 | "buddy": "bundle-buddy dist/js/*.map", 17 | "api": "babel-node --presets=env,stage-2 src/api/generate-data/index.js" 18 | }, 19 | "dependencies": { 20 | "animejs": "^2.0.1", 21 | "clipboard": "^1.6.1", 22 | "hello-color": "^1.0.1", 23 | "lodash.debounce": "^4.0.8", 24 | "postcss": "^5.2.11", 25 | "postcss-js": "^0.2.0", 26 | "ramda": "^0.23.0", 27 | "scroll-behaviour": "^0.3.3", 28 | "tachyons": "4.9.1", 29 | "vue": "^2.5.2", 30 | "vue-router": "^3.0.1", 31 | "vuex": "^3.0.1", 32 | "vuex-router-sync": "^5.0.0" 33 | }, 34 | "devDependencies": { 35 | "autoprefixer": "^7.1.2", 36 | "babel-cli": "^6.26.0", 37 | "babel-core": "^6.22.1", 38 | "babel-eslint": "^7.1.1", 39 | "babel-loader": "^7.1.1", 40 | "babel-plugin-istanbul": "^4.1.1", 41 | "babel-plugin-ramda": "^1.4.3", 42 | "babel-plugin-transform-runtime": "^6.22.0", 43 | "babel-preset-env": "^1.3.2", 44 | "babel-preset-stage-2": "^6.22.0", 45 | "bundle-buddy": "^0.2.1", 46 | "chai": "^4.1.2", 47 | "chalk": "^2.0.1", 48 | "connect-history-api-fallback": "^1.3.0", 49 | "copy-webpack-plugin": "^4.0.1", 50 | "cross-env": "^5.0.1", 51 | "css-loader": "^0.28.0", 52 | "eslint": "^3.19.0", 53 | "eslint-config-airbnb-base": "^11.3.0", 54 | "eslint-friendly-formatter": "^3.0.0", 55 | "eslint-import-resolver-webpack": "^0.8.3", 56 | "eslint-loader": "^1.7.1", 57 | "eslint-plugin-html": "^3.0.0", 58 | "eslint-plugin-import": "^2.7.0", 59 | "eventsource-polyfill": "^0.9.6", 60 | "express": "^4.14.1", 61 | "extract-text-webpack-plugin": "^3.0.0", 62 | "file-loader": "^1.1.4", 63 | "friendly-errors-webpack-plugin": "^1.6.1", 64 | "html-webpack-plugin": "^2.30.1", 65 | "http-proxy-middleware": "^0.17.3", 66 | "inject-loader": "^3.0.0", 67 | "karma": "^1.4.1", 68 | "karma-coverage": "^1.1.1", 69 | "karma-mocha": "^1.3.0", 70 | "karma-phantomjs-launcher": "^1.0.2", 71 | "karma-phantomjs-shim": "^1.4.0", 72 | "karma-sinon-chai": "^1.3.1", 73 | "karma-sourcemap-loader": "^0.3.7", 74 | "karma-spec-reporter": "0.0.31", 75 | "karma-webpack": "^2.0.2", 76 | "mocha": "^3.2.0", 77 | "opn": "^5.1.0", 78 | "optimize-css-assets-webpack-plugin": "^3.2.0", 79 | "ora": "^1.2.0", 80 | "phantomjs-prebuilt": "^2.1.14", 81 | "portfinder": "^1.0.13", 82 | "raw-loader": "^0.5.1", 83 | "rimraf": "^2.6.0", 84 | "semver": "^5.3.0", 85 | "shelljs": "^0.7.6", 86 | "sinon": "^4.0.0", 87 | "sinon-chai": "^2.8.0", 88 | "url-loader": "^0.5.8", 89 | "vue-loader": "^13.3.0", 90 | "vue-style-loader": "^3.0.1", 91 | "vue-template-compiler": "^2.5.2", 92 | "webpack": "^3.6.0", 93 | "webpack-bundle-analyzer": "^2.9.0", 94 | "webpack-dev-middleware": "^1.12.0", 95 | "webpack-hot-middleware": "^2.18.2", 96 | "webpack-merge": "^4.1.0" 97 | }, 98 | "engines": { 99 | "node": ">= 4.0.0", 100 | "npm": ">= 3.0.0" 101 | }, 102 | "browserslist": [ 103 | "> 1%", 104 | "last 2 versions", 105 | "not ie <= 8" 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 47 | -------------------------------------------------------------------------------- /src/api/colours.json: -------------------------------------------------------------------------------- 1 | { 2 | "solid": { 3 | "Black": { 4 | "value": "#000", 5 | "negative": "#f8f8f8" 6 | }, 7 | "Near Black": { 8 | "value": "#111", 9 | "negative": "#e7e7e7" 10 | }, 11 | "Dark Gray": { 12 | "value": "#333", 13 | "negative": "#c5c5c5" 14 | }, 15 | "Mid Gray": { 16 | "value": "#555", 17 | "negative": "#c9c9c9" 18 | }, 19 | "Gray": { 20 | "value": "#777", 21 | "negative": "#060606" 22 | }, 23 | "Silver": { 24 | "value": "#999", 25 | "negative": "#323232" 26 | }, 27 | "Light Silver": { 28 | "value": "#aaa", 29 | "negative": "#3f3f3f" 30 | }, 31 | "Moon Gray": { 32 | "value": "#ccc", 33 | "negative": "#383838" 34 | }, 35 | "Light Gray": { 36 | "value": "#eee", 37 | "negative": "#161616" 38 | }, 39 | "Near White": { 40 | "value": "#f4f4f4", 41 | "negative": "#121212" 42 | }, 43 | "White": { 44 | "value": "#fff", 45 | "negative": "#090909" 46 | }, 47 | "Dark Red": { 48 | "value": "#e7040f", 49 | "negative": "#e0ffff" 50 | }, 51 | "Red": { 52 | "value": "#ff4136", 53 | "negative": "#002732" 54 | }, 55 | "Light Red": { 56 | "value": "#ff725c", 57 | "negative": "#003a4e" 58 | }, 59 | "Orange": { 60 | "value": "#ff6300", 61 | "negative": "#002875" 62 | }, 63 | "Gold": { 64 | "value": "#ffb700", 65 | "negative": "#0035e7" 66 | }, 67 | "Yellow": { 68 | "value": "#ffd700", 69 | "negative": "#222eff" 70 | }, 71 | "Light Yellow": { 72 | "value": "#fbf1a9", 73 | "negative": "#0f135c" 74 | }, 75 | "Purple": { 76 | "value": "#5e2ca5", 77 | "negative": "#9acc54" 78 | }, 79 | "Light Purple": { 80 | "value": "#a463f2", 81 | "negative": "#002400" 82 | }, 83 | "Dark Pink": { 84 | "value": "#d5008f", 85 | "negative": "#cbffe9" 86 | }, 87 | "Hot Pink": { 88 | "value": "#ff41b4", 89 | "negative": "#003300" 90 | }, 91 | "Pink": { 92 | "value": "#ff80cc", 93 | "negative": "#004c02" 94 | }, 95 | "Light Pink": { 96 | "value": "#ffa3d7", 97 | "negative": "#005622" 98 | }, 99 | "Dark Green": { 100 | "value": "#137752", 101 | "negative": "#ffdeff" 102 | }, 103 | "Green": { 104 | "value": "#19a974", 105 | "negative": "#620023" 106 | }, 107 | "Light Green": { 108 | "value": "#9eebcf", 109 | "negative": "#671b35" 110 | }, 111 | "Navy": { 112 | "value": "#001b44", 113 | "negative": "#f8ddb4" 114 | }, 115 | "Dark Blue": { 116 | "value": "#00449e", 117 | "negative": "#f8b45b" 118 | }, 119 | "Blue": { 120 | "value": "#357edd", 121 | "negative": "#340000" 122 | }, 123 | "Light Blue": { 124 | "value": "#96ccff", 125 | "negative": "#703806" 126 | }, 127 | "Lightest Blue": { 128 | "value": "#cdecff", 129 | "negative": "#371808" 130 | }, 131 | "Washed Blue": { 132 | "value": "#f6fffe", 133 | "negative": "#10090a" 134 | }, 135 | "Washed Green": { 136 | "value": "#e8fdf5", 137 | "negative": "#1b0b11" 138 | }, 139 | "Washed Yellow": { 140 | "value": "#fffceb", 141 | "negative": "#090c19" 142 | }, 143 | "Washed Red": { 144 | "value": "#ffdfdf", 145 | "negative": "#042525" 146 | } 147 | }, 148 | "alpha": { 149 | "Transparent": "transparent", 150 | "Black 90": "rgba(0,0,0,.9)", 151 | "Black 80": "rgba(0,0,0,.8)", 152 | "Black 70": "rgba(0,0,0,.7)", 153 | "Black 60": "rgba(0,0,0,.6)", 154 | "Black 50": "rgba(0,0,0,.5)", 155 | "Black 40": "rgba(0,0,0,.4)", 156 | "Black 30": "rgba(0,0,0,.3)", 157 | "Black 20": "rgba(0,0,0,.2)", 158 | "Black 10": "rgba(0,0,0,.1)", 159 | "Black 05": "rgba(0,0,0,.05)", 160 | "Black 025": "rgba(0,0,0,.025)", 161 | "Black 0125": "rgba(0,0,0,.0125)", 162 | "White 90": "rgba(255,255,255,.9)", 163 | "White 80": "rgba(255,255,255,.8)", 164 | "White 70": "rgba(255,255,255,.7)", 165 | "White 60": "rgba(255,255,255,.6)", 166 | "White 50": "rgba(255,255,255,.5)", 167 | "White 40": "rgba(255,255,255,.4)", 168 | "White 30": "rgba(255,255,255,.3)", 169 | "White 20": "rgba(255,255,255,.2)", 170 | "White 10": "rgba(255,255,255,.1)", 171 | "White 05": "rgba(255,255,255,.05)", 172 | "White 025": "rgba(255,255,255,.025)", 173 | "White 0125": "rgba(255,255,255,.0125)" 174 | } 175 | } -------------------------------------------------------------------------------- /src/api/generate-data/colours.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import fs from 'fs'; 3 | import postcssJs from 'postcss-js'; 4 | import postcss from 'postcss'; 5 | import hello from 'hello-color'; 6 | import R from 'ramda'; 7 | import { root, renameKeys, renameKeysBy, toTitleCase } from '../../utils'; 8 | 9 | /** 10 | * Colours 11 | */ 12 | const coloursModule = fs.readFileSync('node_modules/tachyons/src/_colors.css'); 13 | 14 | const coloursRoot = postcss.parse(coloursModule); 15 | 16 | const groupByAlpha = R.groupBy( 17 | R.compose(R.test(/(rgba.+|transparent)/), R.last), 18 | ); 19 | 20 | const addNegatives = R.map(colour => ({ 21 | value: colour, 22 | negative: hello(colour, { contrast: 4.5, lightness: 1 / 8 }).color, 23 | })); 24 | 25 | console.info(' 🎨 Parsing Tachyons Colours module'); 26 | 27 | export const colours = R.compose( 28 | R.over(R.lensProp('solid'), addNegatives), 29 | R.map(R.fromPairs), 30 | renameKeys({ 31 | false: 'solid', 32 | true: 'alpha', 33 | }), 34 | groupByAlpha, 35 | R.toPairs, 36 | renameKeysBy(toTitleCase), 37 | root, 38 | postcssJs.objectify, 39 | )(coloursRoot); 40 | 41 | fs.writeFile( 42 | './src/api/colours.json', 43 | JSON.stringify(colours, null, 2), 44 | err => { 45 | if (err) { 46 | return console.error(err); 47 | } 48 | 49 | console.info(' 🖨️ Colours file was saved'); 50 | }, 51 | ); 52 | -------------------------------------------------------------------------------- /src/api/generate-data/css-obj.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import fs from 'fs'; 3 | import postcssJs from 'postcss-js'; 4 | import postcss from 'postcss'; 5 | import R from 'ramda'; 6 | 7 | import { selectors, atMediaClasses, renameKeys } from '../../utils'; 8 | 9 | /** 10 | * All Styles 11 | */ 12 | const tachyonsCss = fs.readFileSync('node_modules/tachyons/css/tachyons.css'); 13 | 14 | const cssRoot = postcss.parse(tachyonsCss); 15 | 16 | console.info(' 🤖 Parsing Tachyons'); 17 | 18 | const cssObj = R.compose( 19 | R.map(renameKeys({ cssFloat: 'float' })), 20 | R.converge(R.merge, [selectors, atMediaClasses]), 21 | postcssJs.objectify, 22 | )(cssRoot); 23 | 24 | fs.writeFile('./src/api/styles.json', JSON.stringify(cssObj, null, 2), err => { 25 | if (err) { 26 | return console.error(err); 27 | } 28 | 29 | console.info(' 🖨️ CSS Object file was saved'); 30 | }); 31 | -------------------------------------------------------------------------------- /src/api/generate-data/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Parse tachyons into data that drives the app 4 | * This script is executed at build time 5 | */ 6 | console.log('\n'); 7 | require('./css-obj'); 8 | require('./colours'); 9 | require('./scales'); 10 | -------------------------------------------------------------------------------- /src/api/generate-data/scales.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import fs from 'fs'; 3 | import postcssJs from 'postcss-js'; 4 | import postcss from 'postcss'; 5 | import R from 'ramda'; 6 | import { 7 | allSelectorsToClassNames, 8 | nonMediaValuesBy, 9 | root, 10 | renameKeys, 11 | } from '../../utils'; 12 | 13 | const valuesFromModule = (valueGetter, moduleName) => { 14 | const module = fs.readFileSync( 15 | `node_modules/tachyons/src/_${moduleName}.css`, 16 | ); 17 | 18 | const root = postcss.parse(module); 19 | return R.compose(valueGetter, postcssJs.objectify)(root); 20 | }; 21 | 22 | const scaleParsers = { 23 | spacing: R.compose(R.values, root), 24 | 25 | 'type-scale': nonMediaValuesBy(R.prop('fontSize')), 26 | 27 | 'font-weight': R.compose( 28 | R.omit(['.b', '.normal']), 29 | nonMediaValuesBy(R.prop('fontWeight')), 30 | ), 31 | 32 | 'border-radius': nonMediaValuesBy( 33 | R.ifElse(R.has('borderRadius'), R.prop('borderRadius'), R.identity), 34 | ), 35 | 36 | 'border-widths': nonMediaValuesBy( 37 | R.ifElse(R.has('borderWidth'), R.prop('borderWidth'), R.identity), 38 | ), 39 | 40 | heights: R.compose( 41 | R.omit(['.h-auto', '.h-inherit', '.min-h-100', '.min-vh-100']), 42 | nonMediaValuesBy(R.prop('height')), 43 | ), 44 | 45 | widths: R.compose(R.omit(['.w-auto']), nonMediaValuesBy(R.prop('width'))), 46 | 47 | 'max-widths': R.compose( 48 | R.omit(['.mw-none']), 49 | nonMediaValuesBy(R.prop('maxWidth')), 50 | ), 51 | 52 | opacity: nonMediaValuesBy(R.prop('opacity')), 53 | 54 | 'box-shadow': nonMediaValuesBy(R.prop('boxShadow')), 55 | }; 56 | 57 | console.info(' 📏 Parsing all scale based modules'); 58 | 59 | const scales = R.compose( 60 | R.map(allSelectorsToClassNames), 61 | renameKeys({ 62 | 'type-scale': 'type', 63 | 'font-weight': 'fontWeight', 64 | 'border-radius': 'borderRadius', 65 | 'border-widths': 'borderWidths', 66 | 'max-widths': 'maxWidths', 67 | 'box-shadow': 'shadow', 68 | }), 69 | R.mapObjIndexed, 70 | )(valuesFromModule, scaleParsers); 71 | 72 | fs.writeFile('./src/api/scales.json', JSON.stringify(scales, null, 2), err => { 73 | if (err) { 74 | return console.error(err); 75 | } 76 | 77 | console.info(' 🖨️ Scales file was saved'); 78 | }); 79 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { dependencies } from '../../package.json'; 2 | import { cssObj, groupedClasses, propNamesList, classNames } from './styles'; 3 | import colours from './colours.json'; 4 | import scales from './scales.json'; 5 | 6 | const version = dependencies.tachyons.replace('^', 'v'); 7 | 8 | export { 9 | classNames, 10 | cssObj, 11 | groupedClasses, 12 | propNamesList, 13 | colours, 14 | scales, 15 | version, 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/scales.json: -------------------------------------------------------------------------------- 1 | { 2 | "spacing": { 3 | "0": "0", 4 | "1": ".25rem", 5 | "2": ".5rem", 6 | "3": "1rem", 7 | "4": "2rem", 8 | "5": "4rem", 9 | "6": "8rem", 10 | "7": "16rem" 11 | }, 12 | "type": { 13 | "f-6,f-headline": "6rem", 14 | "f-5,f-subheadline": "5rem", 15 | "f1": "3rem", 16 | "f2": "2.25rem", 17 | "f3": "1.5rem", 18 | "f4": "1.25rem", 19 | "f5": "1rem", 20 | "f6": ".875rem", 21 | "f7": ".75rem" 22 | }, 23 | "fontWeight": { 24 | "fw1": "100", 25 | "fw2": "200", 26 | "fw3": "300", 27 | "fw4": "400", 28 | "fw5": "500", 29 | "fw6": "600", 30 | "fw7": "700", 31 | "fw8": "800", 32 | "fw9": "900" 33 | }, 34 | "borderRadius": { 35 | "br0": "0", 36 | "br1": ".125rem", 37 | "br2": ".25rem", 38 | "br3": ".5rem", 39 | "br4": "1rem", 40 | "br-100": "100%", 41 | "br-pill": "9999px", 42 | "br--bottom": { 43 | "borderTopLeftRadius": "0", 44 | "borderTopRightRadius": "0" 45 | }, 46 | "br--top": { 47 | "borderBottomLeftRadius": "0", 48 | "borderBottomRightRadius": "0" 49 | }, 50 | "br--right": { 51 | "borderTopLeftRadius": "0", 52 | "borderBottomLeftRadius": "0" 53 | }, 54 | "br--left": { 55 | "borderTopRightRadius": "0", 56 | "borderBottomRightRadius": "0" 57 | } 58 | }, 59 | "borderWidths": { 60 | "bw0": "0", 61 | "bw1": ".125rem", 62 | "bw2": ".25rem", 63 | "bw3": ".5rem", 64 | "bw4": "1rem", 65 | "bw5": "2rem", 66 | "bt-0": { 67 | "borderTopWidth": "0" 68 | }, 69 | "br-0": { 70 | "borderRightWidth": "0" 71 | }, 72 | "bb-0": { 73 | "borderBottomWidth": "0" 74 | }, 75 | "bl-0": { 76 | "borderLeftWidth": "0" 77 | } 78 | }, 79 | "heights": { 80 | "h1": "1rem", 81 | "h2": "2rem", 82 | "h3": "4rem", 83 | "h4": "8rem", 84 | "h5": "16rem", 85 | "h-25": "25%", 86 | "h-50": "50%", 87 | "h-75": "75%", 88 | "h-100": "100%", 89 | "vh-25": "25vh", 90 | "vh-50": "50vh", 91 | "vh-75": "75vh", 92 | "vh-100": "100vh" 93 | }, 94 | "widths": { 95 | "w1": "1rem", 96 | "w2": "2rem", 97 | "w3": "4rem", 98 | "w4": "8rem", 99 | "w5": "16rem", 100 | "w-10": "10%", 101 | "w-20": "20%", 102 | "w-25": "25%", 103 | "w-30": "30%", 104 | "w-33": "33%", 105 | "w-34": "34%", 106 | "w-40": "40%", 107 | "w-50": "50%", 108 | "w-60": "60%", 109 | "w-70": "70%", 110 | "w-75": "75%", 111 | "w-80": "80%", 112 | "w-90": "90%", 113 | "w-100": "100%", 114 | "w-third": "calc(100% / 3)", 115 | "w-two-thirds": "calc(100% / 1.5)" 116 | }, 117 | "maxWidths": { 118 | "mw-100": "100%", 119 | "mw1": "1rem", 120 | "mw2": "2rem", 121 | "mw3": "4rem", 122 | "mw4": "8rem", 123 | "mw5": "16rem", 124 | "mw6": "32rem", 125 | "mw7": "48rem", 126 | "mw8": "64rem", 127 | "mw9": "96rem" 128 | }, 129 | "opacity": { 130 | "o-100": "1", 131 | "o-90": ".9", 132 | "o-80": ".8", 133 | "o-70": ".7", 134 | "o-60": ".6", 135 | "o-50": ".5", 136 | "o-40": ".4", 137 | "o-30": ".3", 138 | "o-20": ".2", 139 | "o-10": ".1", 140 | "o-05": ".05", 141 | "o-025": ".025", 142 | "o-0": "0" 143 | }, 144 | "shadow": { 145 | "shadow-1": "0px 0px 4px 2px rgba( 0, 0, 0, 0.2 )", 146 | "shadow-2": "0px 0px 8px 2px rgba( 0, 0, 0, 0.2 )", 147 | "shadow-3": "2px 2px 4px 2px rgba( 0, 0, 0, 0.2 )", 148 | "shadow-4": "2px 2px 8px 0px rgba( 0, 0, 0, 0.2 )", 149 | "shadow-5": "4px 4px 8px 0px rgba( 0, 0, 0, 0.2 )" 150 | } 151 | } -------------------------------------------------------------------------------- /src/api/styles.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | import { 3 | toKebabCase, 4 | } from '../utils'; 5 | import cssObj from './styles.json'; 6 | 7 | 8 | /** 9 | * All Styles 10 | */ 11 | export { cssObj }; 12 | 13 | function splitRules(props, className) { 14 | return R.map(R.pair(className), props); 15 | } 16 | 17 | export const classNames = R.compose( 18 | R.identity, 19 | )(cssObj); 20 | 21 | 22 | export const groupedClasses = R.compose( 23 | R.map(R.map(R.head)), 24 | R.groupBy(R.tail), 25 | R.unnest, 26 | R.values, 27 | R.mapObjIndexed(splitRules), 28 | R.map(R.keys), 29 | )(cssObj); 30 | 31 | 32 | const getNameVariations = R.juxt([R.toLower, toKebabCase]); 33 | 34 | export const propNamesList = R.compose( 35 | R.mergeAll, 36 | R.map( 37 | R.compose( 38 | R.fromPairs, 39 | R.converge(R.xprod, [getNameVariations, R.of]), 40 | ), 41 | ), 42 | R.keys, 43 | )(groupedClasses); 44 | -------------------------------------------------------------------------------- /src/class-names/class-names-item.component.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /src/class-names/class-names-list.component.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 46 | -------------------------------------------------------------------------------- /src/class-names/class-names.module.js: -------------------------------------------------------------------------------- 1 | import R, { __ } from 'ramda'; 2 | import { propNamesList, cssObj } from '../api'; 3 | import * as utils from '../utils'; 4 | 5 | export const findByPartialMatch = query => R.compose( 6 | R.find( 7 | R.pipe(R.toLower, R.invoker(1, 'includes')(query)), 8 | ), 9 | R.values, 10 | )(propNamesList); 11 | 12 | export const findGroup = R.converge(R.or, [ 13 | R.compose( 14 | R.prop(__, propNamesList), 15 | R.toLower, 16 | ), 17 | findByPartialMatch, 18 | ]); 19 | 20 | const parseBaseSelector = R.compose(R.nth(1), R.match(utils.mediaQueryRegex)); 21 | 22 | const groupByClassName = R.groupBy( 23 | R.ifElse(R.test(utils.mediaQueryRegex), 24 | parseBaseSelector, 25 | R.identity, 26 | ), 27 | ); 28 | 29 | export const groupClasses = R.compose( 30 | R.values, 31 | R.mapObjIndexed((mqNames, className) => ({ 32 | name: className, 33 | value: cssObj[className], 34 | mqNames, 35 | })), 36 | R.map(R.tail), 37 | groupByClassName, 38 | ); 39 | 40 | const isNilOrEmpty = R.either(R.isNil, R.isEmpty); 41 | 42 | function findByProperty(query, classGroups) { 43 | const name = R.unless( 44 | isNilOrEmpty, 45 | findGroup, 46 | )(query); 47 | 48 | return classGroups[name] ? groupClasses(classGroups[name]) : []; 49 | } 50 | 51 | const hasClassName = R.ifElse(R.isEmpty, 52 | R.always(/notValidClass/), 53 | query => new RegExp(query, 'ig') 54 | ); 55 | 56 | const findByClassName = query => R.compose( 57 | R.map(utils.renameKeys({ 58 | 0: 'name', 59 | 1: 'value', 60 | })), 61 | R.filter( 62 | R.compose( 63 | R.test(hasClassName(query)), 64 | R.head, 65 | ), 66 | ), 67 | R.toPairs, 68 | ); 69 | 70 | /** 71 | * initial state 72 | */ 73 | const classNamesState = { 74 | query: null, 75 | isActive: false, 76 | byClassName: false, 77 | }; 78 | 79 | 80 | /** 81 | * Getters 82 | */ 83 | const getters = { 84 | searchResults: ( 85 | { query = '', byClassName }, 86 | _, 87 | { tachyons: { classGroups, classNames } } 88 | ) => { 89 | if (byClassName) { 90 | return findByClassName(query)(classNames); 91 | } 92 | 93 | return findByProperty(query, classGroups); 94 | }, 95 | query: ({ query }) => query, 96 | }; 97 | 98 | 99 | /** 100 | * Actions 101 | */ 102 | const actions = { 103 | }; 104 | 105 | 106 | /** 107 | * Mutations 108 | */ 109 | const mutations = { 110 | searchForClassByProp(state, { query }) { 111 | state.query = query; 112 | }, 113 | 114 | activateTerminal(state) { 115 | state.isActive = true; 116 | }, 117 | 118 | deactivateTerminal(state) { 119 | state.isActive = false; 120 | }, 121 | 122 | toggleSearchByClassName(state) { 123 | state.byClassName = !state.byClassName; 124 | }, 125 | }; 126 | 127 | 128 | export default { 129 | namespaced: true, 130 | state: classNamesState, 131 | getters, 132 | actions, 133 | mutations, 134 | }; 135 | -------------------------------------------------------------------------------- /src/class-names/class-names.view.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 49 | -------------------------------------------------------------------------------- /src/class-names/index.js: -------------------------------------------------------------------------------- 1 | export { default as classNamesModule } from './class-names.module'; 2 | 3 | export default () => import(/* webpackChunkName: "class-names" */ './class-names.view'); 4 | -------------------------------------------------------------------------------- /src/class-names/terminal.component.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 54 | -------------------------------------------------------------------------------- /src/components/animated-line.component.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 59 | -------------------------------------------------------------------------------- /src/components/app-header.component.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 58 | -------------------------------------------------------------------------------- /src/components/clipboard-button.component.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 43 | -------------------------------------------------------------------------------- /src/components/field-label.component.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const requireComponent = require.context('./', true, /\.component\.vue$/); 4 | 5 | requireComponent.keys().forEach((fileName) => { 6 | let baseComponentConfig = requireComponent(fileName); 7 | 8 | baseComponentConfig = baseComponentConfig.default || baseComponentConfig; 9 | const baseComponentName = 10 | baseComponentConfig.name || 11 | fileName.replace(/^.+\//, '').replace(/\.\w+$/, ''); 12 | 13 | Vue.component(baseComponentName, baseComponentConfig); 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/nav-link.component.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /src/components/navigation.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/section-menu-item.component.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/section-menu.component.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /src/components/src-link.component.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /src/components/text-field.component.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .ty-center { 2 | position: relative; 3 | top: 50%; 4 | transform: perspective(1px) translateY(-50%); 5 | } 6 | 7 | .preserve-3d { transform-style: preserve-3d; } 8 | .flex-grow { flex: 1 0 8rem; } 9 | .origin-0-0 { transform-origin: 0 0; } 10 | .sticky { position: -webkit-sticky; position: sticky; } 11 | .z--1 { z-index: -1; } 12 | 13 | .debug-grid-green { 14 | background-image: 15 | linear-gradient(transparent 7px, #00a699 8px), 16 | linear-gradient(90deg, transparent 7px, #00a699 8px); 17 | background-size: 8px 8px; 18 | background-position: 0 3px; 19 | } 20 | 21 | .bg-grid { 22 | background-image: 23 | linear-gradient(transparent 15px, #00a699 16px), 24 | linear-gradient(90deg, transparent 15px, #00a699 16px); 25 | background-size: 16px 16px; 26 | } 27 | 28 | .active.animated-shadow:after, 29 | .animated-shadow:hover:after { opacity: 1; } 30 | 31 | .bg-checkered { 32 | background-color: #ffffff; 33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Cg fill='%23dddddd' fill-opacity='0.4'%3E%3Cpath fill-rule='evenodd' d='M0 0h4v4H0V0zm4 4h4v4H4V4z'/%3E%3C/g%3E%3C/svg%3E"); 34 | } 35 | 36 | 37 | /* Custom Colours */ 38 | .watermelon { color: #ff5a5f; } 39 | .bg-watermelon { background-color: #ff5a5f; } 40 | .b--watermelon { border-color: #ff5a5f; } 41 | 42 | .persian-green { color: #00a699; } 43 | .bg-persian-green { background-color: #00a699; } 44 | .b--persian-green { border-color: #00a699; } 45 | 46 | .washed-pink { color: #ffaa91; } 47 | .bg-washed-pink { background-color: #ffaa91; } 48 | .b--washed-pink { border-color: #ffaa91; } 49 | 50 | .rust { color: #ffb400; } 51 | .bg-rust { background-color: #ffb400; } 52 | .b--rust { border-color: #ffb400; } 53 | 54 | /* Animations */ 55 | .animated-shadow { 56 | cursor: pointer; 57 | position: relative; 58 | transition: all .5s cubic-bezier(0.165, 0.84, 0.44, 1); 59 | } 60 | .animated-shadow:after { 61 | content: ''; 62 | box-shadow: 0 1px 4px 0 #aaa; 63 | opacity: 0; 64 | position: absolute; 65 | top: 0; 66 | left: 0; 67 | width: 100%; 68 | height: 100%; 69 | z-index: -1; 70 | transition: opacity 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); 71 | } 72 | 73 | /* Easings */ 74 | .ease-in-out-cubic { 75 | transition-duration: 0.6s; 76 | transition-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 77 | } 78 | .swift-out { 79 | transition-duration: 0.6s; 80 | transition-timing-function: cubic-bezier(0.55, 0, 0.1, 1); 81 | } 82 | 83 | .an-o { transition-property: opacity; } 84 | .an-t { transition-property: transform; } 85 | .an-c { transition-property: color; } 86 | 87 | /* Transitions */ 88 | .slide-left-enter, 89 | .slide-right-leave-active { 90 | transform: translate(100%, 0); 91 | } 92 | .slide-left-leave-active, 93 | .slide-right-enter { 94 | transform: translate(-100%, 0); 95 | } 96 | 97 | .fade-in-down-enter-active { 98 | transition: opacity 600ms cubic-bezier(0.19, 1, 0.22, 1), 99 | transform 600ms cubic-bezier(0.19, 1, 0.22, 1); 100 | } 101 | .fade-in-down-enter { 102 | opacity: 0; 103 | transform: translate3d(0, -50%, 0); 104 | } 105 | .fade-in-down-enter-to { 106 | opacity: 1; 107 | transform: translate3d(0, 0, 0); 108 | } 109 | 110 | .fade-enter-active { 111 | transition: all .3s ease; 112 | } 113 | .fade-leave-active { 114 | transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0); 115 | } 116 | .fade-enter, 117 | .fade-leave-to { 118 | opacity: 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { polyfill } from 'scroll-behaviour'; 3 | import { sync } from 'vuex-router-sync'; 4 | import router from './router'; 5 | import App from './App'; 6 | import store from './store'; 7 | import './components'; 8 | 9 | polyfill(); 10 | 11 | sync(store, router); 12 | 13 | require('tachyons'); 14 | require('./index.css'); 15 | 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: '#app', 19 | template: '', 20 | router, 21 | store, 22 | ...App, 23 | }); 24 | -------------------------------------------------------------------------------- /src/palette/colour-block.component.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /src/palette/colour-chip.component.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /src/palette/index.js: -------------------------------------------------------------------------------- 1 | export default () => import(/* webpackChunkName: "palette" */ './palette.view'); 2 | -------------------------------------------------------------------------------- /src/palette/palette.view.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 40 | -------------------------------------------------------------------------------- /src/router/foo.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | import ClassNamesView from '@/class-names'; 5 | import ScalesView from '@/scales'; 6 | import PaletteView from '@/palette'; 7 | import ToolsView from '@/tools'; 8 | 9 | Vue.use(Router); 10 | 11 | export default new Router({ 12 | scrollBehavior(to) { 13 | if (to.hash) { 14 | return { selector: to.hash }; 15 | } 16 | 17 | return { y: 0 }; 18 | }, 19 | routes: [ 20 | { path: '/tools', component: ToolsView, meta: { index: 3 } }, 21 | { path: '/palette', component: PaletteView, meta: { index: 2 } }, 22 | { path: '/scales', component: ScalesView, meta: { index: 1 } }, 23 | { path: '/classes', component: ClassNamesView, meta: { index: 0 } }, 24 | { path: '/', redirect: '/classes' }, 25 | ], 26 | }); 27 | -------------------------------------------------------------------------------- /src/scales/border-section.component.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | -------------------------------------------------------------------------------- /src/scales/box-scale-grid.component.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | -------------------------------------------------------------------------------- /src/scales/heights-section.component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 36 | -------------------------------------------------------------------------------- /src/scales/index.js: -------------------------------------------------------------------------------- 1 | export { default as scalesModule } from './scales.module'; 2 | 3 | export default () => import(/* webpackChunkName: "scales" */ './scales.view'); 4 | -------------------------------------------------------------------------------- /src/scales/opacity-section/index.js: -------------------------------------------------------------------------------- 1 | import OpacitySection from './opacity-section.component'; 2 | 3 | export default OpacitySection; 4 | -------------------------------------------------------------------------------- /src/scales/opacity-section/opacity-scale.component.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /src/scales/opacity-section/opacity-section.component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/scales/scales.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * initial state 3 | */ 4 | const scalesState = { 5 | categories: [ 6 | 'Spacing', 7 | 'Typography', 8 | 'Widths', 9 | 'Heights', 10 | 'Border', 11 | 'Shadow', 12 | 'Opacity', 13 | ], 14 | activeCategoryIndex: 0, 15 | }; 16 | 17 | /** 18 | * Mutations 19 | */ 20 | const mutations = { 21 | selectScaleCategory(state, payload) { 22 | state.activeCategoryIndex = payload; 23 | }, 24 | }; 25 | 26 | 27 | export default { 28 | namespaced: true, 29 | state: scalesState, 30 | mutations, 31 | }; 32 | -------------------------------------------------------------------------------- /src/scales/scales.view.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 128 | -------------------------------------------------------------------------------- /src/scales/shadow-section.component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/scales/spacing-section/index.js: -------------------------------------------------------------------------------- 1 | import SpacingSection from './spacing-section.component'; 2 | 3 | export default SpacingSection; 4 | -------------------------------------------------------------------------------- /src/scales/spacing-section/spacing-scale.component.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 55 | -------------------------------------------------------------------------------- /src/scales/spacing-section/spacing-section.component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/scales/typography-section/index.js: -------------------------------------------------------------------------------- 1 | import TypographySection from './typography-section.component'; 2 | 3 | export default TypographySection; 4 | -------------------------------------------------------------------------------- /src/scales/typography-section/type-scale-grid.component.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 67 | -------------------------------------------------------------------------------- /src/scales/typography-section/typography-section.component.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | -------------------------------------------------------------------------------- /src/scales/widths-section/index.js: -------------------------------------------------------------------------------- 1 | import WidthsSection from './widths-section.component'; 2 | 3 | export default WidthsSection; 4 | -------------------------------------------------------------------------------- /src/scales/widths-section/width-scale-grid.component.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /src/scales/widths-section/widths-section.component.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 43 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import tachyons from './modules/tachyons'; 4 | import { scalesModule } from '../scales'; 5 | import { classNamesModule } from '../class-names'; 6 | import { toolsModule } from '../tools'; 7 | 8 | Vue.use(Vuex); 9 | 10 | const debug = process.env.NODE_ENV !== 'production'; 11 | 12 | const store = new Vuex.Store({ 13 | modules: { 14 | tachyons, 15 | classNames: classNamesModule, 16 | tools: toolsModule, 17 | scales: scalesModule, 18 | }, 19 | strict: debug, 20 | }); 21 | 22 | export default store; 23 | -------------------------------------------------------------------------------- /src/store/modules/tachyons.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | import { 3 | groupedClasses, 4 | colours, 5 | scales, 6 | version, 7 | classNames, 8 | } from '../../api'; 9 | import * as utils from '../../utils'; 10 | 11 | const classNamesLens = R.lensPath([1, 'classNames']); 12 | const brClassNames = custom => `h3 bg-light-gray ba b--moon-gray ${custom}`; 13 | const bwClassNames = custom => `h3 br b--moon-gray ${custom}`; 14 | 15 | 16 | /** 17 | * initial state 18 | */ 19 | const tachyonsState = { 20 | version, 21 | classGroups: {}, 22 | colours: {}, 23 | scales: {}, 24 | classNames: {}, 25 | }; 26 | 27 | 28 | /** 29 | * Getters 30 | */ 31 | const getters = { 32 | solidColours: R.path(['colours', 'solid']), 33 | 34 | spacingScale: R.compose( 35 | R.map(R.replace(/\./, '0.')), 36 | R.path(['scales', 'spacing']), 37 | ), 38 | 39 | typeScale: R.compose( 40 | utils.renameKeysBy(R.compose( 41 | R.head, 42 | R.split(','), 43 | )), 44 | R.path(['scales', 'type']), 45 | ), 46 | 47 | fontWeight: R.compose( 48 | R.fromPairs, 49 | R.sort((a, b) => b[1] - a[1]), 50 | R.toPairs, 51 | R.path(['scales', 'fontWeight']), 52 | ), 53 | 54 | borderRadius: R.compose( 55 | R.map(R.fromPairs), 56 | R.groupBy(R.cond([ 57 | [utils.classNameIsPositional, R.always('positional')], 58 | [R.T, R.always('scale')], 59 | ])), 60 | R.map(R.cond([ 61 | [utils.testFirst(/pill/), R.set(classNamesLens, brClassNames('w4'))], 62 | [ 63 | utils.classNameIsPositional, 64 | R.set(classNamesLens, brClassNames('w4 br3')), 65 | ], 66 | [R.T, R.set(classNamesLens, brClassNames('w3'))], 67 | ])), 68 | R.toPairs, 69 | R.map(R.objOf('value')), 70 | R.path(['scales', 'borderRadius']), 71 | ), 72 | 73 | borderWidths: R.compose( 74 | R.map(R.fromPairs), 75 | R.groupBy(R.cond([ 76 | [utils.testFirst(/(b|l|t|r)-/), R.always('resets')], 77 | [R.T, R.always('scale')], 78 | ])), 79 | R.map(R.cond([ 80 | [ 81 | utils.testFirst(/(b|l|t|r)-/), 82 | R.set(classNamesLens, bwClassNames('w4 bw2 ba')), 83 | ], 84 | [R.T, R.set(classNamesLens, bwClassNames('w3'))], 85 | ])), 86 | R.toPairs, 87 | R.map(R.objOf('value')), 88 | R.path(['scales', 'borderWidths']), 89 | ), 90 | 91 | widths: R.compose( 92 | R.map(R.fromPairs), 93 | R.groupBy(R.cond([ 94 | [utils.testFirst(/third/), R.always('third')], 95 | [utils.testFirst(/^w-\d+/), R.always('percent')], 96 | [utils.testFirst(/^w\d+/g), R.always('scale')], 97 | ])), 98 | R.toPairs, 99 | R.path(['scales', 'widths']), 100 | ), 101 | 102 | heights: R.compose( 103 | R.map(R.fromPairs), 104 | R.groupBy(R.cond([ 105 | [utils.testFirst(/^h\d+/), R.always('scale')], 106 | [utils.testFirst(/^h-\d+/), R.always('percent')], 107 | [utils.testFirst(/^vh-\d+/), R.always('vh')], 108 | ])), 109 | R.map(R.set(classNamesLens, 'w3 bg-light-gray bb b--persian-green bw1')), 110 | R.toPairs, 111 | R.map(R.objOf('value')), 112 | R.path(['scales', 'heights']), 113 | ), 114 | 115 | opacityScale: R.compose( 116 | R.fromPairs, 117 | R.sort(([, a], [, b]) => a - b), 118 | R.toPairs, 119 | R.map(Number), 120 | R.path(['scales', 'opacity']), 121 | ), 122 | 123 | shadowScale: R.compose( 124 | R.fromPairs, 125 | R.map(R.set(classNamesLens, 'w4 h4')), 126 | R.toPairs, 127 | R.path(['scales', 'shadow']), 128 | ), 129 | 130 | maxWidths: R.path(['scales', 'maxWidths']), 131 | }; 132 | 133 | 134 | /** 135 | * Mutations 136 | */ 137 | const mutations = { 138 | loadStyles(state) { 139 | state.classGroups = groupedClasses; 140 | state.colours = colours; 141 | state.scales = scales; 142 | state.classNames = classNames; 143 | }, 144 | }; 145 | 146 | 147 | export default { 148 | namespaced: true, 149 | state: tachyonsState, 150 | getters, 151 | mutations, 152 | }; 153 | -------------------------------------------------------------------------------- /src/tools/css-src.component.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /src/tools/index.js: -------------------------------------------------------------------------------- 1 | export { default as toolsModule } from './tools.module'; 2 | 3 | export default () => import(/* webpackChunkName: "tools" */ './tools.view'); 4 | -------------------------------------------------------------------------------- /src/tools/media-queryfy/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './media-queryfy.component.vue'; 2 | -------------------------------------------------------------------------------- /src/tools/media-queryfy/media-queryfy.component.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 85 | -------------------------------------------------------------------------------- /src/tools/media-queryfy/skin-css.component.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /src/tools/skin-generator/index.js: -------------------------------------------------------------------------------- 1 | import SkinGenerator from './skin-generator.component'; 2 | 3 | export default SkinGenerator; 4 | -------------------------------------------------------------------------------- /src/tools/skin-generator/skin-generator.component.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /src/tools/tools.module.js: -------------------------------------------------------------------------------- 1 | import { skinCss, mqFy } from './utils'; 2 | 3 | /** 4 | * initial state 5 | */ 6 | const toolsState = { 7 | skin: { 8 | colour: '#4e4bec', 9 | name: 'neon-blue', 10 | }, 11 | mq: { 12 | name: 'sticky', 13 | rules: 'position: sticky;', 14 | sm: '30em', 15 | lg: '60em', 16 | }, 17 | }; 18 | 19 | /** 20 | * Getters 21 | */ 22 | const getters = { 23 | skinCss: ({ skin: { name, colour } }) => skinCss(name, colour), 24 | mqfyCss: ({ mq: { name, sm, lg, rules } }) => mqFy(name, sm, lg, rules), 25 | }; 26 | 27 | /** 28 | * Mutations 29 | */ 30 | const mutations = { 31 | updateSkin(state, payload) { 32 | // eslint-disable-next-line 33 | state.skin[payload.type] = payload.value; 34 | }, 35 | 36 | updateMq(state, payload) { 37 | // eslint-disable-next-line 38 | state.mq[payload.type] = payload.value; 39 | }, 40 | }; 41 | 42 | export default { 43 | namespaced: true, 44 | state: toolsState, 45 | getters, 46 | mutations, 47 | }; 48 | -------------------------------------------------------------------------------- /src/tools/tools.view.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/tools/utils.js: -------------------------------------------------------------------------------- 1 | export function skinCss(name, colour) { 2 | return ` 3 | .${name} { color: ${colour}; } 4 | .bg-${name} { background-color: ${colour}; } 5 | .b--${name} { border-color: ${colour}; } 6 | 7 | .hover-${name}:hover, 8 | .hover-${name}:focus { color: ${colour}; } 9 | 10 | .hover-bg-${name}:hover, 11 | .hover-bg-${name}:focus { background-color: ${colour}; }`; 12 | } 13 | 14 | const formatRules = (rules, padding = '') => rules.split('\n').join(`\n${padding}`); 15 | 16 | export function mqFy(name, sm, lg, rules) { 17 | return ` 18 | .${name} { 19 | ${formatRules(rules, ' ')} 20 | } 21 | 22 | @media screen and (min-width: ${sm}) { 23 | .${name}-ns { 24 | ${formatRules(rules, ' ')} 25 | } 26 | } 27 | 28 | @media screen and (min-width: ${sm}) and (max-width: ${lg}) { 29 | .${name}-m { 30 | ${formatRules(rules, ' ')} 31 | } 32 | } 33 | 34 | @media screen and (min-width: ${lg}) { 35 | .${name}-l { 36 | ${formatRules(rules, ' ')} 37 | } 38 | }`; 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda'; 2 | 3 | /** 4 | * Key name manipulators 5 | */ 6 | export const renameKeys = R.curry((keysMap, obj) => ( 7 | R.reduce((acc, key) => { 8 | acc[keysMap[key] || key] = obj[key]; 9 | return acc; 10 | }, {}, R.keys(obj)) 11 | )); 12 | 13 | export const renameKeysBy = fn => obj => 14 | R.compose( 15 | R.fromPairs, 16 | R.map(R.adjust(fn, 0)), 17 | R.toPairs, 18 | )(obj); 19 | 20 | export const filterWithKeys = pred => obj => R.compose( 21 | R.fromPairs, 22 | R.filter(R.apply(pred)), 23 | R.toPairs, 24 | )(obj); 25 | 26 | 27 | /** 28 | * Case convertors 29 | */ 30 | export const toTitleCase = s => s.replace( 31 | /\.?([0-9]+|[A-Z])/g, 32 | (x, y) => ` ${y}`, 33 | ).replace(/^-/, '').trim(); 34 | 35 | export const toKebabCase = s => s.replace( 36 | /\.?([A-Z])/g, 37 | (x, y) => `-${y.toLowerCase()}`, 38 | ).replace(/^-/, ''); 39 | 40 | export const toCamelCase = s => s.replace( 41 | /\W+(.)/g, 42 | (x, y) => `-${y.toUpperCase()}`, 43 | ).replace(/-/g, ''); 44 | 45 | export const selectorToClassName = R.replace(/(\n|\.)/g, ''); 46 | export const allSelectorsToClassNames = renameKeysBy(selectorToClassName); 47 | 48 | /** 49 | * Comparators 50 | */ 51 | export const isMediaRule = R.test(/^@media.+/); 52 | export const testFirst = regex => R.compose(R.test(regex), R.head); 53 | export const classNameIsPositional = testFirst(/(top|left|right|bottom)/); 54 | export const mediaQueryRegex = /(.+)-(ns|m|l)$/; 55 | 56 | // Getters 57 | export const root = R.prop(':root'); 58 | 59 | export const selectors = R.pickBy(R.compose(R.test(/^\./), R.nthArg(1))); 60 | 61 | export const atMediaRules = R.pickBy(R.compose(R.test(/^@/), R.nthArg(1))); 62 | 63 | export const atMediaClasses = R.compose( 64 | R.mergeAll, 65 | R.values, 66 | atMediaRules, 67 | ); 68 | 69 | export const nonMediaValuesBy = R.curry((getProp, root) => R.compose( 70 | R.map(getProp), 71 | filterWithKeys(R.complement(isMediaRule)), 72 | )(root)); 73 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/.gitkeep -------------------------------------------------------------------------------- /static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #603cba 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/favicon.ico -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tachyons tldr", 3 | "short_name": "tchyns tldr", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /static/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/mstile-144x144.png -------------------------------------------------------------------------------- /static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/mstile-150x150.png -------------------------------------------------------------------------------- /static/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/mstile-310x150.png -------------------------------------------------------------------------------- /static/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/mstile-310x310.png -------------------------------------------------------------------------------- /static/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tachyons-css/tachyons-tldr/c93a1fd02989d56daac55cee985251cd356f2c5e/static/mstile-70x70.png -------------------------------------------------------------------------------- /static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 13 | 21 | 36 | 44 | 45 | 46 | --------------------------------------------------------------------------------