diff --git a/.gitignore b/.gitignore index 29013a1..89b38ab 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ node_modules # testing coverage/ test/__snapshots__ -.jest-test-results.json + # production /build @@ -35,6 +35,10 @@ components/**/*.d.ts components/**/*.js components/**/*.map +templates/**/*.d.ts +templates/**/*.js +templates/**/*.map + utils/**/*.d.ts utils/**/*.js utils/**/*.map diff --git a/README.md b/README.md index cbf59b6..7fa45ff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ For end-user documentation, visit: http://jenkinsci.github.io/jenkins-design-lan The Jenkins Design Language is a React component library with associated assets and styles available to use with [Jenkins Blue Ocean](https://github.com/jenkinsci/blueocean-plugin) and extensions, as well as any other project you may choose to use this library with. -## Building +### Building This uses `lerna` to build, but needs some specific options, so they're baked into the `package.json`: @@ -19,7 +19,7 @@ npm run bootstrap npm run clean ``` -## Generating a new Component +### Generating a new Component This will provide options for generating a new component within the /components. @@ -27,7 +27,7 @@ This will provide options for generating a new component within the /components. $ npm run generate ``` -## Storybook +### Storybook React Storybook is an easy way to develop components with real-time feedback, run in the browser. To run Storybook, just run: @@ -37,10 +37,6 @@ npm start Then go to: http://localhost:9001/ -### Tests within storybook - -Storybook may embed test results from Jest tests. For an example, see: [Button.stories.tsx](./components/Button/Button.stories.tsx#L11). In particular, note the fact that you must return the name of the suite, e.g. `require('./Button.test') && 'Button'`. - ### Testing Components ``` diff --git a/components/Button/Button.stories.tsx b/components/Button/Button.stories.tsx index c6984b0..a12e88d 100644 --- a/components/Button/Button.stories.tsx +++ b/components/Button/Button.stories.tsx @@ -2,12 +2,6 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { Button } from './Button'; -declare var specs: any; - -const stories = storiesOf('Button', module); - -stories.add('should display text', () => { - const story = ; - specs(() => require('./Button.test.tsx') && 'Button'); - return story; +storiesOf('Button', module).add('should display text', () => { + return ; }); diff --git a/components/Dialog/Dialog.md b/components/Dialog/Dialog.md new file mode 100644 index 0000000..99fed3a --- /dev/null +++ b/components/Dialog/Dialog.md @@ -0,0 +1,13 @@ +TODO - add description + +#### < Component > Types + +```jsx +TODO; +``` + +#### < Component > Sizes + +```jsx +TODO; +``` diff --git a/components/Dialog/Dialog.scss b/components/Dialog/Dialog.scss new file mode 100644 index 0000000..a07ce74 --- /dev/null +++ b/components/Dialog/Dialog.scss @@ -0,0 +1,77 @@ +$dialog-spacing: 32px; +$dialog-border-radius: 3px; +$dialog-box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.5); +$dialog-background: rgb(255, 255, 255); +$dialog-overlay-background: rgba(0, 0, 0, 0.25); +$dialog-header-height: 80px; +$dialog-header-border-radius: 3px; +$dialog-header-background: #4a90e2; +$dialog-header-color: #ffffff; +$dialog-content-height: 564px; + +.Dialog { + position: absolute; + box-shadow: $dialog-box-shadow; + background: $dialog-background; + border-radius: $dialog-border-radius; + border: none; + max-width: 95%; + max-height: 90%; +} + +.Dialog-Overlay { + position: fixed; + background: $dialog-overlay-background; + z-index: $zindex-dialog; + left: 0; + top: 0; + bottom: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; +} + +.Dialog-header { + display: flex; + align-items: center; + width: 100%; + height: $dialog-header-height; + flex-shrink: 0; + color: $dialog-header-color; + background: $dialog-header-background; + border-top-left-radius: $dialog-header-border-radius; + border-top-right-radius: $dialog-header-border-radius; + padding: 0 $dialog-spacing; + + > h3 { + font-size: 24px; + } +} + +.Dialog-content-scroll { + overflow: auto; + flex-shrink: 1; + + .Dialog--medium-size & { + max-width: $dialog-content-height; + } +} + +.Dialog-content-margin { + margin: $dialog-spacing; + margin-bottom: $dialog-spacing / 2; +} + +.Dialog-button-bar { + padding: 0 $dialog-spacing; + margin: $dialog-spacing / 2 0 $dialog-spacing; + display: flex; + width: 100%; + justify-content: flex-start; + flex-shrink: 0; + + & > button + button { + margin-left: 0.5em; + } +} diff --git a/components/Dialog/Dialog.stories.tsx b/components/Dialog/Dialog.stories.tsx new file mode 100644 index 0000000..f9eec31 --- /dev/null +++ b/components/Dialog/Dialog.stories.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { Dialog } from './Dialog'; + +const stories = storiesOf('Dialog', module); + +stories.add('should display a modal', () => { + let content = Some modal content!; + return {content}; +}); diff --git a/components/Dialog/Dialog.test.tsx b/components/Dialog/Dialog.test.tsx new file mode 100644 index 0000000..9927178 --- /dev/null +++ b/components/Dialog/Dialog.test.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import * as Enzyme from 'enzyme'; +import { Dialog } from './Dialog'; + +describe('Dialog', () => { + it('should render a dialog with content', () => { + const content = 'testing with some content'; + const mounted = Enzyme.mount( + + {content} + + ); + expect(mounted.find('span').get(0).props.children).toEqual(content); + }); +}); diff --git a/components/Dialog/Dialog.tsx b/components/Dialog/Dialog.tsx new file mode 100644 index 0000000..7780709 --- /dev/null +++ b/components/Dialog/Dialog.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import * as ReactModal from 'react-modal'; +import * as classNames from 'classnames'; + +export interface DialogProps { + readonly title?: string; + readonly className?: string; + readonly onDismiss?: () => void; + readonly shouldCloseOnEsc?: boolean; + children: JSX.Element; +} + +export interface DialogState { + readonly showModal: boolean; +} + +export class Dialog extends React.Component { + constructor(props: DialogProps) { + super(props); + + this.state = { showModal: true }; + this.handleCloseModal = this.handleCloseModal.bind(this); + } + + handleCloseModal() { + this.setState({ + ...this.state, + showModal: false, + }); + + if (this.props.onDismiss) { + this.props.onDismiss(); + } + } + + render() { + const shouldCloseOnEsc = this.props.hasOwnProperty('shouldCloseOnEsc') + ? this.props.shouldCloseOnEsc + : true; + + const defaultProps = { + className: classNames('Dialog', this.props.className), + overlayClassName: 'Dialog-Overlay', + onRequestClose: this.handleCloseModal, + isOpen: this.state.showModal, + shouldCloseOnEsc, + }; + + return ( +
+ + {this.props.title && } + {this.props.children} + +
+ Close +
+
+
+
+ ); + } +} + +export interface DialogHeaderProps { + title: string; +} + +export function DialogHeader(props: DialogHeaderProps) { + return ( +
+

{props.title}

+
+ ); +} + +export interface DialogContentProps { + children: JSX.Element; +} + +export function DialogContent(props: DialogContentProps) { + return ( +
+
{props.children}
+
+ ); +} + +export interface DialogButtonsProps { + children: JSX.Element; +} + +export function DialogButtons(props: DialogButtonsProps) { + return
{props.children}
; +} diff --git a/components/Dialog/README.md b/components/Dialog/README.md new file mode 100644 index 0000000..40a2b87 --- /dev/null +++ b/components/Dialog/README.md @@ -0,0 +1,3 @@ +# Your component + +This is < component > documentation diff --git a/components/Dialog/package.json b/components/Dialog/package.json new file mode 100644 index 0000000..6642815 --- /dev/null +++ b/components/Dialog/package.json @@ -0,0 +1,43 @@ +{ + "name": "@jdl2/dialog", + "version": "0.0.1", + "main": "./Dialog", + "scripts": { + "build": "tsc", + "test": "jest" + }, + "files": [ + "*.js", + "*.d.ts", + "*.d.map", + "*.scss" + ], + "dependencies": { + "prop-types": "*", + "react": "*", + "react-addons-css-transition-group": "*", + "react-dom": "*" + }, + "devDependencies": { + "@storybook/addon-jest": "^3.4.8", + "@types/classnames": "^2.2.4", + "@types/enzyme": "^3.1.9", + "@types/react": "*", + "@types/react-addons-css-transition-group": "*", + "@types/react-dom": "*", + "@types/react-modal": "^3.1.2", + "@types/react-test-renderer": "^16.0.1", + "@types/react-transition-group": "*", + "classnames": "^2.2.6", + "enzyme": "^3.3.0", + "jest": "^22.4.2", + "jest-storybook-facade": "0.0.8", + "react-modal": "^3.4.5", + "react-test-renderer": "^16.4.1", + "storybook-addon-specifications": "^2.1.2", + "typescript": "*" + }, + "jest": { + "preset": "typewebjest/jest-preset.json" + } +} diff --git a/components/Dialog/tsconfig.json b/components/Dialog/tsconfig.json new file mode 100644 index 0000000..63b5082 --- /dev/null +++ b/components/Dialog/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/components/Input/Input.stories.tsx b/components/Input/Input.stories.tsx index 8cb0502..c27db8c 100644 --- a/components/Input/Input.stories.tsx +++ b/components/Input/Input.stories.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { Input } from './Input'; -storiesOf('Input', module) -.add('should display an input', () => { - return console.log('')} />; +storiesOf('Input', module).add('should display an input', () => { + return ; }); diff --git a/components/Input/Input.test.tsx b/components/Input/Input.test.tsx index a99b363..1ab0436 100644 --- a/components/Input/Input.test.tsx +++ b/components/Input/Input.test.tsx @@ -1,9 +1,17 @@ -describe('Description: ', () => { - it('should contain 3 items', () => { - expect(3).toBe(3); +import * as React from 'react'; +import * as Enzyme from 'enzyme'; +import { Input } from './Input'; + +describe('Input: ', () => { + it('should display correct input', () => { + const input = Enzyme.mount( console.log(e)} />); + input.simulate('change', { target: { value: 'changed value.' } }); + expect(input.length).toBe(1); }); - it('should work fine', () => { - expect(true).toBe(true); + it('should display an input without onchange set', () => { + const input = Enzyme.mount(); + input.simulate('change', { target: { value: 'changed value.' } }); + expect(input.length).toBe(1); }); }); diff --git a/components/Input/Input.tsx b/components/Input/Input.tsx index 9318976..8481922 100644 --- a/components/Input/Input.tsx +++ b/components/Input/Input.tsx @@ -1,11 +1,21 @@ import * as React from 'react'; +import { css, CssProps } from '@jdl2/css-util'; -export interface InputProps { - onChange: (value: string) => void; +export interface InputProps extends CssProps { + onChange?: (value: string) => void; } -export function Input({ onChange }: InputProps) { +export function Input(props: InputProps) { + const { onChange } = props; return ( - onChange(e.target.value)} /> + { + if (onChange) { + onChange(e.target.value); + } + }} + /> ); } diff --git a/components/Input/package.json b/components/Input/package.json index 59d144b..76c2580 100644 --- a/components/Input/package.json +++ b/components/Input/package.json @@ -18,6 +18,7 @@ "access": "public" }, "dependencies": { + "@jdl2/css-util": "^2.0.0-alpha.9", "prop-types": "*", "react": "*", "react-addons-css-transition-group": "*", diff --git a/css/index.scss b/css/index.scss index 4fa7a43..6d1e486 100644 --- a/css/index.scss +++ b/css/index.scss @@ -4,6 +4,7 @@ @import '../components/Button/Button.scss'; @import '../components/Table/Table.scss'; @import '../components/Input/Input.scss'; +@import '../components/Dialog/Dialog.scss'; .clickable { cursor: pointer; diff --git a/jdl-component-creator.js b/jdl-component-creator.js index 41af46a..841a44a 100755 --- a/jdl-component-creator.js +++ b/jdl-component-creator.js @@ -6,7 +6,7 @@ const TEMPLATE_CHOICE_NAME = 'template-choice'; const COMPONENT_NAME = 'component-name'; const filePrefixesToNotModify = ['README', 'package', 'tsconfig']; -const templateChoices = fs.readdirSync(`${__dirname}/components/templates`); +const templateChoices = fs.readdirSync(`${__dirname}/templates`); const questions = [ { @@ -29,7 +29,7 @@ const questions = [ inquirer.prompt(questions).then(answers => { const templateChoice = answers[TEMPLATE_CHOICE_NAME]; const componentChoice = answers[COMPONENT_NAME]; - const templatePath = `${__dirname}/components/templates/${templateChoice}`; + const templatePath = `${__dirname}/templates/${templateChoice}`; fs.mkdirSync(`${__dirname}/components/${componentChoice}`); diff --git a/templates/base/index.stories.tsx b/templates/base/index.stories.tsx index 4019b90..ca06bf6 100644 --- a/templates/base/index.stories.tsx +++ b/templates/base/index.stories.tsx @@ -4,12 +4,9 @@ // import { storiesOf } from '@storybook/react'; // import { Button } from './Button'; -// declare var specs: any; - // const stories = storiesOf('Button', module); // // stories.add('should display text', () => { // const story = ; -// specs(() => require('./Button.test.tsx') && 'Button'); // return story; // }); diff --git a/templates/base/index.test.tsx b/templates/base/index.test.tsx index 9173e0e..c9566ad 100644 --- a/templates/base/index.test.tsx +++ b/templates/base/index.test.tsx @@ -1,9 +1,10 @@ -import * as React from 'react'; -import * as Enzyme from 'enzyme'; - // TODO rewrite for your component + +// import * as React from 'react'; +// import * as Enzyme from 'enzyme'; + describe('Component', () => { - it('should create and render a button with text', () => { + it('should ...', () => { expect(true).toBe(true); }); });