├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierrc.js ├── LICENSE.txt ├── README.md ├── package.json ├── rollup.config.js ├── scripts └── assets-append-version.js ├── src ├── api.ts ├── controller.ts ├── index.ts ├── plugin.ts ├── sass │ └── plugin.scss ├── tsconfig-dts.json ├── tsconfig.json └── view.ts └── test └── browser.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{html}] 4 | indent_size = 2 5 | indent_style = tab 6 | 7 | [*.{js,json,ts}] 8 | indent_size = 2 9 | indent_style = tab 10 | 11 | [*.md] 12 | indent_size = 2 13 | indent_style = space 14 | 15 | [*.scss] 16 | indent_size = 2 17 | indent_style = tab 18 | 19 | [*.yml] 20 | indent_size = 2 21 | indent_style = space 22 | 23 | [package.json] 24 | indent_size = 2 25 | indent_style = space 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint', 'simple-import-sort'], 10 | root: true, 11 | rules: { 12 | camelcase: 'off', 13 | 'no-unused-vars': 'off', 14 | 'sort-imports': 'off', 15 | 16 | 'prettier/prettier': 'error', 17 | 'simple-import-sort/imports': 'error', 18 | '@typescript-eslint/naming-convention': [ 19 | 'error', 20 | { 21 | selector: 'variable', 22 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'], 23 | custom: { 24 | regex: '^opt_', 25 | match: false, 26 | }, 27 | }, 28 | ], 29 | '@typescript-eslint/explicit-function-return-type': 'off', 30 | '@typescript-eslint/no-empty-function': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | '@typescript-eslint/no-unused-vars': [ 33 | 'error', 34 | { 35 | argsIgnorePattern: '^_', 36 | }, 37 | ], 38 | 39 | // TODO: Resolve latest lint warnings 40 | '@typescript-eslint/explicit-module-boundary-types': 'off', 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ 2 | /dist/ 3 | /*.zip 4 | 5 | ### https://raw.github.com/github/gitignore//Node.gitignore 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # Snowpack dependency directory (https://snowpack.dev/) 51 | web_modules/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | out 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Stores VSCode versions used for testing VSCode extensions 115 | .vscode-test 116 | 117 | # yarn v2 118 | .yarn/cache 119 | .yarn/unplugged 120 | .yarn/build-state.yml 121 | .yarn/install-state.gz 122 | .pnp.* 123 | 124 | 125 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | bracketSpacing: false, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | useTabs: true, 7 | }; 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 cocopon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tweakpane plugin chromatic 2 | Color palette viewer for [Tweakpane][tweakpane]. 3 | 4 | ![tweakpane-plugin-chromatic](https://github.com/brunoimbrizi/tweakpane-plugin-chromatic/assets/880280/d5ecf5bc-bb27-4dd2-b223-c620d744df0a) 5 | 6 | 7 | ## Demo 8 | 9 | [Plugin demo on StackBlitz](https://stackblitz.com/edit/js-rkko7x) 10 | 11 | 12 | 13 | ## Installation 14 | 15 | 16 | ### Browser 17 | ```html 18 | 19 | 20 | 24 | ``` 25 | 26 | 27 | ### Package 28 | ```js 29 | 30 | import {Pane} from 'tweakpane'; 31 | import * as ChromaticPlugin from 'tweakpane-plugin-chromatic'; 32 | 33 | const pane = new Pane(); 34 | pane.registerPlugin(ChromaticPlugin); 35 | ``` 36 | 37 | 38 | ## Usage 39 | ```js 40 | pane.addBlade({ 41 | view: 'chromatic', 42 | label: 'palette', 43 | colors: ['red', 'blue', 'green'] 44 | }); 45 | ``` 46 | 47 | 48 | [tweakpane]: https://github.com/cocopon/tweakpane/ 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tweakpane-plugin-chromatic", 3 | "version": "0.0.3", 4 | "description": "Color palette viewer for Tweakpane", 5 | "main": "dist/tweakpane-plugin-chromatic.js", 6 | "types": "dist/types/index.d.ts", 7 | "author": "brunoimbrizi", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/brunoimbrizi/tweakpane-plugin-chromatic.git" 12 | }, 13 | "keywords": [ 14 | "tweakpane", 15 | "tweakpane-plugin", 16 | "palette", 17 | "chromatic" 18 | ], 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "prepare": "run-s clean build", 24 | "prepublishOnly": "npm test", 25 | "start": "npm run watch", 26 | "test": "eslint --ext .ts \"src/**/*.ts\"", 27 | "assets": "run-s clean build assets:version assets:zip", 28 | "assets:version": "node scripts/assets-append-version.js", 29 | "assets:zip": "zip -x \"*types*\" -j -r $(cat package.json | npx json name)-$(cat package.json | npx json version).zip dist", 30 | "clean": "rimraf dist *.tgz *.zip", 31 | "build": "run-p build:*", 32 | "build:dev": "rollup --config rollup.config.js", 33 | "build:dts": "tsc --project src/tsconfig-dts.json", 34 | "build:prod": "rollup --config rollup.config.js --environment BUILD:production", 35 | "format": "run-p format:*", 36 | "format:scss": "prettier --parser scss --write \"src/sass/**/*.scss\"", 37 | "format:ts": "eslint --ext .ts --fix \"src/**/*.ts\"", 38 | "watch": "run-p watch:*", 39 | "watch:sass": "onchange --initial --kill \"src/sass/**/*.scss\" -- npm run build:dev", 40 | "watch:ts": "onchange --initial --kill \"src/**/*.ts\" -- rollup --config rollup.config.js" 41 | }, 42 | "devDependencies": { 43 | "@rollup/plugin-alias": "^3.1.2", 44 | "@rollup/plugin-node-resolve": "^13.0.0", 45 | "@rollup/plugin-replace": "^2.4.1", 46 | "@rollup/plugin-typescript": "^8.2.0", 47 | "@tweakpane/core": "^1.1.4", 48 | "@typescript-eslint/eslint-plugin": "^4.15.2", 49 | "@typescript-eslint/parser": "^4.15.2", 50 | "autoprefixer": "^10.2.4", 51 | "eslint": "^7.20.0", 52 | "eslint-config-prettier": "^8.1.0", 53 | "eslint-plugin-prettier": "^3.3.1", 54 | "eslint-plugin-simple-import-sort": "^7.0.0", 55 | "npm-run-all": "^4.1.5", 56 | "onchange": "^7.1.0", 57 | "postcss": "^8.2.6", 58 | "prettier": "^2.2.1", 59 | "rimraf": "^3.0.2", 60 | "rollup": "^2.39.1", 61 | "rollup-plugin-cleanup": "^3.2.1", 62 | "rollup-plugin-terser": "^7.0.2", 63 | "sass": "^1.49.9", 64 | "typescript": "^4.1.5" 65 | }, 66 | "peerDependencies": { 67 | "tweakpane": "^3.1.4" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import Alias from '@rollup/plugin-alias'; 2 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 3 | import Replace from '@rollup/plugin-replace'; 4 | import Typescript from '@rollup/plugin-typescript'; 5 | import Autoprefixer from 'autoprefixer'; 6 | import Postcss from 'postcss'; 7 | import Cleanup from 'rollup-plugin-cleanup'; 8 | import {terser as Terser} from 'rollup-plugin-terser'; 9 | import Sass from 'sass'; 10 | 11 | import Package from './package.json'; 12 | 13 | async function compileCss() { 14 | const css = Sass.renderSync({ 15 | file: 'src/sass/plugin.scss', 16 | outputStyle: 'compressed', 17 | }).css.toString(); 18 | 19 | const result = await Postcss([Autoprefixer]).process(css, { 20 | from: undefined, 21 | }); 22 | return result.css.replace(/'/g, "\\'").trim(); 23 | } 24 | 25 | function getPlugins(css, shouldMinify) { 26 | const plugins = [ 27 | // Use ES6 source files to avoid CommonJS transpiling 28 | Alias({ 29 | entries: [ 30 | { 31 | find: '@tweakpane/core', 32 | replacement: './node_modules/@tweakpane/core/dist/es6/index.js', 33 | }, 34 | ], 35 | }), 36 | Typescript({ 37 | tsconfig: 'src/tsconfig.json', 38 | }), 39 | nodeResolve(), 40 | Replace({ 41 | __css__: css, 42 | preventAssignment: false, 43 | }), 44 | ]; 45 | if (shouldMinify) { 46 | plugins.push(Terser()); 47 | } 48 | return [ 49 | ...plugins, 50 | // https://github.com/microsoft/tslib/issues/47 51 | Cleanup({ 52 | comments: 'none', 53 | }), 54 | ]; 55 | } 56 | 57 | function getDistName(packageName) { 58 | // `@tweakpane/plugin-foobar` -> `tweakpane-plugin-foobar` 59 | // `tweakpane-plugin-foobar` -> `tweakpane-plugin-foobar` 60 | return packageName 61 | .split(/[@/-]/) 62 | .reduce((comps, comp) => (comp !== '' ? [...comps, comp] : comps), []) 63 | .join('-'); 64 | } 65 | 66 | function getUmdName(packageName) { 67 | // `@tweakpane/plugin-foobar` -> `TweakpaneFoobarPlugin` 68 | // `tweakpane-plugin-foobar` -> `TweakpaneFoobarPlugin` 69 | return ( 70 | packageName 71 | .split(/[@/-]/) 72 | .map((comp) => 73 | comp !== 'plugin' ? comp.charAt(0).toUpperCase() + comp.slice(1) : '', 74 | ) 75 | .join('') + 'Plugin' 76 | ); 77 | } 78 | 79 | export default async () => { 80 | const production = process.env.BUILD === 'production'; 81 | const postfix = production ? '.min' : ''; 82 | 83 | const distName = getDistName(Package.name); 84 | const css = await compileCss(); 85 | return { 86 | input: 'src/index.ts', 87 | external: ['tweakpane'], 88 | output: { 89 | file: `dist/${distName}${postfix}.js`, 90 | format: 'umd', 91 | globals: { 92 | tweakpane: 'Tweakpane', 93 | }, 94 | name: getUmdName(Package.name), 95 | }, 96 | plugins: getPlugins(css, production), 97 | 98 | // Suppress `Circular dependency` warning 99 | onwarn(warning, rollupWarn) { 100 | if (warning.code === 'CIRCULAR_DEPENDENCY') { 101 | return; 102 | } 103 | rollupWarn(warning); 104 | }, 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /scripts/assets-append-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Fs = require('fs'); 4 | const Glob = require('glob'); 5 | const Path = require('path'); 6 | const Package = require('../package'); 7 | 8 | const PATTERN = 'dist/*'; 9 | 10 | const paths = Glob.sync(PATTERN); 11 | paths.forEach((path) => { 12 | const fileName = Path.basename(path); 13 | if (Fs.statSync(path).isDirectory()) { 14 | return; 15 | } 16 | 17 | const ext = fileName.match(/(\..+)$/)[1]; 18 | const base = Path.basename(fileName, ext); 19 | const versionedPath = Path.join( 20 | Path.dirname(path), 21 | `${base}-${Package.version}${ext}`, 22 | ); 23 | Fs.renameSync(path, versionedPath); 24 | }); 25 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import {BladeApi, LabelController} from '@tweakpane/core'; 2 | 3 | import {PluginController} from './controller'; 4 | 5 | export class PluginApi extends BladeApi> { 6 | get label(): string | null | undefined { 7 | return this.controller_.props.get('label'); 8 | } 9 | 10 | set label(label: string | null | undefined) { 11 | this.controller_.props.set('label', label); 12 | } 13 | 14 | get colors(): Array { 15 | return this.controller_.valueController.props.get('colors'); 16 | } 17 | 18 | set colors(value: Array) { 19 | this.controller_.valueController.props.set('colors', value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, ViewProps} from '@tweakpane/core'; 2 | 3 | import {PluginProps, PluginView} from './view'; 4 | 5 | interface Config { 6 | props: PluginProps; 7 | viewProps: ViewProps; 8 | } 9 | 10 | export class PluginController implements Controller { 11 | public readonly props: PluginProps; 12 | public readonly view: PluginView; 13 | public readonly viewProps: ViewProps; 14 | 15 | constructor(doc: Document, config: Config) { 16 | this.props = config.props; 17 | this.viewProps = config.viewProps; 18 | 19 | this.view = new PluginView(doc, { 20 | props: this.props, 21 | viewProps: this.viewProps, 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {ChromaticBladePlugin} from './plugin'; 2 | 3 | export const plugins = [ChromaticBladePlugin]; 4 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseBladeParams, 3 | BladePlugin, 4 | LabelController, 5 | LabelPropsObject, 6 | ParamsParsers, 7 | parseParams, 8 | ValueMap, 9 | } from '@tweakpane/core'; 10 | 11 | import {PluginApi} from './api'; 12 | import {PluginController} from './controller'; 13 | import {PluginPropsObject} from './view'; 14 | 15 | export interface PluginBladeParams extends BaseBladeParams { 16 | colors: Array; 17 | view: 'chromatic'; 18 | label?: string; 19 | } 20 | 21 | export const ChromaticBladePlugin: BladePlugin = { 22 | id: 'blade-chromatic', 23 | type: 'blade', 24 | css: '__css__', 25 | 26 | accept(params) { 27 | const p = ParamsParsers; 28 | const result = parseParams(params, { 29 | view: p.required.constant('chromatic'), 30 | colors: p.required.array(p.required.string), 31 | label: p.optional.string, 32 | }); 33 | return result ? {params: result} : null; 34 | }, 35 | 36 | controller(args) { 37 | const controller = new PluginController(args.document, { 38 | props: ValueMap.fromObject({ 39 | colors: args.params.colors, 40 | }), 41 | viewProps: args.viewProps, 42 | }); 43 | 44 | return new LabelController(args.document, { 45 | blade: args.blade, 46 | props: ValueMap.fromObject({ 47 | label: args.params.label, 48 | }), 49 | valueController: controller, 50 | }); 51 | }, 52 | 53 | api(args) { 54 | if (!(args.controller instanceof LabelController)) { 55 | return null; 56 | } 57 | if (!(args.controller.valueController instanceof PluginController)) { 58 | return null; 59 | } 60 | return new PluginApi(args.controller); 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/sass/plugin.scss: -------------------------------------------------------------------------------- 1 | // Import core styles 2 | @import '../../node_modules/@tweakpane/core/lib/sass/plugin'; 3 | 4 | // Additional style for the plugin 5 | .#{$prefix}-chromv { 6 | cursor: pointer; 7 | display: grid; 8 | grid-template-columns: repeat(10, 1fr); 9 | height: var(--bld-us); 10 | overflow: hidden; 11 | position: relative; 12 | column-gap: 1px; 13 | align-items: center; 14 | 15 | &.#{$prefix}-v-disabled { 16 | opacity: 0.5; 17 | } 18 | 19 | &_col { 20 | height: 60%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/tsconfig-dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "../dist/types" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2015"], 4 | "moduleResolution": "Node", 5 | "strict": true, 6 | "target": "ES6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/view.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bindValueMap, 3 | ClassName, 4 | isEmpty, 5 | removeChildNodes, 6 | ValueMap, 7 | View, 8 | ViewProps, 9 | } from '@tweakpane/core'; 10 | 11 | export type PluginPropsObject = { 12 | colors: Array; 13 | }; 14 | 15 | export type PluginProps = ValueMap; 16 | 17 | interface Config { 18 | props: PluginProps; 19 | viewProps: ViewProps; 20 | } 21 | 22 | const className = ClassName('chrom'); 23 | 24 | export class PluginView implements View { 25 | public readonly element: HTMLElement; 26 | 27 | constructor(doc: Document, config: Config) { 28 | this.element = doc.createElement('div'); 29 | this.element.classList.add(className()); 30 | config.viewProps.bindClassModifiers(this.element); 31 | 32 | bindValueMap(config.props, 'colors', (value: Array) => { 33 | if (isEmpty(value)) { 34 | removeChildNodes(this.element); 35 | } else { 36 | removeChildNodes(this.element); 37 | 38 | const colors = config.props.value('colors'); 39 | 40 | colors.rawValue.forEach((color: string) => { 41 | const colorElem = doc.createElement('div'); 42 | colorElem.classList.add(className('col')); 43 | colorElem.style.backgroundColor = color; 44 | this.element.appendChild(colorElem); 45 | }); 46 | } 47 | }); 48 | 49 | config.viewProps.handleDispose(() => { 50 | // Called when the view is disposing 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 35 | 36 | --------------------------------------------------------------------------------