mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 17:46:56 +03:00
feature(devui): convert to TypeScript (#629)
* stash * and those * stash * stash * stash * stash * tests * fix errors * revert * stash * fix lint * prettier
This commit is contained in:
parent
f4405ac0f0
commit
727d753081
|
@ -7,6 +7,3 @@ build
|
||||||
coverage
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
__snapshots__
|
__snapshots__
|
||||||
|
|
||||||
# Ignore until TS linting is configured in redux-devtools package
|
|
||||||
packages/redux-devtools/index.d.ts
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -31,6 +31,11 @@
|
||||||
"raw-loader": "^4.0.1",
|
"raw-loader": "^4.0.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
|
"stylelint": "^13.6.1",
|
||||||
|
"stylelint-config-prettier": "^8.0.2",
|
||||||
|
"stylelint-config-standard": "^20.0.0",
|
||||||
|
"stylelint-config-styled-components": "^0.1.1",
|
||||||
|
"stylelint-processor-styled-components": "^1.10.0",
|
||||||
"ts-jest": "^26.2.0",
|
"ts-jest": "^26.2.0",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
|
@ -40,14 +45,11 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"build": "lerna run prepare --since master --stream --sort -- --scripts-prepend-node-path",
|
|
||||||
"build:all": "lerna run build",
|
"build:all": "lerna run build",
|
||||||
"publish": "lerna publish",
|
"publish": "lerna publish",
|
||||||
"canary": "lerna publish --canary preminor --npm-tag alpha",
|
"canary": "lerna publish --canary preminor --npm-tag alpha",
|
||||||
"next": "lerna publish --bump prerelease --npm-tag next",
|
"next": "lerna publish --bump prerelease --npm-tag next",
|
||||||
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\" --cache",
|
"lint:all": "lerna run lint",
|
||||||
"lint:fix": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix --cache",
|
|
||||||
"lint:all": "eslint \"**/*.{js,jsx,ts,tsx}\"",
|
|
||||||
"prettify": "prettier --write .",
|
"prettify": "prettier --write .",
|
||||||
"prettier:check": "prettier --check .",
|
"prettier:check": "prettier --check .",
|
||||||
"test": "jest --onlyChanged",
|
"test": "jest --onlyChanged",
|
||||||
|
|
|
@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||||
|
|
||||||
**Note:** Version bump only for package d3-state-visualizer
|
**Note:** Version bump only for package d3-state-visualizer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 1.3.3 (2020-08-14)
|
## 1.3.3 (2020-08-14)
|
||||||
|
|
||||||
**Note:** Version bump only for package d3-state-visualizer
|
**Note:** Version bump only for package d3-state-visualizer
|
||||||
|
|
|
@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||||
|
|
||||||
**Note:** Version bump only for package d3-state-visualizer-tree-example
|
**Note:** Version bump only for package d3-state-visualizer-tree-example
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 0.0.1 (2020-08-14)
|
## 0.0.1 (2020-08-14)
|
||||||
|
|
||||||
**Note:** Version bump only for package d3-state-visualizer-tree-example
|
**Note:** Version bump only for package d3-state-visualizer-tree-example
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
"presets": [
|
||||||
"plugins": [
|
"@babel/preset-env",
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/preset-react",
|
||||||
"@babel/plugin-proposal-export-default-from"
|
"@babel/preset-typescript"
|
||||||
]
|
],
|
||||||
|
"plugins": ["@babel/plugin-proposal-class-properties"]
|
||||||
}
|
}
|
||||||
|
|
1
packages/devui/.eslintignore
Normal file
1
packages/devui/.eslintignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
lib
|
29
packages/devui/.eslintrc.js
Normal file
29
packages/devui/.eslintrc.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: '../../.eslintrc',
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
extends: '../../eslintrc.ts.react.base.json',
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['tests/*.ts', 'tests/*.tsx'],
|
||||||
|
extends: '../../eslintrc.ts.react.jest.base.json',
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tests/tsconfig.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['.storybook/tests/*.ts', '.storybook/tests/*.tsx'],
|
||||||
|
extends: '../../eslintrc.ts.react.jest.base.json',
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./.storybook/tsconfig.json'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Container } from '../src';
|
import { Container } from '../src';
|
||||||
import { listSchemes, listThemes } from '../src/utils/theme';
|
import { listSchemes, listThemes } from '../src/utils/theme';
|
||||||
import '../src/presets.js';
|
import '../src/presets';
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
4
packages/devui/.storybook/tsconfig.json
Normal file
4
packages/devui/.storybook/tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.react.base.json",
|
||||||
|
"include": ["../src", "."]
|
||||||
|
}
|
|
@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
||||||
|
|
||||||
**Note:** Version bump only for package devui
|
**Note:** Version bump only for package devui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.0.0-5](https://github.com/reduxjs/redux-devtools/compare/devui@1.0.0-4...devui@1.0.0-5) (2020-08-14)
|
# [1.0.0-5](https://github.com/reduxjs/redux-devtools/compare/devui@1.0.0-4...devui@1.0.0-5) (2020-08-14)
|
||||||
|
|
||||||
**Note:** Version bump only for package devui
|
**Note:** Version bump only for package devui
|
||||||
|
|
4
packages/devui/jest.config.js
Normal file
4
packages/devui/jest.config.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
|
||||||
|
};
|
|
@ -2,79 +2,69 @@
|
||||||
"name": "devui",
|
"name": "devui",
|
||||||
"version": "1.0.0-6",
|
"version": "1.0.0-6",
|
||||||
"description": "Reusable React components for building DevTools monitors and apps.",
|
"description": "Reusable React components for building DevTools monitors and apps.",
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/devui",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
|
||||||
"files": [
|
"files": [
|
||||||
"lib",
|
"lib",
|
||||||
"fonts"
|
"fonts"
|
||||||
],
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/reduxjs/redux-devtools.git"
|
"url": "https://github.com/reduxjs/redux-devtools.git"
|
||||||
},
|
},
|
||||||
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run storybook",
|
"start": "npm run storybook",
|
||||||
"build": "rimraf ./lib && babel ./src --out-dir ./lib --ignore tests,stories",
|
|
||||||
"lint:css": "stylelint \"./src/**/*.js\"",
|
|
||||||
"test:update": "npm run jest -- -u",
|
|
||||||
"test": "jest --no-cache",
|
|
||||||
"storybook": "start-storybook -p 6006 -s ./fonts",
|
"storybook": "start-storybook -p 6006 -s ./fonts",
|
||||||
"build-storybook": "build-storybook -s ./fonts",
|
"build-storybook": "build-storybook -s ./fonts",
|
||||||
"prepare": "npm run build",
|
"build": "npm run build:types && npm run build:js",
|
||||||
"prepublishOnly": "npm run test && npm run build"
|
"build:types": "tsc --emitDeclarationOnly",
|
||||||
},
|
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
|
||||||
"bugs": {
|
"clean": "rimraf lib",
|
||||||
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
"test": "jest",
|
||||||
},
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
"homepage": "https://github.com/reduxjs/redux-devtools",
|
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
||||||
"devDependencies": {
|
"lint:css": "stylelint \"./src/**/*.js\"",
|
||||||
"@babel/cli": "^7.10.5",
|
"type-check": "tsc --noEmit",
|
||||||
"@babel/core": "^7.11.1",
|
"type-check:watch": "npm run type-check -- --watch",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
"preversion": "npm run type-check && npm run lint && npm run test",
|
||||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.11.0",
|
|
||||||
"@babel/preset-env": "^7.11.0",
|
|
||||||
"@babel/preset-react": "^7.10.4",
|
|
||||||
"@storybook/addon-essentials": "^6.0.21",
|
|
||||||
"@storybook/react": "^6.0.21",
|
|
||||||
"babel-loader": "^8.1.0",
|
|
||||||
"enzyme": "^3.11.0",
|
|
||||||
"enzyme-adapter-react-16": "^1.15.3",
|
|
||||||
"enzyme-to-json": "^3.5.0",
|
|
||||||
"jest": "^26.2.2",
|
|
||||||
"jsdom": "^16.4.0",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"react-dom": "^16.13.1",
|
|
||||||
"react-is": "^16.13.1",
|
|
||||||
"react-test-renderer": "^16.13.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"stylelint": "^13.6.1",
|
|
||||||
"stylelint-config-prettier": "^8.0.2",
|
|
||||||
"stylelint-config-standard": "^20.0.0",
|
|
||||||
"stylelint-config-styled-components": "^0.1.1",
|
|
||||||
"stylelint-processor-styled-components": "^1.10.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.3.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base16": "^1.0.0",
|
"base16": "^1.0.0",
|
||||||
"codemirror": "^5.56.0",
|
"codemirror": "^5.56.0",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react-icons": "^3.10.0",
|
"react-icons": "^3.10.0",
|
||||||
"react-is": "^16.13.1",
|
|
||||||
"react-jsonschema-form": "^1.8.1",
|
"react-jsonschema-form": "^1.8.1",
|
||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
"redux-devtools-themes": "^1.0.0",
|
"redux-devtools-themes": "^1.0.0",
|
||||||
"simple-element-resize-detector": "^1.3.0",
|
"simple-element-resize-detector": "^1.3.0",
|
||||||
"styled-components": "^5.1.1"
|
"styled-components": "^5.1.1"
|
||||||
},
|
},
|
||||||
"jest": {
|
"devDependencies": {
|
||||||
"setupFilesAfterEnv": [
|
"@storybook/addon-essentials": "^6.0.21",
|
||||||
"<rootDir>/tests/setup.js"
|
"@storybook/react": "^6.0.21",
|
||||||
]
|
"@types/codemirror": "^0.0.97",
|
||||||
|
"@types/enzyme": "^3.10.5",
|
||||||
|
"@types/enzyme-adapter-react-16": "^1.0.6",
|
||||||
|
"@types/react-jsonschema-form": "^1.7.4",
|
||||||
|
"@types/react-select": "^3.0.19",
|
||||||
|
"csstype": "^3.0.2",
|
||||||
|
"enzyme": "^3.11.0",
|
||||||
|
"enzyme-adapter-react-16": "^1.15.3",
|
||||||
|
"enzyme-to-json": "^3.5.0",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js"
|
"peerDependencies": {
|
||||||
|
"react": "^16.3.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
|
||||||
import * as styles from './styles';
|
|
||||||
import { commonStyle, tooltipStyle } from './styles/common';
|
|
||||||
|
|
||||||
const ButtonWrapper = createStyledComponent(styles, 'button');
|
|
||||||
const TooltipWrapper = createStyledComponent(tooltipStyle);
|
|
||||||
const CommonWrapper = createStyledComponent(commonStyle);
|
|
||||||
|
|
||||||
export default class Button extends Component {
|
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
return (
|
|
||||||
nextProps.children !== this.props.children ||
|
|
||||||
nextProps.disabled !== this.props.disabled ||
|
|
||||||
nextProps.mark !== this.props.mark ||
|
|
||||||
nextProps.size !== this.props.size ||
|
|
||||||
nextProps.primary !== this.props.primary ||
|
|
||||||
nextProps.tooltipPosition !== this.props.tooltipPosition ||
|
|
||||||
nextProps.title !== this.props.title
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseUp = (e) => {
|
|
||||||
e.target.blur();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const button = (
|
|
||||||
<ButtonWrapper
|
|
||||||
theme={this.props.theme}
|
|
||||||
aria-label={this.props.title}
|
|
||||||
primary={this.props.primary}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
onMouseUp={this.onMouseUp}
|
|
||||||
onClick={this.props.onClick}
|
|
||||||
type={this.props.type}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</ButtonWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Wrapper = this.props.title ? TooltipWrapper : CommonWrapper;
|
|
||||||
return (
|
|
||||||
<Wrapper
|
|
||||||
theme={this.props.theme}
|
|
||||||
tooltipTitle={this.props.title}
|
|
||||||
tooltipPosition={this.props.tooltipPosition}
|
|
||||||
size={this.props.size}
|
|
||||||
mark={this.props.mark}
|
|
||||||
>
|
|
||||||
{button}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button.propTypes = {
|
|
||||||
children: PropTypes.any.isRequired,
|
|
||||||
title: PropTypes.string,
|
|
||||||
tooltipPosition: PropTypes.oneOf([
|
|
||||||
'top',
|
|
||||||
'bottom',
|
|
||||||
'left',
|
|
||||||
'right',
|
|
||||||
'bottom-left',
|
|
||||||
'bottom-right',
|
|
||||||
'top-left',
|
|
||||||
'top-right',
|
|
||||||
]),
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
type: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
primary: PropTypes.bool,
|
|
||||||
size: PropTypes.oneOf(['big', 'normal', 'small']),
|
|
||||||
mark: PropTypes.oneOf([
|
|
||||||
false,
|
|
||||||
'base08',
|
|
||||||
'base09',
|
|
||||||
'base0A',
|
|
||||||
'base0B',
|
|
||||||
'base0C',
|
|
||||||
'base0D',
|
|
||||||
'base0E',
|
|
||||||
'base0F',
|
|
||||||
]),
|
|
||||||
theme: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
Button.defaultProps = {
|
|
||||||
tooltipPosition: 'top',
|
|
||||||
};
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { MdFiberManualRecord } from 'react-icons/md';
|
import { MdFiberManualRecord } from 'react-icons/md';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Button from './';
|
import Button from './';
|
||||||
|
import { ButtonProps } from './Button';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Button',
|
title: 'Button',
|
||||||
|
@ -16,7 +18,7 @@ const Container = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<ButtonProps> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Button {...args} />
|
<Button {...args} />
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -25,6 +27,7 @@ const Template = (args) => (
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
Default.args = {
|
Default.args = {
|
||||||
title: 'Hello Tooltip! \\a And from new line hello!',
|
title: 'Hello Tooltip! \\a And from new line hello!',
|
||||||
|
tooltipPosition: 'top',
|
||||||
primary: true,
|
primary: true,
|
||||||
size: 'normal',
|
size: 'normal',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
@ -41,6 +44,7 @@ export const Mark = Template.bind({});
|
||||||
Mark.args = {
|
Mark.args = {
|
||||||
mark: 'base08',
|
mark: 'base08',
|
||||||
title: 'Hello Tooltip',
|
title: 'Hello Tooltip',
|
||||||
|
tooltipPosition: 'top',
|
||||||
size: 'normal',
|
size: 'normal',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
children: <MdFiberManualRecord />,
|
children: <MdFiberManualRecord />,
|
128
packages/devui/src/Button/Button.tsx
Normal file
128
packages/devui/src/Button/Button.tsx
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import React, { Component, ReactNode } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
|
import * as styles from './styles';
|
||||||
|
import { commonStyle, tooltipStyle } from './styles/common';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
|
const ButtonWrapper = createStyledComponent(styles, 'button');
|
||||||
|
const TooltipWrapper = createStyledComponent(tooltipStyle);
|
||||||
|
const CommonWrapper = createStyledComponent(commonStyle);
|
||||||
|
|
||||||
|
export type TooltipPosition =
|
||||||
|
| 'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'left'
|
||||||
|
| 'right'
|
||||||
|
| 'bottom-left'
|
||||||
|
| 'bottom-right'
|
||||||
|
| 'top-left'
|
||||||
|
| 'top-right';
|
||||||
|
|
||||||
|
export type Size = 'big' | 'normal' | 'small';
|
||||||
|
|
||||||
|
export type Mark =
|
||||||
|
| 'base08'
|
||||||
|
| 'base09'
|
||||||
|
| 'base0A'
|
||||||
|
| 'base0B'
|
||||||
|
| 'base0C'
|
||||||
|
| 'base0D'
|
||||||
|
| 'base0E'
|
||||||
|
| 'base0F';
|
||||||
|
|
||||||
|
export interface ButtonProps {
|
||||||
|
children: ReactNode;
|
||||||
|
title?: string;
|
||||||
|
tooltipPosition: TooltipPosition;
|
||||||
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
type?: 'button' | 'reset' | 'submit';
|
||||||
|
disabled?: boolean;
|
||||||
|
primary?: boolean;
|
||||||
|
size?: Size;
|
||||||
|
mark?: Mark | false;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Button extends Component<ButtonProps> {
|
||||||
|
shouldComponentUpdate(nextProps: ButtonProps) {
|
||||||
|
return (
|
||||||
|
nextProps.children !== this.props.children ||
|
||||||
|
nextProps.disabled !== this.props.disabled ||
|
||||||
|
nextProps.mark !== this.props.mark ||
|
||||||
|
nextProps.size !== this.props.size ||
|
||||||
|
nextProps.primary !== this.props.primary ||
|
||||||
|
nextProps.tooltipPosition !== this.props.tooltipPosition ||
|
||||||
|
nextProps.title !== this.props.title
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
|
e.currentTarget.blur();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const button = (
|
||||||
|
<ButtonWrapper
|
||||||
|
theme={this.props.theme}
|
||||||
|
aria-label={this.props.title}
|
||||||
|
primary={this.props.primary}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
onMouseUp={this.onMouseUp}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
type={this.props.type}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</ButtonWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Wrapper = this.props.title ? TooltipWrapper : CommonWrapper;
|
||||||
|
return (
|
||||||
|
<Wrapper
|
||||||
|
theme={this.props.theme}
|
||||||
|
tooltipTitle={this.props.title}
|
||||||
|
tooltipPosition={this.props.tooltipPosition}
|
||||||
|
size={this.props.size}
|
||||||
|
mark={this.props.mark}
|
||||||
|
>
|
||||||
|
{button}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.any.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
tooltipPosition: PropTypes.oneOf([
|
||||||
|
'top',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
'right',
|
||||||
|
'bottom-left',
|
||||||
|
'bottom-right',
|
||||||
|
'top-left',
|
||||||
|
'top-right',
|
||||||
|
]),
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
type: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
primary: PropTypes.bool,
|
||||||
|
size: PropTypes.oneOf(['big', 'normal', 'small']),
|
||||||
|
mark: PropTypes.oneOf([
|
||||||
|
false,
|
||||||
|
'base08',
|
||||||
|
'base09',
|
||||||
|
'base0A',
|
||||||
|
'base0B',
|
||||||
|
'base0C',
|
||||||
|
'base0D',
|
||||||
|
'base0E',
|
||||||
|
'base0F',
|
||||||
|
]),
|
||||||
|
theme: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
tooltipPosition: 'top',
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import { fadeIn } from '../../utils/animations';
|
import { fadeIn } from '../../utils/animations';
|
||||||
import colorEffect from '../../utils/color';
|
import colorEffect from '../../utils/color';
|
||||||
|
import { Mark, Size, TooltipPosition } from '../Button';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
const both = (tooltipPosition) => {
|
const both = (tooltipPosition: TooltipPosition) => {
|
||||||
switch (tooltipPosition) {
|
switch (tooltipPosition) {
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
return `
|
return `
|
||||||
|
@ -46,7 +48,7 @@ const both = (tooltipPosition) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const before = (tooltipPosition) => {
|
const before = (tooltipPosition: TooltipPosition) => {
|
||||||
switch (tooltipPosition) {
|
switch (tooltipPosition) {
|
||||||
case 'bottom-left':
|
case 'bottom-left':
|
||||||
return `
|
return `
|
||||||
|
@ -69,7 +71,7 @@ const before = (tooltipPosition) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const after = (tooltipPosition, color) => {
|
const after = (tooltipPosition: TooltipPosition, color: string) => {
|
||||||
switch (tooltipPosition) {
|
switch (tooltipPosition) {
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
return `
|
return `
|
||||||
|
@ -110,13 +112,13 @@ const after = (tooltipPosition, color) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDirection = (tooltipPosition) => {
|
const getDirection = (tooltipPosition: TooltipPosition) => {
|
||||||
return tooltipPosition.indexOf('-') > 0
|
return tooltipPosition.indexOf('-') > 0
|
||||||
? tooltipPosition.substring(0, tooltipPosition.indexOf('-'))
|
? tooltipPosition.substring(0, tooltipPosition.indexOf('-'))
|
||||||
: tooltipPosition;
|
: tooltipPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSize = (size) => {
|
const getSize = (size: Size | undefined) => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 'big':
|
case 'big':
|
||||||
return 'min-height: 34px; padding: 2px 12px;';
|
return 'min-height: 34px; padding: 2px 12px;';
|
||||||
|
@ -127,7 +129,16 @@ const getSize = (size) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commonStyle = ({ theme, mark, size }) => css`
|
interface CommonStyleProps {
|
||||||
|
size: Size | undefined;
|
||||||
|
mark: Mark | false | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const commonStyle = ({
|
||||||
|
theme,
|
||||||
|
mark,
|
||||||
|
size,
|
||||||
|
}: ThemedStyledProps<CommonStyleProps, Theme>) => css`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -164,13 +175,20 @@ export const commonStyle = ({ theme, mark, size }) => css`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
interface TooltipStyleProps {
|
||||||
|
tooltipTitle: string | undefined;
|
||||||
|
tooltipPosition: TooltipPosition;
|
||||||
|
size: Size | undefined;
|
||||||
|
mark: Mark | false | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const tooltipStyle = ({
|
export const tooltipStyle = ({
|
||||||
theme,
|
theme,
|
||||||
tooltipTitle,
|
tooltipTitle,
|
||||||
tooltipPosition,
|
tooltipPosition,
|
||||||
mark,
|
mark,
|
||||||
size,
|
size,
|
||||||
}) => css`
|
}: ThemedStyledProps<TooltipStyleProps, Theme>) => css`
|
||||||
${commonStyle({ theme, mark, size })}
|
${commonStyle({ theme, mark, size })}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
|
@ -1,6 +1,16 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, primary, disabled }) => css`
|
export interface StyleProps {
|
||||||
|
primary: boolean | undefined;
|
||||||
|
disabled: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
primary,
|
||||||
|
disabled,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
outline: none;
|
outline: none;
|
|
@ -1,7 +1,13 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import { ripple } from '../../utils/animations';
|
import { ripple } from '../../utils/animations';
|
||||||
|
import { StyleProps } from './default';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, primary, disabled }) => css`
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
primary,
|
||||||
|
disabled,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
outline: none;
|
outline: none;
|
|
@ -1,10 +1,23 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from 'styled-components';
|
||||||
import { getTheme } from '../utils/theme';
|
import { getTheme, ThemeData } from '../utils/theme';
|
||||||
import { MainContainerWrapper, ContainerWrapper } from './styles';
|
import { MainContainerWrapper, ContainerWrapper } from './styles';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
const Container = ({ themeData, className, theme, children }) => {
|
interface Props {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
themeData?: ThemeData;
|
||||||
|
theme?: Theme;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container: React.FunctionComponent<Props> = ({
|
||||||
|
themeData,
|
||||||
|
className,
|
||||||
|
theme,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
if (!themeData) {
|
if (!themeData) {
|
||||||
return (
|
return (
|
||||||
<ContainerWrapper className={className} theme={theme}>
|
<ContainerWrapper className={className} theme={theme}>
|
||||||
|
@ -24,8 +37,8 @@ const Container = ({ themeData, className, theme, children }) => {
|
||||||
|
|
||||||
Container.propTypes = {
|
Container.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
themeData: PropTypes.object,
|
themeData: PropTypes.any,
|
||||||
theme: PropTypes.object,
|
theme: PropTypes.any,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import ContextMenu from './';
|
import ContextMenu from './';
|
||||||
import { items } from './data';
|
import { items } from './data';
|
||||||
|
import { ContextMenuProps } from './ContextMenu';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'ContextMenu',
|
title: 'ContextMenu',
|
||||||
|
@ -16,7 +18,7 @@ const Container = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<ContextMenuProps> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<ContextMenu {...args} />
|
<ContextMenu {...args} />
|
||||||
</Container>
|
</Container>
|
|
@ -5,13 +5,34 @@ import styles from './styles/index';
|
||||||
|
|
||||||
const ContextMenuWrapper = createStyledComponent(styles);
|
const ContextMenuWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
export default class ContextMenu extends Component {
|
type ReactButtonElement = React.ReactElement<
|
||||||
constructor(props) {
|
JSX.IntrinsicElements['button'],
|
||||||
|
'button'
|
||||||
|
>;
|
||||||
|
type Item = { name: string; value?: string } | ReactButtonElement;
|
||||||
|
|
||||||
|
function isReactButtonElement(item: Item): item is ReactButtonElement {
|
||||||
|
return (item as ReactButtonElement).type === 'button';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContextMenuProps {
|
||||||
|
items: Item[];
|
||||||
|
onClick: (value: string) => void;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
visible?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ContextMenu extends Component<ContextMenuProps> {
|
||||||
|
constructor(props: ContextMenuProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.updateItems(props.items);
|
this.updateItems(props.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
menu?: HTMLDivElement | null;
|
||||||
|
items?: React.ReactNode[];
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps: ContextMenuProps) {
|
||||||
if (
|
if (
|
||||||
nextProps.items !== this.props.items ||
|
nextProps.items !== this.props.items ||
|
||||||
nextProps.visible !== this.props.visible
|
nextProps.visible !== this.props.visible
|
||||||
|
@ -24,25 +45,25 @@ export default class ContextMenu extends Component {
|
||||||
this.amendPosition();
|
this.amendPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: ContextMenuProps) {
|
||||||
if (prevProps.x !== this.props.x || prevProps.y !== this.props.y) {
|
if (prevProps.x !== this.props.x || prevProps.y !== this.props.y) {
|
||||||
this.amendPosition();
|
this.amendPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp = (e) => {
|
onMouseUp: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.target.blur();
|
e.currentTarget.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
onClick = (e) => {
|
onClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
this.props.onClick(e.target.value);
|
this.props.onClick(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
amendPosition() {
|
amendPosition() {
|
||||||
const { x, y } = this.props;
|
const { x, y } = this.props;
|
||||||
const { scrollTop, scrollLeft } = document.documentElement;
|
const { scrollTop, scrollLeft } = document.documentElement;
|
||||||
const { innerWidth, innerHeight } = window;
|
const { innerWidth, innerHeight } = window;
|
||||||
const rect = this.menu.getBoundingClientRect();
|
const rect = this.menu!.getBoundingClientRect();
|
||||||
let left = x + scrollLeft;
|
let left = x + scrollLeft;
|
||||||
let top = y + scrollTop;
|
let top = y + scrollTop;
|
||||||
|
|
||||||
|
@ -59,14 +80,14 @@ export default class ContextMenu extends Component {
|
||||||
left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
|
left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.menu.style.top = `${top}px`;
|
this.menu!.style.top = `${top}px`;
|
||||||
this.menu.style.left = `${left}px`;
|
this.menu!.style.left = `${left}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateItems(items) {
|
updateItems(items: Item[]) {
|
||||||
this.items = items.map((item) => {
|
this.items = items.map((item) => {
|
||||||
|
if (isReactButtonElement(item)) return item;
|
||||||
const value = item.value || item.name;
|
const value = item.value || item.name;
|
||||||
if (item.type === 'button') return item;
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={value}
|
key={value}
|
||||||
|
@ -80,7 +101,7 @@ export default class ContextMenu extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
menuRef = (c) => {
|
menuRef: React.RefCallback<HTMLDivElement> = (c) => {
|
||||||
this.menu = c;
|
this.menu = c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,12 +117,12 @@ export default class ContextMenu extends Component {
|
||||||
</ContextMenuWrapper>
|
</ContextMenuWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu.propTypes = {
|
static propTypes = {
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
x: PropTypes.number.isRequired,
|
x: PropTypes.number.isRequired,
|
||||||
y: PropTypes.number.isRequired,
|
y: PropTypes.number.isRequired,
|
||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
export default from './ContextMenu';
|
|
1
packages/devui/src/ContextMenu/index.ts
Normal file
1
packages/devui/src/ContextMenu/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './ContextMenu';
|
|
@ -1,6 +1,18 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export default ({ theme, left, top, visible }) => css`
|
interface StyleProps {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
visible: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
theme,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
visible,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
${visible
|
${visible
|
||||||
? `
|
? `
|
||||||
visibility: visible;
|
visibility: visible;
|
|
@ -1,13 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Dialog from './';
|
import Dialog from './';
|
||||||
import { schema, uiSchema, formData } from '../Form/schema';
|
import { schema, uiSchema, formData } from '../Form/schema';
|
||||||
|
import { DialogProps } from './Dialog';
|
||||||
|
import { Props as FormProps } from '../Form/Form';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Dialog',
|
title: 'Dialog',
|
||||||
component: Dialog,
|
component: Dialog,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => <Dialog {...args} />;
|
const Template: Story<DialogProps | (DialogProps & FormProps<unknown>)> = (
|
||||||
|
args
|
||||||
|
) => <Dialog {...args} />;
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
Default.args = {
|
Default.args = {
|
|
@ -4,20 +4,60 @@ import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import * as styles from './styles';
|
import * as styles from './styles';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import Form from '../Form';
|
import Form from '../Form';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
import { Props as FormProps } from '../Form/Form';
|
||||||
|
|
||||||
const DialogWrapper = createStyledComponent(styles);
|
const DialogWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
export default class Dialog extends (PureComponent || Component) {
|
export interface DialogProps {
|
||||||
|
open?: boolean;
|
||||||
|
title?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
actions?: React.ReactNode[];
|
||||||
|
submitText?: string;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
noHeader?: boolean;
|
||||||
|
noFooter?: boolean;
|
||||||
|
modal?: boolean;
|
||||||
|
onDismiss: (
|
||||||
|
e: React.MouseEvent<HTMLButtonElement | HTMLDivElement> | false
|
||||||
|
) => void;
|
||||||
|
onSubmit: () => void;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rest<P> = Omit<
|
||||||
|
DialogProps & FormProps<P>,
|
||||||
|
| 'modal'
|
||||||
|
| 'open'
|
||||||
|
| 'fullWidth'
|
||||||
|
| 'title'
|
||||||
|
| 'children'
|
||||||
|
| 'actions'
|
||||||
|
| 'noHeader'
|
||||||
|
| 'noFooter'
|
||||||
|
| 'submitText'
|
||||||
|
| 'onDismiss'
|
||||||
|
>;
|
||||||
|
function isForm<P>(rest?: FormProps<P>): rest is FormProps<P> {
|
||||||
|
return (rest as FormProps<P>).schema !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Dialog<P> extends (PureComponent || Component)<
|
||||||
|
DialogProps | (DialogProps & FormProps<P>)
|
||||||
|
> {
|
||||||
|
submitButton?: HTMLInputElement | null;
|
||||||
|
|
||||||
onSubmit = () => {
|
onSubmit = () => {
|
||||||
if (this.submitButton) this.submitButton.click();
|
if (this.submitButton) this.submitButton.click();
|
||||||
else this.props.onSubmit();
|
else this.props.onSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
getFormButtonRef = (node) => {
|
getFormButtonRef: React.RefCallback<HTMLInputElement> = (node) => {
|
||||||
this.submitButton = node;
|
this.submitButton = node;
|
||||||
};
|
};
|
||||||
|
|
||||||
onKeyDown = (e) => {
|
onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
|
||||||
if (e.keyCode === 27 /* esc */) {
|
if (e.keyCode === 27 /* esc */) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onDismiss(false);
|
this.props.onDismiss(false);
|
||||||
|
@ -38,7 +78,7 @@ export default class Dialog extends (PureComponent || Component) {
|
||||||
onDismiss,
|
onDismiss,
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const schema = rest.schema;
|
const schema = (rest as Rest<P>).schema;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogWrapper
|
<DialogWrapper
|
||||||
|
@ -47,7 +87,7 @@ export default class Dialog extends (PureComponent || Component) {
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
theme={rest.theme}
|
theme={rest.theme}
|
||||||
>
|
>
|
||||||
<div onClick={!modal && onDismiss} />
|
<div onClick={!modal ? onDismiss : undefined} />
|
||||||
<div>
|
<div>
|
||||||
{!noHeader && (
|
{!noHeader && (
|
||||||
<div className="mc-dialog--header">
|
<div className="mc-dialog--header">
|
||||||
|
@ -57,8 +97,8 @@ export default class Dialog extends (PureComponent || Component) {
|
||||||
)}
|
)}
|
||||||
<div className="mc-dialog--body">
|
<div className="mc-dialog--body">
|
||||||
{children}
|
{children}
|
||||||
{schema && (
|
{isForm(rest as FormProps<P>) && (
|
||||||
<Form {...rest}>
|
<Form {...(rest as FormProps<P>)}>
|
||||||
{!noFooter && (
|
{!noFooter && (
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -97,19 +137,19 @@ export default class Dialog extends (PureComponent || Component) {
|
||||||
</DialogWrapper>
|
</DialogWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.propTypes = {
|
static propTypes = {
|
||||||
open: PropTypes.bool,
|
open: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
actions: PropTypes.node,
|
actions: PropTypes.node,
|
||||||
submitText: PropTypes.string,
|
submitText: PropTypes.string,
|
||||||
fullWidth: PropTypes.bool,
|
fullWidth: PropTypes.bool,
|
||||||
noHeader: PropTypes.bool,
|
noHeader: PropTypes.bool,
|
||||||
noFooter: PropTypes.bool,
|
noFooter: PropTypes.bool,
|
||||||
modal: PropTypes.bool,
|
modal: PropTypes.bool,
|
||||||
onDismiss: PropTypes.func,
|
onDismiss: PropTypes.func,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
theme: PropTypes.object,
|
theme: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
}
|
|
@ -1,6 +1,16 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, open, fullWidth }) => css`
|
export interface StyleProps {
|
||||||
|
open: boolean | undefined;
|
||||||
|
fullWidth: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
open,
|
||||||
|
fullWidth,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
|
@ -1,6 +1,12 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { StyleProps } from './default';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, open, fullWidth }) => css`
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
open,
|
||||||
|
fullWidth,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
|
@ -1,91 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import CodeMirror from 'codemirror';
|
|
||||||
import { defaultStyle, themedStyle } from './styles';
|
|
||||||
|
|
||||||
const EditorContainer = styled.div('', ({ theme }) =>
|
|
||||||
theme.scheme === 'default' && theme.light ? defaultStyle : themedStyle(theme)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on [CodeMirror](http://codemirror.net/).
|
|
||||||
*/
|
|
||||||
export default class Editor extends Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.cm = CodeMirror(this.node, {
|
|
||||||
value: this.props.value,
|
|
||||||
mode: this.props.mode,
|
|
||||||
lineNumbers: this.props.lineNumbers,
|
|
||||||
lineWrapping: this.props.lineWrapping,
|
|
||||||
readOnly: this.props.readOnly,
|
|
||||||
autofocus: this.props.autofocus,
|
|
||||||
foldGutter: this.props.foldGutter,
|
|
||||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.cm.on('change', (doc, change) => {
|
|
||||||
this.props.onChange(doc.getValue(), change);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
||||||
if (nextProps.value !== this.cm.getValue()) {
|
|
||||||
this.cm.setValue(nextProps.value);
|
|
||||||
}
|
|
||||||
if (nextProps.readOnly !== this.props.readOnly) {
|
|
||||||
this.cm.setOption('readOnly', nextProps.readOnly);
|
|
||||||
}
|
|
||||||
if (nextProps.lineNumbers !== this.props.lineNumbers) {
|
|
||||||
this.cm.setOption('lineNumbers', nextProps.lineNumbers);
|
|
||||||
}
|
|
||||||
if (nextProps.lineWrapping !== this.props.lineWrapping) {
|
|
||||||
this.cm.setOption('lineWrapping', nextProps.lineWrapping);
|
|
||||||
}
|
|
||||||
if (nextProps.foldGutter !== this.props.foldGutter) {
|
|
||||||
this.cm.setOption('foldGutter', nextProps.foldGutter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
const node = this.node;
|
|
||||||
node.removeChild(node.children[0]);
|
|
||||||
this.cm = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRef = (node) => {
|
|
||||||
this.node = node;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <EditorContainer ref={this.getRef} theme={this.props.theme} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Editor.propTypes = {
|
|
||||||
value: PropTypes.string,
|
|
||||||
mode: PropTypes.string,
|
|
||||||
lineNumbers: PropTypes.bool,
|
|
||||||
lineWrapping: PropTypes.bool,
|
|
||||||
readOnly: PropTypes.bool,
|
|
||||||
theme: PropTypes.object,
|
|
||||||
foldGutter: PropTypes.bool,
|
|
||||||
autofocus: PropTypes.bool,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
Editor.defaultProps = {
|
|
||||||
value: '',
|
|
||||||
mode: 'javascript',
|
|
||||||
lineNumbers: true,
|
|
||||||
lineWrapping: false,
|
|
||||||
readOnly: false,
|
|
||||||
foldGutter: true,
|
|
||||||
autofocus: false,
|
|
||||||
};
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Editor from './';
|
import Editor from './';
|
||||||
import { default as WithTabsComponent } from './WithTabs';
|
import { default as WithTabsComponent, WithTabsProps } from './WithTabs';
|
||||||
|
import { EditorProps } from './Editor';
|
||||||
|
|
||||||
const value = `
|
const value = `
|
||||||
var themes = [];
|
var themes = [];
|
||||||
|
@ -15,7 +17,7 @@ export default {
|
||||||
component: Editor,
|
component: Editor,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => <Editor {...args} />;
|
const Template: Story<EditorProps> = (args) => <Editor {...args} />;
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
Default.args = {
|
Default.args = {
|
||||||
|
@ -33,12 +35,14 @@ Default.argTypes = {
|
||||||
onChange: { control: { disable: true } },
|
onChange: { control: { disable: true } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const WithTabsTemplate = (args) => <WithTabsComponent {...args} />;
|
const WithTabsTemplate: Story<WithTabsProps> = (args) => (
|
||||||
|
<WithTabsComponent {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
export const WithTabs = WithTabsTemplate.bind({});
|
export const WithTabs = WithTabsTemplate.bind({});
|
||||||
WithTabs.args = {
|
WithTabs.args = {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
align: 'left',
|
position: 'left',
|
||||||
};
|
};
|
||||||
WithTabs.argTypes = {
|
WithTabs.argTypes = {
|
||||||
value: { control: { disable: true } },
|
value: { control: { disable: true } },
|
111
packages/devui/src/Editor/Editor.tsx
Normal file
111
packages/devui/src/Editor/Editor.tsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import CodeMirror from 'codemirror';
|
||||||
|
import { defaultStyle, themedStyle } from './styles';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
|
const EditorContainer = styled.div(
|
||||||
|
('' as unknown) as TemplateStringsArray,
|
||||||
|
({ theme }: { theme: Theme }) =>
|
||||||
|
theme.scheme === 'default' && theme.light
|
||||||
|
? defaultStyle
|
||||||
|
: themedStyle(theme)
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface EditorProps {
|
||||||
|
value: string;
|
||||||
|
mode: string;
|
||||||
|
lineNumbers: boolean;
|
||||||
|
lineWrapping: boolean;
|
||||||
|
readOnly: boolean;
|
||||||
|
theme?: Theme;
|
||||||
|
foldGutter: boolean;
|
||||||
|
autofocus: boolean;
|
||||||
|
onChange: (value: string, change: CodeMirror.EditorChangeLinkedList) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on [CodeMirror](http://codemirror.net/).
|
||||||
|
*/
|
||||||
|
export default class Editor extends Component<EditorProps> {
|
||||||
|
cm?: CodeMirror.Editor | null;
|
||||||
|
node?: HTMLDivElement | null;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.cm = CodeMirror(this.node!, {
|
||||||
|
value: this.props.value,
|
||||||
|
mode: this.props.mode,
|
||||||
|
lineNumbers: this.props.lineNumbers,
|
||||||
|
lineWrapping: this.props.lineWrapping,
|
||||||
|
readOnly: this.props.readOnly,
|
||||||
|
autofocus: this.props.autofocus,
|
||||||
|
foldGutter: this.props.foldGutter,
|
||||||
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.cm.on('change', (doc, change) => {
|
||||||
|
this.props.onChange(doc.getValue(), change);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps: EditorProps) {
|
||||||
|
if (nextProps.value !== this.cm!.getValue()) {
|
||||||
|
this.cm!.setValue(nextProps.value);
|
||||||
|
}
|
||||||
|
if (nextProps.readOnly !== this.props.readOnly) {
|
||||||
|
this.cm!.setOption('readOnly', nextProps.readOnly);
|
||||||
|
}
|
||||||
|
if (nextProps.lineNumbers !== this.props.lineNumbers) {
|
||||||
|
this.cm!.setOption('lineNumbers', nextProps.lineNumbers);
|
||||||
|
}
|
||||||
|
if (nextProps.lineWrapping !== this.props.lineWrapping) {
|
||||||
|
this.cm!.setOption('lineWrapping', nextProps.lineWrapping);
|
||||||
|
}
|
||||||
|
if (nextProps.foldGutter !== this.props.foldGutter) {
|
||||||
|
this.cm!.setOption('foldGutter', nextProps.foldGutter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const node = this.node!;
|
||||||
|
node.removeChild(node.children[0]);
|
||||||
|
this.cm = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRef: React.RefCallback<HTMLDivElement> = (node) => {
|
||||||
|
this.node = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <EditorContainer ref={this.getRef} theme={this.props.theme} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
mode: PropTypes.string,
|
||||||
|
lineNumbers: PropTypes.bool,
|
||||||
|
lineWrapping: PropTypes.bool,
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
|
theme: PropTypes.object,
|
||||||
|
foldGutter: PropTypes.bool,
|
||||||
|
autofocus: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
value: '',
|
||||||
|
mode: 'javascript',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: false,
|
||||||
|
readOnly: false,
|
||||||
|
foldGutter: true,
|
||||||
|
autofocus: false,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component, ComponentType } from 'react';
|
||||||
import Editor from './';
|
import Editor from './';
|
||||||
import Tabs from '../Tabs';
|
import Tabs from '../Tabs';
|
||||||
|
|
||||||
|
@ -10,25 +10,35 @@ const value2 = `
|
||||||
const func2 = () => {}
|
const func2 = () => {}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export interface WithTabsProps {
|
||||||
|
position: 'left' | 'right' | 'center';
|
||||||
|
lineNumbers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabProps {
|
||||||
|
value: string;
|
||||||
|
lineNumbers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
export default class WithTabs extends Component {
|
export default class WithTabs extends Component<WithTabsProps> {
|
||||||
state = {
|
state = {
|
||||||
selected: 'Function 1',
|
selected: 'Function 1',
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { align, lineNumbers } = this.props;
|
const { position, lineNumbers } = this.props;
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs<TabProps>
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
name: 'Function 1',
|
name: 'Function 1',
|
||||||
component: Editor,
|
component: (Editor as unknown) as ComponentType<TabProps>,
|
||||||
selector: () => ({ value: value1, lineNumbers }),
|
selector: () => ({ value: value1, lineNumbers }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Function 2',
|
name: 'Function 2',
|
||||||
component: Editor,
|
component: (Editor as unknown) as ComponentType<TabProps>,
|
||||||
selector: () => ({ value: value2, lineNumbers }),
|
selector: () => ({ value: value2, lineNumbers }),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -36,7 +46,7 @@ export default class WithTabs extends Component {
|
||||||
onClick={(selected) => {
|
onClick={(selected) => {
|
||||||
this.setState({ selected });
|
this.setState({ selected });
|
||||||
}}
|
}}
|
||||||
align={align}
|
position={position}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const defaultStyle = `
|
export const defaultStyle = `
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -10,7 +11,7 @@ export const defaultStyle = `
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const themedStyle = (theme) => css`
|
export const themedStyle = (theme: Theme) => css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
> div {
|
> div {
|
|
@ -1,13 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Form from './';
|
import Form from './';
|
||||||
import { schema, uiSchema, formData } from './schema';
|
import { schema, uiSchema, formData } from './schema';
|
||||||
|
import { Props as FormProps } from './Form';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
component: Form,
|
component: Form,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => <Form {...args} />;
|
const Template: Story<FormProps<unknown>> = (args) => <Form {...args} />;
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
Default.args = {
|
Default.args = {
|
|
@ -1,17 +1,26 @@
|
||||||
import React, { PureComponent, Component } from 'react';
|
import React, { PureComponent, Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import JSONSchemaForm from 'react-jsonschema-form';
|
import JSONSchemaForm, { FormProps } from 'react-jsonschema-form';
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import customWidgets from './widgets';
|
import customWidgets from './widgets';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
const FormContainer = createStyledComponent(styles, JSONSchemaForm);
|
const FormContainer = createStyledComponent(styles, JSONSchemaForm);
|
||||||
|
|
||||||
|
export interface Props<T> extends FormProps<T> {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
submitText?: string;
|
||||||
|
primaryButton?: boolean;
|
||||||
|
noSubmit?: boolean;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around [`react-jsonschema-form`](https://github.com/mozilla-services/react-jsonschema-form) with custom widgets.
|
* Wrapper around [`react-jsonschema-form`](https://github.com/mozilla-services/react-jsonschema-form) with custom widgets.
|
||||||
*/
|
*/
|
||||||
export default class Form extends (PureComponent || Component) {
|
export default class Form<T> extends (PureComponent || Component)<Props<T>> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
widgets,
|
widgets,
|
||||||
|
@ -22,7 +31,10 @@ export default class Form extends (PureComponent || Component) {
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<FormContainer {...rest} widgets={{ ...customWidgets, ...widgets }}>
|
<FormContainer
|
||||||
|
{...(rest as Props<unknown>)}
|
||||||
|
widgets={{ ...customWidgets, ...widgets }}
|
||||||
|
>
|
||||||
{noSubmit ? (
|
{noSubmit ? (
|
||||||
<noscript />
|
<noscript />
|
||||||
) : (
|
) : (
|
||||||
|
@ -40,17 +52,17 @@ export default class Form extends (PureComponent || Component) {
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Form.propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
submitText: PropTypes.string,
|
submitText: PropTypes.string,
|
||||||
primaryButton: PropTypes.bool,
|
primaryButton: PropTypes.bool,
|
||||||
noSubmit: PropTypes.bool,
|
noSubmit: PropTypes.bool,
|
||||||
schema: PropTypes.object.isRequired,
|
schema: PropTypes.object.isRequired,
|
||||||
uiSchema: PropTypes.object,
|
uiSchema: PropTypes.object,
|
||||||
formData: PropTypes.any,
|
formData: PropTypes.any,
|
||||||
widgets: PropTypes.objectOf(
|
widgets: PropTypes.objectOf(
|
||||||
PropTypes.oneOfType([PropTypes.func, PropTypes.object])
|
PropTypes.oneOfType([PropTypes.func, PropTypes.object])
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
}
|
|
@ -1,89 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
schema: {
|
|
||||||
title: 'Example form',
|
|
||||||
description: 'A simple form example.',
|
|
||||||
type: 'object',
|
|
||||||
required: ['name'],
|
|
||||||
properties: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
title: 'Full name',
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
type: 'integer',
|
|
||||||
title: 'Age',
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
type: 'string',
|
|
||||||
title: 'Bio',
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: 'string',
|
|
||||||
title: 'Password',
|
|
||||||
minLength: 3,
|
|
||||||
},
|
|
||||||
multipleChoicesList: {
|
|
||||||
type: 'array',
|
|
||||||
title: 'A multiple choices list',
|
|
||||||
items: {
|
|
||||||
type: 'string',
|
|
||||||
enum: ['foo', 'bar', 'fuzz'],
|
|
||||||
},
|
|
||||||
uniqueItems: true,
|
|
||||||
},
|
|
||||||
numberEnum: {
|
|
||||||
type: 'number',
|
|
||||||
title: 'Number enum',
|
|
||||||
enum: [1, 2, 3],
|
|
||||||
},
|
|
||||||
numberEnumRadio: {
|
|
||||||
type: 'number',
|
|
||||||
title: 'Number enum',
|
|
||||||
enum: [1, 2, 3],
|
|
||||||
},
|
|
||||||
integerRange: {
|
|
||||||
title: 'Integer range',
|
|
||||||
type: 'integer',
|
|
||||||
minimum: 42,
|
|
||||||
maximum: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
uiSchema: {
|
|
||||||
name: {
|
|
||||||
'ui:autofocus': true,
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
'ui:widget': 'updown',
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
'ui:widget': 'textarea',
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
'ui:widget': 'password',
|
|
||||||
'ui:help': 'Hint: Make it strong!',
|
|
||||||
},
|
|
||||||
date: {
|
|
||||||
'ui:widget': 'alt-datetime',
|
|
||||||
},
|
|
||||||
multipleChoicesList: {
|
|
||||||
'ui:widget': 'checkboxes',
|
|
||||||
},
|
|
||||||
numberEnumRadio: {
|
|
||||||
'ui:widget': 'radio',
|
|
||||||
'ui:options': {
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
integerRange: {
|
|
||||||
'ui:widget': 'range',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
formData: {
|
|
||||||
name: 'Chuck Norris',
|
|
||||||
age: 75,
|
|
||||||
bio: 'Roundhouse kicking asses since 1940',
|
|
||||||
password: 'noneed',
|
|
||||||
integerRange: 52,
|
|
||||||
},
|
|
||||||
};
|
|
91
packages/devui/src/Form/schema.ts
Normal file
91
packages/devui/src/Form/schema.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import { JSONSchema6 } from 'json-schema';
|
||||||
|
|
||||||
|
export const schema: JSONSchema6 = {
|
||||||
|
title: 'Example form',
|
||||||
|
description: 'A simple form example.',
|
||||||
|
type: 'object',
|
||||||
|
required: ['name'],
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Full name',
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: 'integer',
|
||||||
|
title: 'Age',
|
||||||
|
},
|
||||||
|
bio: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Bio',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
title: 'Password',
|
||||||
|
minLength: 3,
|
||||||
|
},
|
||||||
|
multipleChoicesList: {
|
||||||
|
type: 'array',
|
||||||
|
title: 'A multiple choices list',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['foo', 'bar', 'fuzz'],
|
||||||
|
},
|
||||||
|
uniqueItems: true,
|
||||||
|
},
|
||||||
|
numberEnum: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'Number enum',
|
||||||
|
enum: [1, 2, 3],
|
||||||
|
},
|
||||||
|
numberEnumRadio: {
|
||||||
|
type: 'number',
|
||||||
|
title: 'Number enum',
|
||||||
|
enum: [1, 2, 3],
|
||||||
|
},
|
||||||
|
integerRange: {
|
||||||
|
title: 'Integer range',
|
||||||
|
type: 'integer',
|
||||||
|
minimum: 42,
|
||||||
|
maximum: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uiSchema = {
|
||||||
|
name: {
|
||||||
|
'ui:autofocus': true,
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
'ui:widget': 'updown',
|
||||||
|
},
|
||||||
|
bio: {
|
||||||
|
'ui:widget': 'textarea',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
'ui:widget': 'password',
|
||||||
|
'ui:help': 'Hint: Make it strong!',
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
'ui:widget': 'alt-datetime',
|
||||||
|
},
|
||||||
|
multipleChoicesList: {
|
||||||
|
'ui:widget': 'checkboxes',
|
||||||
|
},
|
||||||
|
numberEnumRadio: {
|
||||||
|
'ui:widget': 'radio',
|
||||||
|
'ui:options': {
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
integerRange: {
|
||||||
|
'ui:widget': 'range',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formData = {
|
||||||
|
name: 'Chuck Norris',
|
||||||
|
age: 75,
|
||||||
|
bio: 'Roundhouse kicking asses since 1940',
|
||||||
|
password: 'noneed',
|
||||||
|
integerRange: 52,
|
||||||
|
};
|
|
@ -1,6 +1,8 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export default ({ theme }) => css`
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export default ({ theme }: ThemedStyledProps<{}, Theme>) => css`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
line-height: 1.846;
|
line-height: 1.846;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
|
@ -1,31 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Select from '../Select';
|
|
||||||
import Slider from '../Slider';
|
|
||||||
|
|
||||||
/* eslint-disable react/prop-types */
|
|
||||||
const SelectWidget = ({ options, multi, ...rest }) => (
|
|
||||||
<Select options={options.enumOptions} multiple={multi} {...rest} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const RangeWidget = ({
|
|
||||||
schema,
|
|
||||||
readonly,
|
|
||||||
autofocus,
|
|
||||||
label, // eslint-disable-line
|
|
||||||
options, // eslint-disable-line
|
|
||||||
formContext, // eslint-disable-line
|
|
||||||
registry, // eslint-disable-line
|
|
||||||
...rest
|
|
||||||
}) => (
|
|
||||||
<Slider
|
|
||||||
{...rest}
|
|
||||||
autoFocus={autofocus}
|
|
||||||
readOnly={readonly}
|
|
||||||
min={schema.minimum}
|
|
||||||
max={schema.maximum}
|
|
||||||
step={schema.multipleOf}
|
|
||||||
withValue
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default { SelectWidget, RangeWidget };
|
|
30
packages/devui/src/Form/widgets.tsx
Normal file
30
packages/devui/src/Form/widgets.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FieldProps, Widget, WidgetProps } from 'react-jsonschema-form';
|
||||||
|
import Select from '../Select';
|
||||||
|
import Slider from '../Slider';
|
||||||
|
|
||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
const SelectWidget: Widget = ({ options, ...rest }) => (
|
||||||
|
<Select options={options.enumOptions} {...rest} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const RangeWidget: Widget = (({
|
||||||
|
schema,
|
||||||
|
disabled,
|
||||||
|
label, // eslint-disable-line
|
||||||
|
options, // eslint-disable-line
|
||||||
|
formContext, // eslint-disable-line
|
||||||
|
registry, // eslint-disable-line
|
||||||
|
...rest
|
||||||
|
}: WidgetProps & { registry: FieldProps['registry'] }) =>
|
||||||
|
(
|
||||||
|
<Slider
|
||||||
|
{...rest}
|
||||||
|
disabled={disabled}
|
||||||
|
min={schema.minimum}
|
||||||
|
max={schema.maximum}
|
||||||
|
withValue
|
||||||
|
/>
|
||||||
|
) as unknown) as Widget;
|
||||||
|
|
||||||
|
export default { SelectWidget, RangeWidget };
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Notification from './';
|
import Notification from './';
|
||||||
|
import { NotificationProps } from './Notification';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -15,7 +17,7 @@ export default {
|
||||||
component: Notification,
|
component: Notification,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<NotificationProps> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Notification {...args} />
|
<Notification {...args} />
|
||||||
</Container>
|
</Container>
|
|
@ -6,11 +6,21 @@ import { MdError } from 'react-icons/md';
|
||||||
import { MdCheckCircle } from 'react-icons/md';
|
import { MdCheckCircle } from 'react-icons/md';
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
const NotificationWrapper = createStyledComponent(styles);
|
const NotificationWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
export default class Notification extends Component {
|
export type Type = 'info' | 'success' | 'warning' | 'error';
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
|
export interface NotificationProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
type: Type;
|
||||||
|
onClose?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Notification extends Component<NotificationProps> {
|
||||||
|
shouldComponentUpdate(nextProps: NotificationProps) {
|
||||||
return (
|
return (
|
||||||
nextProps.children !== this.props.children ||
|
nextProps.children !== this.props.children ||
|
||||||
nextProps.type !== this.props.type
|
nextProps.type !== this.props.type
|
||||||
|
@ -43,15 +53,15 @@ export default class Notification extends Component {
|
||||||
</NotificationWrapper>
|
</NotificationWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.any.isRequired,
|
||||||
|
type: PropTypes.oneOf(['info', 'success', 'warning', 'error']),
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
theme: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
type: 'info',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.propTypes = {
|
|
||||||
children: PropTypes.any.isRequired,
|
|
||||||
type: PropTypes.oneOf(['info', 'success', 'warning', 'error']),
|
|
||||||
onClose: PropTypes.func,
|
|
||||||
theme: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
Notification.defaultProps = {
|
|
||||||
type: 'info',
|
|
||||||
};
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
import { Type } from '../Notification';
|
||||||
|
|
||||||
const getBackground = (theme, type) => {
|
const getBackground = (theme: Theme, type: Type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'success':
|
case 'success':
|
||||||
return `background-color: ${theme.base0B};`;
|
return `background-color: ${theme.base0B};`;
|
||||||
|
@ -13,7 +15,11 @@ const getBackground = (theme, type) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ theme, type }) => css`
|
interface StyleProps {
|
||||||
|
type: Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({ theme, type }: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import SegmentedControl from './';
|
import SegmentedControl from './';
|
||||||
|
import { SegmentedControlProps } from './SegmentedControl';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -15,7 +17,7 @@ export default {
|
||||||
component: SegmentedControl,
|
component: SegmentedControl,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<Omit<SegmentedControlProps, 'values'>> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<SegmentedControl values={['Button1', 'Button2', 'Button3']} {...args} />
|
<SegmentedControl values={['Button1', 'Button2', 'Button3']} {...args} />
|
||||||
</Container>
|
</Container>
|
|
@ -2,23 +2,32 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
const SegmentedWrapper = createStyledComponent(styles);
|
const SegmentedWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
export default class SegmentedControl extends Component {
|
export interface SegmentedControlProps {
|
||||||
shouldComponentUpdate(nextProps) {
|
values: string[];
|
||||||
|
selected?: string;
|
||||||
|
onClick: (value: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SegmentedControl extends Component<SegmentedControlProps> {
|
||||||
|
shouldComponentUpdate(nextProps: SegmentedControlProps) {
|
||||||
return (
|
return (
|
||||||
nextProps.disabled !== this.props.disabled ||
|
nextProps.disabled !== this.props.disabled ||
|
||||||
nextProps.selected !== this.props.selected
|
nextProps.selected !== this.props.selected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = (e) => {
|
onClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
this.props.onClick(e.target.value);
|
this.props.onClick(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseUp = (e) => {
|
onMouseUp: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.target.blur();
|
e.currentTarget.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -39,12 +48,12 @@ export default class SegmentedControl extends Component {
|
||||||
</SegmentedWrapper>
|
</SegmentedWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SegmentedControl.propTypes = {
|
static propTypes = {
|
||||||
values: PropTypes.array.isRequired,
|
values: PropTypes.array.isRequired,
|
||||||
selected: PropTypes.string,
|
selected: PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
theme: PropTypes.object,
|
theme: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
}
|
|
@ -1,7 +1,15 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import color from '../../utils/color';
|
import color from '../../utils/color';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export default ({ theme, disabled }) => css`
|
interface StyleProps {
|
||||||
|
disabled: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
theme,
|
||||||
|
disabled,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Select from './';
|
import Select from './';
|
||||||
import { options } from './options';
|
import { options } from './options';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
|
import { SelectProps } from './Select';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -20,7 +22,10 @@ export default {
|
||||||
component: Select,
|
component: Select,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = ({ value, ...args }) => (
|
type TemplateArgs = Omit<SelectProps, 'value'> & { value: string };
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const Template: Story<TemplateArgs> = ({ value, ...args }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Select
|
<Select
|
||||||
options={options}
|
options={options}
|
|
@ -1,12 +1,17 @@
|
||||||
import React, { PureComponent, Component } from 'react';
|
import React, { PureComponent, Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReactSelect from 'react-select';
|
import ReactSelect, { Props as ReactSelectProps } from 'react-select';
|
||||||
import createThemedComponent from '../utils/createThemedComponent';
|
import createThemedComponent from '../utils/createThemedComponent';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
|
export interface SelectProps extends Omit<ReactSelectProps, 'theme'> {
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around [React Select](https://github.com/JedWatson/react-select).
|
* Wrapper around [React Select](https://github.com/JedWatson/react-select).
|
||||||
*/
|
*/
|
||||||
class Select extends (PureComponent || Component) {
|
export class Select extends (PureComponent || Component)<SelectProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
|
@ -37,17 +42,17 @@ class Select extends (PureComponent || Component) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
isClearable: PropTypes.bool, // should it be possible to reset value
|
||||||
|
isDisabled: PropTypes.bool, // whether the Select is disabled or not
|
||||||
|
isLoading: PropTypes.bool, // whether the Select is loading externally or not
|
||||||
|
maxMenuHeight: PropTypes.number, // maximum css height for the opened menu of options
|
||||||
|
isMulti: PropTypes.bool, // multi-value input
|
||||||
|
isSearchable: PropTypes.bool, // whether to enable searching feature or not
|
||||||
|
value: PropTypes.any, // initial field value
|
||||||
|
menuPlacement: PropTypes.oneOf(['auto', 'bottom', 'top']), // value to control the opening direction
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Select.propTypes = {
|
|
||||||
isClearable: PropTypes.bool, // should it be possible to reset value
|
|
||||||
isDisabled: PropTypes.bool, // whether the Select is disabled or not
|
|
||||||
isLoading: PropTypes.bool, // whether the Select is loading externally or not
|
|
||||||
maxMenuHeight: PropTypes.number, // maximum css height for the opened menu of options
|
|
||||||
isMulti: PropTypes.bool, // multi-value input
|
|
||||||
isSearchable: PropTypes.bool, // whether to enable searching feature or not
|
|
||||||
value: PropTypes.any, // initial field value
|
|
||||||
menuPlacement: PropTypes.oneOf(['auto', 'bottom', 'top']), // value to control the opening direction
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createThemedComponent(Select);
|
export default createThemedComponent(Select);
|
|
@ -1 +0,0 @@
|
||||||
export default from './Select';
|
|
1
packages/devui/src/Select/index.ts
Normal file
1
packages/devui/src/Select/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Select';
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Slider from './';
|
import Slider from './';
|
||||||
|
import { SliderProps } from './Slider';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -15,7 +17,7 @@ export default {
|
||||||
component: Slider,
|
component: Slider,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<SliderProps> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Slider {...args} />
|
<Slider {...args} />
|
||||||
</Container>
|
</Container>
|
|
@ -3,12 +3,25 @@ import PropTypes from 'prop-types';
|
||||||
import createStyledComponent from '../utils/createStyledComponent';
|
import createStyledComponent from '../utils/createStyledComponent';
|
||||||
import * as styles from './styles';
|
import * as styles from './styles';
|
||||||
import { containerStyle } from './styles/common';
|
import { containerStyle } from './styles/common';
|
||||||
|
import { Theme } from '../themes/default';
|
||||||
|
|
||||||
const SliderWrapper = createStyledComponent(styles);
|
const SliderWrapper = createStyledComponent(styles);
|
||||||
const ContainerWithValue = createStyledComponent(containerStyle);
|
const ContainerWithValue = createStyledComponent(containerStyle);
|
||||||
|
|
||||||
export default class Slider extends Component {
|
export interface SliderProps {
|
||||||
shouldComponentUpdate(nextProps) {
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
label?: string;
|
||||||
|
sublabel?: string;
|
||||||
|
withValue?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
theme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Slider extends Component<SliderProps> {
|
||||||
|
shouldComponentUpdate(nextProps: SliderProps) {
|
||||||
return (
|
return (
|
||||||
nextProps.label !== this.props.label ||
|
nextProps.label !== this.props.label ||
|
||||||
nextProps.value !== this.props.value ||
|
nextProps.value !== this.props.value ||
|
||||||
|
@ -19,7 +32,7 @@ export default class Slider extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (e) => {
|
onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
this.props.onChange(parseFloat(e.target.value));
|
this.props.onChange(parseFloat(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,18 +66,18 @@ export default class Slider extends Component {
|
||||||
</SliderWrapper>
|
</SliderWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
value: PropTypes.number,
|
||||||
|
min: PropTypes.number,
|
||||||
|
max: PropTypes.number,
|
||||||
|
label: PropTypes.string,
|
||||||
|
sublabel: PropTypes.string,
|
||||||
|
withValue: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
theme: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = { value: 0, min: 0, max: 100 };
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider.propTypes = {
|
|
||||||
value: PropTypes.number,
|
|
||||||
min: PropTypes.number,
|
|
||||||
max: PropTypes.number,
|
|
||||||
label: PropTypes.string,
|
|
||||||
sublabel: PropTypes.string,
|
|
||||||
withValue: PropTypes.bool,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
theme: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
Slider.defaultProps = { value: 0, min: 0, max: 100 };
|
|
|
@ -1 +0,0 @@
|
||||||
export default from './Slider';
|
|
1
packages/devui/src/Slider/index.ts
Normal file
1
packages/devui/src/Slider/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Slider';
|
|
@ -1,6 +1,8 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const containerStyle = ({ theme }) => css`
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export const containerStyle = ({ theme }: ThemedStyledProps<{}, Theme>) => css`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -6,10 +6,22 @@ Based on:
|
||||||
http://codepen.io/thebabydino/pen/YPOPxr
|
http://codepen.io/thebabydino/pen/YPOPxr
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import { prefixSelectors } from '../../utils/autoPrefix';
|
import { prefixSelectors } from '../../utils/autoPrefix';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, percent, disabled, withLabel }) => css`
|
export interface StyleProps {
|
||||||
|
percent: number;
|
||||||
|
disabled: boolean;
|
||||||
|
withLabel: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
percent,
|
||||||
|
disabled,
|
||||||
|
withLabel,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
|
@ -1,9 +1,16 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import { prefixSelectors } from '../../utils/autoPrefix';
|
import { prefixSelectors } from '../../utils/autoPrefix';
|
||||||
import color from '../../utils/color';
|
import color from '../../utils/color';
|
||||||
import { animationCurve } from '../../utils/animations';
|
import { animationCurve } from '../../utils/animations';
|
||||||
|
import { StyleProps } from './default';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, percent, disabled, withLabel }) => css`
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
percent,
|
||||||
|
disabled,
|
||||||
|
withLabel,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import Tabs from './';
|
import Tabs from './';
|
||||||
import { tabs, simple10Tabs } from './data';
|
import { tabs, simple10Tabs } from './data';
|
||||||
|
import { TabsProps } from './Tabs';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -16,7 +18,7 @@ export default {
|
||||||
component: Tabs,
|
component: Tabs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = (args) => (
|
const Template: Story<TabsProps<unknown>> = (args) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Tabs {...args} />
|
<Tabs {...args} />
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -35,7 +37,9 @@ Default.argTypes = {
|
||||||
onClick: { control: { disable: true } },
|
onClick: { control: { disable: true } },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithContent = Template.bind({});
|
export const WithContent = (Template as Story<
|
||||||
|
TabsProps<{ selected: string }>
|
||||||
|
>).bind({});
|
||||||
WithContent.args = {
|
WithContent.args = {
|
||||||
tabs,
|
tabs,
|
||||||
selected: 'Tab2',
|
selected: 'Tab2',
|
|
@ -1,29 +1,44 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TabsHeader from './TabsHeader';
|
import TabsHeader, { ReactButtonElement, Tab } from './TabsHeader';
|
||||||
import { TabsContainer } from './styles/common';
|
import { TabsContainer } from './styles/common';
|
||||||
|
|
||||||
export default class Tabs extends Component {
|
export type Position = 'left' | 'right' | 'center';
|
||||||
constructor(props) {
|
|
||||||
|
export interface TabsProps<P> {
|
||||||
|
tabs: Tab<P>[];
|
||||||
|
selected?: string;
|
||||||
|
main?: boolean;
|
||||||
|
onClick: (value: string) => void;
|
||||||
|
collapsible?: boolean;
|
||||||
|
position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tabs<P> extends Component<TabsProps<P>> {
|
||||||
|
constructor(props: TabsProps<P>) {
|
||||||
super(props);
|
super(props);
|
||||||
this.updateTabs(props);
|
this.updateTabs(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
tabsHeader?: ReactButtonElement[];
|
||||||
|
SelectedComponent?: React.ComponentType<P>;
|
||||||
|
selector?: () => P;
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps: TabsProps<P>) {
|
||||||
if (nextProps.selected !== this.props.selected) {
|
if (nextProps.selected !== this.props.selected) {
|
||||||
this.updateTabs(nextProps);
|
this.updateTabs(nextProps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp = (e) => {
|
onMouseUp: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.target.blur();
|
e.currentTarget.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
onClick = (e) => {
|
onClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
this.props.onClick(e.target.value);
|
this.props.onClick(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTabs(props) {
|
updateTabs(props: TabsProps<P>) {
|
||||||
const tabs = props.tabs;
|
const tabs = props.tabs;
|
||||||
const selected = props.selected;
|
const selected = props.selected;
|
||||||
|
|
||||||
|
@ -34,7 +49,7 @@ export default class Tabs extends Component {
|
||||||
isSelected = true;
|
isSelected = true;
|
||||||
if (tab.component) {
|
if (tab.component) {
|
||||||
this.SelectedComponent = tab.component;
|
this.SelectedComponent = tab.component;
|
||||||
if (tab.selector) this.selector = () => tab.selector(tab);
|
if (tab.selector) this.selector = () => tab.selector!(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -54,7 +69,7 @@ export default class Tabs extends Component {
|
||||||
render() {
|
render() {
|
||||||
const tabsHeader = (
|
const tabsHeader = (
|
||||||
<TabsHeader
|
<TabsHeader
|
||||||
tabs={this.tabsHeader}
|
tabs={this.tabsHeader!}
|
||||||
items={this.props.tabs}
|
items={this.props.tabs}
|
||||||
main={this.props.main}
|
main={this.props.main}
|
||||||
collapsible={this.props.collapsible}
|
collapsible={this.props.collapsible}
|
||||||
|
@ -76,20 +91,20 @@ export default class Tabs extends Component {
|
||||||
<TabsContainer position={this.props.position}>
|
<TabsContainer position={this.props.position}>
|
||||||
{tabsHeader}
|
{tabsHeader}
|
||||||
<div>
|
<div>
|
||||||
<this.SelectedComponent {...(this.selector && this.selector())} />
|
<this.SelectedComponent {...(this.selector! && this.selector!())} />
|
||||||
</div>
|
</div>
|
||||||
</TabsContainer>
|
</TabsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
tabs: PropTypes.array.isRequired,
|
||||||
|
selected: PropTypes.string,
|
||||||
|
main: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
collapsible: PropTypes.bool,
|
||||||
|
position: PropTypes.oneOf(['left', 'right', 'center']),
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = { position: 'left' };
|
||||||
}
|
}
|
||||||
|
|
||||||
Tabs.propTypes = {
|
|
||||||
tabs: PropTypes.array.isRequired,
|
|
||||||
selected: PropTypes.string,
|
|
||||||
main: PropTypes.bool,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
collapsible: PropTypes.bool,
|
|
||||||
position: PropTypes.oneOf(['left', 'right', 'center']),
|
|
||||||
};
|
|
||||||
|
|
||||||
Tabs.defaultProps = { position: 'left' };
|
|
|
@ -8,20 +8,50 @@ import * as styles from './styles';
|
||||||
|
|
||||||
const TabsWrapper = createStyledComponent(styles);
|
const TabsWrapper = createStyledComponent(styles);
|
||||||
|
|
||||||
export default class TabsHeader extends Component {
|
export type ReactButtonElement = React.ReactElement<
|
||||||
constructor(props) {
|
JSX.IntrinsicElements['button'],
|
||||||
super(props);
|
'button'
|
||||||
this.state = {
|
>;
|
||||||
visibleTabs: props.tabs.slice(),
|
|
||||||
hiddenTabs: [],
|
|
||||||
subMenuOpened: false,
|
|
||||||
contextMenu: undefined,
|
|
||||||
};
|
|
||||||
this.iconWidth = 0;
|
|
||||||
this.hiddenTabsWidth = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
export interface Tab<P> {
|
||||||
|
name: string;
|
||||||
|
value?: string;
|
||||||
|
component?: React.ComponentType<P>;
|
||||||
|
selector?: (tab: this) => P;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props<P> {
|
||||||
|
tabs: ReactButtonElement[];
|
||||||
|
items: Tab<P>[];
|
||||||
|
main: boolean | undefined;
|
||||||
|
onClick: (value: string) => void;
|
||||||
|
position: 'left' | 'right' | 'center';
|
||||||
|
collapsible: boolean | undefined;
|
||||||
|
selected: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
visibleTabs: ReactButtonElement[];
|
||||||
|
hiddenTabs: ReactButtonElement[];
|
||||||
|
subMenuOpened: boolean;
|
||||||
|
contextMenu: { top: number; left: number } | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TabsHeader<P> extends Component<Props<P>, State> {
|
||||||
|
state: State = {
|
||||||
|
visibleTabs: this.props.tabs.slice(),
|
||||||
|
hiddenTabs: [],
|
||||||
|
subMenuOpened: false,
|
||||||
|
contextMenu: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
iconWidth = 0;
|
||||||
|
hiddenTabsWidth: number[] = [];
|
||||||
|
tabsWrapperRef?: HTMLDivElement | null;
|
||||||
|
tabsRef?: HTMLDivElement | null;
|
||||||
|
resizeDetector?: HTMLIFrameElement;
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps: Props<P>) {
|
||||||
if (
|
if (
|
||||||
nextProps.tabs !== this.props.tabs ||
|
nextProps.tabs !== this.props.tabs ||
|
||||||
nextProps.selected !== this.props.selected ||
|
nextProps.selected !== this.props.selected ||
|
||||||
|
@ -38,7 +68,7 @@ export default class TabsHeader extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: Props<P>) {
|
||||||
const { collapsible } = this.props;
|
const { collapsible } = this.props;
|
||||||
if (!collapsible) {
|
if (!collapsible) {
|
||||||
if (prevProps.collapsible !== collapsible) this.disableResizeEvents();
|
if (prevProps.collapsible !== collapsible) this.disableResizeEvents();
|
||||||
|
@ -47,8 +77,11 @@ export default class TabsHeader extends Component {
|
||||||
|
|
||||||
let shouldCollapse = false;
|
let shouldCollapse = false;
|
||||||
if (this.iconWidth === 0) {
|
if (this.iconWidth === 0) {
|
||||||
const tabButtons = this.tabsRef.children;
|
const tabButtons = this.tabsRef!.children;
|
||||||
if (this.tabsRef.children[tabButtons.length - 1].value === 'expandIcon') {
|
if (
|
||||||
|
(this.tabsRef!.children[tabButtons.length - 1] as HTMLButtonElement)
|
||||||
|
.value === 'expandIcon'
|
||||||
|
) {
|
||||||
this.iconWidth = tabButtons[
|
this.iconWidth = tabButtons[
|
||||||
tabButtons.length - 1
|
tabButtons.length - 1
|
||||||
].getBoundingClientRect().width;
|
].getBoundingClientRect().width;
|
||||||
|
@ -75,12 +108,12 @@ export default class TabsHeader extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
enableResizeEvents() {
|
enableResizeEvents() {
|
||||||
this.resizeDetector = observeResize(this.tabsWrapperRef, this.collapse);
|
this.resizeDetector = observeResize(this.tabsWrapperRef!, this.collapse);
|
||||||
window.addEventListener('mousedown', this.hideSubmenu);
|
window.addEventListener('mousedown', this.hideSubmenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableResizeEvents() {
|
disableResizeEvents() {
|
||||||
this.resizeDetector.remove();
|
this.resizeDetector!.remove();
|
||||||
window.removeEventListener('mousedown', this.hideSubmenu);
|
window.removeEventListener('mousedown', this.hideSubmenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,13 +123,13 @@ export default class TabsHeader extends Component {
|
||||||
const { selected, tabs } = this.props;
|
const { selected, tabs } = this.props;
|
||||||
const tabsWrapperRef = this.tabsWrapperRef;
|
const tabsWrapperRef = this.tabsWrapperRef;
|
||||||
const tabsRef = this.tabsRef;
|
const tabsRef = this.tabsRef;
|
||||||
const tabButtons = this.tabsRef.children;
|
const tabButtons = this.tabsRef!.children;
|
||||||
const visibleTabs = this.state.visibleTabs;
|
const visibleTabs = this.state.visibleTabs;
|
||||||
const hiddenTabs = this.state.hiddenTabs;
|
const hiddenTabs = this.state.hiddenTabs;
|
||||||
let tabsWrapperRight = tabsWrapperRef.getBoundingClientRect().right;
|
let tabsWrapperRight = tabsWrapperRef!.getBoundingClientRect().right;
|
||||||
if (!tabsWrapperRight) return; // tabs are hidden
|
if (!tabsWrapperRight) return; // tabs are hidden
|
||||||
|
|
||||||
const tabsRefRight = tabsRef.getBoundingClientRect().right;
|
const tabsRefRight = tabsRef!.getBoundingClientRect().right;
|
||||||
let i = visibleTabs.length - 1;
|
let i = visibleTabs.length - 1;
|
||||||
let hiddenTab;
|
let hiddenTab;
|
||||||
|
|
||||||
|
@ -104,16 +137,16 @@ export default class TabsHeader extends Component {
|
||||||
if (
|
if (
|
||||||
this.props.position === 'right' &&
|
this.props.position === 'right' &&
|
||||||
hiddenTabs.length > 0 &&
|
hiddenTabs.length > 0 &&
|
||||||
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
||||||
tabsWrapperRef.getBoundingClientRect().width
|
tabsWrapperRef!.getBoundingClientRect().width
|
||||||
) {
|
) {
|
||||||
while (
|
while (
|
||||||
i < tabs.length - 1 &&
|
i < tabs.length - 1 &&
|
||||||
tabsRef.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
tabsRef!.getBoundingClientRect().width + this.hiddenTabsWidth[0] <
|
||||||
tabsWrapperRef.getBoundingClientRect().width
|
tabsWrapperRef!.getBoundingClientRect().width
|
||||||
) {
|
) {
|
||||||
hiddenTab = hiddenTabs.shift();
|
hiddenTab = hiddenTabs.shift();
|
||||||
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
|
visibleTabs.splice(Number(hiddenTab!.key), 0, hiddenTab!);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -123,7 +156,7 @@ export default class TabsHeader extends Component {
|
||||||
tabButtons[i].getBoundingClientRect().right >=
|
tabButtons[i].getBoundingClientRect().right >=
|
||||||
tabsWrapperRight - this.iconWidth
|
tabsWrapperRight - this.iconWidth
|
||||||
) {
|
) {
|
||||||
if (tabButtons[i].value !== selected) {
|
if ((tabButtons[i] as HTMLButtonElement).value !== selected) {
|
||||||
hiddenTabs.unshift(...visibleTabs.splice(i, 1));
|
hiddenTabs.unshift(...visibleTabs.splice(i, 1));
|
||||||
this.hiddenTabsWidth.unshift(
|
this.hiddenTabsWidth.unshift(
|
||||||
tabButtons[i].getBoundingClientRect().width
|
tabButtons[i].getBoundingClientRect().width
|
||||||
|
@ -142,7 +175,7 @@ export default class TabsHeader extends Component {
|
||||||
tabsWrapperRight - this.iconWidth
|
tabsWrapperRight - this.iconWidth
|
||||||
) {
|
) {
|
||||||
hiddenTab = hiddenTabs.shift();
|
hiddenTab = hiddenTabs.shift();
|
||||||
visibleTabs.splice(Number(hiddenTab.key), 0, hiddenTab);
|
visibleTabs.splice(Number(hiddenTab!.key), 0, hiddenTab!);
|
||||||
this.hiddenTabsWidth.shift();
|
this.hiddenTabsWidth.shift();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -154,15 +187,15 @@ export default class TabsHeader extends Component {
|
||||||
this.setState({ subMenuOpened: false, contextMenu: undefined });
|
this.setState({ subMenuOpened: false, contextMenu: undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
getTabsWrapperRef = (node) => {
|
getTabsWrapperRef: React.RefCallback<HTMLDivElement> = (node) => {
|
||||||
this.tabsWrapperRef = node;
|
this.tabsWrapperRef = node;
|
||||||
};
|
};
|
||||||
|
|
||||||
getTabsRef = (node) => {
|
getTabsRef: React.RefCallback<HTMLDivElement> = (node) => {
|
||||||
this.tabsRef = node;
|
this.tabsRef = node;
|
||||||
};
|
};
|
||||||
|
|
||||||
expandMenu = (e) => {
|
expandMenu: React.MouseEventHandler = (e) => {
|
||||||
const rect = e.currentTarget.children[0].getBoundingClientRect();
|
const rect = e.currentTarget.children[0].getBoundingClientRect();
|
||||||
this.setState({
|
this.setState({
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
|
@ -202,14 +235,14 @@ export default class TabsHeader extends Component {
|
||||||
</TabsWrapper>
|
</TabsWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TabsHeader.propTypes = {
|
static propTypes = {
|
||||||
tabs: PropTypes.array.isRequired,
|
tabs: PropTypes.array.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
main: PropTypes.bool,
|
main: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
position: PropTypes.string,
|
position: PropTypes.string,
|
||||||
collapsible: PropTypes.bool,
|
collapsible: PropTypes.bool,
|
||||||
selected: PropTypes.string,
|
selected: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
const Component = ({ selected }) => (
|
const Component = ({ selected }: { selected: string }) => (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -17,7 +17,9 @@ const Component = ({ selected }) => (
|
||||||
);
|
);
|
||||||
/* eslint-enable react/prop-types */
|
/* eslint-enable react/prop-types */
|
||||||
|
|
||||||
const selector = (tab) => ({ selected: tab.name });
|
const selector = (tab: { name: string; value?: string }) => ({
|
||||||
|
selected: tab.name,
|
||||||
|
});
|
||||||
|
|
||||||
export const tabs = [
|
export const tabs = [
|
||||||
{
|
{
|
||||||
|
@ -37,6 +39,6 @@ export const tabs = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const simple10Tabs = [];
|
export const simple10Tabs: { name: string; value: string }[] = [];
|
||||||
for (let i = 1; i <= 10; i++)
|
for (let i = 1; i <= 10; i++)
|
||||||
simple10Tabs.push({ name: `Tab${i}`, value: `${i}` });
|
simple10Tabs.push({ name: `Tab${i}`, value: `${i}` });
|
|
@ -1 +0,0 @@
|
||||||
export default from './Tabs';
|
|
1
packages/devui/src/Tabs/index.ts
Normal file
1
packages/devui/src/Tabs/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Tabs';
|
|
@ -1,6 +1,11 @@
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Position } from '../Tabs';
|
||||||
|
|
||||||
export const TabsContainer = styled.div`
|
interface StyleProps {
|
||||||
|
position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TabsContainer = styled.div<StyleProps>`
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
|
@ -1,6 +1,14 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
export const style = ({ theme, main }) => css`
|
export interface StyleProps {
|
||||||
|
main: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
main,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 1;
|
flex: 0 0 1;
|
||||||
padding-left: 1px;
|
padding-left: 1px;
|
|
@ -1,7 +1,12 @@
|
||||||
import { css } from 'styled-components';
|
import { css, ThemedStyledProps } from 'styled-components';
|
||||||
import { ripple } from '../../utils/animations';
|
import { ripple } from '../../utils/animations';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
import { StyleProps } from './default';
|
||||||
|
|
||||||
export const style = ({ theme, main }) => css`
|
export const style = ({
|
||||||
|
theme,
|
||||||
|
main,
|
||||||
|
}: ThemedStyledProps<StyleProps, Theme>) => css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 1;
|
flex: 0 0 1;
|
||||||
padding-left: 1px;
|
padding-left: 1px;
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Story } from '@storybook/react';
|
||||||
import { MdPlayArrow } from 'react-icons/md';
|
import { MdPlayArrow } from 'react-icons/md';
|
||||||
import { MdFiberManualRecord } from 'react-icons/md';
|
import { MdFiberManualRecord } from 'react-icons/md';
|
||||||
import { MdKeyboardArrowLeft } from 'react-icons/md';
|
import { MdKeyboardArrowLeft } from 'react-icons/md';
|
||||||
|
@ -16,6 +17,9 @@ import {
|
||||||
} from '../';
|
} from '../';
|
||||||
import { options } from '../Select/options';
|
import { options } from '../Select/options';
|
||||||
import { simple10Tabs } from '../Tabs/data';
|
import { simple10Tabs } from '../Tabs/data';
|
||||||
|
import { BorderPosition } from './styles/Toolbar';
|
||||||
|
import { TooltipPosition } from '../Button/Button';
|
||||||
|
import { Position } from '../Tabs/Tabs';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -35,12 +39,27 @@ export default {
|
||||||
component: Toolbar,
|
component: Toolbar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template = ({
|
interface TemplateArgs {
|
||||||
|
borderPosition: BorderPosition;
|
||||||
|
title?: string;
|
||||||
|
tooltipPosition: TooltipPosition;
|
||||||
|
disabled?: boolean;
|
||||||
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
label: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Template: Story<TemplateArgs> = ({
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
borderPosition,
|
borderPosition,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
title,
|
title,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
tooltipPosition,
|
tooltipPosition,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
disabled,
|
disabled,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
onClick,
|
onClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
label,
|
label,
|
||||||
}) => (
|
}) => (
|
||||||
<Container>
|
<Container>
|
||||||
|
@ -104,17 +123,41 @@ Default.argTypes = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabsTemplate = ({
|
interface TabsTemplateArgs {
|
||||||
|
title?: string;
|
||||||
|
tooltipPosition: TooltipPosition;
|
||||||
|
disabled?: boolean;
|
||||||
|
buttonOnClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
label: ReactNode;
|
||||||
|
selected?: string;
|
||||||
|
main?: boolean;
|
||||||
|
tabsOnClick: (value: string) => void;
|
||||||
|
collapsible?: boolean;
|
||||||
|
position: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsTemplate: Story<TabsTemplateArgs> = ({
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
title,
|
title,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
tooltipPosition,
|
tooltipPosition,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
disabled,
|
disabled,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
buttonOnClick,
|
buttonOnClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
label,
|
label,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
selected,
|
selected,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
main,
|
main,
|
||||||
tabOnClick,
|
// eslint-disable-next-line react/prop-types
|
||||||
|
tabsOnClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
collapsible,
|
collapsible,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
position,
|
position,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
}) => (
|
}) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
@ -130,7 +173,7 @@ const TabsTemplate = ({
|
||||||
tabs={simple10Tabs}
|
tabs={simple10Tabs}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
main={main}
|
main={main}
|
||||||
onClick={tabOnClick}
|
onClick={tabsOnClick}
|
||||||
collapsible={collapsible}
|
collapsible={collapsible}
|
||||||
position={position}
|
position={position}
|
||||||
/>
|
/>
|
||||||
|
@ -176,7 +219,7 @@ Tabs.argTypes = {
|
||||||
buttonOnClick: {
|
buttonOnClick: {
|
||||||
action: 'button clicked',
|
action: 'button clicked',
|
||||||
},
|
},
|
||||||
tabOnClick: {
|
tabsOnClick: {
|
||||||
action: 'tab selected',
|
action: 'tab selected',
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
|
@ -187,19 +230,48 @@ Tabs.argTypes = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WithSliderTemplate = ({
|
interface WithSliderTemplateArgs {
|
||||||
|
title?: string;
|
||||||
|
tooltipPosition: TooltipPosition;
|
||||||
|
playOnClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
label?: string;
|
||||||
|
withValue?: boolean;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
previousStateOnClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
nextStateOnClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
selected?: string;
|
||||||
|
segmentedControlOnClick: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithSliderTemplate: Story<WithSliderTemplateArgs> = ({
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
title,
|
title,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
tooltipPosition,
|
tooltipPosition,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
playOnClick,
|
playOnClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
value,
|
value,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
min,
|
min,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
max,
|
max,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
label,
|
label,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
withValue,
|
withValue,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
onChange,
|
onChange,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
previousStateOnClick,
|
previousStateOnClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
nextStateOnClick,
|
nextStateOnClick,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
selected,
|
selected,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
segmentedControlOnClick,
|
segmentedControlOnClick,
|
||||||
}) => (
|
}) => (
|
||||||
<Container>
|
<Container>
|
|
@ -1,3 +0,0 @@
|
||||||
export Toolbar from './styles/Toolbar';
|
|
||||||
export Divider from './styles/Divider';
|
|
||||||
export Spacer from './styles/Spacer';
|
|
3
packages/devui/src/Toolbar/index.ts
Normal file
3
packages/devui/src/Toolbar/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as Toolbar } from './styles/Toolbar';
|
||||||
|
export { default as Divider } from './styles/Divider';
|
||||||
|
export { default as Spacer } from './styles/Spacer';
|
|
@ -1,6 +1,16 @@
|
||||||
import styled from 'styled-components';
|
import styled, { ThemedStyledInterface } from 'styled-components';
|
||||||
|
import { Theme } from '../../themes/default';
|
||||||
|
|
||||||
const Toolbar = styled.div`
|
export type BorderPosition = 'top' | 'bottom';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
fullHeight?: boolean;
|
||||||
|
compact?: boolean;
|
||||||
|
borderPosition?: BorderPosition;
|
||||||
|
noBorder?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Toolbar = (styled as ThemedStyledInterface<Theme>).div<Props>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scheme: 'default',
|
scheme: 'default',
|
||||||
|
author: 'Mihail Diordiev (https://github.com/zalmoxisus)',
|
||||||
base00: '#ffffff',
|
base00: '#ffffff',
|
||||||
base01: '#f3f3f3',
|
base01: '#f3f3f3',
|
||||||
base02: '#e8e8e8',
|
base02: '#e8e8e8',
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user