├── packages ├── docs │ ├── src │ │ ├── .nojekyll │ │ ├── logo.png │ │ ├── _coverpage.md │ │ ├── _sidebar.md │ │ ├── welcome.md │ │ ├── advanced │ │ │ └── annotate.md │ │ ├── deploy.md │ │ └── usage.md │ └── package.json ├── svg-icon-builder │ ├── .gitignore │ ├── bin │ │ └── icon.js │ ├── tsconfig.json │ ├── svg │ │ ├── debugger.svg │ │ ├── console.svg │ │ └── data.svg │ ├── src │ │ ├── icon.template │ │ ├── logger.ts │ │ └── cli.ts │ └── package.json ├── load-config │ ├── __test__ │ │ ├── configs │ │ │ ├── invalid │ │ │ │ └── ds.config.json │ │ │ ├── extends │ │ │ │ └── ds.config.json │ │ │ └── basic │ │ │ │ └── ds.config.json │ │ └── extends.test.ts │ ├── jest.config.js │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── cli │ ├── bin │ │ └── ds.js │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── cli-utils │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── get-repo-root.ts │ │ │ ├── get-monorepo-root.ts │ │ │ ├── load-user-webpack-config.ts │ │ │ ├── lerna.ts │ │ │ └── monorepo-name.ts │ │ └── which.ts │ ├── tsconfig.json │ └── package.json ├── core │ ├── __tests__ │ │ ├── test-plugin │ │ │ ├── package.json │ │ │ ├── index.js │ │ │ └── command.js │ │ ├── index.test.ts │ │ └── plugins.test.ts │ ├── jest.config.js │ ├── tsconfig.json │ └── package.json ├── create │ ├── src │ │ ├── index.ts │ │ ├── copy.ts │ │ ├── init.ts │ │ └── template.ts │ ├── tsconfig.json │ ├── webpack.config.js │ ├── README.md │ ├── scripts │ │ └── update-templates.js │ └── package.json ├── hooks │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ │ ├── index.tsx │ │ ├── useReducedMotion.tsx │ │ ├── usePrefersHighContrast.tsx │ │ ├── usePrefersLessContrast.tsx │ │ ├── useDarkMode.tsx │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── useStickyState.test.tsx.snap │ │ │ ├── useClickOutside.test.tsx │ │ │ ├── useReduceMotion.test.tsx │ │ │ ├── usePrefersHighContrast.test.tsx │ │ │ ├── usePrefersLessContrast.test.tsx │ │ │ ├── useKeyboardNavigation.test.tsx │ │ │ └── useStickyState.test.tsx │ │ ├── useConditionalAnimation.ts │ │ ├── useClickOutside.ts │ │ ├── useLocale.ts │ │ ├── useMatchMedia.tsx │ │ ├── useStickyState.ts │ │ └── useOverflowing.tsx │ ├── package.json │ └── CHANGELOG.md ├── utils │ ├── jest.config.js │ ├── src │ │ └── utils │ │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ ├── createInstanceIfDefined.test.tsx.snap │ │ │ │ ├── styled.test.tsx.snap │ │ │ │ └── createSlots.test.tsx.snap │ │ │ ├── omit.test.ts │ │ │ ├── logger.test.ts │ │ │ ├── displayName.test.tsx │ │ │ ├── getProp.test.tsx │ │ │ ├── createInstanceIfDefined.test.tsx │ │ │ ├── debounce.test.ts │ │ │ ├── portal.test.tsx │ │ │ └── isReactInstanceOf.test.tsx │ │ │ ├── arrayify.ts │ │ │ ├── fromEntries.ts │ │ │ ├── getProp.ts │ │ │ ├── displayName.ts │ │ │ ├── createInstanceIfDefined.tsx │ │ │ ├── omit.ts │ │ │ ├── debounce.ts │ │ │ ├── isReactInstanceOf.ts │ │ │ ├── logger.ts │ │ │ └── focus-lock.tsx │ ├── tsconfig.json │ └── package.json ├── next-esm-css │ ├── jest.config.js │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── babel-plugin-include-styles │ ├── src │ │ ├── exists.ts │ │ ├── __tests__ │ │ │ ├── exists.test.ts │ │ │ ├── helpers │ │ │ │ └── utils.ts │ │ │ └── with-deps.test.ts │ │ └── resolve-package.ts │ ├── tsconfig.json │ ├── jest.config.js │ ├── README.md │ └── package.json ├── babel-plugin-replace-styles │ ├── src │ │ ├── exists.ts │ │ └── __tests__ │ │ │ ├── exists.test.ts │ │ │ ├── helpers │ │ │ └── utils.ts │ │ │ └── index.test.ts │ ├── tsconfig.json │ ├── jest.config.js │ ├── package.json │ └── README.md ├── plugin │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts ├── stylelint-config │ ├── package.json │ └── index.js └── eslint-config │ └── package.json ├── plugins ├── clean │ ├── README.md │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ │ └── command.ts │ └── package.json ├── bundle │ ├── README.md │ ├── webpack.base.config.js │ ├── tsconfig.json │ ├── src │ │ ├── command.ts │ │ └── index.ts │ └── package.json ├── playroom │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── command.ts │ │ └── snippets.ts ├── storybook │ ├── preset.js │ ├── preview.js │ ├── .storybook │ │ ├── preview-head.html │ │ ├── preset.js │ │ └── preview.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ └── story2sketch.config.ts │ └── package.json ├── test │ ├── jest.config.base.js │ ├── tsconfig.json │ ├── src │ │ ├── jest │ │ │ └── setupTests.ts │ │ ├── jest.config.base.ts │ │ ├── command.ts │ │ └── index.ts │ └── package.json ├── build │ ├── babel.config.js │ ├── postcss.config.js │ ├── tsconfig.json │ ├── README.md │ ├── src │ │ ├── configs │ │ │ ├── babel.config.ts │ │ │ └── postcss.config.ts │ │ └── utils.ts │ └── package.json ├── update │ ├── README.md │ ├── tsconfig.json │ ├── src │ │ └── command.ts │ └── package.json ├── create-command │ ├── src │ │ ├── command.ts │ │ └── index.ts │ ├── tsconfig.json │ └── package.json ├── lint │ ├── .eslintignore │ ├── tsconfig.json │ ├── src │ │ ├── interfaces.ts │ │ ├── command.ts │ │ ├── index.ts │ │ └── utils │ │ │ └── helperUtils.ts │ └── package.json ├── dev │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── command.ts ├── proof │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ └── command.ts │ ├── proof.config.base.js │ └── package.json └── size │ ├── tsconfig.json │ ├── src │ ├── utils │ │ └── BuildUtils.ts │ └── RelativeCommentsPlugin.ts │ └── package.json ├── typings ├── degit.d.ts ├── maxstache.d.ts ├── find-free-port.d.ts ├── jest-dom.d.ts ├── story2sketch.d.ts ├── stylelint-formatter-pretty.d.ts ├── react-element-to-jsx-string.d.ts ├── storybook-cli-extract.d.ts ├── eslint-formatter-pretty.d.ts ├── postcss.d.ts ├── file-size.d.ts ├── template-directory.d.ts ├── postcss-icss-selectors.d.ts ├── npm-which.d.ts ├── storybook-react-standalone.d.ts ├── createStylelint.d.ts ├── playroom.d.ts ├── babel-config.d.ts ├── postcss-load.config.d.ts └── package-size.d.ts ├── ds.config.json ├── scripts ├── template-plugin │ ├── README.md │ ├── src │ │ ├── command.ts │ │ └── index.ts │ ├── tsconfig.json │ └── package.json └── create-plugin.js ├── .npmrc ├── logo.png ├── lerna.json ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── CODE_OF_CONDUCT.md ├── .eslintrc ├── tsconfig.dev.json ├── tsconfig.json ├── jest.config.js ├── tsconfig.build.json ├── CONTRIBUTING.md ├── action.yml ├── LICENSE ├── .gitignore ├── CODE_OF_CONDUCT.md ├── renovate.json └── package.json /packages/docs/src/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/.gitignore: -------------------------------------------------------------------------------- 1 | output/ -------------------------------------------------------------------------------- /plugins/clean/README.md: -------------------------------------------------------------------------------- 1 | # Clean Plugin 2 | -------------------------------------------------------------------------------- /plugins/bundle/README.md: -------------------------------------------------------------------------------- 1 | # Bundle Plugin 2 | -------------------------------------------------------------------------------- /plugins/playroom/README.md: -------------------------------------------------------------------------------- 1 | # Playroom Plugin 2 | -------------------------------------------------------------------------------- /typings/degit.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'degit'; 2 | -------------------------------------------------------------------------------- /ds.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "noVersionWarning": true 3 | } 4 | -------------------------------------------------------------------------------- /scripts/template-plugin/README.md: -------------------------------------------------------------------------------- 1 | # {{title}} Plugin 2 | -------------------------------------------------------------------------------- /typings/maxstache.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'maxstache'; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /typings/find-free-port.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'find-free-port'; 2 | -------------------------------------------------------------------------------- /packages/load-config/__test__/configs/invalid/ds.config.json: -------------------------------------------------------------------------------- 1 | 1234 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intuit/design-systems-cli/HEAD/logo.png -------------------------------------------------------------------------------- /plugins/storybook/preset.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./.storybook/preset') -------------------------------------------------------------------------------- /typings/jest-dom.d.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | -------------------------------------------------------------------------------- /plugins/storybook/preview.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./.storybook/preview'); 2 | -------------------------------------------------------------------------------- /typings/story2sketch.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'story2sketch/lib/server/Story2sketch'; 2 | -------------------------------------------------------------------------------- /typings/stylelint-formatter-pretty.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'stylelint-formatter-pretty'; 2 | -------------------------------------------------------------------------------- /packages/load-config/__test__/configs/extends/ds.config.json: -------------------------------------------------------------------------------- 1 | "../basic/ds.config.json" 2 | -------------------------------------------------------------------------------- /plugins/test/jest.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/jest.config.base'); 2 | -------------------------------------------------------------------------------- /typings/react-element-to-jsx-string.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-element-to-jsx-string'; 2 | -------------------------------------------------------------------------------- /packages/load-config/__test__/configs/basic/ds.config.json: -------------------------------------------------------------------------------- 1 | { "lint": { "noCache": true } } 2 | -------------------------------------------------------------------------------- /plugins/build/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/configs/babel.config').default; 2 | -------------------------------------------------------------------------------- /plugins/build/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/configs/postcss.config'); 2 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.15.4", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /plugins/update/README.md: -------------------------------------------------------------------------------- 1 | # Update Plugin 2 | 3 | Update the installed version of `@design-systems/cli`. 4 | -------------------------------------------------------------------------------- /packages/docs/src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intuit/design-systems-cli/HEAD/packages/docs/src/logo.png -------------------------------------------------------------------------------- /plugins/create-command/src/command.ts: -------------------------------------------------------------------------------- 1 | import cli from '@design-systems/create/dist/cli'; 2 | 3 | export default cli; 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # What Changed 2 | 3 | # Why 4 | 5 | Todo: 6 | 7 | - [ ] Add tests 8 | - [ ] Add docs 9 | -------------------------------------------------------------------------------- /plugins/bundle/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const config = require('./dist/webpack.base.config'); 2 | 3 | module.exports = config.default; 4 | -------------------------------------------------------------------------------- /packages/cli/bin/ds.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // eslint-disable-next-line import/no-unassigned-import 4 | require('../dist/cli'); 5 | -------------------------------------------------------------------------------- /packages/cli-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './create-logger'; 3 | export { default as which } from './which'; 4 | -------------------------------------------------------------------------------- /plugins/lint/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.d.ts 2 | **/node_modules 3 | **/dist 4 | **/out 5 | target/ 6 | **/templates/component/ 7 | **/templates/monorepo/ -------------------------------------------------------------------------------- /typings/storybook-cli-extract.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@storybook/cli/dist/cjs/extract' { 2 | export * from '@storybook/cli/dist/ts3.9/extract' 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./packages/eslint-config/index.js", 3 | "rules": { 4 | "complexity": 0, 5 | "no-await-in-loop": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/__tests__/test-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "design-systems-plugin-echo", 3 | "main": "index.js", 4 | "version": "0.0.1" 5 | } 6 | -------------------------------------------------------------------------------- /packages/create/src/index.ts: -------------------------------------------------------------------------------- 1 | import run from './run'; 2 | 3 | export { default as cli } from './cli'; 4 | export * from './run'; 5 | export default run; 6 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/bin/icon.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line import/no-unassigned-import 4 | require('../dist/cjs/index.js'); 5 | -------------------------------------------------------------------------------- /typings/eslint-formatter-pretty.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'eslint-formatter-pretty' { 2 | const format: (results: any) => string; 3 | 4 | export default format; 5 | } 6 | -------------------------------------------------------------------------------- /typings/postcss.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'postcss-mixins'; 2 | declare module 'postcss-hexrgba'; 3 | declare module 'postcss-modules'; 4 | declare module 'postcss-clean'; 5 | -------------------------------------------------------------------------------- /typings/file-size.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'file-size' { 2 | export default function getSizes( 3 | size: number 4 | ): { 5 | human(type: string): number; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/__tests__/test-plugin/index.js: -------------------------------------------------------------------------------- 1 | module.exports = class { 2 | run({ value }) { 3 | // eslint-disable-next-line no-console 4 | console.log(value); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/template-plugin/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | 3 | const command: CliCommand = { 4 | name: '{{kebab}}' 5 | }; 6 | 7 | export default command; 8 | -------------------------------------------------------------------------------- /packages/hooks/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require("@design-systems/test/jest.config.base"); 2 | const { name } = require("./package.json"); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name, 8 | }; -------------------------------------------------------------------------------- /packages/utils/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name 8 | }; 9 | -------------------------------------------------------------------------------- /packages/load-config/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name 8 | }; 9 | -------------------------------------------------------------------------------- /packages/next-esm-css/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name 8 | }; 9 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/__snapshots__/createInstanceIfDefined.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`it should create the instance 1`] = ` 4 | 5 | A string 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/omit.test.ts: -------------------------------------------------------------------------------- 1 | import { omit } from '../omit'; 2 | 3 | test('it should omit the specified props', () => { 4 | expect(omit({ foo: 'a', bar: 'b' }, ['foo'])).toStrictEqual({ 5 | bar: 'b' 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | 5 | "references": [ 6 | { 7 | "path": "packages/cli" 8 | }, 9 | { 10 | "path": "packages/babel-plugin-include-styles" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | // Monorepo support 5 | "baseUrl": ".", 6 | "paths": { 7 | "@design-systems/*": ["packages/*/src", "plugins/*/src"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /typings/template-directory.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'copy-template-dir' { 2 | export default function template( 3 | source: string, 4 | output: string, 5 | options: any, 6 | cb: (err: Error, createdFiles: string[]) => void 7 | ): void; 8 | } 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/src/exists.ts: -------------------------------------------------------------------------------- 1 | /** Check if a module exits */ 2 | export default function exists(name: string) { 3 | try { 4 | require.resolve(name); 5 | return true; 6 | } catch (error) { 7 | return false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/src/exists.ts: -------------------------------------------------------------------------------- 1 | /** Check if a module exits */ 2 | export default function exists(name: string) { 3 | try { 4 | require.resolve(name); 5 | return true; 6 | } catch (error) { 7 | return false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/next-esm-css/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/logger.test.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '../logger'; 2 | 3 | const log = jest.spyOn(global.console, 'log'); 4 | 5 | test('Log is noop when not in development', () => { 6 | logger.log('Testing!'); 7 | expect(log).not.toHaveBeenCalled(); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "./typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "./typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /typings/postcss-icss-selectors.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'postcss-icss-selectors' { 2 | import { Plugin } from 'postcss'; 3 | 4 | const plugin: Plugin<{ 5 | mode: 'local' | 'global'; 6 | generateScopedName: (name: string) => string; 7 | }>; 8 | export = plugin; 9 | } 10 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true, 9 | "baseUrl": ".", 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /typings/npm-which.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'npm-which' { 2 | interface Options { 3 | env?: string; 4 | cwd?: string; 5 | } 6 | 7 | interface Which { 8 | sync: (cmd: string, options?: Options) => string; 9 | } 10 | 11 | export default function create(cwd: string): Which; 12 | } 13 | -------------------------------------------------------------------------------- /packages/hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": [ 4 | "src/**/*", 5 | "../../typings/**/*" 6 | ], 7 | "compilerOptions": { 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "composite": true 11 | } 12 | } -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/src/__tests__/exists.test.ts: -------------------------------------------------------------------------------- 1 | import exists from '../exists'; 2 | 3 | test('should detect module exists', () => { 4 | expect(exists('./index.ts')).toBe(true); 5 | }); 6 | 7 | test('should detect module does not exists', () => { 8 | expect(exists('./foo.ts')).toBe(false); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/src/__tests__/exists.test.ts: -------------------------------------------------------------------------------- 1 | import exists from '../exists'; 2 | 3 | test('should detect module exists', () => { 4 | expect(exists('./index.ts')).toBe(true); 5 | }); 6 | 7 | test('should detect module does not exists', () => { 8 | expect(exists('./foo.ts')).toBe(false); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/svg/debugger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/displayName.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { displayName } from '../displayName'; 3 | 4 | test('it should attach the name', () => { 5 | const Text: React.FC = ({ children }) =>
{children}
; 6 | expect(displayName(Text, 'MyName').displayName).toBe('MyName'); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name, 8 | testPathIgnorePatterns: [ 9 | ...(base.testPathIgnorePatterns || []), 10 | 'test-plugin' 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /plugins/clean/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | collectCoverageFrom: [...base.collectCoverageFrom, '!**/command.ts'], 8 | displayName: name, 9 | setupFilesAfterEnv: undefined 10 | }; 11 | -------------------------------------------------------------------------------- /typings/storybook-react-standalone.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@storybook/react/standalone' { 2 | interface StorybookArgs { 3 | mode: string; 4 | port?: number; 5 | configDir: string; 6 | outputDir?: string; 7 | ci?: boolean; 8 | } 9 | function storybook(args: StorybookArgs): void; 10 | 11 | export = storybook; 12 | } 13 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as lerna } from './lerna'; 2 | export { default as getRepoRoot } from './get-repo-root'; 3 | export { default as getMonorepoRoot } from './get-monorepo-root'; 4 | export { default as monorepoName } from './monorepo-name'; 5 | export { default as loadUserWebpackConfig } from './load-user-webpack-config'; 6 | -------------------------------------------------------------------------------- /packages/core/__tests__/test-plugin/command.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'echo', 3 | description: 'shout into the void', 4 | examples: ['ds echo "Hello World"'], 5 | options: [ 6 | { 7 | name: 'value', 8 | type: String, 9 | defaultOption: true, 10 | description: 'what to shout' 11 | } 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /packages/utils/src/utils/arrayify.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalize a value to an array. 3 | * 4 | * @param value - The value to potentially convert to an array 5 | * 6 | * @example 7 | * arrayify('a') // = ['a'] 8 | * arrayify(['a']) // = ['a'] 9 | */ 10 | export const arrayify = (value: T | T[]) => 11 | Array.isArray(value) ? value : [value]; 12 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/svg/console.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /typings/createStylelint.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'stylelint/lib/createStylelint' { 2 | import stylelint from 'stylelint'; 3 | 4 | function createStylelint( 5 | options: Partial 6 | ): { 7 | getConfigForFile: (path: string) => { config: stylelint.Configuration }; 8 | }; 9 | 10 | export default createStylelint; 11 | } 12 | -------------------------------------------------------------------------------- /packages/create/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "src/templates.json", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [{ "path": "../cli-utils" }, { "path": "../plugin" }] 12 | } 13 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/__snapshots__/styled.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should render as custom element 1`] = ` 4 | 8 | link 9 | 10 | `; 11 | 12 | exports[`should render as jsx element 1`] = ` 13 | 16 | `; 17 | -------------------------------------------------------------------------------- /typings/playroom.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'playroom/lib' { 2 | type ErrorCallback = (e: Error) => void; 3 | 4 | interface Commands { 5 | start: (cb: ErrorCallback) => void; 6 | build: (cb: ErrorCallback) => void; 7 | } 8 | 9 | interface Config { 10 | cwd?: string; 11 | } 12 | 13 | export default function load(config: Config): Commands; 14 | } 15 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name, 8 | moduleNameMapper: undefined, 9 | testPathIgnorePatterns: [...base.testPathIgnorePatterns, '__tests__/utils.ts'] 10 | }; 11 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | const { name } = require('./package.json'); 3 | 4 | module.exports = { 5 | ...base, 6 | name, 7 | displayName: name, 8 | moduleNameMapper: undefined, 9 | testPathIgnorePatterns: [...base.testPathIgnorePatterns, '__tests__/utils.ts'] 10 | }; 11 | -------------------------------------------------------------------------------- /packages/utils/src/utils/fromEntries.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Object.fromEntries ponyfill while we wait for better babel support. 3 | * 4 | * @param entries - Array of object entries. 5 | * @example 6 | * fromEntries([['key', 'value']]) 7 | */ 8 | export const fromEntries = (entries: [string, any][]) => 9 | Object.assign({}, ...entries.map(([name, value]) => ({ [name]: value }))); 10 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/getProp.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { getProp } from '../getProp'; 3 | 4 | test('it should get the prop', () => { 5 | const Text: React.FC<{ id: string }> = ({ children }) => ( 6 |
{children}
7 | ); 8 | const instance = ; 9 | 10 | expect(getProp(instance, 'id')).toBe('foo'); 11 | }); 12 | -------------------------------------------------------------------------------- /typings/babel-config.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@design-systems/build/babel.config'; 2 | declare module '@royriojas/get-exports-from-file' { 3 | interface Result { 4 | exported: ({ name: string; default?: boolean })[]; 5 | } 6 | 7 | const fn: { 8 | cjs: (path: string) => Promise; 9 | es6: (path: string) => Promise; 10 | }; 11 | 12 | export = fn; 13 | } 14 | -------------------------------------------------------------------------------- /packages/docs/src/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![logo](./logo.png) 4 | 5 | > A CLI toolbox for creating design systems. 6 | 7 | - Create a design-system in minutes 8 | - Typescript, CSS, styled-components support 9 | - Outputs CJS and MJS 10 | - No tooling configuration required 11 | 12 | [GitHub](https://github.com/intuit/design-systems-cli) 13 | [Get Started](/welcome) 14 | -------------------------------------------------------------------------------- /plugins/build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/clean/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/lint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/proof/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/size/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/update/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/utils/src/utils/getProp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Attempt to retrieve a prop from a react node. 3 | * 4 | * @param el - The react node to get props from 5 | * @param prop - The name of the prop to get 6 | * 7 | * @example 8 | * getProp(child, 'id'); 9 | */ 10 | export const getProp = (el: React.ReactNode, prop: string) => 11 | el && typeof el === 'object' && 'props' in el && el.props[prop]; 12 | -------------------------------------------------------------------------------- /scripts/template-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /plugins/storybook/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 7 | 12 | -------------------------------------------------------------------------------- /plugins/bundle/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" }, 14 | { "path": "../build" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /plugins/playroom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" }, 14 | { "path": "../build" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /plugins/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" }, 14 | { "path": "../build" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/src/resolve-package.ts: -------------------------------------------------------------------------------- 1 | import findUp from 'find-up'; 2 | 3 | /** Get the package json for a require statement */ 4 | const resolvePackage = (name: string) => { 5 | try { 6 | return findUp.sync('package.json', { 7 | cwd: require.resolve(name) 8 | }); 9 | } catch (error) { 10 | // During testing fs will be undefiend 11 | } 12 | } 13 | 14 | export default resolvePackage; 15 | -------------------------------------------------------------------------------- /packages/load-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "../../packages/core" 14 | }, 15 | { 16 | "path": "../../packages/plugin" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /plugins/storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" }, 14 | { "path": "../build" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /typings/postcss-load.config.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'postcss-load-config' { 2 | export interface PostCSSConfig { 3 | plugins: any[]; 4 | options?: any; 5 | } 6 | 7 | interface Load { 8 | (context: object, path?: string, options?: object): Promise; 9 | sync(context: object, path?: string, options?: object): PostCSSConfig; 10 | } 11 | 12 | const load: Load; 13 | 14 | export default load; 15 | } 16 | -------------------------------------------------------------------------------- /plugins/create-command/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | 5 | "compilerOptions": { 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "composite": true 9 | }, 10 | 11 | "references": [ 12 | { "path": "../../packages/cli-utils" }, 13 | { "path": "../../packages/plugin" }, 14 | { "path": "../../packages/create" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "composite": true 8 | }, 9 | "references": [ 10 | { 11 | "path": "../load-config" 12 | }, 13 | { 14 | "path": "../cli-utils" 15 | }, 16 | { 17 | "path": "../core" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /plugins/build/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/build 2 | 3 | The main build plugin for the design-systems cli. 4 | 5 | ### Hooks 6 | 7 | The build plugin exposes some hooks to allow for plugins to manipulate the build output. 8 | 9 | **processCSSFiles** 10 | 11 | The **processCSSFiles** hook allows you to change the final CSS output. It's passed an array of CSS files to be minified and merged -- enabling you to change the order or contents of the CSS. 12 | -------------------------------------------------------------------------------- /plugins/create-command/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from '@design-systems/plugin'; 2 | import create, { 3 | CreateComponentArgs, 4 | CreateSystemArgs 5 | } from '@design-systems/create'; 6 | 7 | /** A plugin to the CLI that run the create script */ 8 | export default class CreatePlugin 9 | implements Plugin { 10 | async run(args: CreateComponentArgs | CreateSystemArgs) { 11 | create(args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/template-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createLogger } from '@design-systems/cli-utils'; 3 | import { Plugin } from '@design-systems/plugin'; 4 | 5 | export interface {{pascal}}Args { 6 | fix?: boolean; 7 | } 8 | 9 | export default class {{pascal}}Plugin implements Plugin<{{pascal}}Args> { 10 | private logger = createLogger({ scope: '{{kebab}}' }) 11 | 12 | async run(args: {{pascal}}Args) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typings/package-size.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'package-size' { 2 | export interface Options { 3 | registry?: string; 4 | resolve?: string[] | string; 5 | } 6 | 7 | interface SizeStats { 8 | name: string; 9 | versionedName: string; 10 | size: number; 11 | minified: number; 12 | gzipped: number; 13 | } 14 | 15 | export default function getSizes( 16 | packages: string, 17 | options: Options 18 | ): SizeStats; 19 | } 20 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@design-systems/test/jest.config.base'); 2 | 3 | module.exports = { 4 | ...base, 5 | setupFilesAfterEnv: [ 6 | '@testing-library/jest-dom/extend-expect' 7 | ], 8 | roots: ['', '/plugins/', '/packages/'], 9 | coverageDirectory: '/coverage/', 10 | collectCoverageFrom: [ 11 | 12 | ], 13 | testPathIgnorePatterns: [ 14 | ...(base.testPathIgnorePatterns || []), 15 | 'test-plugin' 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /packages/hooks/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './useClickOutside'; 2 | export * from './useConditionalAnimation'; 3 | export * from './useDarkMode'; 4 | export * from './useKeyboardNavigation'; 5 | export * from './useLocale'; 6 | export * from './useMatchMedia'; 7 | export * from './useOverflowing'; 8 | export * from './usePopper'; 9 | export * from './useReducedMotion'; 10 | export * from './usePrefersHighContrast'; 11 | export * from './usePrefersLessContrast'; 12 | export * from './useStickyState'; 13 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/get-repo-root.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import findUp from 'find-up'; 3 | 4 | /** Determine the directory of the repo */ 5 | const getRepoRoot = (cwd: string = process.cwd()) => { 6 | try { 7 | const repoRoot = findUp.sync('package.json', { 8 | cwd 9 | }); 10 | 11 | if (!repoRoot) { 12 | return ''; 13 | } 14 | 15 | return path.dirname(repoRoot); 16 | } catch (error) { 17 | return ''; 18 | } 19 | }; 20 | 21 | export default getRepoRoot; 22 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "strict": true, 6 | "allowSyntheticDefaultImports": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "esModuleInterop": true, 11 | "experimentalDecorators": true, 12 | "lib": ["esnext", "dom"], 13 | "resolveJsonModule": true, 14 | "downlevelIteration": true, 15 | "importHelpers": true, 16 | "jsx": "preserve" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /plugins/update/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | 3 | const command: CliCommand = { 4 | name: 'update', 5 | description: 'Update the installed version of `@design-systems/cli`', 6 | examples: ['ds update'], 7 | options: [ 8 | { 9 | name: 'release-notes', 10 | type: Boolean, 11 | description: 12 | 'Get the release notes of what you would get by running `ds update`. Will not perform the update.' 13 | } 14 | ] 15 | }; 16 | 17 | export default command; 18 | -------------------------------------------------------------------------------- /plugins/clean/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | 3 | const command: CliCommand = { 4 | name: 'clean', 5 | description: 'Remove dependencies and build files', 6 | options: [ 7 | { 8 | name: 'no-modules', 9 | description: "Don't delete the node_modules", 10 | type: Boolean 11 | }, 12 | { 13 | name: 'no-dist', 14 | description: "Don't delete the built dist directories", 15 | type: Boolean 16 | } 17 | ] 18 | }; 19 | 20 | export default command; 21 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/get-monorepo-root.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import findUp from 'find-up'; 3 | 4 | /** Determine the directory of the monorepo */ 5 | const getMonorepoRoot = (cwd: string = process.cwd()) => { 6 | try { 7 | const monorepoRoot = findUp.sync('lerna.json', { 8 | cwd 9 | }); 10 | 11 | if (!monorepoRoot) { 12 | return ''; 13 | } 14 | 15 | return path.dirname(monorepoRoot); 16 | } catch (error) { 17 | return ''; 18 | } 19 | }; 20 | 21 | export default getMonorepoRoot; 22 | -------------------------------------------------------------------------------- /packages/hooks/src/useReducedMotion.tsx: -------------------------------------------------------------------------------- 1 | import { useMatchMedia } from "./useMatchMedia"; 2 | 3 | /** 4 | * Determine if the user has "prefers-reduced-motion" enabled in their browser. 5 | * 6 | * @example 7 | * const Example = () => { 8 | * const isReducedMotion = useReducedMotion(); 9 | * 10 | * return ( 11 | *
Content
12 | * ); 13 | * }; 14 | */ 15 | export const useReducedMotion = () => { 16 | return useMatchMedia("(prefers-reduced-motion: reduce)"); 17 | }; -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/src/__tests__/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { transformSync } from '@babel/core'; 2 | import plugin from '../..'; 3 | 4 | /** Run the plugin on some code */ 5 | export const transform = (source: string, filename = '/test.js') => { 6 | const results = transformSync(source, { 7 | filename, 8 | plugins: [[plugin, { scope: 'cgds' }]], 9 | configFile: false 10 | }); 11 | 12 | if (results && results.code) { 13 | return results.code; 14 | } 15 | 16 | throw Error('Something went wrong.'); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/load-config/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/load-config 2 | 3 | This package handles loading the config for a `@design-systems/cli` project. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm i @design-systems/load-config 9 | # or with yarn 10 | yarn add @design-systems/load-config 11 | ``` 12 | 13 | ```js 14 | import { loadConfig, validateConfig } from '@design-systems/load-config'; 15 | 16 | loadConfig().then(config => { 17 | const validatedConfig = validateConfig(config); 18 | console.log('Loaded with config', validatedConfig); 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | We do not allow contributors to claim issues. If you find something interesting you can contribute to the repo, feel free to raise a PR 4 | 5 | 1. Fork the repo 6 | 1. Install dependencies locally `yarn` 7 | 1. Make sure it builds `yarn start` 8 | 1. Make your changes locally 9 | 1. Ensure the code coverage is the same or higher than before your changes. 10 | 1. Create a PR to the `master` branch 11 | 12 | To scaffold a new plugin inside this repo run the following command: 13 | 14 | `yarn run create:plugin "my plugin"` 15 | 16 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/src/__tests__/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { transformSync } from '@babel/core'; 2 | import plugin from '../../index'; 3 | 4 | /** Run the plugin on some code */ 5 | export const transform = (source: string, filename = 'test/test.js') => { 6 | const results = transformSync(source, { 7 | filename, 8 | plugins: [[plugin, { scope: 'cgds', use: 'test' }]], 9 | configFile: false, 10 | }); 11 | 12 | if (results && results.code) { 13 | return results.code; 14 | } 15 | 16 | throw Error('Something went wrong.'); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/hooks/src/usePrefersHighContrast.tsx: -------------------------------------------------------------------------------- 1 | import { useMatchMedia } from './useMatchMedia'; 2 | 3 | /** 4 | * Determine if the user has "prefers-contrast: more" enabled in their browser. 5 | * 6 | * @example 7 | * const Example = () => { 8 | * const isPrefersHighContrast = usePrefersHighContrast(); 9 | * 10 | * return ( 11 | *
Content
12 | * ); 13 | * }; 14 | */ 15 | export const usePrefersHighContrast = () => { 16 | return useMatchMedia('(prefers-contrast: more)'); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/hooks/src/usePrefersLessContrast.tsx: -------------------------------------------------------------------------------- 1 | import { useMatchMedia } from './useMatchMedia'; 2 | 3 | /** 4 | * Determine if the user has "prefers-contrast: less" enabled in their browser. 5 | * 6 | * @example 7 | * const Example = () => { 8 | * const isPrefersLessContrast = usePrefersLessContrast(); 9 | * 10 | * return ( 11 | *
Content
12 | * ); 13 | * }; 14 | */ 15 | export const usePrefersLessContrast = () => { 16 | return useMatchMedia('(prefers-contrast: less)'); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/cli-utils/src/which.ts: -------------------------------------------------------------------------------- 1 | import npmWhich from 'npm-which'; 2 | import path from 'path'; 3 | 4 | const w = npmWhich(process.cwd()); 5 | 6 | /** Locate a program or locally installed node module executable. */ 7 | export default function which(cmd: string): string { 8 | try { 9 | return w.sync(cmd); 10 | } catch (e) { 11 | try { 12 | return w.sync(cmd, { 13 | cwd: __dirname 14 | }); 15 | } catch (err) { 16 | return w.sync(cmd, { 17 | cwd: path.join(__dirname, '..', '..', '..') 18 | }); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/createInstanceIfDefined.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createInstanceIfDefined } from '../createInstanceIfDefined'; 3 | 4 | test('it should create the instance', () => { 5 | const Text: React.FC = ({ children }) =>
{children}
; 6 | 7 | expect(createInstanceIfDefined('A string', Text)).toMatchSnapshot(); 8 | }); 9 | 10 | test('it not create the instance', () => { 11 | const Text: React.FC = ({ children }) =>
{children}
; 12 | 13 | expect(createInstanceIfDefined(null, Text)).toBeUndefined(); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/hooks/src/useDarkMode.tsx: -------------------------------------------------------------------------------- 1 | import { useMatchMedia } from "./useMatchMedia"; 2 | 3 | /** 4 | * Determine if the user has a "prefers-color-scheme" mode enabled in their browser. 5 | * This is helpful for detecting if a user prefers dark mode. 6 | * 7 | * @example 8 | * const Example = () => { 9 | * const isDarkMode = useDarkMode(); 10 | * 11 | * return ( 12 | *
Content
13 | * ); 14 | * }; 15 | */ 16 | export const useDarkMode = () => { 17 | return useMatchMedia("(prefers-color-scheme: dark)"); 18 | }; -------------------------------------------------------------------------------- /packages/next-esm-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/next-esm-css", 3 | "version": "4.15.4", 4 | "main": "./src/index.js", 5 | "repository": "https://github.com/intuit/design-systems-cli.git", 6 | "author": "Andrew Lisowski lisowski54@gmail.com", 7 | "contributors": [ 8 | "Andrew Lisowski lisowski54@gmail.com" 9 | ], 10 | "license": "MIT", 11 | "files": [ 12 | "dist", 13 | "src", 14 | "!*.test.*", 15 | "!__snapshots__", 16 | "!__tests__" 17 | ], 18 | "dependencies": { 19 | "@babel/runtime": "^7.13.9" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/babel-plugin-include-styles 2 | 3 | This babel plugin will automatically include the styles for a scoped monorepo design system built with `@design-systems/cli`. 4 | 5 | ## Usage 6 | 7 | .babelrc: 8 | 9 | ```json 10 | { 11 | "plugins": [ 12 | ["@design-systems/babel-plugin-include-styles", { "scope": "my-scope" }] 13 | ] 14 | } 15 | ``` 16 | 17 | ## Example 18 | 19 | Input: 20 | 21 | ```js 22 | import Card from '@my-scope/card'; 23 | ``` 24 | 25 | Output: 26 | 27 | ```js 28 | import Card from '@my-scope/card'; 29 | import '@my-scope/card/dist/main.css'; 30 | ``` 31 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | on: 3 | pull_request: 4 | branches: [master] 5 | jobs: 6 | format: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | ref: ${{ github.head_ref }} 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '12.x' 15 | - name: Format 16 | run: | 17 | yarn install --frozen-lockfile 18 | yarn run format 19 | - name: Commit changes 20 | uses: stefanzweifel/git-auto-commit-action@v4.14.1 21 | with: 22 | commit_message: Apply formatting changes 23 | branch: ${{ github.head_ref }} 24 | -------------------------------------------------------------------------------- /packages/utils/src/utils/displayName.ts: -------------------------------------------------------------------------------- 1 | export interface DisplayNamed { 2 | /** The name various dev tools should use to display the component */ 3 | displayName?: string; 4 | } 5 | 6 | /** 7 | * Set a displayName on a component. This name is used in various dev tools. 8 | * 9 | * @param comp - The component to set the display name on 10 | * @param name - The display name for the component 11 | * 12 | * @example 13 | * displayName(Component, 'MyCoolComponent'); 14 | */ 15 | export const displayName = (comp: T, name: string) => { 16 | const newComp: T & DisplayNamed = comp; 17 | newComp.displayName = name; 18 | 19 | return newComp; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/__snapshots__/useStickyState.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`it should detect when an element is not sticking 1`] = ` 4 |
8 |
12 | not sticky 13 |
14 |
15 | `; 16 | 17 | exports[`it should detect when an element is sticking 1`] = ` 18 |
22 |
26 | sticky 27 |
28 |
29 | `; 30 | -------------------------------------------------------------------------------- /packages/utils/src/utils/createInstanceIfDefined.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * Create an instance of the component only if the element is defined. 5 | * 6 | * @param node - The node to check if it's defined 7 | * @param Component - The component to wrap the node in 8 | * 9 | * @example 10 | * const child = 'Foo' 11 | * createInstanceIfDefined(child, Wrapper) 12 | * // 'Foo' 13 | * const other = null 14 | * createInstanceIfDefined(other, Wrapper) 15 | * // undefined 16 | */ 17 | export const createInstanceIfDefined = ( 18 | node: React.ReactNode, 19 | Component: React.ComponentType 20 | ) => (node ? {node} : undefined); 21 | -------------------------------------------------------------------------------- /packages/load-config/__test__/extends.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import { loadConfig } from '../src'; 4 | 5 | jest.mock('@proof-ui/cli'); 6 | 7 | test('should handle no config', async () => { 8 | expect(await loadConfig({ cwd: '/' })).toStrictEqual({}); 9 | }); 10 | 11 | test('should return a valid config', async () => { 12 | const config = { lint: { noCache: true } }; 13 | 14 | expect( 15 | await loadConfig({ cwd: path.join(__dirname, './configs/extends') }) 16 | ).toStrictEqual(config); 17 | }); 18 | 19 | test('should throw on invalid config', async () => { 20 | expect(() => 21 | loadConfig({ cwd: path.join(__dirname, './configs/invalid') }) 22 | ).toThrow(Error); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/debounce.test.ts: -------------------------------------------------------------------------------- 1 | import { debounce } from '../debounce'; 2 | 3 | test('it should call the function once', () => { 4 | jest.useFakeTimers(); 5 | const spy = jest.fn(); 6 | const callback = debounce(spy); 7 | 8 | callback(); 9 | callback(); 10 | callback(); 11 | 12 | jest.runOnlyPendingTimers(); 13 | expect(spy).toHaveBeenCalledTimes(1); 14 | }); 15 | 16 | test('it should call the function immediately', () => { 17 | jest.useFakeTimers(); 18 | const spy = jest.fn(); 19 | const callback = debounce(spy, 100, true); 20 | 21 | callback(); 22 | callback(); 23 | callback(); 24 | 25 | jest.runOnlyPendingTimers(); 26 | expect(spy).toHaveBeenCalledTimes(1); 27 | }); 28 | -------------------------------------------------------------------------------- /scripts/template-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/{{kebab}}", 3 | "version": "{{version}}", 4 | "description": "The {{kebab}} command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/intuit/design-systems-cli.git" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "dependencies": { 22 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 23 | "@design-systems/plugin": "link:../../packages/plugin" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/plugin", 3 | "version": "4.15.4", 4 | "description": "Plugin structure for the @design-systems CLI", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/intuit/design-systems-cli.git" 20 | }, 21 | "dependencies": { 22 | "command-line-application": "0.10.1", 23 | "tslib": "2.0.1", 24 | "utility-types": "3.10.0" 25 | }, 26 | "keywords": [ 27 | "cli" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /plugins/test/src/jest/setupTests.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | 3 | if (typeof window !== 'undefined') { 4 | window.matchMedia = 5 | window.matchMedia || 6 | function() { 7 | return { 8 | matches: false 9 | }; 10 | }; 11 | } 12 | 13 | 14 | // @ts-ignore 15 | if (global.document) { 16 | /** Create a mock for createRange */ 17 | document.createRange = () => ({ 18 | /** Start stub */ 19 | setStart: () => { 20 | // range polyfill 21 | }, 22 | /** End stub */ 23 | setEnd: () => { 24 | // range polyfill 25 | }, 26 | // @ts-ignore 27 | commonAncestorContainer: { 28 | nodeName: 'BODY', 29 | ownerDocument: document 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | 14 | **Describe the solution you'd like** 15 | 16 | 17 | 18 | **Describe alternatives you've considered** 19 | 20 | 21 | 22 | **Additional context** 23 | 24 | -------------------------------------------------------------------------------- /packages/create/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | target: 'node', 6 | entry: './src/init.ts', 7 | output: { 8 | filename: 'bundle.js' 9 | }, 10 | resolve: { 11 | extensions: ['.ts', '.js', '.json'] 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.tsx?$/, 17 | loader: [ 18 | { 19 | loader: 'ts-loader', 20 | options: { 21 | transpileOnly: true 22 | } 23 | } 24 | ] 25 | } 26 | ] 27 | }, 28 | plugins: [ 29 | new webpack.BannerPlugin({ 30 | banner: '#!/usr/bin/env node', 31 | raw: true 32 | }) 33 | ], 34 | node: { 35 | __dirname: false 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | 12 | 13 | **To Reproduce** 14 | 15 | 16 | 17 | **Expected behavior** 18 | 19 | 20 | 21 | **Screenshots** 22 | 23 | 24 | 25 | **Desktop (please complete the following information):** 26 | 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | 33 | -------------------------------------------------------------------------------- /packages/create/src/copy.ts: -------------------------------------------------------------------------------- 1 | import copy from 'copy-template-dir'; 2 | import fs from 'fs'; 3 | 4 | /** Copy a template dir to some location. */ 5 | export default async function template( 6 | source: string, 7 | output: string, 8 | options: Record 9 | ): Promise { 10 | return new Promise((resolve, reject) => { 11 | copy(source, output, options, (err, createdFiles) => { 12 | if (err) { 13 | return reject(err); 14 | } 15 | 16 | createdFiles.forEach(file => { 17 | const contents = fs.readFileSync(file, { encoding: 'utf8' }); 18 | fs.writeFileSync( 19 | file, 20 | contents.replace(/\\{/g, '{').replace(/\\}/g, '}') 21 | ); 22 | }); 23 | 24 | resolve(createdFiles); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /plugins/proof/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from '@design-systems/cli-utils'; 2 | import { Plugin } from '@design-systems/plugin'; 3 | import { CLIArguments, main } from '@proof-ui/cli'; 4 | import { getConfig } from '@proof-ui/config'; 5 | 6 | /** 7 | * The design-systems plugin for cross-browser integration testing through proof 8 | */ 9 | export default class ProofPlugin implements Plugin { 10 | private logger = createLogger({ scope: 'proof' }); 11 | 12 | async run(args: CLIArguments) { 13 | try { 14 | const config = await getConfig(); 15 | const results = await main({ config, args }); 16 | 17 | if (results && results.failures) { 18 | process.exit(1); 19 | } 20 | } catch (e) { 21 | this.logger.error(e); 22 | process.exit(1); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/load-user-webpack-config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import webpack from 'webpack'; 4 | 5 | import getMonorepoRoot from './get-monorepo-root'; 6 | 7 | /** 8 | * Modifies the webpack config used for storybook and playroom by trying 9 | * to load the user's custom config. 10 | */ 11 | const loadUserWebpackConfig = ( 12 | config: webpack.Configuration, 13 | env: 'storybook' | 'playroom' 14 | ) => { 15 | const customConfig = path.join( 16 | getMonorepoRoot(), 17 | '.storybook/webpack.config.js' 18 | ); 19 | 20 | if (fs.existsSync(customConfig)) { 21 | // eslint-disable-next-line 22 | const userConfig = require(customConfig); 23 | return userConfig({ config, env }); 24 | } 25 | 26 | return config; 27 | }; 28 | 29 | export default loadUserWebpackConfig; 30 | -------------------------------------------------------------------------------- /packages/docs/src/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | - [Introduction](welcome.md) 3 | - [Getting Started](getting-started.md) 4 | - [Usage](usage.md) 5 | 6 | - Commands 7 | 8 | - [ds build](generated/build.md) 9 | - [ds bundle](generated/bundle.md) 10 | - [ds clean](generated/clean.md) 11 | - [ds create](generated/create.md) 12 | - [ds dev](generated/dev.md) 13 | - [ds lint](generated/lint.md) 14 | - [ds playroom](generated/playroom.md) 15 | - [ds proof](generated/proof.md) 16 | - [ds size](generated/size.md) 17 | - [ds storybook](generated/storybook.md) 18 | - [ds test](generated/test.md) 19 | - [ds update](generated/update.md) 20 | 21 | - Advanced 22 | 23 | - [Configuration](generated/configuration.md) 24 | - [GitHub Annotations](advanced/annotate.md) 25 | - [Plugins](advanced/plugins.md) 26 | 27 | - [Deploy](deploy.md) 28 | - [FAQ](faq.md) 29 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/useClickOutside.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, fireEvent } from '@testing-library/react'; 3 | import { useClickOutside } from '../useClickOutside'; 4 | 5 | test('it should return an element with the correct class - string', () => { 6 | const spy = jest.fn(); 7 | const Test = () => { 8 | const ref = useClickOutside(spy); 9 | 10 | return ( 11 |
12 | Click outside of me 13 |
14 | ); 15 | }; 16 | 17 | const { getByTestId } = render( 18 |
19 | 20 |
21 | ); 22 | 23 | fireEvent.mouseUp(getByTestId('target')); 24 | expect(spy).toHaveBeenCalledWith(false); 25 | fireEvent.mouseUp(getByTestId('root')); 26 | expect(spy).toHaveBeenCalledWith(true); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/src/icon.template: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import makeClass from 'clsx'; 3 | import { IconProps, IconSizes } from '../props'; 4 | 5 | import styles from '../Icon.css'; 6 | 7 | /** 8 | * {{iconName}} 9 | * 10 | * @param iconProps - SVG props 11 | */ 12 | const {{iconName}} = (iconProps: IconProps) => { 13 | const { className, fill, size, inline, ...html } = iconProps; 14 | const iconSize = size ? IconSizes[size] : undefined; 15 | 16 | return ( 17 | 27 | {{svg}} 28 | 29 | ); 30 | }; 31 | 32 | export default {{iconName}}; 33 | -------------------------------------------------------------------------------- /packages/cli-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/cli-utils", 3 | "version": "4.15.4", 4 | "description": "Utility functions for the @design-systems CLI", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/intuit/design-systems-cli.git" 20 | }, 21 | "dependencies": { 22 | "find-up": "5.0.0", 23 | "npm-which": "3.0.1", 24 | "signale": "1.4.0", 25 | "tslib": "2.0.1", 26 | "utility-types": "3.10.0", 27 | "webpack": "4.44.1" 28 | }, 29 | "devDependencies": { 30 | "@types/signale": "1.4.1" 31 | }, 32 | "keywords": [ 33 | "cli" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /plugins/storybook/.storybook/preset.js: -------------------------------------------------------------------------------- 1 | const modifyWebpack = require('./modify-webpack'); 2 | const path = require('path'); 3 | const glob = require('fast-glob'); 4 | 5 | const addons = [ 6 | // Panels 7 | '@alisowski/storybook-addon-notes/register-panel', 8 | 'storybook-addon-react-docgen', 9 | 'storybook-addon-jsx', 10 | '@storybook/addon-knobs', 11 | '@storybook/addon-actions', 12 | 13 | // Tools 14 | '@storybook/addon-backgrounds', 15 | 'storybook-dark-mode', 16 | 'storybook-addon-sketch/preset', 17 | '@storybook/addon-viewport', 18 | '@storybook/addon-a11y', 19 | ]; 20 | 21 | const stories = glob.sync( 22 | path.join( 23 | process.env.COMPONENT || process.cwd(), 24 | '**/*.stories.(tsx|js|jsx|mdx)' 25 | ), 26 | { ignore: ['**/node_modules'] } 27 | ); 28 | 29 | module.exports = { 30 | addons, 31 | stories, 32 | webpackFinal: modifyWebpack, 33 | }; 34 | -------------------------------------------------------------------------------- /plugins/proof/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | import { getAppDefinition } from '@proof-ui/cli'; 3 | import { getConfig } from '@proof-ui/config'; 4 | 5 | /** 6 | * Use the proof config to generate the CLI arg definitions 7 | */ 8 | async function getCommand(): Promise { 9 | const base = await getAppDefinition(await getConfig()); 10 | 11 | const options = (base.options || []).filter(command => { 12 | if (command.name === 'verbose') { 13 | return false; 14 | } 15 | 16 | return true; 17 | }); 18 | 19 | return { 20 | name: 'proof', 21 | description: 'Run automation against a storybook', 22 | examples: [ 23 | { 24 | desc: 'Run an axe scan on your local storybook', 25 | example: 'ds proof --local --local-grid --a11y' 26 | } 27 | ], 28 | options 29 | }; 30 | } 31 | 32 | export default getCommand; 33 | -------------------------------------------------------------------------------- /plugins/build/src/configs/babel.config.ts: -------------------------------------------------------------------------------- 1 | import babel from '@babel/core'; 2 | 3 | export default function (api: babel.ConfigAPI) { 4 | const isTest = api.env('test'); 5 | 6 | return { 7 | sourceMaps: true, 8 | presets: [ 9 | ['@babel/preset-env', { modules: api.env('module') ? false : 'auto' }], 10 | // This must come after env! otherwise interfaces might remain in the mjs files 11 | '@babel/preset-typescript', 12 | '@babel/preset-react', 13 | isTest && 'jest', 14 | ].filter(Boolean), 15 | plugins: [ 16 | '@babel/plugin-proposal-class-properties', 17 | '@babel/plugin-syntax-dynamic-import', 18 | 'babel-plugin-styled-components', 19 | 'macros', 20 | [ 21 | '@babel/plugin-transform-runtime', 22 | { 23 | regenerator: true, 24 | version: '7.11.2', 25 | }, 26 | ], 27 | ].filter(Boolean), 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /packages/hooks/src/useConditionalAnimation.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | /** 4 | * Only run a specific animation once a condition has changed. 5 | * This fixes a bug where the exit/entrance animation will run on first 6 | * render. 7 | * 8 | * @example 9 | * const Example = () => { 10 | * const exitAnimation = useConditionalAnimation( 11 | * styles.hide, 12 | * "animation-exit", 13 | * isOpen 14 | * ); 15 | * return ( 16 | *
Click outside of me
17 | * ); 18 | * }; 19 | */ 20 | export const useConditionalAnimation = ( 21 | defaultClass: string, 22 | animation: string, 23 | condition: boolean 24 | ) => { 25 | const entranceAnimation = React.useRef(defaultClass); 26 | 27 | React.useEffect(() => { 28 | if (condition) { 29 | entranceAnimation.current = animation; 30 | } 31 | }, [condition, animation]); 32 | 33 | return entranceAnimation.current; 34 | }; -------------------------------------------------------------------------------- /plugins/clean/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/clean", 3 | "version": "4.15.4", 4 | "description": "The clean command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build", 10 | "test": "../../packages/cli/bin/ds.js test" 11 | }, 12 | "files": [ 13 | "dist" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/intuit/design-systems-cli.git" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "dependencies": { 23 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 24 | "@design-systems/plugin": "link:../../packages/plugin", 25 | "fast-glob": "3.2.5", 26 | "fs-extra": "9.0.1", 27 | "tslib": "2.0.1" 28 | }, 29 | "devDependencies": { 30 | "@types/fs-extra": "9.0.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugins/dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/dev", 3 | "version": "4.15.4", 4 | "description": "The dev command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/intuit/design-systems-cli.git" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "dependencies": { 22 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 23 | "@design-systems/plugin": "link:../../packages/plugin", 24 | "chokidar": "3.4.2", 25 | "dedent": "0.7.0", 26 | "find-package-json": "1.2.0", 27 | "resolve-cwd": "3.0.0", 28 | "tslib": "2.0.1" 29 | }, 30 | "devDependencies": { 31 | "@types/find-package-json": "1.1.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/create/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/create 2 | 3 | Initialize a `@design-systems/cli` monorepo. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | npm init @design-systems 9 | ``` 10 | 11 | ## Updating templates 12 | 13 | 1. Update the master branch of one of the templates 14 | 2. Run the `templates:pick` then `templates:update` scripts from `packages/create` 15 | 16 | These scripts will do the following: 17 | 18 | - Checkout each branch 19 | - Cherry pick master 20 | - Push updated branch to remote 21 | - Record new SHA for updated template 22 | - Edit the `templates.json` file locally 23 | 24 | You should then make a pull request to [@design-systems/cli](https://github.com/intuit/design-systems-cli) with the updated SHAs. 25 | 26 | ### Setup 27 | 28 | For this script to work you must have the templates cloned to you machine and the must be siblings to the `cli`. 29 | 30 | ```txt 31 | cli/ 32 | monorepo-template/ 33 | component-template/ 34 | package-template/ 35 | ``` 36 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/docs", 3 | "version": "4.15.4", 4 | "private": true, 5 | "author": "Andrew Lisowski ", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/intuit/design-systems-cli.git" 9 | }, 10 | "dependencies": { 11 | "@design-systems/core": "link:../core", 12 | "@design-systems/load-config": "link:../load-config", 13 | "command-line-docs": "0.0.6", 14 | "dedent": "0.7.0", 15 | "mkdirp": "1.0.4", 16 | "tslib": "2.0.1" 17 | }, 18 | "scripts": { 19 | "generate": "./generate-docs.js", 20 | "dev": "yarn generate && docsify serve src", 21 | "deploy": "NODE_ENV=production && yarn generate && push-dir --cleanup --verbose --dir=src --branch=gh-pages --message='Updating Docs [skip ci]'" 22 | }, 23 | "devDependencies": { 24 | "command-line-docs": "0.0.6", 25 | "docsify-cli": "4.4.0", 26 | "push-dir": "0.4.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/utils/src/utils/omit.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Omit keys from a type. 3 | * 4 | * @example 5 | * Omit<{foo: string, bar: string}, 'foo'> 6 | * // = { bar: string } 7 | */ 8 | export type Omit = Pick>; 9 | 10 | /** 11 | * Omit keys from an object. 12 | * 13 | * @param obj - The object to omit props from 14 | * @param keys - A list of keys to omit 15 | * 16 | * @example Here is a simple example 17 | * ```ts 18 | * const result = omit({ foo: 'a', bar: 'b' }, 'foo') 19 | * // result = { bar: 'b' } 20 | * ``` 21 | * 22 | * @example Here is a another example 23 | * ```ts 24 | * const result = omit({ baz: 'a', bar: 'b' }, 'baz') 25 | * // result = { bar: 'b' } 26 | * ``` 27 | */ 28 | export const omit = ( 29 | obj: Props, 30 | keys: Prop[] 31 | ) => 32 | Object.fromEntries( 33 | Object.entries(obj).filter(([key]) => !keys.includes(key as Prop)) 34 | ) as Omit; 35 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/lerna.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import getMonorepoRoot from './get-monorepo-root'; 5 | 6 | export interface LernaStuff { 7 | /** The version of the monorepo */ 8 | version: string; 9 | /** The location of lerna.json */ 10 | configLocation: string; 11 | /** The config inside lerna.json */ 12 | config: Record; 13 | } 14 | 15 | /** Get the lerna config and its location. */ 16 | export default function lerna(): LernaStuff | undefined { 17 | const monorepoRoot = getMonorepoRoot(); 18 | 19 | if (!monorepoRoot) { 20 | return; 21 | } 22 | 23 | const lernaConfigFile = path.join(monorepoRoot, 'lerna.json'); 24 | const lernaConfig: Record = JSON.parse( 25 | readFileSync(lernaConfigFile, 'utf-8') 26 | ); 27 | 28 | return { 29 | configLocation: lernaConfigFile, 30 | version: lernaConfig.version, 31 | config: lernaConfig 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /plugins/storybook/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/storybook 2 | 3 | A plugin for `@design-syste/cli` to run and build a storybook. 4 | 5 | This package also contains: 6 | 7 | - `@design-systems/storybook/preset` - A preset with all of our recommended defaults for storybook 8 | - `@design-systems/storybook/preview` - Configures the preset in the preview 9 | 10 | ## Manual Configuration 11 | 12 | If you scafolded your design system on v2 then you should not need to do anything. 13 | If you are migrating from v1 to v2 you will need to add the following. 14 | v2 removed lots of custom logic around storybook and now everything is basically just a preset! 15 | 16 | ### Steps 17 | 18 | The preset *must* be in your `.storybook/main.js`. 19 | 20 | ```js 21 | { 22 | "presets": ["@design-systems/storybook/preset"] 23 | } 24 | ``` 25 | 26 | And you *must* import `@design-systems/storybook/preview` in your `.storybook/preview.js`. 27 | 28 | ```js 29 | import '@design-systems/storybook/preview'; 30 | ``` 31 | -------------------------------------------------------------------------------- /plugins/storybook/src/story2sketch.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export default ({ input, output }: { input: string; output: string }) => ({ 4 | output: path.join(process.cwd(), output), 5 | input: path.join(process.cwd(), input), 6 | url: `file://${path.join(process.cwd(), input)}`, 7 | concurrency: 2, 8 | verbose: true, 9 | fixPseudo: true, 10 | outputBy: 'kind', 11 | pageTitle: 'design-system', 12 | puppeteerOptions: { 13 | args: ['--no-sandbox', '--disable-setuid-sandbox'] 14 | }, 15 | viewports: { 16 | mobile: { 17 | width: 320, 18 | height: 900, 19 | symbolPrefix: 'Mobile/' 20 | }, 21 | tablet: { 22 | width: 768, 23 | height: 900, 24 | symbolPrefix: 'Tablet/' 25 | }, 26 | standard: { 27 | width: 1024, 28 | height: 768, 29 | symbolPrefix: 'Desktop/' 30 | }, 31 | large: { 32 | width: 1440, 33 | height: 800, 34 | symbolPrefix: 'LargeDesktop/' 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /packages/docs/src/welcome.md: -------------------------------------------------------------------------------- 1 |
2 | Designing a system 3 |
4 | 5 | # Welcome 6 | 7 | `@design-systems/cli` is a command line application (CLI) for creating and working with design systems in [react](https://reactjs.org/). 8 | It takes care of the boring parts of setting up a monorepo based component library so you can do what you do best, create amazing components! 9 | 10 | ## Features 11 | 12 | 🏗 Scaffold components and entire design systems 13 | 14 | 🎯 Build your components for multiple outputs (`cjs` and `esm`) 15 | 16 | 💅🏼 Write styles with styled-components or css-modules 17 | 18 | 📖 Craft excellent components using [Storybook](https://storybook.js.org/) 19 | 20 | 🧸 Let component consumer try your components with [playroom](https://github.com/seek-oss/playroom) 21 | 22 | 👮🏼‍♀️ Testing and linting support 23 | 24 | 💻 Typescript supported out of the box 25 | 26 | ⚖️ Track the size of your components and debug the changes 27 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/babel-plugin-replace-styles", 3 | "version": "4.15.4", 4 | "description": "A babel plugin that replaces CSS imports for a scoped monorepo design system built with `@design-systems/cli`", 5 | "main": "./dist/index.js", 6 | "types": "./dist", 7 | "author": "Tyler Krupicka Tyler_Krupicka@intuit.com", 8 | "contributors": [ 9 | "Tyler Krupicka Tyler_Krupicka@intuit.com" 10 | ], 11 | "license": "MIT", 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build tsconfig.json", 17 | "test": "../cli/bin/ds.js test", 18 | "lint": "../cli/bin/ds.js lint" 19 | }, 20 | "files": [ 21 | "dist", 22 | "!*.test.*", 23 | "!__snapshots__", 24 | "!__tests__" 25 | ], 26 | "dependencies": { 27 | "tslib": "2.0.1" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.13.8", 31 | "@babel/types": "^7.13.0", 32 | "@types/node": "14.14.31" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugins/lint/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import stylelint from 'stylelint'; 2 | 3 | export interface LintArgs { 4 | /** Attempt to fix the lint errors. */ 5 | fix?: boolean; 6 | /** Do not use any cached results from previous runs. */ 7 | noCache?: boolean; 8 | /** Post lint results as annotations to PR. Only in CI. */ 9 | annotate?: boolean; 10 | /** An optional list of files to lint */ 11 | files?: string[]; 12 | /** An optional number of warnings to trigger nonzero exit code, default: 1 */ 13 | maxWarnings?: number; 14 | } 15 | 16 | export type StylelintResult = stylelint.LinterResult & { 17 | /** Lines with needless disables */ 18 | needlessDisables?: { 19 | /** The files the needless disables are found in */ 20 | source: string; 21 | /** Ranges with needless disables */ 22 | ranges: { 23 | /** Rule name */ 24 | unusedRule: string; 25 | /** Line number */ 26 | start: number; 27 | }[]; 28 | }[]; 29 | }; 30 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/useReduceMotion.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useReducedMotion } from '../useReducedMotion'; 3 | 4 | test("it detect if reduced motion isn't set", () => { 5 | // @ts-ignore 6 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 7 | matches: false, 8 | addListener: jest.fn(), 9 | removeListener: jest.fn() 10 | })); 11 | 12 | const { result } = renderHook(() => useReducedMotion()); 13 | expect(result.current).toBe(false); 14 | }); 15 | 16 | test('it detect if reduced motion is set', () => { 17 | const addListener = jest.fn(); 18 | const removeListener = jest.fn(); 19 | // @ts-ignore 20 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 21 | matches: true, 22 | addListener, 23 | removeListener 24 | })); 25 | 26 | const { result, unmount } = renderHook(() => useReducedMotion()); 27 | 28 | expect(result.current).toBe(true); 29 | unmount(); 30 | expect(removeListener).toHaveBeenCalled(); 31 | }); -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/__snapshots__/createSlots.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`it should gather all instances into one bucket 1`] = ` 4 |
5 |
6 | top 7 |
8 |
9 | I 10 |
11 |
12 | have 13 |
14 |
15 | lots 16 |
17 |
18 | of 19 |
20 |
21 | bodies. 22 |
23 |
24 | bottom 25 |
26 |
27 | `; 28 | 29 | exports[`it should order the elements correctly 1`] = ` 30 |
31 |
32 | top 33 |
34 |
35 | middle 36 |
37 |
38 | bottom 39 |
40 |
41 | `; 42 | 43 | exports[`it should slot props 1`] = ` 44 |
45 | foo 46 |
47 | `; 48 | 49 | exports[`slots using tokens it should find all the slots 1`] = ` 50 |
51 |
52 | top 53 |
54 |
55 | middle 56 |
57 |
58 | bottom 59 |
60 |
61 | `; 62 | -------------------------------------------------------------------------------- /plugins/bundle/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | import dedent from 'dedent'; 3 | 4 | const command: CliCommand = { 5 | name: 'bundle', 6 | description: 'Creates a webpack bundle of your component', 7 | options: [ 8 | { 9 | name: 'debug', 10 | alias: 'd', 11 | type: Boolean, 12 | description: 'Produce a non-minified "debug" bundle' 13 | } 14 | ], 15 | footer: [ 16 | { 17 | header: 'Custom Babel Config', 18 | content: 19 | 'To customize your babel configuration, create a .babelrc at the root of your project' 20 | }, 21 | { 22 | header: 'Custom webpack config', 23 | content: [ 24 | dedent` 25 | To customize your webpack configuration, create webpack.config.js file at the root of the project. 26 | 27 | You'll want to extend the base config: \`@design-systems/bundle/webpack.config.base.js\` for things to function properly. 28 | ` 29 | ] 30 | } 31 | ] 32 | }; 33 | 34 | export default command; 35 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/src/logger.ts: -------------------------------------------------------------------------------- 1 | import { Signale, DefaultMethods, SignaleOptions } from 'signale'; 2 | 3 | const options: SignaleOptions< 4 | DefaultMethods | 'skip' | 'trace' | 'debug' | 'done' | 'disable' 5 | > = { 6 | types: { 7 | debug: { 8 | badge: '🦄', 9 | color: 'magenta', 10 | label: 'debug' 11 | }, 12 | skip: { 13 | badge: '🤷', 14 | color: 'yellow', 15 | label: 'Skipping...' 16 | }, 17 | info: { 18 | badge: '💾', 19 | color: 'cyan', 20 | label: 'info' 21 | }, 22 | complete: { 23 | badge: '🌟', 24 | color: 'green', 25 | label: 'complete' 26 | }, 27 | await: { 28 | badge: '⏳', 29 | color: 'cyan', 30 | label: 'awaiting' 31 | }, 32 | done: { 33 | badge: '🎉', 34 | color: 'greenBright', 35 | label: 'done' 36 | }, 37 | error: { 38 | badge: '🚒', 39 | color: 'red', 40 | label: 'error' 41 | } 42 | } 43 | }; 44 | const Logger = new Signale(options); 45 | export default Logger; 46 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/usePrefersHighContrast.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { usePrefersHighContrast } from '../usePrefersHighContrast'; 3 | 4 | test('it detect if prefers contrast more is set', () => { 5 | // @ts-ignore 6 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 7 | matches: true, 8 | addListener: jest.fn(), 9 | removeListener: jest.fn(), 10 | })); 11 | 12 | const { result } = renderHook(() => usePrefersHighContrast()); 13 | expect(result.current).toBe(true); 14 | }); 15 | 16 | test('it detect if prefers contrast less is set', () => { 17 | const addListener = jest.fn(); 18 | const removeListener = jest.fn(); 19 | // @ts-ignore 20 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 21 | matches: false, 22 | addListener, 23 | removeListener, 24 | })); 25 | 26 | const { result, unmount } = renderHook(() => usePrefersHighContrast()); 27 | 28 | expect(result.current).toBe(false); 29 | unmount(); 30 | expect(removeListener).toHaveBeenCalled(); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/usePrefersLessContrast.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { usePrefersLessContrast } from '../usePrefersLessContrast'; 3 | 4 | test('it detect if prefers contrast more is set', () => { 5 | // @ts-ignore 6 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 7 | matches: false, 8 | addListener: jest.fn(), 9 | removeListener: jest.fn(), 10 | })); 11 | 12 | const { result } = renderHook(() => usePrefersLessContrast()); 13 | expect(result.current).toBe(false); 14 | }); 15 | 16 | test('it detect if prefers contrast less is set', () => { 17 | const addListener = jest.fn(); 18 | const removeListener = jest.fn(); 19 | // @ts-ignore 20 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 21 | matches: true, 22 | addListener, 23 | removeListener, 24 | })); 25 | 26 | const { result, unmount } = renderHook(() => usePrefersLessContrast()); 27 | 28 | expect(result.current).toBe(true); 29 | unmount(); 30 | expect(removeListener).toHaveBeenCalled(); 31 | }); 32 | -------------------------------------------------------------------------------- /plugins/proof/proof.config.base.js: -------------------------------------------------------------------------------- 1 | const BabelPlugin = require('@proof-ui/babel-plugin').default; 2 | const JunitPlugin = require('@proof-ui/junit-plugin').default; 3 | const SkipPlugin = require('@proof-ui/skip-tests-plugin').default; 4 | const A11yPlugin = require('@proof-ui/a11y-plugin').default; 5 | const AddAllPlugin = require('@proof-ui/add-all-plugin').default; 6 | 7 | const babelConfig = { 8 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 9 | presets: [ 10 | '@babel/preset-env', 11 | '@babel/preset-typescript', 12 | 'babel-preset-power-assert' 13 | ], 14 | plugins: [ 15 | [ 16 | '@babel/plugin-transform-runtime', 17 | { 18 | regenerator: true 19 | } 20 | ] 21 | ] 22 | }; 23 | 24 | module.exports = { 25 | url: 'localhost:6006', 26 | logLevel: 'info', 27 | testMatch: './**/__automation__/**/*.proof.ts', 28 | plugins: [ 29 | new AddAllPlugin(), 30 | new JunitPlugin(), 31 | new SkipPlugin(), 32 | new A11yPlugin({ config: {} }), 33 | new BabelPlugin({ 34 | config: babelConfig 35 | }) 36 | ] 37 | }; 38 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/babel-plugin-include-styles", 3 | "version": "4.15.4", 4 | "description": "A babel plugin that automatically includes the styles for a scoped monorepo design system built with `@design-systems/cli`", 5 | "main": "./dist/index.js", 6 | "types": "./dist", 7 | "author": "Andrew Lisowski lisowski54@gmail.com", 8 | "contributors": [ 9 | "Andrew Lisowski lisowski54@gmail.com" 10 | ], 11 | "license": "MIT", 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build tsconfig.json", 17 | "test": "../cli/bin/ds.js test", 18 | "lint": "../cli/bin/ds.js lint" 19 | }, 20 | "files": [ 21 | "dist", 22 | "!*.test.*", 23 | "!__snapshots__", 24 | "!__tests__" 25 | ], 26 | "dependencies": { 27 | "@babel/traverse": "^7.13.0", 28 | "find-up": "5.0.0", 29 | "tslib": "2.0.1" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.13.8", 33 | "@babel/types": "^7.13.0", 34 | "@types/node": "14.14.31" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli-utils/src/utils/monorepo-name.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import getMonorepoRoot from './get-monorepo-root'; 5 | 6 | const DEFAULT_NAME = 'monorepo'; 7 | 8 | /** Determine if the monorepo name contains a scope. */ 9 | function getScope(pkgName: string): string { 10 | const match = pkgName.match(/@([\S]+)\//); 11 | if (!match) { 12 | return pkgName; 13 | } 14 | 15 | return match[1]; 16 | } 17 | 18 | /** Determine the monorepo's name. */ 19 | const monorepoName = () => { 20 | try { 21 | const monorepoRoot = getMonorepoRoot(); 22 | 23 | if (!monorepoRoot) { 24 | return DEFAULT_NAME; 25 | } 26 | 27 | const pkgName = JSON.parse( 28 | fs.readFileSync(path.join(monorepoRoot, 'package.json'), 'utf-8') 29 | ).name as string; 30 | 31 | let parsed = pkgName; 32 | 33 | if (pkgName && pkgName.startsWith('@')) { 34 | parsed = getScope(pkgName); 35 | } 36 | 37 | return parsed || DEFAULT_NAME; 38 | } catch (error) { 39 | return DEFAULT_NAME; 40 | } 41 | }; 42 | 43 | export default monorepoName; 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Intuit 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, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /plugins/update/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/update", 3 | "version": "4.15.4", 4 | "description": "The update command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/intuit/design-systems-cli.git" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "dependencies": { 22 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 23 | "@design-systems/plugin": "link:../../packages/plugin", 24 | "chalk": "4.1.0", 25 | "colorette": "1.2.2", 26 | "fs-extra": "9.0.1", 27 | "marked": "1.1.1", 28 | "marked-terminal": "4.1.1", 29 | "node-fetch": "2.6.1", 30 | "semver": "7.5.2", 31 | "tslib": "2.0.1" 32 | }, 33 | "devDependencies": { 34 | "@types/marked-terminal": "3.1.1", 35 | "@types/node-fetch": "2.5.8", 36 | "@types/semver": "7.3.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugins/test/src/jest.config.base.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | module.exports = { 4 | roots: ['', '/src'], 5 | reporters: ['default', 'jest-junit'], 6 | setupFilesAfterEnv: [ 7 | '@testing-library/jest-dom/extend-expect', 8 | 'polyfill-object.fromentries' 9 | ], 10 | collectCoverage: true, 11 | verbose: true, 12 | moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], 13 | setupFiles: [path.join(__dirname, './jest/setupTests.js')], 14 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/helpers/'], 15 | moduleNameMapper: { 16 | '\\.(css|md)$': 'identity-obj-proxy' 17 | }, 18 | transform: { 19 | '\\.(js|jsx|ts|tsx)$': path.join(__dirname, './jest/transform.js') 20 | }, 21 | coverageDirectory: 'target/coverage', 22 | coverageReporters: ['text', 'cobertura', 'html', 'lcov', 'json-summary'], 23 | collectCoverageFrom: [ 24 | '**/src/**', 25 | '!**/*.json', 26 | '!**/theme.*', 27 | '!**/*.stories.*', 28 | '!**/*.snippet.*', 29 | '!**/__tests__/**', 30 | '!**/*.snap', 31 | '!**/dist/**' 32 | ], 33 | testEnvironment: "jsdom" 34 | }; 35 | -------------------------------------------------------------------------------- /packages/stylelint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/stylelint-config", 3 | "version": "4.15.4", 4 | "description": "The stylelint-config of the @design-systems CLI", 5 | "main": "index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/intuit/design-systems-cli.git" 11 | }, 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "keywords": [ 16 | "stylelint", 17 | "stylelint-config" 18 | ], 19 | "dependencies": { 20 | "stylelint-a11y": "1.2.3", 21 | "stylelint-config-css-modules": "2.2.0", 22 | "stylelint-config-prettier": "8.0.2", 23 | "stylelint-config-recommended": "3.0.0", 24 | "stylelint-config-styled-components": "0.1.1", 25 | "stylelint-declaration-block-no-ignored-properties": "2.3.0", 26 | "stylelint-order": "4.1.0", 27 | "stylelint-processor-styled-components": "1.10.0", 28 | "stylelint-selector-tag-no-without-class": "2.0.3", 29 | "tslib": "2.0.1" 30 | }, 31 | "peerDependencies": { 32 | "stylelint": ">7.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/svg/data.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/useKeyboardNavigation.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { renderHook } from '@testing-library/react-hooks'; 3 | import { fireEvent, render } from '@testing-library/react'; 4 | import { useKeyboardNavigation } from '../useKeyboardNavigation'; 5 | 6 | test('defaults to false', () => { 7 | // @ts-ignore 8 | jest.spyOn(window, 'matchMedia').mockImplementation(() => ({ 9 | matches: false, 10 | addListener: jest.fn(), 11 | removeListener: jest.fn() 12 | })); 13 | 14 | const { result } = renderHook(() => useKeyboardNavigation()); 15 | expect(result.current).toBe(false); 16 | }); 17 | 18 | test('returns true when a user tabs', () => { 19 | const Test = () => { 20 | const isKeyboardNavigation = useKeyboardNavigation(); 21 | // eslint-disable-next-line jest/no-if 22 | return isKeyboardNavigation ?
: null; 23 | }; 24 | 25 | const { getByTestId, queryByTestId } = render( 26 |
27 | 28 |
29 | ); 30 | 31 | fireEvent.keyDown(getByTestId('root'), { key: 'Tab' }); 32 | expect(queryByTestId('keyboardNav')).toBeDefined(); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/utils/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | // https://davidwalsh.name/javascript-debounce-function 2 | 3 | type Args = any[]; 4 | type Base = (...args: Args) => void; 5 | 6 | export const WINDOW_RESIZE_DELAY = 150; 7 | 8 | /** 9 | * Only call a function once every 'wait" seconds. 10 | * 11 | * @param func - the callback to debounce 12 | * @param wait - how long to wait until to run the callback 13 | * @param immediate - run the callback immediately 14 | * 15 | * @example 16 | * const onClick = debounce( 17 | * () => console.log('I was clicked'), 18 | * 1000 19 | * ); 20 | */ 21 | export function debounce( 22 | func: Base, 23 | wait = WINDOW_RESIZE_DELAY, 24 | immediate = false 25 | ): Base { 26 | let timeout: NodeJS.Timeout | null; 27 | 28 | return function x(...args: Args) { 29 | /** The function that actually runs the user's function. */ 30 | const later = () => { 31 | timeout = null; 32 | 33 | if (!immediate) func(...args); 34 | }; 35 | 36 | const callNow = immediate && !timeout; 37 | 38 | if (timeout) { 39 | clearTimeout(timeout); 40 | } 41 | 42 | timeout = setTimeout(later, wait); 43 | 44 | if (callNow) func(...args); 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src/**/*.ts", "../../typings/**/*"], 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "composite": true 8 | }, 9 | "references": [ 10 | { 11 | "path": "../plugin" 12 | }, 13 | { 14 | "path": "../cli-utils" 15 | }, 16 | { 17 | "path": "../../plugins/build" 18 | }, 19 | { 20 | "path": "../../plugins/bundle" 21 | }, 22 | { 23 | "path": "../../plugins/clean" 24 | }, 25 | { 26 | "path": "../../plugins/create-command" 27 | }, 28 | { 29 | "path": "../../plugins/dev" 30 | }, 31 | { 32 | "path": "../../plugins/lint" 33 | }, 34 | { 35 | "path": "../../plugins/playroom" 36 | }, 37 | { 38 | "path": "../../plugins/size" 39 | }, 40 | { 41 | "path": "../../plugins/storybook" 42 | }, 43 | { 44 | "path": "../../plugins/test" 45 | }, 46 | { 47 | "path": "../../plugins/update" 48 | }, 49 | { 50 | "path": "../../plugins/proof" 51 | }, 52 | { 53 | "path": "../../plugins/bundle" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /packages/plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MultiCommand as AppMultiCommand, 3 | Command as AppCommand, 4 | Option as AppOption 5 | } from 'command-line-application'; 6 | import { Overwrite } from 'utility-types'; 7 | 8 | /** An Option for the @design-systems/cli */ 9 | export type Option = AppOption & { 10 | /** Whether the Option should be configurable via ds.config.json */ 11 | config?: boolean; 12 | }; 13 | 14 | interface Configurable { 15 | /** Options for the @design-systems/cli */ 16 | options?: Option[]; 17 | } 18 | 19 | /** An Command for the @design-systems/cli */ 20 | export type Command = Overwrite; 21 | 22 | /** An multi command for the @design-systems/cli */ 23 | export type MultiCommand = Overwrite< 24 | Overwrite, 25 | { 26 | /** Commands for the @design-systems/cli multi command */ 27 | commands: CliCommand[]; 28 | } 29 | >; 30 | 31 | /** Register a command with the cli */ 32 | export type CliCommand = Command | MultiCommand; 33 | 34 | /** A plugin to the @design-systems/cli */ 35 | export interface Plugin { 36 | /** Ran when the user inputs the registered command */ 37 | run(args: T): Promise; 38 | } 39 | -------------------------------------------------------------------------------- /plugins/lint/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | 3 | const command: CliCommand = { 4 | name: 'lint', 5 | description: 'Lint the project using eslint + stylelint', 6 | examples: ['ds lint'], 7 | options: [ 8 | { 9 | name: 'fix', 10 | type: Boolean, 11 | description: 'Try to fix the errors.' 12 | }, 13 | { 14 | name: 'no-cache', 15 | type: Boolean, 16 | description: 'Do not use any cached results from previous runs.', 17 | config: true 18 | }, 19 | { 20 | name: 'annotate', 21 | type: Boolean, 22 | description: 'Post lint results as annotations to PR. Only in CI.', 23 | config: true 24 | }, 25 | { 26 | name: 'files', 27 | type: String, 28 | description: 29 | 'A list of files to lint. Omit to automatically scan the repo.', 30 | multiple: true, 31 | defaultOption: true 32 | }, 33 | { 34 | name: 'max-warnings', 35 | type: Number, 36 | description: 'Number of warnings to trigger nonzero exit code.', 37 | multiple: false, 38 | defaultValue: Number.MAX_SAFE_INTEGER 39 | } 40 | ] 41 | }; 42 | 43 | export default command; 44 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/cli", 3 | "version": "4.15.4", 4 | "description": "A cli for scaffolding and building design systems", 5 | "author": "Adam Dierkens ", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc --build", 9 | "start": "yarn build --watch" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "files": [ 15 | "bin", 16 | "dist" 17 | ], 18 | "bin": { 19 | "ds": "./bin/ds.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/intuit/design-systems-cli.git" 24 | }, 25 | "dependencies": { 26 | "@design-systems/cli-utils": "link:../cli-utils", 27 | "@design-systems/core": "link:../core", 28 | "@design-systems/load-config": "link:../load-config", 29 | "command-line-application": "0.10.1", 30 | "deepmerge": "4.2.2", 31 | "dlv": "1.1.3", 32 | "dotenv": "8.2.0", 33 | "env-ci": "5.0.2", 34 | "fs-extra": "9.0.1", 35 | "tslib": "2.0.1", 36 | "update-check": "1.5.4" 37 | }, 38 | "keywords": [ 39 | "cli" 40 | ], 41 | "devDependencies": { 42 | "@types/dotenv": "6.1.1", 43 | "@types/env-ci": "3.1.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugins/playroom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/playroom", 3 | "version": "4.15.4", 4 | "description": "The playroom command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/intuit/design-systems-cli.git" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "dependencies": { 22 | "@babel/core": "^7.13.8", 23 | "@babel/register": "^7.13.8", 24 | "@design-systems/build": "link:../../plugins/build", 25 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 26 | "@design-systems/plugin": "link:../../packages/plugin", 27 | "babel-plugin-import-redirect": "1.1.1", 28 | "css-loader": "3.5.3", 29 | "dedent": "0.7.0", 30 | "fast-glob": "3.2.5", 31 | "file-loader": "6.2.0", 32 | "fs-extra": "9.0.1", 33 | "playroom": "0.25.0", 34 | "react-element-to-jsx-string": "14.3.2", 35 | "style-loader": "1.2.1", 36 | "tslib": "2.0.1", 37 | "webpack": "4.44.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/utils/src/utils/isReactInstanceOf.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type Renderable = 4 | | React.ComponentType 5 | | { 6 | /** a function to render the component */ 7 | render: React.ComponentType; 8 | }; 9 | 10 | /** 11 | * Determine whether a HTML element is an instance of a React component. 12 | * 13 | * @param element - Element to check the instance of 14 | * @param component - The component to check for 15 | * 16 | * @example 17 | * isReactInstanceOf(child, MyComponent) 18 | */ 19 | export function isReactInstanceOf( 20 | // IDK how to get rid of this any 21 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 22 | element: any, 23 | component: Renderable 24 | ) { 25 | const isComponent = 26 | element && element.type && typeof element.type !== 'string'; 27 | 28 | // For functional types 29 | if (element && element.type === component) { 30 | return true; 31 | } 32 | 33 | const renderFn = 'render' in component ? component.render : component; 34 | 35 | return ( 36 | isComponent && 37 | element.type.prototype && 38 | renderFn.prototype && 39 | element.type.prototype instanceof renderFn 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /packages/next-esm-css/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/next-esm-css 2 | 3 | This package enables using the `esm` output from the `ds build` command in a Next.js based project. 4 | Currently Next.js doesn't allow `node_modules` to import their own CSS so using components from a `ds` based project doesn't work. 5 | [See this issue](https://github.com/vercel/next.js/issues/13282). 6 | 7 | ## Installation 8 | 9 | ```sh 10 | npm i @design-systems/next-esm-css 11 | # or with yarn 12 | yarn add @design-systems/next-esm-css 13 | ``` 14 | 15 | ## Usage 16 | 17 | Simply require this package in your `next.config.js` and provide either a list of package names or a regex for everything in your design system scope. 18 | 19 | **`next.config.js`:** 20 | 21 | ```js 22 | const withEsmCss = require('@design-systems/next-esm-css')([ 23 | '@your-design-system-scope/.*', 24 | ]); 25 | 26 | module.exports = withEsmCss(); 27 | ``` 28 | 29 | Now Next.js won't break when you import a component build with `ds`! 30 | 31 | ## Inspration 32 | 33 | This package heavily borrows from [next-transpiled-modules](https://www.npmjs.com/package/next-transpile-modules). 34 | You could use that package instead, but this package is a bit faster when building since it only affects CSS. 35 | -------------------------------------------------------------------------------- /packages/load-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/load-config", 3 | "version": "4.15.4", 4 | "main": "./dist/index.js", 5 | "types": "./dist", 6 | "repository": "https://github.com/intuit/design-systems-cli.git", 7 | "author": "Andrew Lisowski lisowski54@gmail.com", 8 | "contributors": [ 9 | "Andrew Lisowski lisowski54@gmail.com" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "license": "MIT", 15 | "scripts": { 16 | "build": "tsc --build", 17 | "start": "yarn build --watch", 18 | "test": "../cli/bin/ds.js test" 19 | }, 20 | "files": [ 21 | "dist", 22 | "!*.test.*", 23 | "!__snapshots__", 24 | "!__tests__" 25 | ], 26 | "dependencies": { 27 | "@design-systems/core": "link:../core", 28 | "@design-systems/plugin": "link:../plugin", 29 | "change-case": "4.1.1", 30 | "cosmiconfig": "7.0.0", 31 | "dedent": "0.7.0", 32 | "deepmerge": "4.2.2", 33 | "dlv": "1.1.3", 34 | "flat": "5.0.2", 35 | "resolve": "1.17.0", 36 | "tslib": "2.0.1" 37 | }, 38 | "devDependencies": { 39 | "@types/cosmiconfig": "5.0.3", 40 | "@types/dlv": "1.1.2", 41 | "@types/flat": "5.0.1", 42 | "@types/resolve": "1.17.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugins/create-command/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/create-command", 3 | "version": "4.15.4", 4 | "description": "The create command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | "templates" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/intuit/design-systems-cli.git" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "dependencies": { 23 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 24 | "@design-systems/create": "link:../../packages/create", 25 | "@design-systems/plugin": "link:../../packages/plugin", 26 | "change-case": "4.1.1", 27 | "cli-spinners": "2.5.0", 28 | "colorette": "1.2.2", 29 | "copy-template-dir": "1.4.0", 30 | "dedent": "0.7.0", 31 | "degit": "https://github.com/hipstersmoothie/degit.git#private-release", 32 | "fs-extra": "9.0.1", 33 | "inquirer": "7.3.3", 34 | "progress-estimator": "0.3.0", 35 | "terminal-link": "2.1.1", 36 | "title-case": "3.0.3", 37 | "tslib": "2.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import endent from 'endent'; 2 | import { transform } from './helpers/utils'; 3 | import exists from '../exists'; 4 | 5 | jest.mock('../exists.ts'); 6 | jest.mock('@cgds/test/package.json', () => ({}), { virtual: true }); 7 | 8 | test('should not change css import', () => { 9 | const source = 'import "../main.css";'; 10 | expect(transform(source)).toBe('import "../main.css";'); 11 | }); 12 | 13 | test('should change the import', () => { 14 | const source = ` 15 | import styles from './Button.css'; 16 | import "../main.css"; 17 | `; 18 | // @ts-ignore 19 | exists.mockReturnValueOnce(true).mockReturnValueOnce(true); 20 | // @ts-ignore 21 | exists.mockReturnValueOnce(true).mockReturnValueOnce(true); 22 | expect(transform(source, '@cgds/button/dist/test.js')).toBe(endent` 23 | import styles from "./Button-test.css"; 24 | import "../test.css"; 25 | `); 26 | }); 27 | 28 | test('should not change the import if the new file does not exist', () => { 29 | const source = 'import "../main.css";'; 30 | // @ts-ignore 31 | exists.mockReturnValueOnce(true).mockReturnValueOnce(false); 32 | expect(transform(source, '@cgds/button/dist/test.js')).toBe('import "../test.css";'); 33 | }); 34 | -------------------------------------------------------------------------------- /plugins/build/src/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import colorette from 'colorette'; 3 | 4 | /** Convert a path to its output destination. */ 5 | export function getOutPath( 6 | inDir: string, 7 | fPath: string, 8 | outDir: string 9 | ): string { 10 | return path.join(outDir, path.relative(inDir, path.dirname(fPath))); 11 | } 12 | 13 | interface FormatErrorOptions { 14 | /** File the error is in */ 15 | file: string; 16 | /** Line the error is on */ 17 | line: string; 18 | /** Column in line the error is on */ 19 | column: string; 20 | /** Tool used to create error. */ 21 | tool: string; 22 | /** Message for the error */ 23 | message: string; 24 | /** Source code where the error is */ 25 | code: string; 26 | } 27 | 28 | /** Format an error in a Typescript style so they match across tools */ 29 | export function formatError({ 30 | file, 31 | line, 32 | column, 33 | tool, 34 | message, 35 | code 36 | }: FormatErrorOptions) { 37 | return `${colorette.cyanBright( 38 | path.relative(process.env.INIT_CWD || process.cwd(), file) 39 | )}:${colorette.yellowBright(line)}:${colorette.yellowBright( 40 | column 41 | )} - ${colorette.redBright('error')} ${colorette.dim( 42 | tool 43 | )}: ${message.trim()}\n${code.trim()}`; 44 | } 45 | -------------------------------------------------------------------------------- /packages/core/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import getApplicationDefinition, { getCommands, getPlugin } from '../src'; 3 | 4 | describe('getCommands', () => { 5 | test('should find commands', async () => { 6 | const commands = await getCommands(); 7 | expect(commands.length > 0).toBe(true); 8 | }); 9 | }); 10 | 11 | describe('getPlugin', () => { 12 | test('should not find fake plugin', async () => { 13 | const plugin = await getPlugin('foo-bar'); 14 | expect(plugin).toBeUndefined(); 15 | }); 16 | 17 | test('should find real plugin', async () => { 18 | const plugin = await getPlugin('test'); 19 | expect(plugin).not.toBeUndefined(); 20 | }); 21 | 22 | test('should find user plugin', async () => { 23 | const plugin = await getPlugin('echo', { 24 | plugins: [path.join(__dirname, './test-plugin')] 25 | }); 26 | expect(plugin).not.toBeUndefined(); 27 | }); 28 | }); 29 | 30 | describe('getApplicationDefinition', () => { 31 | test('get CLI definition', async () => { 32 | const cli = await getApplicationDefinition(); 33 | expect(cli.name).not.toBeUndefined(); 34 | expect(cli.description).not.toBeUndefined(); 35 | // @ts-ignore 36 | expect(cli.commands.length > 0).toBe(true); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /plugins/dev/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | import dedent from 'dedent'; 3 | 4 | const command: CliCommand = { 5 | name: 'dev', 6 | description: 7 | 'Builds a component, any dependent component in the same monorepo, and starts storybook for just the component.', 8 | examples: ['ds dev'], 9 | footer: [ 10 | { 11 | header: 'Solving Circular Dependencies for Stories', 12 | content: 13 | dedent` 14 | In a monorepo full of components you might get circlular dependencies when writing rich component documentation. 15 | 16 | To get around this problem "ds-cli" supports a special package.json property to track these dependenceies called "storyDependencies". 17 | Because it's not an official part of the package.json "lerna" won't use these dependencies to find the build order. 18 | 19 | The "dev" command will build these dependencies while running. 20 | ` 21 | }, 22 | { 23 | code: true, 24 | content: dedent` 25 | \`\`\`json 26 | { 27 | "storyDependencies": { 28 | "@my-system/field": "link:../Field" 29 | } 30 | } 31 | \`\`\` 32 | ` 33 | } 34 | ] 35 | }; 36 | 37 | export default command; 38 | -------------------------------------------------------------------------------- /plugins/bundle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/bundle", 3 | "version": "4.15.4", 4 | "description": "The bundle command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/intuit/design-systems-cli.git" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "dependencies": { 22 | "@design-systems/build": "link:../build", 23 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 24 | "@design-systems/plugin": "link:../../packages/plugin", 25 | "case-sensitive-paths-webpack-plugin": "2.3.0", 26 | "dedent": "0.7.0", 27 | "duplicate-package-checker-webpack-plugin": "3.0.0", 28 | "get-monorepo-packages": "1.2.0", 29 | "postcss-loader": "^4.2.0", 30 | "style-loader": "1.2.1", 31 | "terser-webpack-plugin": "4.1.0", 32 | "tslib": "2.0.1", 33 | "webpack": "4.44.1" 34 | }, 35 | "devDependencies": { 36 | "@types/case-sensitive-paths-webpack-plugin": "2.1.4", 37 | "@types/duplicate-package-checker-webpack-plugin": "2.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | 3 | # These files are generated 4 | packages/docs/src/generated 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | tsconfig.tsbuildinfo 13 | .docz 14 | junit.xml 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | 65 | # VSCode workspace settings 66 | .vscode 67 | 68 | dist 69 | .DS_Store 70 | -------------------------------------------------------------------------------- /plugins/proof/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/proof", 3 | "version": "4.15.4", 4 | "description": "The proof command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | "proof.config.base.js" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/intuit/design-systems-cli.git" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "dependencies": { 23 | "@babel/core": "^7.13.8", 24 | "@babel/plugin-transform-runtime": "^7.13.9", 25 | "@babel/preset-typescript": "^7.13.0", 26 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 27 | "@design-systems/plugin": "link:../../packages/plugin", 28 | "@proof-ui/a11y-plugin": "^0.1.4", 29 | "@proof-ui/add-all-plugin": "^0.1.4", 30 | "@proof-ui/babel-plugin": "^0.1.4", 31 | "@proof-ui/cli": "^0.1.4", 32 | "@proof-ui/config": "^0.1.4", 33 | "@proof-ui/junit-plugin": "^0.1.4", 34 | "@proof-ui/skip-tests-plugin": "^0.1.4", 35 | "babel-preset-power-assert": "3.0.0", 36 | "tslib": "2.0.1" 37 | }, 38 | "devDependencies": { 39 | "@types/signale": "1.4.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugins/test/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | 3 | const command: CliCommand = { 4 | name: 'test', 5 | description: 6 | 'Runs all tests found in `src/**/__tests__/**.test.js` through jest', 7 | examples: [ 8 | { desc: 'Run all the tests in watch mode', example: 'ds test -w' }, 9 | { 10 | desc: 'Run tests on specific files', 11 | example: 'ds test __tests__/basic __tests__/complex' 12 | } 13 | ], 14 | options: [ 15 | { 16 | name: 'update', 17 | alias: 'u', 18 | type: Boolean, 19 | description: 'Update snapshots' 20 | }, 21 | { 22 | name: 'watch', 23 | alias: 'w', 24 | type: Boolean, 25 | description: 'Watch for changes' 26 | }, 27 | { 28 | name: 'annotate', 29 | type: Boolean, 30 | description: 'Post test results as annotations to PR. Only in CI.', 31 | config: true 32 | }, 33 | { 34 | name: 'files', 35 | type: String, 36 | defaultOption: true, 37 | multiple: true, 38 | description: 'Array of files or directories to find test' 39 | }, 40 | { 41 | name: 'runInBand', 42 | type: Boolean, 43 | description: 'Run all tests serially in the current process.' 44 | } 45 | ] 46 | }; 47 | 48 | export default command; 49 | -------------------------------------------------------------------------------- /plugins/lint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/lint", 3 | "version": "4.15.4", 4 | "description": "The lint command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | ".eslintignore" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/intuit/design-systems-cli.git" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "dependencies": { 23 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 24 | "@design-systems/eslint-config": "link:../../packages/eslint-config", 25 | "@design-systems/plugin": "link:../../packages/plugin", 26 | "@design-systems/stylelint-config": "link:../../packages/stylelint-config", 27 | "eslint": "7.6.0", 28 | "eslint-formatter-github": "^1.0.0", 29 | "eslint-formatter-pretty": "4.0.0", 30 | "fast-glob": "3.2.5", 31 | "get-monorepo-packages": "1.2.0", 32 | "stylelint": "13.7.0", 33 | "stylelint-formatter-github": "^1.0.0", 34 | "stylelint-formatter-pretty": "2.1.0", 35 | "tslib": "2.0.1" 36 | }, 37 | "devDependencies": { 38 | "@types/eslint": "7.2.2", 39 | "@types/stylelint": "9.10.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugins/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/test", 3 | "version": "4.15.4", 4 | "description": "The test command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | "jest.config.base.js" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/intuit/design-systems-cli.git" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "dependencies": { 23 | "@babel/core": "^7.13.8", 24 | "@design-systems/build": "link:../build", 25 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 26 | "@design-systems/plugin": "link:../../packages/plugin", 27 | "@jest/transform": "27.2.1", 28 | "@jest/types": "27.1.1", 29 | "@testing-library/jest-dom": "5.11.4", 30 | "@types/jest": "26.0.20", 31 | "babel-plugin-istanbul": "6.0.0", 32 | "env-ci": "5.0.2", 33 | "identity-obj-proxy": "3.0.0", 34 | "jest": "27.2.1", 35 | "jest-github-reporter": "^1.0.0", 36 | "jest-junit": "11.1.0", 37 | "polyfill-object.fromentries": "1.0.1", 38 | "tslib": "2.0.1" 39 | }, 40 | "devDependencies": { 41 | "@types/babel__core": "7.1.9", 42 | "@types/env-ci": "3.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugins/lint/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createLogger, 3 | } from '@design-systems/cli-utils'; 4 | import { Plugin } from '@design-systems/plugin'; 5 | import { lintJS, lintCSS } from './utils/lintUtils'; 6 | import {LintArgs} from './interfaces'; 7 | 8 | const logger = createLogger({ scope: 'lint' }); 9 | 10 | /** A plugin to lint the project's files for errors. */ 11 | export default class LintPlugin implements Plugin { 12 | async run(args: LintArgs) { 13 | if (process.env.CLI_APP_ID && process.env.CLI_PRIVATE_KEY) { 14 | process.env.ESLINT_APP_ID = 15 | process.env.ESLINT_APP_ID || process.env.CLI_APP_ID; 16 | process.env.ESLINT_PRIVATE_KEY = 17 | process.env.ESLINT_PRIVATE_KEY || process.env.CLI_PRIVATE_KEY; 18 | 19 | process.env.STYLELINT_APP_ID = 20 | process.env.STYLELINT_APP_ID || process.env.CLI_APP_ID; 21 | process.env.STYLELINT_PRIVATE_KEY = 22 | process.env.STYLELINT_PRIVATE_KEY || process.env.CLI_PRIVATE_KEY; 23 | } 24 | 25 | try { 26 | const jsReturnCode = await lintJS(args); 27 | const cssReturnCode = await lintCSS(args); 28 | 29 | logger.debug({ jsReturnCode, cssReturnCode }); 30 | 31 | if (jsReturnCode + cssReturnCode > 0) { 32 | process.exit(1); 33 | } 34 | } catch (e) { 35 | logger.error(e); 36 | process.exit(1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/docs/src/advanced/annotate.md: -------------------------------------------------------------------------------- 1 | # GitHub Annotations 2 | 3 | `@design-systems` CLI ship with the ability to create [GitHub Checks](https://developer.github.com/v3/checks/) and annotations for some of it's commands. 4 | 5 | Commands with annotations support: 6 | 7 | - `lint` - Add annotations in JS/TS and CSS for errors and warnings 8 | - `test` - Add annotations in test files for failed tests 9 | 10 | Just use the `--annotate` flag and install the GitHub apps then the commands should create annotations when run in a CI environment. 11 | 12 | Apps to install: 13 | 14 | - [stylelint-results](https://github.com/apps/stylelint-results) 15 | - [jest-results](https://github.com/apps/jest-results) 16 | - [eslint-results](https://github.com/apps/eslint-results) 17 | 18 | ## Customize GitHub App 19 | 20 | You can customize the GitHub app that is used to create the annotations. This step is necassary to support GitHub enterprise instances. 21 | 22 | Either set environment variables for each tool: 23 | 24 | - `JEST_APP_ID` 25 | - `JEST_PRIVATE_KEY` 26 | - `ESLINT_APP_ID` 27 | - `ESLINT_PRIVATE_KEY` 28 | - `STYLELINT_APP_ID` 29 | - `STYLELINT_PRIVATE_KEY` 30 | 31 | Or if you want just 1 GitHub app to manage the annotations: 32 | 33 | - `CLI_APP_ID` 34 | - `CLI_PRIVATE_KEY` 35 | 36 | If given `CLI_APP_ID` and `CLI_PRIVATE_KEY` it is assumed that you want annotations so you do not need to use the `--annoation` flag. 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Open source projects are “living.” Contributions in the form of issues and pull requests are welcomed and encouraged. When you contribute, you explicitly say you are part of the community and abide by its Code of Conduct. 2 | 3 | # The Code 4 | 5 | At Intuit, we foster a kind, respectful, harassment-free cooperative community. Our open source community works to: 6 | 7 | - Be kind and respectful; 8 | - Act as a global community; 9 | - Conduct ourselves professionally. 10 | 11 | As members of this community, we will not tolerate behaviors including, but not limited to: 12 | 13 | - Violent threats or language; 14 | - Discriminatory or derogatory jokes or language; 15 | - Public or private harassment of any kind; 16 | - Other conduct considered inappropriate in a professional setting. 17 | 18 | ## Reporting Concerns 19 | 20 | If you see someone violating the Code of Conduct please email TechOpenSource@intuit.com 21 | 22 | ## Scope 23 | 24 | This code of conduct applies to: 25 | 26 | All repos and communities for Intuit-managed projects, whether or not the text is included in a Intuit-managed project’s repository; 27 | 28 | Individuals or teams representing projects in official capacity, such as via official social media channels or at in-person meetups. 29 | 30 | ## Attribution 31 | 32 | This Code of Conduct is partly inspired by and based on those of Amazon, CocoaPods, GitHub, Microsoft, thoughtbot, and on the Contributor Covenant version 1.4.1. -------------------------------------------------------------------------------- /packages/hooks/src/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | /** 4 | * This hook allows you to determine when a user clicks outside of an HTML Element. 5 | * 6 | * @param callback - Run when the document is clicked 7 | * 8 | * @example 9 | * const Example = () => { 10 | * const ref = useClickOutside(() => console.log('clicked outside!')); 11 | * 12 | * return ( 13 | *
{'Click outside of me'}
14 | * ); 15 | * }; 16 | */ 17 | export const useClickOutside = ( 18 | callback: (clickedOutside: boolean) => void 19 | ) => { 20 | const trigger: React.MutableRefObject = React.useRef(null); 21 | 22 | React.useEffect(() => { 23 | const doc = (trigger.current && trigger.current.ownerDocument) || document; 24 | /** The event handler determining if the user clicked outside. */ 25 | const onDocumentClick = (e: Event) => { 26 | if (trigger.current && trigger.current.contains(e.target as Node)) { 27 | callback(false); 28 | e.stopPropagation(); 29 | } else { 30 | callback(true); 31 | } 32 | }; 33 | 34 | doc.addEventListener("mouseup", onDocumentClick); 35 | doc.addEventListener("touchend", onDocumentClick); 36 | 37 | return () => { 38 | doc.removeEventListener("mouseup", onDocumentClick); 39 | doc.removeEventListener("touchend", onDocumentClick); 40 | }; 41 | }, [callback]); 42 | 43 | return trigger; 44 | }; -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/portal.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cleanup, render } from '@testing-library/react'; 3 | import { Portal } from '../portal'; 4 | 5 | afterEach(() => { 6 | cleanup() 7 | }) 8 | 9 | test('it should render the content in a dom node outside of the parent', () => { 10 | const { getByTestId } = render( 11 |
12 | 13 |
Rendered elsewhere
14 |
15 |
16 | ); 17 | 18 | expect(getByTestId('portal')).toBeInTheDocument(); 19 | expect( 20 | getByTestId('root').querySelector('[data-testid="portal"]') 21 | ).not.toBeInTheDocument(); 22 | }); 23 | 24 | test('it should work with hydrate', () => { 25 | const options = { hydrate: true }; 26 | const { getByTestId } = render( 27 |
28 | 29 |
Rendered elsewhere
30 |
31 |
, 32 | options 33 | ); 34 | expect(getByTestId('portal')).toBeInTheDocument(); 35 | }); 36 | 37 | test('it should clean up', () => { 38 | const { queryByTestId, unmount } = render( 39 |
40 | 41 |
Rendered elsewhere
42 |
43 |
44 | ); 45 | expect(queryByTestId('portal')).toBeInTheDocument(); 46 | unmount(); 47 | expect(queryByTestId('portal')).toBeNull(); 48 | }); 49 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Open source projects are “living.” Contributions in the form of issues and pull requests are welcomed and encouraged. When you contribute, you explicitly say you are part of the community and abide by its Code of Conduct. 2 | 3 | # The Code 4 | 5 | At Intuit, we foster a kind, respectful, harassment-free cooperative community. Our open source community works to: 6 | 7 | - Be kind and respectful; 8 | - Act as a global community; 9 | - Conduct ourselves professionally. 10 | 11 | As members of this community, we will not tolerate behaviors including, but not limited to: 12 | 13 | - Violent threats or language; 14 | - Discriminatory or derogatory jokes or language; 15 | - Public or private harassment of any kind; 16 | - Other conduct considered inappropriate in a professional setting. 17 | 18 | ## Reporting Concerns 19 | 20 | If you see someone violating the Code of Conduct please email TechOpenSource@intuit.com 21 | 22 | ## Scope 23 | 24 | This code of conduct applies to: 25 | 26 | All repos and communities for Intuit-managed projects, whether or not the text is included in a Intuit-managed project’s repository; 27 | 28 | Individuals or teams representing projects in official capacity, such as via official social media channels or at in-person meetups. 29 | 30 | ## Attribution 31 | 32 | This Code of Conduct is partly inspired by and based on those of Amazon, CocoaPods, GitHub, Microsoft, thoughtbot, and on the Contributor Covenant version 1.4.1. 33 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/eslint-config", 3 | "version": "4.15.4", 4 | "description": "The eslint-config of the @design-systems CLI", 5 | "main": "index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/intuit/design-systems-cli.git" 11 | }, 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "dependencies": { 16 | "@design-systems/cli-utils": "link:../cli-utils", 17 | "@kendallgassner/eslint-plugin-package-json": "0.2.1", 18 | "@typescript-eslint/eslint-plugin": "4.16.1", 19 | "@typescript-eslint/parser": "4.16.1", 20 | "babel-eslint": "10.1.0", 21 | "eslint": "7.6.0", 22 | "eslint-config-airbnb": "18.2.0", 23 | "eslint-config-prettier": "6.11.0", 24 | "eslint-config-xo": "0.32.1", 25 | "eslint-config-xo-react": "0.23.0", 26 | "eslint-import-resolver-lerna": "https://github.com/hipstersmoothie/eslint-import-resolver-lerna.git#catch", 27 | "eslint-plugin-import": "2.22.0", 28 | "eslint-plugin-jest": "23.20.0", 29 | "eslint-plugin-jsdoc": "30.2.2", 30 | "eslint-plugin-jsx-a11y": "6.3.1", 31 | "eslint-plugin-mdx": "1.8.1", 32 | "eslint-plugin-no-explicit-type-exports": "0.11.4", 33 | "eslint-plugin-prettier": "3.1.4", 34 | "eslint-plugin-react": "7.20.6", 35 | "eslint-plugin-react-hooks": "4.2.0", 36 | "tslib": "2.0.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'command-line-application'; 2 | 3 | const cli: Command = { 4 | name: 'cli', 5 | description: 'Convert a directory of SVG files to wrapped React Components.', 6 | examples: ['icon --svg ./svg --out ./icons'], 7 | require: ['svg', 'out'], 8 | options: [ 9 | { 10 | name: 'svg', 11 | type: String, 12 | description: 'The path to the directory of .svg files to convert.', 13 | }, 14 | { 15 | name: 'template', 16 | type: String, 17 | description: 'The path to the template where the SVG will be added.', 18 | }, 19 | { 20 | name: 'out', 21 | type: String, 22 | description: 'The directory to put the results in.', 23 | }, 24 | { 25 | name: 'overwrite', 26 | type: Boolean, 27 | description: 'Overwrite output files that already exist.', 28 | }, 29 | { 30 | name: 'no-mapping', 31 | type: Boolean, 32 | description: 'Whether to output a mapping file.', 33 | defaultValue: false, 34 | }, 35 | { 36 | name: 'no-strip-color', 37 | type: Boolean, 38 | description: 'Whether to disable removing color from the SVGs', 39 | defaultValue: false, 40 | }, 41 | { 42 | name: 'name-suffix', 43 | type: String, 44 | description: 'The suffix to put after the name of a generated icon', 45 | defaultValue: 'Icon', 46 | }, 47 | ], 48 | }; 49 | 50 | export default cli; 51 | -------------------------------------------------------------------------------- /packages/utils/src/utils/__tests__/isReactInstanceOf.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import * as React from 'react'; 4 | import { isReactInstanceOf } from '../isReactInstanceOf'; 5 | 6 | test('it determine if the child is an instance - Function Component', () => { 7 | const Text = () =>
foo
; 8 | const instance = ; 9 | 10 | expect(isReactInstanceOf(instance, Text)).toBe(true); 11 | }); 12 | 13 | test('it determine if the child is not an instance - Function Component', () => { 14 | const Test = () =>
foo
; 15 | const instance =
bar
; 16 | 17 | expect(isReactInstanceOf(instance, Test)).toBe(false); 18 | }); 19 | 20 | test('it determine if the child is an instance - Class Component', () => { 21 | class Test extends React.Component { 22 | render() { 23 | return
foo
; 24 | } 25 | } 26 | 27 | const instance = ; 28 | 29 | expect(isReactInstanceOf(instance, Test)).toBe(true); 30 | }); 31 | 32 | test('it determine if the child is not an instance - Class Component', () => { 33 | class Test extends React.Component { 34 | render() { 35 | return
foo
; 36 | } 37 | } 38 | 39 | const instance =
bar
; 40 | 41 | expect(isReactInstanceOf(instance, Test)).toBe(false); 42 | }); 43 | 44 | test('handles non function', () => { 45 | const instance =
bar
; 46 | 47 | // @ts-ignore 48 | expect(isReactInstanceOf(instance, {})).toBe(false); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/babel-plugin-include-styles/src/__tests__/with-deps.test.ts: -------------------------------------------------------------------------------- 1 | import { transform } from './helpers/utils'; 2 | import exists from '../exists'; 3 | import resolvePackage from '../resolve-package'; 4 | 5 | jest.mock('../exists.ts'); 6 | jest.mock('../resolve-package.ts'); 7 | 8 | jest.mock('@cgds/last/package.json', () => ({ name: '@cgds/last' }), { 9 | virtual: true 10 | }); 11 | 12 | jest.mock( 13 | '@cgds/other/package.json', 14 | () => ({ name: '@cgds/other', dependencies: { '@cgds/last': '1.0.0' } }), 15 | { virtual: true } 16 | ); 17 | 18 | jest.mock( 19 | '@cgds/test/package.json', 20 | () => ({ name: '@cgds/test', dependencies: { '@cgds/other': '1.0.0' } }), 21 | { virtual: true } 22 | ); 23 | 24 | const resolveSpy = resolvePackage as jest.Mock; 25 | 26 | test("should add dependency's css", () => { 27 | // @ts-ignore 28 | exists.mockReturnValueOnce(true); 29 | resolveSpy.mockReturnValueOnce('@cgds/test/package.json'); 30 | // @cgds/other doesn't have css itself, but import components that do have css 31 | // @ts-ignore 32 | exists.mockReturnValueOnce(false); 33 | resolveSpy.mockReturnValueOnce('@cgds/other/package.json'); 34 | // @ts-ignore 35 | exists.mockReturnValueOnce(true); 36 | resolveSpy.mockReturnValueOnce('@cgds/last/package.json'); 37 | 38 | const source = 'import Test from "@cgds/test";'; 39 | expect(transform(source)).toBe( 40 | 'import Test from "@cgds/test";\nimport "@cgds/last/dist/main.css";\nimport "@cgds/test/dist/main.css";' 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/hooks", 3 | "version": "4.15.4", 4 | "main": "./dist/cjs/index.js", 5 | "module": "./dist/esm/index.js", 6 | "types": "./dist", 7 | "repository": "https://github.com/intuit/design-systems-cli", 8 | "author": "Melinda Li contact.melindali@gmail.com", 9 | "sideEffects": false, 10 | "contributors": [ 11 | "Tyler Krupicka Tyler_Krupicka@intuit.com" 12 | ], 13 | "license": "MIT", 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "scripts": { 18 | "clean": "ds clean", 19 | "build": "ds build", 20 | "start": "ds build --watch", 21 | "test": "ds test", 22 | "lint": "ds lint", 23 | "size": "ds size", 24 | "docs": "ts-readme" 25 | }, 26 | "files": [ 27 | "dist", 28 | "src", 29 | "!*.test.*", 30 | "!__snapshots__", 31 | "!__tests__" 32 | ], 33 | "dependencies": { 34 | "@babel/runtime": "7.14.0", 35 | "@design-systems/cli": "link:../cli", 36 | "@design-systems/utils": "link:../utils", 37 | "@popperjs/core": "^2.9.0", 38 | "@testing-library/react": "11.2.7", 39 | "@testing-library/react-hooks": "7.0.0", 40 | "fromentries": "1.3.2", 41 | "react-fast-compare": "3.2.0", 42 | "resize-observer-polyfill": "1.5.1", 43 | "tslib": "^2.3.0", 44 | "use-isomorphic-layout-effect": "1.1.1" 45 | }, 46 | "peerDependencies": { 47 | "react": ">= 16.8.6", 48 | "react-dom": ">= 16.8.6" 49 | }, 50 | "devDependencies": { 51 | "ts-readme": "1.1.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/hooks/src/useLocale.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | /** Get the locale using navigator */ 4 | const getLocale = (initial?: string) => { 5 | let modern: string | undefined; 6 | if (navigator.languages && navigator.languages.length) { 7 | [modern] = navigator.languages; 8 | } 9 | 10 | const old = navigator.language; 11 | return modern || old || initial || "en-US"; 12 | }; 13 | 14 | /** 15 | * This hook allows you to get the users Internationalization locale string. 16 | * If one is not found, it defaults to US English, or a provided default. 17 | * 18 | * @param initial - A default locale string, if you want something different than en-US. 19 | * 20 | * @example 21 | * const Example = () => { 22 | * const locale = useLocale(); 23 | * const localeModifiedDefault = useLocale("en-GB"); 24 | * 25 | * return ( 26 | *
The users locale is {locale}
27 | * ); 28 | * }; 29 | */ 30 | export const useLocale = (initial?: string) => { 31 | const [locale, setLocale] = React.useState(getLocale(initial)); 32 | 33 | /** Watch for locale changes */ 34 | React.useEffect(() => { 35 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/languagechange_event 36 | /** Set locale state */ 37 | const languageListener = () => { 38 | setLocale(getLocale()); 39 | }; 40 | 41 | window.addEventListener("languagechange", languageListener); 42 | 43 | return () => { 44 | window.removeEventListener("languagechange", languageListener); 45 | }; 46 | }); 47 | 48 | return locale; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/core/__tests__/plugins.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { tryRequire, requirePlugin, getPlugins } from '../src/plugins'; 3 | 4 | describe('tryRequire', () => { 5 | test("should not find module that doesn't exists", async () => { 6 | expect(tryRequire('foo-bar')).toStrictEqual({}); 7 | }); 8 | 9 | test('should find module that does exists', async () => { 10 | const plugin = tryRequire('@design-systems/test'); 11 | expect(plugin).not.toStrictEqual({}); 12 | expect(plugin).not.toBeUndefined(); 13 | }); 14 | }); 15 | 16 | describe('requirePlugin', () => { 17 | test('should find official plugin', async () => { 18 | expect(requirePlugin('test')).not.toBeUndefined(); 19 | }); 20 | 21 | test('should find official plugin-command', async () => { 22 | expect(requirePlugin('create')).not.toBeUndefined(); 23 | }); 24 | 25 | test('should find plugin from path', async () => { 26 | expect( 27 | requirePlugin(path.join(__dirname, './test-plugin')) 28 | ).not.toBeUndefined(); 29 | }); 30 | 31 | test("should not find plugins that don't exist", async () => { 32 | expect(requirePlugin('foo-bar')).toBeUndefined(); 33 | }); 34 | }); 35 | 36 | describe('getPlugins', () => { 37 | test('should work with no plugins in config', async () => { 38 | expect(getPlugins()).toHaveLength(0); 39 | }); 40 | 41 | test('should get plugins from config', async () => { 42 | const plugins = getPlugins({ 43 | plugins: [path.join(__dirname, './test-plugin')] 44 | }); 45 | expect(Object.keys(plugins[0][1]).length > 0).toBe(true); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/core", 3 | "version": "4.15.4", 4 | "description": "The core structure of the @design-systems CLI", 5 | "author": "Adam Dierkens ", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "scripts": { 9 | "build": "tsc --build", 10 | "start": "yarn build --watch", 11 | "test": "../cli/bin/ds.js test" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "files": [ 17 | "bin", 18 | "dist" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/intuit/design-systems-cli.git" 23 | }, 24 | "dependencies": { 25 | "@design-systems/build": "link:../../plugins/build", 26 | "@design-systems/bundle": "link:../../plugins/bundle", 27 | "@design-systems/clean": "link:../../plugins/clean", 28 | "@design-systems/cli-utils": "link:../cli-utils", 29 | "@design-systems/create-command": "link:../../plugins/create-command", 30 | "@design-systems/dev": "link:../../plugins/dev", 31 | "@design-systems/lint": "link:../../plugins/lint", 32 | "@design-systems/playroom": "link:../../plugins/playroom", 33 | "@design-systems/plugin": "link:../plugin", 34 | "@design-systems/proof": "link:../../plugins/proof", 35 | "@design-systems/size": "link:../../plugins/size", 36 | "@design-systems/storybook": "link:../../plugins/storybook", 37 | "@design-systems/test": "link:../../plugins/test", 38 | "@design-systems/update": "link:../../plugins/update", 39 | "import-cwd": "3.0.0", 40 | "tslib": "2.0.1" 41 | }, 42 | "keywords": [ 43 | "cli" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

5 | 6 |

A CLI toolbox for creating design systems.

7 |
8 | 9 | [Read the full documentation](https://intuit.github.io/design-systems-cli/#/welcome) 10 | 11 | ``` 12 | ds 13 | 14 | A toolbox for creating design systems 15 | 16 | Synopsis 17 | 18 | $ ds 19 | 20 | Commands 21 | 22 | dev Builds a component, any dependent component in the same monorepo, and starts storybook for just the component. 23 | clean Remove dependencies and build files 24 | build Builds a React Component located in `./src` and outputs `cjs`, `mjs`, and `css` into `dist` 25 | test Runs all tests found in `src/**/__tests__/**.test.js` through jest 26 | lint Lint the project using eslint + stylelint 27 | storybook Create a storybook for your component 28 | playroom Create a playroom for your components 29 | size Determine how the bundle size will be affected by your changes. 30 | create Scaffold a new `ds` component or system 31 | update Update the installed version of `@design-systems/cli` 32 | proof Run automation against a storybook 33 | bundle Creates a webpack bundle of your component 34 | 35 | Global Options 36 | 37 | -v, --verbose Output the debug logs. Debug: -v Trace: -vv 38 | --version Print the current version of the current @design-systems/cli 39 | --no-version-warning Prevent the CLI from warning about new versions 40 | -h, --help Display the help output 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/hooks/src/__tests__/useStickyState.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, act } from '@testing-library/react'; 3 | import { useStickyState } from '../useStickyState'; 4 | 5 | test('it should detect when an element is not sticking', () => { 6 | jest.useFakeTimers(); 7 | const Test = () => { 8 | const ref = React.useRef(null); 9 | const isSticky = useStickyState(ref); 10 | 11 | return ( 12 |
17 | {isSticky ? 'sticky' : 'not sticky'} 18 |
19 | ); 20 | }; 21 | 22 | const { container } = render( 23 |
24 | 25 |
26 | ); 27 | 28 | act(() => { 29 | jest.runOnlyPendingTimers(); 30 | }); 31 | 32 | expect(container.firstChild).toMatchSnapshot(); 33 | }); 34 | 35 | test('it should detect when an element is sticking', () => { 36 | jest.useFakeTimers(); 37 | const Test = () => { 38 | const ref = React.useRef(null); 39 | const isSticky = useStickyState(ref); 40 | 41 | return ( 42 |
47 | {isSticky ? 'sticky' : 'not sticky'} 48 |
49 | ); 50 | }; 51 | 52 | const { container } = render( 53 |
54 | 55 |
56 | ); 57 | 58 | act(() => { 59 | jest.runOnlyPendingTimers(); 60 | }); 61 | 62 | expect(container.firstChild).toMatchSnapshot(); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/hooks/src/useMatchMedia.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * Determine if the user has media preferences enabled in their browser. 5 | * 6 | * @example 7 | * const Example = () => { 8 | * const isReducedMotion = useMatchMedia('(prefers-reduced-motion: reduce)'); 9 | * 10 | * return ( 11 | *
Content
12 | * ); 13 | * }; 14 | */ 15 | export const useMatchMedia = (selector: string) => { 16 | const [mediaState, setMediaState] = React.useState( 17 | window ? window.matchMedia(selector).matches : false 18 | ); 19 | 20 | React.useEffect(() => { 21 | if (!window) { 22 | return; 23 | } 24 | 25 | const mediaQuery = window.matchMedia(selector); 26 | 27 | /** Ran when the user changes this setting. */ 28 | const changeMatchMedia = () => setMediaState(!mediaState); 29 | 30 | /** Use newer method if is available, else fall back to deprecated method */ 31 | if (typeof mediaQuery.addEventListener === 'function') { 32 | /** This method listens when the user toggles the reduced motion option */ 33 | /** https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener */ 34 | mediaQuery.addEventListener('change', changeMatchMedia); 35 | } else { 36 | mediaQuery.addListener(changeMatchMedia); 37 | } 38 | 39 | return () => { 40 | if (typeof mediaQuery.removeEventListener === 'function') { 41 | mediaQuery.removeEventListener('change', changeMatchMedia); 42 | } else { 43 | mediaQuery.removeListener(changeMatchMedia); 44 | } 45 | }; 46 | }, [mediaState, selector]); 47 | 48 | return mediaState; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/utils/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | type logFunction = (message?: any, ...optionalParams: any[]) => void; 4 | 5 | /** Noop version of a logging function */ 6 | // eslint-disable-next-line 7 | const noop = (message?: any, ...optionalParams: any[]) => {}; 8 | 9 | /** Subset of console.log calls that are removed outside of development */ 10 | interface LoggerInterface { 11 | /** 12 | * The `console.debug()` function is an alias for {@link console.log()}. 13 | */ 14 | debug: logFunction; 15 | /** 16 | * Prints to `stderr` with newline. 17 | */ 18 | error: logFunction; 19 | /** 20 | * The {@link console.info()} function is an alias for {@link console.log()}. 21 | */ 22 | info: logFunction; 23 | /** 24 | * Prints to `stdout` with newline. 25 | */ 26 | log: logFunction; 27 | /** 28 | * The {@link console.warn()} function is an alias for {@link console.error()}. 29 | */ 30 | warn: logFunction; 31 | } 32 | 33 | /** Logger which no-ops in production */ 34 | const noopLogger: LoggerInterface = { 35 | debug: noop, 36 | error: noop, 37 | info: noop, 38 | log: noop, 39 | warn: noop 40 | }; 41 | 42 | /** Logger which returns console in development mode */ 43 | const consoleLogger: LoggerInterface = { 44 | debug: console.debug, 45 | error: console.error, 46 | info: console.info, 47 | log: console.log, 48 | warn: console.warn 49 | }; 50 | 51 | /** 52 | * Logger 53 | * A logging utility which maps to console in development but 54 | * is a no-op in production. 55 | */ 56 | export const logger = 57 | process.env.NODE_ENV === 'development' ? consoleLogger : noopLogger; 58 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/utils", 3 | "version": "4.15.4", 4 | "description": "Helper utilities for developing react components", 5 | "main": "./dist/cjs/index.js", 6 | "module": "./dist/esm/index.js", 7 | "types": "./dist", 8 | "sideEffects": false, 9 | "repository": "https://github.com/intuit/design-systems-cli.git", 10 | "author": "Andrew Lisowski lisowski54@gmail.com", 11 | "contributors": [ 12 | "Andrew Lisowski lisowski54@gmail.com" 13 | ], 14 | "license": "MIT", 15 | "scripts": { 16 | "build": "ds build", 17 | "test": "ds test", 18 | "lint": "ds lint", 19 | "docs": "ts-readme" 20 | }, 21 | "files": [ 22 | "dist", 23 | "src", 24 | "!*.test.*", 25 | "!__snapshots__", 26 | "!__tests__" 27 | ], 28 | "dependencies": { 29 | "@babel/runtime": "^7.13.9", 30 | "clsx": "^1.0.4", 31 | "focus-lock": "^0.8.0", 32 | "react-merge-refs": "^1.0.0" 33 | }, 34 | "peerDependencies": { 35 | "@types/react": "*", 36 | "react": ">= 16.8.6", 37 | "react-dom": ">= 16.8.6" 38 | }, 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "devDependencies": { 43 | "@design-systems/cli": "link:../cli", 44 | "@testing-library/jest-dom": "4.0.0", 45 | "@testing-library/react": "8.0.7", 46 | "@testing-library/react-hooks": "1.1.0", 47 | "@types/classnames": "2.2.11", 48 | "@types/react-dom": "16.9.0", 49 | "auto": "10.36.5", 50 | "husky": "3.0.1", 51 | "lint-staged": "9.2.1", 52 | "prettier": "1.19.1", 53 | "react": "16.8.6", 54 | "react-dom": "16.8.6", 55 | "react-test-renderer": "16.8.6", 56 | "ts-readme": "1.1.3", 57 | "typescript": "4.2.2" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/create/scripts/update-templates.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { execSync } = require('child_process'); 6 | 7 | const templatesPath = path.join(__dirname, '../src/templates.json'); 8 | const currentTemplates = JSON.parse(fs.readFileSync(templatesPath, 'utf8')); 9 | 10 | const sources = { 11 | system: path.join(__dirname, '../../../../monorepo-template'), 12 | component: path.join(__dirname, '../../../../component-template'), 13 | package: path.join(__dirname, '../../../../package-template') 14 | }; 15 | 16 | /** Get the SHA for the HEAD of a branch */ 17 | function getSha(branch) { 18 | return execSync(`git rev-parse ${branch}`) 19 | .toString() 20 | .trim(); 21 | } 22 | 23 | /** Go through each template and update their sha if needed */ 24 | Object.entries(currentTemplates).forEach(([type, templates]) => { 25 | // eslint-disable-next-line no-console 26 | console.log('TYPE:', type); 27 | process.chdir(sources[type]); 28 | 29 | templates.forEach(template => { 30 | const branch = template.name === 'ts' ? 'master' : template.name; 31 | const sha = getSha(branch); 32 | 33 | // The templates.json is out of date with the SHAs on the system 34 | if (template.sha !== sha) { 35 | // eslint-disable-next-line no-console 36 | console.log( 37 | `Template "${template.name}" is out of date! Updating to: ${sha}` 38 | ); 39 | // eslint-disable-next-line no-console 40 | console.log({ old: template.sha, new: sha }); 41 | 42 | // eslint-disable-next-line no-param-reassign 43 | template.sha = sha; 44 | } 45 | }); 46 | }); 47 | 48 | fs.writeFileSync(templatesPath, JSON.stringify(currentTemplates, null, 2)); 49 | -------------------------------------------------------------------------------- /packages/docs/src/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploying Your Project 2 | 3 | `@design-systems/cli` can and should have it's [storybook](https://storybook.js.org/) and [playroom](https://github.com/seek-oss/playroom) deployed in some capacity. 4 | 5 | ## 1. Build 6 | 7 | First you must build both tools. 8 | 9 | ```sh 10 | yarn build:storybook && yarn build:playroom 11 | ``` 12 | 13 | Both of these command will output build assets to the `out` directory at the root of the project. 14 | 15 | ```txt 16 | out/ 17 | playroom/ 18 | index.html <- The start page for your playroom 19 | ... 20 | index.html <- The start page for your storybook 21 | ... 22 | ``` 23 | 24 | ## 2. Serve 25 | 26 | After you have built your files you can push this folder to any static website serving solution. This could be anything like [netlify](https://www.netlify.com/), [heroku](https://www.heroku.com/), [AWS S3](https://aws.amazon.com/s3/), etc. 27 | 28 | The simplest solution though is to just use [GitHub Pages](https://pages.github.com/). 29 | 30 | The following script uses [push-dir](https://www.npmjs.com/package/push-dir) to deploy the built storybook + playroom to our project's `gh-pages` branch. 31 | 32 | ```json 33 | { 34 | "scripts": { 35 | "deploy": "push-dir --cleanup --dir=out --branch=gh-pages" 36 | } 37 | } 38 | ``` 39 | 40 | ## 3. Consume 41 | 42 | Once you have done all of the above your storybook will be deploy to the root of your URL and the playroom will be deployed under the `/playroom` path. 43 | 44 | For example, if we were to deploy a project to `https://github.com/pages/design-systems/test-repo`: 45 | 46 | - https://github.com/pages/design-systems/test-repo - serves the storybook 47 | - https://github.com/pages/design-systems/test-repo/playroom - serves the playroom 48 | -------------------------------------------------------------------------------- /packages/svg-icon-builder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/svg-icon-builder", 3 | "version": "4.15.4", 4 | "main": "./dist/cjs/index.js", 5 | "description": "A simple CLI for generating icon components from SVGs.", 6 | "bin": { 7 | "icon-build": "./bin/icon.js" 8 | }, 9 | "types": "./dist", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/intuit/design-systems-cli.git" 13 | }, 14 | "author": "Tyler Krupicka github@tylerkrupicka.com", 15 | "contributors": [ 16 | "Tyler Krupicka github@tylerkrupicka.com", 17 | "Andrew Lisowski lisowski54@gmail.com" 18 | ], 19 | "license": "MIT", 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "scripts": { 24 | "clean": "ds clean", 25 | "build": "ds build", 26 | "start": "ds build --watch", 27 | "test": "yarn build && ./bin/icon.js --svg ./svg --out output --overwrite", 28 | "test:colored": "yarn build && ./bin/icon.js --svg ./svg --out output --no-strip-color --overwrite", 29 | "help": "./bin/icon.js -h", 30 | "lint": "ds lint" 31 | }, 32 | "files": [ 33 | "bin", 34 | "dist", 35 | "!*.test.*", 36 | "!__snapshots__", 37 | "!__tests__" 38 | ], 39 | "dependencies": { 40 | "change-case": "3.1.0", 41 | "command-line-application": "0.10.1", 42 | "endent": "2.1.0", 43 | "fs-extra": "9.0.1", 44 | "maxstache": "1.0.7", 45 | "signale": "1.4.0", 46 | "svgo": "1.3.2" 47 | }, 48 | "devDependencies": { 49 | "@auto-it/jira": "10.36.5", 50 | "@auto-it/slack": "10.36.5", 51 | "@design-systems/cli": "link:../cli", 52 | "@types/change-case": "2.3.1", 53 | "@types/fs-extra": "9.0.1", 54 | "@types/signale": "1.4.1", 55 | "@types/svgo": "1.3.3" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["group:monorepos", "packages:postcss", "schedule:earlyMondays"], 3 | "masterIssue": true, 4 | "automerge": false, 5 | "prConcurrentLimit": 3, 6 | "separateMajorMinor": false, 7 | "patch": { 8 | "labels": ["dependency-update"] 9 | }, 10 | "minor": { 11 | "labels": ["dependency-update"] 12 | }, 13 | "major": { 14 | "labels": ["dependency-update"] 15 | }, 16 | "digest": { 17 | "labels": ["dependency-update"] 18 | }, 19 | "packageRules": [ 20 | { 21 | "matchPackagePatterns": ["@storybook"], 22 | "groupName": "storybook", 23 | "rangeStrategy": "bump" 24 | }, 25 | { 26 | "matchPackagePatterns": ["@babel", "babel"], 27 | "groupName": "babel", 28 | "rangeStrategy": "bump" 29 | }, 30 | { 31 | "packageNames": ["autoprefixer", "icss-utils"], 32 | "matchPackagePatterns": ["^postcss"], 33 | "groupName": "postcss", 34 | "rangeStrategy": "replace" 35 | }, 36 | { 37 | "packageNames": ["@testing-library"], 38 | "matchPackagePatterns": ["^@testing-library"], 39 | "groupName": "testing-library", 40 | "rangeStrategy": "replace" 41 | }, 42 | { 43 | "matchPackagePatterns": ["@proof-ui"], 44 | "groupName": "proof" 45 | }, 46 | { 47 | "matchPackagePatterns": ["@reach"], 48 | "groupName": "reach" 49 | }, 50 | { 51 | "matchPackagePatterns": ["auto", "@auto-it"], 52 | "groupName": "auto", 53 | "excludePackageNames": ["autoprefixer"] 54 | }, 55 | { 56 | "extends": "packages:linters", 57 | "matchPackagePatterns": ["^eslint"], 58 | "groupName": "linters" 59 | }, 60 | { 61 | "matchPackagePatterns": ["stylelint"], 62 | "groupName": "stylelint" 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /plugins/size/src/utils/BuildUtils.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import os from 'os'; 3 | import path from 'path'; 4 | import { getMonorepoRoot, createLogger } from '@design-systems/cli-utils'; 5 | 6 | const logger = createLogger({ scope: 'size' }); 7 | 8 | /** Builds the specified commit */ 9 | export function buildPackages(args: { 10 | /** Merge base to run build */ 11 | mergeBase: string 12 | /** Build command for merge base */ 13 | buildCommand: string 14 | }) { 15 | const id = Math.random().toString(36).substring(7); 16 | const dir = path.join(os.tmpdir(), `commit-build-${id}`); 17 | const root = getMonorepoRoot(); 18 | 19 | const commit = execSync(`git merge-base HEAD ${args.mergeBase}`, { cwd: root }).toString().trim(); 20 | execSync(`git clone . ${dir}`, { cwd: root, stdio: 'ignore' }); 21 | execSync(`git checkout ${commit}`, { cwd: dir, stdio: 'ignore' }); 22 | 23 | logger.info(`Installing dependencies for commit: ${commit} ...`); 24 | execSync('yarn', { cwd: dir }); 25 | 26 | logger.info(`Running command "${args.buildCommand}" for commit: ${commit} ...`); 27 | execSync(args.buildCommand, { 28 | cwd: dir, 29 | stdio: 'inherit' 30 | }); 31 | 32 | return dir; 33 | } 34 | 35 | /** Get path to local built master package */ 36 | export function getLocalPackage( 37 | /** Package name */ 38 | name: string, 39 | /** Path to local built master */ 40 | local: string 41 | ) { 42 | const packages = execSync(`lerna list --ndjson`) 43 | .toString() 44 | .trim() 45 | .split('\n'); 46 | 47 | const pkg = packages 48 | .map((p: string) => JSON.parse(p)) 49 | .find((p) => p.name === name); 50 | 51 | if (!pkg) { 52 | throw new Error(`Package not found: ${name}`); 53 | } 54 | 55 | return path.join(local, path.relative(getMonorepoRoot(), pkg.location)); 56 | } 57 | -------------------------------------------------------------------------------- /plugins/lint/src/utils/helperUtils.ts: -------------------------------------------------------------------------------- 1 | import getPackages from 'get-monorepo-packages'; 2 | import fs from 'fs'; 3 | import stylelint from 'stylelint'; 4 | import path from 'path'; 5 | import { 6 | getMonorepoRoot 7 | } from '@design-systems/cli-utils'; 8 | import { LintArgs, StylelintResult } from '../interfaces'; 9 | 10 | /** Get the folders that contain packages in the monorepo. */ 11 | function getPackageFolders() { 12 | return [ 13 | ...getPackages('.').reduce((all, p) => { 14 | all.add(p.location.split('/')[0]); 15 | return all; 16 | }, new Set()) 17 | ]; 18 | } 19 | 20 | /** Determine when a file was last edited */ 21 | function getLastEdited(filename: string) { 22 | try { 23 | return fs.statSync(filename).mtimeMs; 24 | } catch (error) { 25 | return 0; 26 | } 27 | } 28 | 29 | /** Return a list of files to lint */ 30 | function files(args: LintArgs, types: string) { 31 | const isRoot = getMonorepoRoot() === process.cwd(); 32 | 33 | if (args.files) { 34 | const fileExt = new Set(types.split('|')); 35 | return args.files.filter(f => fileExt.has(path.extname(f).substr(1))); 36 | } 37 | 38 | if (isRoot) { 39 | return [`components/**/src/**/*.(${types})`]; 40 | } 41 | 42 | return [`src/**/*.(${types})`]; 43 | } 44 | 45 | /** Run stylelint with the given options. */ 46 | async function attemptStylelint( 47 | options: Partial 48 | ): Promise { 49 | try { 50 | return (await stylelint.lint(options)) as StylelintResult; 51 | } catch (error) { 52 | if (error.message.includes('No files matching the pattern')) { 53 | return { 54 | results: [], 55 | output: 'no match', 56 | errored: false 57 | }; 58 | } 59 | 60 | throw error; 61 | } 62 | } 63 | 64 | export { getPackageFolders, getLastEdited, files, attemptStylelint, getMonorepoRoot }; 65 | -------------------------------------------------------------------------------- /plugins/storybook/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { withA11y } from '@storybook/addon-a11y'; 2 | import { withPropsTable } from 'storybook-addon-react-docgen'; 3 | import { withKnobs } from '@storybook/addon-knobs'; 4 | import { themes as storybookThemes, ThemeContext } from '@storybook/theming'; 5 | import { jsxDecorator } from 'storybook-addon-jsx'; 6 | import { select } from '@storybook/addon-knobs'; 7 | 8 | let lightLogo; 9 | let darkLogo; 10 | 11 | try { 12 | lightLogo = require(MONOREPO_ROOT + '/.storybook/light-logo.png'); 13 | } catch (error) { 14 | lightLogo = undefined; 15 | } 16 | 17 | try { 18 | darkLogo = require(MONOREPO_ROOT + '/.storybook/dark-logo.png'); 19 | } catch (error) { 20 | darkLogo = undefined; 21 | } 22 | 23 | export const decorators = [withPropsTable, jsxDecorator]; 24 | 25 | const brand = { 26 | brandTitle: MONOREPO_NAME + ' Storybook', 27 | brandUrl: REPO_URL, 28 | }; 29 | 30 | export const parameters = { 31 | backgrounds: { 32 | default: 'white', 33 | values: [ 34 | { name: 'white', value: 'white' }, 35 | { name: 'dark', value: '#2f2f2f' }, 36 | { name: 'twitter', value: '#00aced' }, 37 | { name: 'facebook', value: '#3b5998' }, 38 | ], 39 | }, 40 | knobs: { 41 | escapeHTML: false, 42 | }, 43 | darkMode: { 44 | light: Object.assign({}, storybookThemes.light, brand, { 45 | brandImage: lightLogo, 46 | }), 47 | dark: Object.assign({}, storybookThemes.dark, brand, { 48 | brandImage: darkLogo, 49 | }), 50 | }, 51 | a11y: { 52 | config: { 53 | rules: [ 54 | { 55 | id: 'duplicate-id', 56 | enabled: false, 57 | }, 58 | { 59 | id: 'heading-order', 60 | enabled: false, 61 | }, 62 | { 63 | id: 'label', 64 | none: ['help-same-as-label', 'multiple-label'], 65 | }, 66 | ], 67 | }, 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /plugins/playroom/src/command.ts: -------------------------------------------------------------------------------- 1 | import { CliCommand } from '@design-systems/plugin'; 2 | import dedent from 'dedent'; 3 | 4 | const command: CliCommand = { 5 | name: 'playroom', 6 | description: 'Create a playroom for your components', 7 | commands: [ 8 | { 9 | name: 'start', 10 | description: 'Start a playroom for your components', 11 | examples: ['ds playroom start'] 12 | }, 13 | { 14 | name: 'build', 15 | description: 'Build the playroom for deployment', 16 | examples: ['ds playroom build'] 17 | } 18 | ], 19 | options: [ 20 | { 21 | name: 'exclude', 22 | multiple: true, 23 | type: String, 24 | defaultValue: [], 25 | description: 'Glob of files to exclude from playroom.', 26 | config: true 27 | }, 28 | { 29 | name: 'excludeNamed', 30 | multiple: true, 31 | type: String, 32 | defaultValue: [], 33 | description: 'Array of component names to only use the default export.', 34 | config: true 35 | } 36 | ], 37 | footer: [ 38 | { 39 | header: 'Snippets', 40 | content: 41 | 'Export an arrary of Playroom Snippets in each of your components. These will all be loaded into playroom at once.' 42 | }, 43 | { 44 | header: 'Custom playroom config', 45 | content: dedent` 46 | To customize your playroom configuration, create a playroom.config.js at your package or monorepo root. Export a function that takes the base config as an argument. 47 | 48 | It must uses the passed in base config for playroom to function properly. 49 | ` 50 | }, 51 | { 52 | code: true, 53 | content: dedent` 54 | \`\`\`js 55 | module.exports = baseConfig => ({ 56 | ...baseConfig, 57 | port: 9001, 58 | openBrowser: false, 59 | title: 'Custom Title', 60 | }); 61 | \`\`\` 62 | ` 63 | } 64 | ] 65 | }; 66 | 67 | export default command; 68 | -------------------------------------------------------------------------------- /packages/utils/src/utils/focus-lock.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/no-noninteractive-tabindex */ 2 | 3 | import * as React from 'react'; 4 | import moveFocusInside, { focusInside } from 'focus-lock'; 5 | import mergeRefs from 'react-merge-refs'; 6 | 7 | import { Element } from '..'; 8 | 9 | interface FocusLockProps { 10 | /** Whether the lock is activated */ 11 | active: boolean; 12 | } 13 | 14 | /** A boundary for the focus lock */ 15 | const FocusGuard = ({ active }: FocusLockProps) => ( 16 |
29 | ); 30 | 31 | /** Lock focus withing an area of the app */ 32 | export const FocusLock = React.forwardRef< 33 | HTMLDivElement, 34 | FocusLockProps & Element<'div'> 35 | >(({ active, onBlur = () => undefined, ...html }, ref) => { 36 | const trap = React.useRef(null); 37 | 38 | type TrapCurrent = NonNullable; 39 | 40 | /** Trap the focus within the locks if active */ 41 | const trapFocus = () => { 42 | if (active && !focusInside(trap.current as TrapCurrent)) { 43 | setTimeout( 44 | () => moveFocusInside(trap.current as TrapCurrent, document.activeElement as HTMLInputElement), 45 | 50 46 | ); 47 | } 48 | }; 49 | 50 | React.useEffect(trapFocus); 51 | 52 | return ( 53 | <> 54 | 55 |
{ 59 | if ( 60 | e.relatedTarget && 61 | (e.relatedTarget as HTMLElement).getAttribute('data-focus-guard') 62 | ) { 63 | trapFocus(); 64 | } 65 | 66 | onBlur(e); 67 | }} 68 | /> 69 | 70 | 71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/hooks/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v4.15.1 (Tue May 31 2022) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - remove bin from core package because it doesn't exist [#667](https://github.com/intuit/design-systems-cli/pull/667) (spencer_hamm@intuit.com) 6 | - Merge branch 'master' into remove-core-bin (spencer_hamm@intuit.com) 7 | 8 | #### Authors: 1 9 | 10 | - Spencer Hamm ([@spentacular](https://github.com/spentacular)) 11 | 12 | --- 13 | 14 | # v4.14.0 (Thu Mar 17 2022) 15 | 16 | #### 🚀 Enhancement 17 | 18 | - Add hooks to detect lessContrast + highContrast preferences [#681](https://github.com/intuit/design-systems-cli/pull/681) (kendallg@spotify.com [@kendallgassner](https://github.com/kendallgassner)) 19 | 20 | #### Authors: 2 21 | 22 | - Kendall Gassner ([@kendallgassner](https://github.com/kendallgassner)) 23 | - kendallgassner (kendallg@spotify.com) 24 | 25 | --- 26 | 27 | # v4.12.0 (Wed Jul 28 2021) 28 | 29 | :tada: This release contains work from a new contributor! :tada: 30 | 31 | Thank you, null[@melindali255](https://github.com/melindali255), for all your work! 32 | 33 | #### 🚀 Enhancement 34 | 35 | - Adding hook utilities [#665](https://github.com/intuit/design-systems-cli/pull/665) (melinda_li@intuit.com [@melindali255](https://github.com/melindali255)) 36 | 37 | #### 🐛 Bug Fix 38 | 39 | - changed author (melinda_li@intuit.com) 40 | - make hooks package public (melinda_li@intuit.com) 41 | - useDarkMode and useReducedMotion use useMatchMedia (melinda_li@intuit.com) 42 | - add useMatchMedia to replace useReducedMotion and useDarkMode (melinda_li@intuit.com) 43 | - removed references and cleaned up code (melinda_li@intuit.com) 44 | - add modified build and test scripts (melinda_li@intuit.com) 45 | - add tests (melinda_li@intuit.com) 46 | - fixed export functions (melinda_li@intuit.com) 47 | - add package.json and tsconfig (melinda_li@intuit.com) 48 | - added hooks and readme (melinda_li@intuit.com) 49 | 50 | #### Authors: 2 51 | 52 | - [@melindali255](https://github.com/melindali255) 53 | - mli02 (melinda_li@intuit.com) 54 | -------------------------------------------------------------------------------- /plugins/playroom/src/snippets.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import path from 'path'; 3 | import glob from 'fast-glob'; 4 | import jsxToString from 'react-element-to-jsx-string'; 5 | 6 | interface Snippet { 7 | /** The name of the snippet */ 8 | name: string; 9 | /** the actual snippet */ 10 | code: React.ReactNode; 11 | } 12 | 13 | /** Render a snippet to a string */ 14 | const renderSnippet = (code: string | React.ReactNode) => 15 | typeof code === 'string' 16 | ? code 17 | : jsxToString(code, { 18 | showDefaultProps: false, 19 | /** Find the name of the component */ 20 | displayName: component => 21 | component && 22 | typeof component === 'object' && 23 | ((component as any).type.displayName || 24 | (component as any).type.name || 25 | (component as any).type) 26 | }); 27 | 28 | /** Get all the snippets defined in each component. */ 29 | export default function getSnippets() { 30 | const packageSnippets = glob.sync([ 31 | 'components/**/dist/**/cjs/**/*.snippet.js', 32 | 'dist/**/cjs/**/*.snippet.js' 33 | ]); 34 | 35 | const snippets = packageSnippets.reduce((all, file) => { 36 | // eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requires 37 | const componentSnippets = require(path.resolve(file.toString())).default; 38 | 39 | if (Array.isArray(componentSnippets)) { 40 | return [ 41 | ...all, 42 | ...componentSnippets.map((snippet: Snippet) => ({ 43 | ...snippet, 44 | code: renderSnippet(snippet.code) 45 | })) 46 | ]; 47 | } 48 | 49 | // Support for old format 50 | return [ 51 | ...all, 52 | ...Object.entries(componentSnippets).map( 53 | ([name, code]: [string, any]) => ({ 54 | name, 55 | code: renderSnippet(code) 56 | }) 57 | ) 58 | ]; 59 | }, [] as Snippet[]); 60 | 61 | return snippets; 62 | } 63 | -------------------------------------------------------------------------------- /packages/stylelint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | processors: [ 3 | [ 4 | 'stylelint-processor-styled-components', 5 | { strict: true, ignoreFiles: ['**/*.css'] } 6 | ] 7 | ], 8 | extends: [ 9 | 'stylelint-config-recommended', 10 | 'stylelint-config-css-modules', 11 | 'stylelint-config-styled-components', 12 | 'stylelint-config-prettier' 13 | ], 14 | plugins: [ 15 | 'stylelint-order', 16 | 'stylelint-a11y', 17 | 'stylelint-declaration-block-no-ignored-properties', 18 | 'stylelint-selector-tag-no-without-class' 19 | ], 20 | rules: { 21 | 'length-zero-no-unit': true, 22 | 'shorthand-property-no-redundant-values': true, 23 | 'no-descending-specificity': null, 24 | indentation: 2, 25 | 'order/order': [ 26 | 'dollar-variables', 27 | 'custom-properties', 28 | 'declarations', 29 | 'rules' 30 | ], 31 | 'selector-pseudo-class-no-unknown': [ 32 | true, 33 | { 34 | ignorePseudoClasses: ['theme-root'] 35 | } 36 | ], 37 | 'at-rule-no-unknown': [ 38 | true, 39 | { 40 | ignoreAtRules: ['mixin', 'define-mixin'] 41 | } 42 | ], 43 | 'rule-empty-line-before': [ 44 | 'always-multi-line', 45 | { 46 | ignore: ['first-nested'], 47 | except: ['after-single-line-comment'] 48 | } 49 | ], 50 | 'at-rule-empty-line-before': [ 51 | 'always', 52 | { 53 | except: ['blockless-after-blockless', 'first-nested'] 54 | } 55 | ], 56 | 'declaration-empty-line-before': [ 57 | 'always', 58 | { 59 | ignore: ['after-comment'], 60 | except: ['first-nested', 'after-declaration'] 61 | } 62 | ], 63 | 'plugin/declaration-block-no-ignored-properties': true, 64 | 'a11y/media-prefers-reduced-motion': true, 65 | 'a11y/no-obsolete-attribute': true, 66 | 'a11y/no-obsolete-element': true, 67 | 'a11y/selector-pseudo-class-focus': true, 68 | 'plugin/selector-tag-no-without-class': ['div', 'span'] 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /plugins/test/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from '@design-systems/cli-utils'; 2 | import { Plugin } from '@design-systems/plugin'; 3 | import env from 'env-ci'; 4 | 5 | // eslint-disable-next-line jest/no-jest-import 6 | import { runCLI } from 'jest'; 7 | import createJestAnnotations from 'jest-github-reporter/dist/create-check'; 8 | 9 | export interface TestArgs { 10 | /** Update the test snapshots */ 11 | update?: boolean; 12 | /** Run the tests in watch mode */ 13 | watch?: boolean; 14 | /** Post lint results as annotations to PR. Only in CI. */ 15 | annotate?: boolean; 16 | /** Array of files, directories, or globs to find tests */ 17 | files?: string[]; 18 | /** Run all tests serially in the current process, */ 19 | runInBand?: boolean; 20 | } 21 | 22 | /** A plugin to run tests. */ 23 | export default class TestPlugin implements Plugin { 24 | private logger = createLogger({ scope: 'test' }); 25 | 26 | async run(args: TestArgs) { 27 | process.env.NODE_ENV = 'test'; 28 | 29 | if (process.env.CLI_APP_ID && process.env.CLI_PRIVATE_KEY) { 30 | process.env.JEST_APP_ID = 31 | process.env.JEST_APP_ID || process.env.CLI_APP_ID; 32 | process.env.JEST_PRIVATE_KEY = 33 | process.env.JEST_PRIVATE_KEY || process.env.CLI_PRIVATE_KEY; 34 | } 35 | 36 | const annotate = Boolean( 37 | args.annotate || (process.env.CLI_APP_ID && process.env.CLI_PRIVATE_KEY) 38 | ); 39 | 40 | try { 41 | const { results } = await runCLI( 42 | { 43 | _: [], 44 | $0: 'jest', 45 | updateSnapshot: args.update, 46 | watch: args.watch, 47 | testPathPattern: args.files, 48 | ci: env().isCi, 49 | runInBand: args.runInBand 50 | }, 51 | [process.cwd()] 52 | ); 53 | 54 | if (annotate) { 55 | await createJestAnnotations(results); 56 | } 57 | 58 | if (!results.success) { 59 | process.exit(1); 60 | } 61 | } catch (e) { 62 | this.logger.error(e); 63 | process.exit(1); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/create/src/init.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'command-line-application'; 2 | import dedent from 'dedent'; 3 | import { 4 | setLogLevel, 5 | getMonorepoRoot, 6 | createLogger 7 | } from '@design-systems/cli-utils'; 8 | import { CliCommand } from '@design-systems/plugin'; 9 | 10 | import run, { CreateComponentArgs, CreateSystemArgs } from './run'; 11 | import { system } from './cli'; 12 | 13 | const systemStandalone: CliCommand = { 14 | ...system, 15 | examples: [ 16 | 'npm init @design-systems', 17 | 'npm init @design-systems --name my-design-system --repo hipstersmoothie/material' 18 | ], 19 | options: [ 20 | ...(system.options || []), 21 | { 22 | name: 'verbose', 23 | alias: 'v', 24 | description: 'Output the debug logs. Debug: -v Trace: -vv', 25 | type: Boolean, 26 | multiple: true 27 | } 28 | ] 29 | }; 30 | 31 | const [, , ...processArgs] = process.argv; 32 | 33 | const args = app(systemStandalone, { argv: processArgs }); 34 | const logger = createLogger({ scope: 'create' }); 35 | 36 | if (args) { 37 | // eslint-disable-next-line no-underscore-dangle 38 | args._command = 'system'; 39 | const verbosity = args.verbose ? args.verbose.length : 0; 40 | setLogLevel( 41 | (verbosity === 1 && 'debug') || (verbosity === 2 && 'trace') || 'info' 42 | ); 43 | 44 | const monorepoRoot = getMonorepoRoot(); 45 | 46 | if (monorepoRoot && process.cwd().includes(monorepoRoot)) { 47 | logger.error(dedent`Already in a @design-systems monorepo! 48 | 49 | You seem to be trying to create a new @design-systems monorepo from within another. 50 | 51 | This probably isn\'t what you want. Common situations: 52 | 53 | 1. Ran \`yarn run create\` from a sub-package 54 | => Change directories to the root of the project and re-run command 55 | 56 | 2. Ran \`npm init @design-systems\` within a design system 57 | => Change directories to a folder that isn't in a monorepo (no lerna.json in parent folders) 58 | `); 59 | } else { 60 | run(args as (CreateComponentArgs | CreateSystemArgs)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plugins/storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/storybook", 3 | "version": "4.15.4", 4 | "description": "The storybook command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | ".storybook", 14 | "preset.js", 15 | "preview.js" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/intuit/design-systems-cli.git" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "dependencies": { 25 | "@alisowski/storybook-addon-notes": "^6.0.1", 26 | "@babel/core": "^7.13.8", 27 | "@design-systems/build": "link:../build", 28 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 29 | "@design-systems/plugin": "link:../../packages/plugin", 30 | "@storybook/addon-a11y": "^6.2.9", 31 | "@storybook/addon-actions": "^6.2.9", 32 | "@storybook/addon-backgrounds": "^6.2.9", 33 | "@storybook/addon-knobs": "^6.2.9", 34 | "@storybook/addon-viewport": "^6.2.9", 35 | "@storybook/addons": "^6.2.9", 36 | "@storybook/cli": "^6.2.9", 37 | "@storybook/components": "^6.2.9", 38 | "@storybook/react": "^6.2.9", 39 | "@storybook/theming": "^6.2.9", 40 | "babel-loader": "8.1.0", 41 | "core-js": "3.6.5", 42 | "dedent": "0.7.0", 43 | "fast-glob": "3.2.5", 44 | "get-port": "5.1.1", 45 | "github-url-to-object": "4.0.4", 46 | "source-map-loader": "1.1.0", 47 | "story2sketch": "1.7.0", 48 | "storybook-addon-jsx": "^7.3.0", 49 | "storybook-addon-react-docgen": "^1.2.44", 50 | "storybook-addon-sketch": "^0.2.0", 51 | "storybook-dark-mode": "^1.0.0", 52 | "tslib": "2.0.1", 53 | "webpack": "4.44.1", 54 | "webpack-filter-warnings-plugin": "1.2.1" 55 | }, 56 | "peerDependencies": { 57 | "react": ">= 16", 58 | "react-dom": ">= 16" 59 | }, 60 | "devDependencies": { 61 | "@types/dedent": "0.7.0", 62 | "react": "16.13.1", 63 | "react-dom": "16.13.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plugins/size/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/size", 3 | "version": "4.15.4", 4 | "description": "The size command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/intuit/design-systems-cli.git" 20 | }, 21 | "dependencies": { 22 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 23 | "@design-systems/plugin": "link:../../packages/plugin", 24 | "@royriojas/get-exports-from-file": "https://github.com/hipstersmoothie/get-exports-from-file#all", 25 | "change-case": "4.1.1", 26 | "chokidar": "3.4.2", 27 | "colorette": "1.2.2", 28 | "commently": "6.18.3", 29 | "cross-spawn": "7.0.3", 30 | "css-loader": "3.5.3", 31 | "diff2html": "3.3.0", 32 | "file-size": "1.0.0", 33 | "fs-extra": "9.0.1", 34 | "get-monorepo-packages": "1.2.0", 35 | "gitlog": "4.0.0", 36 | "lodash.chunk": "4.2.0", 37 | "markdown-table": "2.0.0", 38 | "mini-css-extract-plugin": "0.11.0", 39 | "opn": "5.5.0", 40 | "optimize-css-assets-webpack-plugin": "5.0.4", 41 | "signale": "1.4.0", 42 | "table": "6.0.7", 43 | "terser-webpack-plugin": "4.1.0", 44 | "tslib": "2.0.1", 45 | "webpack": "4.44.1", 46 | "webpack-bundle-analyzer": "3.8.0", 47 | "webpack-inject-plugin": "1.5.5", 48 | "webpack-sources": "1.4.3" 49 | }, 50 | "devDependencies": { 51 | "@types/fs-extra": "9.0.1", 52 | "@types/hogan.js": "3.0.0", 53 | "@types/lodash.chunk": "4.2.6", 54 | "@types/markdown-table": "2.0.0", 55 | "@types/mini-css-extract-plugin": "0.9.1", 56 | "@types/opn": "5.1.0", 57 | "@types/optimize-css-assets-webpack-plugin": "5.0.1", 58 | "@types/table": "6.0.0", 59 | "@types/terser-webpack-plugin": "4.1.0", 60 | "@types/webpack-bundle-analyzer": "3.8.0", 61 | "@types/webpack-sources": "1.4.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/create/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/create", 3 | "version": "4.15.4", 4 | "description": "Initialize a @design-systems/cli monorepo", 5 | "main": "dist/index.js", 6 | "bin": "dist/bundle.js", 7 | "author": "Andrew Lisowski ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "scripts": { 13 | "build": "yarn build:typescript && yarn bundle", 14 | "build:typescript": "tsc --build", 15 | "bundle": "webpack && chmod +x ./dist/bundle.js", 16 | "start": "yarn build:typescript --watch", 17 | "templates:pick": "node ./scripts/cherry-pick-master", 18 | "templates:update": "node ./scripts/update-templates" 19 | }, 20 | "files": [ 21 | "dist", 22 | "templates" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/intuit/design-systems-cli.git" 27 | }, 28 | "peerDependencies": { 29 | "@design-systems/cli-utils": ">= 2.x", 30 | "@design-systems/plugin": ">= 2.x", 31 | "change-case": ">=3.x", 32 | "cli-spinners": ">=2.x", 33 | "colorette": ">=1.x", 34 | "command-line-application": ">=0.x", 35 | "copy-template-dir": ">=1.x", 36 | "dedent": ">=0.x", 37 | "degit": "https://github.com/hipstersmoothie/degit.git#private-release", 38 | "fs-extra": ">=8.x", 39 | "inquirer": ">=6.x", 40 | "progress-estimator": ">=0.x", 41 | "terminal-link": ">=1.x", 42 | "title-case": ">=3.x" 43 | }, 44 | "devDependencies": { 45 | "@design-systems/cli-utils": "link:../cli-utils", 46 | "@types/dedent": "0.7.0", 47 | "@types/fs-extra": "9.0.1", 48 | "@types/inquirer": "7.3.1", 49 | "change-case": "4.1.1", 50 | "cli-spinners": "2.5.0", 51 | "colorette": "1.2.2", 52 | "command-line-application": "0.10.1", 53 | "copy-template-dir": "1.4.0", 54 | "dedent": "0.7.0", 55 | "degit": "https://github.com/hipstersmoothie/degit.git#private-release", 56 | "fs-extra": "9.0.1", 57 | "inquirer": "7.3.3", 58 | "progress-estimator": "0.3.0", 59 | "terminal-link": "2.1.1", 60 | "title-case": "3.0.3", 61 | "ts-loader": "8.0.17", 62 | "tslib": "2.0.1", 63 | "webpack": "4.44.1", 64 | "webpack-cli": "3.3.12" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plugins/build/src/configs/postcss.config.ts: -------------------------------------------------------------------------------- 1 | import url from 'postcss-url'; 2 | import nested from 'postcss-nested'; 3 | import autoprefixer from 'autoprefixer'; 4 | import hexRGBA from 'postcss-hexrgba'; 5 | import modules from 'postcss-modules'; 6 | import fs from 'fs-extra'; 7 | import crypto from 'crypto'; 8 | import path from 'path'; 9 | import pkgUp from 'pkg-up'; 10 | 11 | interface PostCSSContext { 12 | env?: string; 13 | outDir: string; 14 | moduleHash?: string; 15 | } 16 | 17 | module.exports = function (ctx: PostCSSContext = { outDir: 'dist' }) { 18 | return { 19 | plugins: [ 20 | nested, 21 | modules({ 22 | localsConvention: 'camelCase', 23 | generateScopedName(name: string, filename: string, css: string) { 24 | const base = path.basename(filename, '.css'); 25 | const pkgJson = pkgUp.sync({ cwd: path.dirname(filename) }); 26 | 27 | if (ctx.env !== 'module') { 28 | return name; 29 | } 30 | 31 | const hash = crypto 32 | .createHash('md5') 33 | .update(base) 34 | .update(name) 35 | .update(css); 36 | 37 | if (ctx.moduleHash) { 38 | hash.update(ctx.moduleHash); 39 | } 40 | 41 | if (pkgJson) { 42 | try { 43 | const pkgJsonContent = fs.readFileSync(pkgJson, 'utf-8'); 44 | hash.update(pkgJsonContent); 45 | } catch (e) {} 46 | } 47 | 48 | const hashString = hash.digest('hex'); 49 | return `${base}-${name}-${hashString.substr(hashString.length - 7)}`; 50 | }, 51 | async getJSON( 52 | _: string, 53 | json: Record, 54 | outputFileName: string 55 | ) { 56 | if (outputFileName) { 57 | const cjs = `${outputFileName.replace('dist', 'dist/cjs')}.js`; 58 | const esm = `${outputFileName.replace('dist', 'dist/esm')}.js`; 59 | 60 | return Promise.all([ 61 | fs.outputFile(cjs, `module.exports = ${JSON.stringify(json)};`), 62 | fs.outputFile(esm, `export default ${JSON.stringify(json)};`), 63 | ]); 64 | } 65 | }, 66 | }), 67 | hexRGBA, 68 | autoprefixer, 69 | url({ url: 'inline' }), 70 | ], 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /scripts/create-plugin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const copy = require('copy-template-dir'); 4 | const path = require('path'); 5 | const log = require('signale'); 6 | const fs = require('fs'); 7 | const { paramCase, pascalCase } = require('change-case'); 8 | const { titleCase } = require('title-case'); 9 | const { app } = require('command-line-application'); 10 | const inquirer = require('inquirer'); 11 | 12 | const { version } = JSON.parse( 13 | fs.readFileSync(path.join(__dirname, '../lerna.json'), 'utf8') 14 | ); 15 | const inDir = path.join(__dirname, './template-plugin'); 16 | 17 | function updateCliTsConfig(name) { 18 | const configPath = path.join(__dirname, '../packages/cli/tsconfig.json'); 19 | const config = JSON.parse(fs.readFileSync(configPath), 'utf8'); 20 | 21 | config.references.push({ path: `../../plugins/${name}` }); 22 | 23 | fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); 24 | } 25 | 26 | async function create(options) { 27 | let { name } = options; 28 | 29 | if (!name) { 30 | ({ name } = await inquirer.prompt([ 31 | { 32 | type: 'input', 33 | name: 'name', 34 | message: `What's the plugin name?` 35 | } 36 | ])); 37 | } 38 | 39 | const kebab = paramCase(name); 40 | const outDir = path.join(__dirname, '../plugins', kebab); 41 | 42 | fs.mkdirSync(outDir); 43 | 44 | const vars = { 45 | version, 46 | title: titleCase(name), 47 | kebab, 48 | pascal: pascalCase(name) 49 | }; 50 | 51 | copy(inDir, outDir, vars, (err, createdFiles) => { 52 | if (err) { 53 | throw err; 54 | } 55 | 56 | createdFiles.forEach(filePath => 57 | log.info(`Created ${path.relative(outDir, filePath)}`) 58 | ); 59 | log.success(`Created @design-systems/${kebab} plugin!`); 60 | }); 61 | 62 | updateCliTsConfig(kebab); 63 | } 64 | 65 | const args = app( 66 | { 67 | name: 'create-plugin', 68 | description: 'Add a new plugin to the @design-sytems/cli project', 69 | examples: ['create-plugin ProgressBar', 'create-plugin --name ProgressBar'], 70 | options: [ 71 | { 72 | name: 'name', 73 | description: 'The name of the new plugin', 74 | type: String, 75 | defaultOption: true 76 | } 77 | ] 78 | }, 79 | { argv: process.argv } 80 | ); 81 | 82 | if (args) { 83 | create(args).catch(e => { 84 | console.error(e); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /packages/hooks/src/useStickyState.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-cond-assign, no-continue */ 2 | 3 | import { RefObject, useEffect, useState } from "react"; 4 | 5 | import { debounce } from "@design-systems/utils"; 6 | 7 | /** Get the nearest scroll container */ 8 | function getScrollParent(element: HTMLElement) { 9 | let style = getComputedStyle(element); 10 | 11 | const excludeStaticParent = style.position === "absolute"; 12 | const overflowRegex = /(auto|scroll|hidden)/; 13 | 14 | if (style.position === "fixed") { 15 | return document.body; 16 | } 17 | 18 | let parent: HTMLElement | null = element; 19 | 20 | while ((parent = parent.parentElement)) { 21 | style = getComputedStyle(parent); 22 | 23 | if (excludeStaticParent && style.position === "static") { 24 | continue; 25 | } 26 | 27 | if ( 28 | overflowRegex.test(style.overflow + style.overflowY + style.overflowX) 29 | ) { 30 | return parent; 31 | } 32 | } 33 | 34 | return document.body; 35 | } 36 | 37 | /** 38 | * Determine if a position:sticky element is sticking. 39 | * 40 | * @param element - The element to detect the sticky state of 41 | * 42 | * @example 43 | * const ref = useRef(null); 44 | * const isSticking = useStickyState(ref); 45 | */ 46 | export const useStickyState = ( 47 | element: RefObject 48 | ) => { 49 | const [isSticky, setIsSticky] = useState(false); 50 | 51 | useEffect(() => { 52 | const { current: stickyTarget } = element; 53 | 54 | if (!stickyTarget || !window) { 55 | return; 56 | } 57 | 58 | const parentElement = getScrollParent(stickyTarget); 59 | 60 | if (!parentElement) { 61 | return; 62 | } 63 | 64 | const calculateIsSticky = debounce(() => { 65 | const pTop = parentElement.scrollTop; 66 | const tTop = stickyTarget.getBoundingClientRect().top; 67 | const difference = Math.round(Math.abs(pTop - tTop)); 68 | const triggerDistance = window 69 | .getComputedStyle(stickyTarget) 70 | .getPropertyValue("top"); 71 | 72 | setIsSticky(difference !== Math.abs(parseInt(triggerDistance, 10))); 73 | }, 25); 74 | 75 | calculateIsSticky(); 76 | 77 | parentElement.addEventListener("scroll", calculateIsSticky); 78 | 79 | return () => parentElement.removeEventListener("scroll", calculateIsSticky); 80 | }, [element]); 81 | 82 | return isSticky; 83 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/monorepo", 3 | "private": true, 4 | "license": "MIT", 5 | "author": { 6 | "email": "lisowski54@gmail.com", 7 | "name": "Andrew Lisowski" 8 | }, 9 | "scripts": { 10 | "start": "tsc --build tsconfig.dev.json --watch", 11 | "clean": "yarn clean:build && yarn clean:modules", 12 | "clean:modules": "lerna clean --yes && rimraf node_modules", 13 | "clean:build": "rimraf packages/*/dist && rimraf packages/*/tsconfig.tsbuildinfo && rimraf plugins/*/tsconfig.tsbuildinfo", 14 | "build": "lerna run build --stream", 15 | "lint": "./packages/cli/bin/ds.js lint", 16 | "test": "./packages/cli/bin/ds.js test", 17 | "create:plugin": "./scripts/create-plugin.js", 18 | "create:package": "./packages/cli/bin/ds.js create package", 19 | "format" : "prettier --write ." 20 | }, 21 | "workspaces": [ 22 | "packages/*", 23 | "plugins/*" 24 | ], 25 | "prettier": { 26 | "singleQuote": true 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/intuit/design-systems-cli.git" 31 | }, 32 | "publishConfig": { 33 | "registry": "https://registry.npmjs.org/", 34 | "access": "public" 35 | }, 36 | "devDependencies": { 37 | "@auto-it/all-contributors": "10.36.5", 38 | "@auto-it/first-time-contributor": "10.36.5", 39 | "@auto-it/released": "10.36.5", 40 | "auto": "10.36.5", 41 | "change-case": "4.1.1", 42 | "copy-template-dir": "1.4.0", 43 | "lerna": "4.0.0", 44 | "prettier": "2.1.2", 45 | "rimraf": "3.0.2", 46 | "signale": "1.4.0", 47 | "typescript": "4.2.2" 48 | }, 49 | "auto": { 50 | "plugins": [ 51 | "npm", 52 | "released", 53 | "first-time-contributor", 54 | [ 55 | "all-contributors", 56 | { 57 | "types": { 58 | "plugin": "**/plugin/**/*", 59 | "code": [ 60 | "**/src/**/*", 61 | "**/package.json", 62 | "**/tsconfig.json" 63 | ] 64 | } 65 | } 66 | ] 67 | ], 68 | "labels": [ 69 | { 70 | "name": "dependency-update", 71 | "changelogTitle": "🔩 Dependency Updates", 72 | "releaseType": "none" 73 | }, 74 | { 75 | "name": "blog-post", 76 | "changelogTitle": "📚 Blog Post", 77 | "releaseType": "none" 78 | } 79 | ] 80 | }, 81 | "engines": { 82 | "node": ">=10.18.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /packages/babel-plugin-replace-styles/README.md: -------------------------------------------------------------------------------- 1 | # @design-systems/babel-plugin-replace-styles 2 | 3 | This babel plugin lets you overwrite CSS imports that are happening inside your design system. 4 | 5 | ### Why would you want to do that? 6 | 7 | Here's a simple example that showcases a potential use-case: 8 | 9 | You have a `Button` component, built using the Design Systems CLI. It has a `main.css` output that gets generated when you build, 10 | and you have a single `PostCSS` build that generates it. Now, another team wants you to auto-prefix your CSS to support older browsers. 11 | Without this plugin, you would need to just add the prefixes and now everyone has to download those, just to support the extra use case. 12 | 13 | _OR_ 14 | 15 | We could use the DS-CLI `build` plugin's multi-build CSS feature, and generate two CSS files: `main.css` and `prefixed.css`. 16 | The only issue is, if we turn on automatic CSS imports, everyone will get `main` and not `prefixed`. 17 | 18 | Enter this plugin. Now the team who needs prefixed CSS can install this and configure it to replace `main` with `prefixed` if it exists. 19 | Everyone only gets the CSS they need imported, and we can still have a nice auto-import by default. 20 | 21 | _Note: This also works well with [postcss-themed](https://github.com/intuit/postcss-themed); you can use this plugin to swap between different css files that contain different themes!_ 22 | 23 | ## Usage 24 | 25 | babel.config.json: 26 | 27 | ```json 28 | { 29 | "plugins": [ 30 | [ 31 | "@design-systems/babel-plugin-replace-styles", 32 | { "scope": "your-ds", "replace": "main", "use": "prefixed" } 33 | ] 34 | ] 35 | } 36 | ``` 37 | 38 | ## Example 39 | 40 | Input: 41 | 42 | ```js 43 | // In node_modules/my-ds/component/dist/esm/index.js 44 | import '../main.css'; 45 | ``` 46 | 47 | Output: 48 | 49 | ```js 50 | import '../prefixed.css'; 51 | ``` 52 | 53 | Notes for setup: 54 | 55 | `babel-loader` will _not_ parse `node_modules` by default. You'll need to use [babel.config.json](https://babeljs.io/docs/en/configuration#babelconfigjs) (a `.babelrc` will not work), and you'll need to configure webpack to parse your node_modules. I've provided an example below: 56 | 57 | ```js 58 | module: { 59 | rules: [ 60 | { 61 | include: [ 62 | path.join(__dirname, 'node_modules/@your-ds/'), 63 | path.join(__dirname, 'src'), 64 | ], 65 | test: /\.(js|jsx|mjs|tsx|ts)$/, 66 | loader: 'babel-loader', 67 | }, 68 | ]; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /plugins/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@design-systems/build", 3 | "version": "4.15.4", 4 | "description": "The build command for @design-systems-cli", 5 | "main": "dist/index.js", 6 | "author": "Adam Dierkens ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "files": [ 12 | "dist", 13 | "postcss.config.js", 14 | "babel.config.js" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/intuit/design-systems-cli.git" 19 | }, 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "dependencies": { 24 | "@babel/code-frame": "^7.12.13", 25 | "@babel/core": "^7.13.8", 26 | "@babel/plugin-proposal-class-properties": "^7.13.0", 27 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 28 | "@babel/plugin-transform-runtime": "^7.13.9", 29 | "@babel/preset-env": "^7.13.9", 30 | "@babel/preset-react": "^7.12.13", 31 | "@babel/preset-typescript": "^7.13.0", 32 | "@babel/runtime": "^7.13.9", 33 | "@design-systems/cli-utils": "link:../../packages/cli-utils", 34 | "@design-systems/plugin": "link:../../packages/plugin", 35 | "autoprefixer": "9.8.6", 36 | "babel-plugin-macros": "3.0.1", 37 | "babel-plugin-styled-components": "1.12.0", 38 | "babel-preset-jest": "26.3.0", 39 | "change-case": "4.1.1", 40 | "chokidar": "3.4.2", 41 | "clean-css": "4.2.3", 42 | "colorette": "1.2.2", 43 | "dedent": "0.7.0", 44 | "fast-glob": "3.2.5", 45 | "fs-extra": "9.0.1", 46 | "icss-utils": "4.1.1", 47 | "minimatch": "3.0.4", 48 | "pkg-up": "3.1.0", 49 | "postcss": "7.0.32", 50 | "postcss-hexrgba": "2.0.1", 51 | "postcss-icss-selectors": "2.0.3", 52 | "postcss-load-config": "2.1.0", 53 | "postcss-modules": "3.2.2", 54 | "postcss-nested": "4.2.3", 55 | "postcss-url": "8.0.0", 56 | "pretty-bytes": "5.6.0", 57 | "pretty-ms": "7.0.0", 58 | "tapable": "1.1.3", 59 | "tslib": "2.0.1", 60 | "type-fest": "0.16.0", 61 | "typescript": "4.2.2" 62 | }, 63 | "devDependencies": { 64 | "@types/autoprefixer": "9.7.2", 65 | "@types/babel__code-frame": "7.0.2", 66 | "@types/babel__core": "7.1.9", 67 | "@types/clean-css": "4.2.2", 68 | "@types/dedent": "0.7.0", 69 | "@types/fs-extra": "9.0.1", 70 | "@types/icss-utils": "4.1.0", 71 | "@types/minimatch": "3.0.3", 72 | "@types/postcss-nested": "4.1.0", 73 | "@types/postcss-url": "8.0.1", 74 | "@types/pretty-bytes": "5.2.0", 75 | "@types/pretty-ms": "5.0.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/create/src/template.ts: -------------------------------------------------------------------------------- 1 | import colorette from 'colorette'; 2 | import terminalLink from 'terminal-link'; 3 | 4 | import installedTemplates from './templates.json'; 5 | 6 | export const creationChoices = ['component', 'system', 'package'] as const; 7 | export type CreationChoice = typeof creationChoices[number]; 8 | 9 | export interface Template { 10 | /** The name of the template */ 11 | name: string; 12 | /** URL to the github repository that contains the template */ 13 | url: string; 14 | /** A short description of what the template does */ 15 | description: string; 16 | /** The commit sha of the version to clone */ 17 | sha: string; 18 | } 19 | 20 | export const templates: Record = installedTemplates; 21 | 22 | /** Get the matching template for the creation type and name */ 23 | export function getTemplate( 24 | type: CreationChoice, 25 | name: string, 26 | userTemplates: Template[] = [] 27 | ) { 28 | const allTemplates = [...userTemplates, ...templates[type]]; 29 | const template = allTemplates.find(t => t.name === name); 30 | 31 | if (template) { 32 | return `${template.url}#${template.sha}`; 33 | } 34 | 35 | return name; 36 | } 37 | 38 | const colorOrder = [ 39 | 'green', 40 | 'cyan', 41 | 'magenta', 42 | 'gray', 43 | 'yellow', 44 | 'greenBright', 45 | 'redBright', 46 | 'yellowBright', 47 | 'blueBright', 48 | 'magentaBright', 49 | 'cyanBright' 50 | ] as const; 51 | 52 | /** List all the available templates for a creation type */ 53 | export function listTemplates( 54 | type: CreationChoice, 55 | defaultTemplate = 'ts', 56 | userTemplates: Template[] = [] 57 | ) { 58 | const allTemplates = [...userTemplates, ...templates[type]]; 59 | let output = `Available templates for "${type}":\n\n`; 60 | 61 | [ 62 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 63 | allTemplates.find(t => t.name === defaultTemplate)!, 64 | ...allTemplates.filter(t => t.name !== defaultTemplate) 65 | ].forEach((template, index) => { 66 | const color = colorette[colorOrder[index % colorOrder.length]]; 67 | 68 | output += terminalLink.isSupported 69 | ? ` ${color(terminalLink(template.name, template.url))}` 70 | : ` ${color(template.name)}`; 71 | 72 | output += ` - ${template.description}`; 73 | 74 | if (template.name === defaultTemplate) { 75 | output += ' [DEFAULT]'; 76 | } 77 | 78 | if (!terminalLink.isSupported) { 79 | output += ` (${template.url})`; 80 | } 81 | 82 | output += '\n'; 83 | }); 84 | 85 | return output; 86 | } 87 | -------------------------------------------------------------------------------- /packages/docs/src/usage.md: -------------------------------------------------------------------------------- 1 | # Using `@design-systems/cli` 2 | 3 | Out of the box, `@design-systems/cli` comes with a lot of different scripts. 4 | When to use what script can be a little confusing. 5 | 6 | Most of the scripts are ran infrequently locally or are used by other scripts, so you really only need to know about a few. 7 | The following will detail the default workflow you and your teammates should use while developing with the CLI. 8 | 9 | ## Creating new packages 10 | 11 | To create a new component in your repo run `yarn run create` from the root. 12 | This will create a new templated component in the `components` folder. 13 | To create a new package in your repo, maybe for a set of shared utils, run `yarn create:package` from the root. 14 | This will create a new templated package in the `packages` folder. 15 | 16 | ## Developing 17 | 18 | The command you will most often use while developing is the `dev` command. 19 | This command can be used at the root of the project or from the component level (example: `components/Card`). 20 | 21 | When `dev` is run from the root it will build all of your components and serve a storybook with all of their stories. 22 | This will reflect what your published storybook will look like. 23 | Once you start adding more components you might notice that builds are taking longer. 24 | Usually you don't need to see all the stories at once, this might even be distracting. 25 | 26 | Instead of developing with a storybook for all of your components you can run `dev` from the component level. 27 | This will do a few things differently: 28 | 29 | - Build the component and any component or package withing the repo it depends on 30 | 31 | _Example_: `@my-design-system/card` => `@my-design-system/icon` => `@my-design-system/styles` = `card` + `icon` + `styles` will all be built) 32 | 33 | - Start a storybook for _just_ that component's stories 34 | 35 | This allows you to develop just a subset of your storybook, decreasing build times and focusing your development. 36 | 37 | ## Testing 38 | 39 | To test your project the commands you will run the most are: 40 | 41 | - `lint` 42 | - `test` 43 | 44 | These commands can be run from the root or package level. 45 | From the root all files will be tested. 46 | From the package only the contents of that package will tested. 47 | 48 | ## Cleaning up 49 | 50 | Sometimes you need to reset your local repo. 51 | To do this use the `clean` command. 52 | It will delete every generated file from your project: 53 | 54 | This includes: 55 | 56 | - `node_modules` 57 | - `dist` 58 | - `out` 59 | - `coverage` 60 | - `tsbuildinfo.json` 61 | -------------------------------------------------------------------------------- /packages/hooks/src/useOverflowing.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { debounce } from "@design-systems/utils"; 3 | 4 | type Side = "top" | "left" | "bottom" | "right"; 5 | type OverflowState = Record; 6 | 7 | /** 8 | * This hook allows you to determine when one of your elements has overflowing. 9 | * 10 | * @param ref - The dom element to detect overflow on 11 | * 12 | * @example 13 | * const Example = () => { 14 | * const ref = useRef(null); 15 | * const { bottom } = useOverflowing(ref); 16 | * 17 | * return ( 18 | *
{ 19 | * bottom 20 | * ? 'The bottom is scrolling' 21 | * : 'now it isn\'t!'} 22 | *
23 | * ); 24 | * }; 25 | */ 26 | export const useOverflowing = ( 27 | ref: React.RefObject 28 | ): OverflowState => { 29 | const [overflowing, setOverflowing] = React.useState({ 30 | top: false, 31 | bottom: false, 32 | left: false, 33 | right: false, 34 | }); 35 | 36 | React.useEffect(() => { 37 | const { current } = ref; 38 | 39 | if (!current) { 40 | return; 41 | } 42 | 43 | const updateOverflowing = debounce(() => { 44 | if (!current) { 45 | return; 46 | } 47 | 48 | const { 49 | scrollHeight, 50 | scrollWidth, 51 | clientHeight, 52 | clientWidth, 53 | scrollTop, 54 | scrollLeft, 55 | } = current; 56 | const scrollX = scrollWidth - clientWidth; 57 | const scrollY = scrollHeight - clientHeight; 58 | 59 | const state: OverflowState = { 60 | top: scrollTop !== 0, 61 | bottom: scrollHeight > clientHeight && scrollY !== scrollTop, 62 | left: scrollLeft !== 0, 63 | right: scrollWidth > clientWidth && scrollX !== scrollLeft, 64 | }; 65 | 66 | if ( 67 | state.bottom !== overflowing.bottom || 68 | state.top !== overflowing.top || 69 | state.left !== overflowing.left || 70 | state.right !== overflowing.right 71 | ) { 72 | setOverflowing(state); 73 | } 74 | }); 75 | 76 | if (!window) { 77 | return; 78 | } 79 | 80 | updateOverflowing(); 81 | 82 | window.addEventListener("resize", updateOverflowing); 83 | current.addEventListener("scroll", updateOverflowing); 84 | 85 | return () => { 86 | window.removeEventListener("resize", updateOverflowing); 87 | current.removeEventListener("scroll", updateOverflowing); 88 | }; 89 | }, [ref, overflowing]); 90 | 91 | return overflowing; 92 | }; -------------------------------------------------------------------------------- /plugins/bundle/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createLogger, getMonorepoRoot, getRepoRoot } from '@design-systems/cli-utils'; 2 | import { Plugin } from '@design-systems/plugin'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import webpack from 'webpack'; 6 | import baseWebpackConfig, { ConfigOptions } from './webpack.base.config'; 7 | 8 | export interface BundleArgs { 9 | /** 10 | * Run the build in debug mode, skipping minification 11 | */ 12 | debug?: boolean; 13 | } 14 | 15 | /** 16 | * Promisify the webpack API 17 | */ 18 | function pack(config: webpack.Configuration): Promise { 19 | return new Promise((resolve, reject) => { 20 | webpack(config, (err, stats) => { 21 | if (err) { 22 | return reject(err); 23 | } 24 | 25 | return resolve(stats); 26 | }); 27 | }); 28 | } 29 | 30 | /** 31 | * Resolve the webpack config to use when building 32 | */ 33 | async function getConfig( 34 | options: ConfigOptions 35 | ): Promise { 36 | const root = getMonorepoRoot() || getRepoRoot(); 37 | const customConfigPath = path.join(root, 'webpack.config.js'); 38 | let config = baseWebpackConfig; 39 | if (fs.existsSync(customConfigPath)) { 40 | // eslint-disable-next-line 41 | const overrideConfig = require(customConfigPath); 42 | config = overrideConfig.default || overrideConfig; 43 | } 44 | 45 | if (typeof config === 'function') { 46 | return config(options); 47 | } 48 | 49 | return config; 50 | } 51 | 52 | /** 53 | * A plugin to _bundle_ a component 54 | */ 55 | export default class BundlePlugin implements Plugin { 56 | private logger = createLogger({ scope: 'bundle' }); 57 | 58 | async bundle(args: BundleArgs, entry: string): Promise { 59 | const stats = await pack( 60 | await getConfig({ 61 | ...args, 62 | entry 63 | }) 64 | ); 65 | const buildInfo = stats.toJson(); 66 | 67 | if (stats.hasErrors()) { 68 | this.logger.error(buildInfo.errors.join('')); 69 | process.exit(1); 70 | } 71 | 72 | this.logger.log(stats.toString({ colors: true })); 73 | } 74 | 75 | async bundleCurrent(args: BundleArgs) { 76 | await this.bundle(args, './src'); 77 | } 78 | 79 | async run(args: BundleArgs) { 80 | // Generate the webpack config 81 | 82 | if (fs.existsSync('lerna.json')) { 83 | this.logger.error('Run this from a component folder instead'); 84 | process.exit(1); 85 | } else { 86 | await this.bundleCurrent(args); 87 | } 88 | 89 | this.logger.await('Created bundle'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /plugins/size/src/RelativeCommentsPlugin.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | 3 | import webpack from 'webpack'; 4 | import { RawSource } from 'webpack-sources'; 5 | import { monorepoName } from '@design-systems/cli-utils'; 6 | import { paramCase } from 'change-case'; 7 | import { RelativeCommentsPluginOptions } from './interfaces'; 8 | 9 | /** Normalizes comments for better diffs */ 10 | export default class RelativeCommentsPlugin { 11 | private options: RelativeCommentsPluginOptions; 12 | 13 | constructor(options: RelativeCommentsPluginOptions) { 14 | this.options = options; 15 | } 16 | 17 | apply(compiler: webpack.Compiler) { 18 | compiler.hooks.compilation.tap('RelativeCommentsPlugin', compilation => { 19 | compilation.hooks.optimizeChunkAssets.tap( 20 | 'RelativeCommentsPlugin', 21 | chunks => { 22 | chunks.forEach(chunk => { 23 | const transformList: string[] = chunk.files.filter( 24 | file => /\.js$/.test(file) && !/^css/.test(file) 25 | ); 26 | 27 | if (!transformList.length) { 28 | return; 29 | } 30 | 31 | transformList.forEach(file => { 32 | /** Normalize the path in a comment */ 33 | const replacePath = (match: string, p1: string) => { 34 | let newPath = p1; 35 | 36 | if (p1.includes('node_modules')) { 37 | newPath = p1 38 | .split('node_modules/')[1] 39 | .replace(this.options.importName, '.'); 40 | } else if (p1.startsWith('../') && p1.includes('dist/')) { 41 | newPath = p1.replace( 42 | /\.\.\/([a-zA-Z0-9_-]+)\//, 43 | (whole: string, packageName: string) => 44 | `@${monorepoName()}/${paramCase(packageName)}/`.trim() 45 | ); 46 | } 47 | 48 | return match.replace(p1, newPath); 49 | }; 50 | 51 | const modifiedSource = compilation.assets[file] 52 | .source() 53 | .replace(/\/\/ CONCATENATED MODULE: (\S+)/g, replacePath) 54 | .replace(/\/\/ EXTERNAL MODULE: (\S+)/g, replacePath) 55 | .split('\n') 56 | .filter((p: string) => p.trim() !== '') 57 | .join('\n'); 58 | 59 | // eslint-disable-next-line no-param-reassign 60 | compilation.assets[file] = new RawSource(modifiedSource); 61 | }); 62 | }); 63 | } 64 | ); 65 | }); 66 | } 67 | } 68 | --------------------------------------------------------------------------------