Migration: base component

This commit is contained in:
Dreamacro 2019-07-02 16:56:17 +08:00
parent 658a26c2fc
commit 73d992ae94
13 changed files with 852 additions and 801 deletions

1306
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,11 +33,11 @@
"@babel/preset-env": "^7.4.5", "@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@types/classnames": "^2.2.8", "@types/classnames": "^2.2.8",
"@types/node": "^12.0.8", "@types/node": "^12.0.10",
"@types/react": "^16.8.19", "@types/react": "^16.8.22",
"@types/react-dom": "^16.8.4", "@types/react-dom": "^16.8.4",
"@types/react-i18next": "^8.1.0", "@types/react-i18next": "^8.1.0",
"@types/react-router-dom": "^4.3.3", "@types/react-router-dom": "^4.3.4",
"@types/react-sortable-hoc": "^0.6.5", "@types/react-sortable-hoc": "^0.6.5",
"@types/react-virtualized": "^9.21.2", "@types/react-virtualized": "^9.21.2",
"@types/yaml": "^1.0.2", "@types/yaml": "^1.0.2",
@ -53,20 +53,20 @@
"offline-plugin": "^5.0.7", "offline-plugin": "^5.0.7",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"react-addons-test-utils": "^15.6.2", "react-addons-test-utils": "^15.6.2",
"react-hot-loader": "^4.11.0", "react-hot-loader": "^4.12.0",
"sass": "^1.21.0", "sass": "^1.22.2",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"stylelint": "^10.1.0", "stylelint": "^10.1.0",
"stylelint-config-standard": "^18.3.0", "stylelint-config-standard": "^18.3.0",
"stylelint-webpack-plugin": "^0.10.5", "stylelint-webpack-plugin": "^0.10.5",
"tslint": "^5.17.0", "tslint": "^5.18.0",
"tslint-config-standard": "^8.0.1", "tslint-config-standard": "^8.0.1",
"tslint-loader": "^3.6.0", "tslint-loader": "^3.6.0",
"webpack": "^4.33.0", "webpack": "^4.35.2",
"webpack-cli": "^3.3.4", "webpack-cli": "^3.3.5",
"webpack-dev-middleware": "^3.7.0", "webpack-dev-middleware": "^3.7.0",
"webpack-dev-server": "^3.7.1", "webpack-dev-server": "^3.7.2",
"webpack-merge": "^4.2.1", "webpack-merge": "^4.2.1",
"webpack-pwa-manifest": "^4.0.0" "webpack-pwa-manifest": "^4.0.0"
}, },
@ -74,20 +74,20 @@
"axios": "^0.19.0", "axios": "^0.19.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"dayjs": "^1.8.14", "dayjs": "^1.8.14",
"eventemitter3": "^3.1.2", "eventemitter3": "^4.0.0",
"i18next": "^11.10.0", "i18next": "^17.0.6",
"i18next-browser-languagedetector": "^2.2.4", "i18next-browser-languagedetector": "^3.0.1",
"mobx": "^5.10.1", "mobx": "^5.10.1",
"mobx-react": "^6.0.3", "mobx-react": "^6.1.1",
"mobx-react-router": "^4.0.7", "mobx-react-router": "^4.0.7",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-i18next": "^7.12.0", "react-i18next": "^10.11.2",
"react-router-dom": "^5.0.1", "react-router-dom": "^5.0.1",
"react-sortable-hoc": "^1.9.1", "react-sortable-hoc": "^1.9.1",
"react-virtualized": "^9.21.1", "react-virtualized": "^9.21.1",
"terser-webpack-plugin": "^1.3.0", "terser-webpack-plugin": "^1.3.0",
"typescript": "^3.5.1", "typescript": "^3.5.2",
"yaml": "^1.6.0" "yaml": "^1.6.0"
} }
} }

View File

@ -10,36 +10,26 @@ interface AlertProps extends BaseComponentProps {
inside?: boolean inside?: boolean
} }
export class Alert extends React.Component<AlertProps, {}> { const iconMap = {
success: 'check',
static defaultProps: AlertProps = { info: 'info',
message: '', warning: 'info',
type: 'info', error: 'close'
inside: false }
}
export function Alert (props: AlertProps) {
iconMap = { const { message = '', type = 'info', inside = false, children, className, style } = props
success: 'check', const classname = classnames('alert', `alert-${inside ? 'note' : 'box'}-${type}`, className)
info: 'info', return (
warning: 'info', <div className={classname} style={style}>
error: 'close' <span className="alert-icon">
} <Icon type={iconMap[type]} size={26} />
</span>
render () { {
const { message, type, inside, children, className, style } = this.props message
? <p className="alert-message">{message}</p>
return ( : <div className="alert-message">{children}</div>
<div className={classnames('alert', `alert-${inside ? 'note' : 'box'}-${type}`, className)} style={style}> }
<span className="alert-icon"> </div>
<Icon type={this.iconMap[type]} size={26} /> )
</span>
{
message
? <p className="alert-message">{message}</p>
: <div className="alert-message">{children}</div>
}
</div>
)
}
} }

View File

@ -1,6 +1,7 @@
import * as React from 'react' import * as React from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import { BaseComponentProps } from '@models' import { BaseComponentProps } from '@models'
import { noop } from '@lib/helper'
import './style.scss' import './style.scss'
interface ButtonProps extends BaseComponentProps { interface ButtonProps extends BaseComponentProps {
@ -8,23 +9,15 @@ interface ButtonProps extends BaseComponentProps {
onClick?: React.MouseEventHandler<HTMLButtonElement> onClick?: React.MouseEventHandler<HTMLButtonElement>
} }
export class Button extends React.Component<ButtonProps, {}> { export function Button (props: ButtonProps) {
const { type = 'normal', onClick = noop, children, className, style } = props
static defaultProps: ButtonProps = { const classname = classnames('button', `button-${type}`, className)
type: 'normal',
onClick: () => {}
}
render () {
const { type, onClick, children, className, style } = this.props
return (
<button
className={classnames('button', `button-${type}`, className)}
style={style}
onClick={onClick}
>{children}</button>
)
}
return (
<button
className={classname}
style={style}
onClick={onClick}
>{children}</button>
)
} }

View File

@ -19,26 +19,22 @@ export interface ButtonSelectProps extends BaseComponentProps {
onSelect?: (value: any) => void onSelect?: (value: any) => void
} }
export class ButtonSelect extends React.Component<ButtonSelectProps, {}> { export function ButtonSelect (props: ButtonSelectProps) {
const { options, value, onSelect } = props
render () {
const { options, value, onSelect } = this.props
return (
<div className="button-select">
{
options.map(option => (
<button
value={option.value}
key={option.value}
className={classnames('button-select-options', { actived: value === option.value })}
onClick={() => onSelect(option.value)}>
{ option.label }
</button>
))
}
</div>
)
}
return (
<div className="button-select">
{
options.map(option => (
<button
value={option.value}
key={option.value}
className={classnames('button-select-options', { actived: value === option.value })}
onClick={() => onSelect(option.value)}>
{ option.label }
</button>
))
}
</div>
)
} }

View File

@ -1,11 +1,11 @@
import * as React from 'react' import * as React from 'react'
import { BaseComponentProps } from '@models/BaseProps' import { BaseComponentProps } from '@models/BaseProps'
import { noop } from '@lib/helper'
import classnames from 'classnames' import classnames from 'classnames'
import './style.scss' import './style.scss'
interface InputProps extends BaseComponentProps { interface InputProps extends BaseComponentProps {
value?: string | number value?: string | number
disabled?: boolean
align?: 'left' | 'center' | 'right' align?: 'left' | 'center' | 'right'
inside?: boolean inside?: boolean
autoFocus?: boolean autoFocus?: boolean
@ -14,41 +14,29 @@ interface InputProps extends BaseComponentProps {
onBlur?: (event?: React.FocusEvent<HTMLInputElement>) => void onBlur?: (event?: React.FocusEvent<HTMLInputElement>) => void
} }
export class Input extends React.Component<InputProps, {}> { export function Input (props: InputProps) {
static defaultProps: InputProps = { const {
value: '', className,
disabled: false, style,
align: 'center', value = '',
inside: false, align = 'center',
autoFocus: false, inside = false,
type: 'text', autoFocus = false,
onChange: () => {}, type = 'text',
onBlur: () => {} onChange = noop,
} onBlur = noop
} = props
const classname = classnames('input', `input-align-${align}`, { 'input-inside': inside }, className)
render () { return (
const { <input
className, className={classname}
style, style={style}
value, value={value}
align, autoFocus={autoFocus}
inside, type={type}
autoFocus, onChange={event => onChange(event.target.value, event)}
type, onBlur={onBlur}
onChange, />
onBlur )
} = this.props
return (
<input
className={classnames('input', `input-align-${align}`, { 'input-inside': inside }, className)}
style={style}
value={value}
autoFocus={autoFocus}
type={type}
onChange={event => onChange(event.target.value, event)}
onBlur={onBlur}
/>
)
}
} }

View File

@ -1,6 +1,7 @@
import * as React from 'react' import * as React from 'react'
import { BaseComponentProps } from '@models/BaseProps' import { BaseComponentProps } from '@models/BaseProps'
import { Icon } from '@components' import { Icon } from '@components'
import { noop } from '@lib/helper'
import classnames from 'classnames' import classnames from 'classnames'
import './style.scss' import './style.scss'
@ -10,26 +11,19 @@ interface SwitchProps extends BaseComponentProps {
onChange?: (checked: boolean) => void onChange?: (checked: boolean) => void
} }
export class Switch extends React.Component<SwitchProps, {}> { export function Switch (props: SwitchProps) {
static defaultProps: SwitchProps = { const { className, checked = false, disabled = false, onChange = noop } = props
checked: false, const classname = classnames('switch', { checked, disabled }, className)
disabled: false,
onChange: () => {}
}
handleClick = () => { function handleClick () {
if (!this.props.disabled) { if (!disabled) {
this.props.onChange(!this.props.checked) onChange(!checked)
} }
} }
render () { return (
const { className, checked, disabled } = this.props <div className={classname} onClick={handleClick}>
<Icon className="switch-icon" type="check" size={20} style={{ fontWeight: 'bold' }} />
return ( </div>
<div className={classnames('switch', { checked, disabled }, className)} onClick={this.handleClick}> )
<Icon className="switch-icon" type="check" size={20} style={{ fontWeight: 'bold' }} />
</div>
)
}
} }

View File

@ -1,5 +1,5 @@
import * as React from 'react' import React, { useState, useRef, useMemo } from 'react'
import { translate } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { BaseComponentProps, I18nProps } from '@models' import { BaseComponentProps, I18nProps } from '@models'
import { noop } from '@lib/helper' import { noop } from '@lib/helper'
import classnames from 'classnames' import classnames from 'classnames'
@ -13,55 +13,41 @@ interface TagsProps extends BaseComponentProps, I18nProps {
canClick: boolean canClick: boolean
} }
interface TagsState { export function Tags (props: TagsProps) {
expand: boolean const { className, data, onClick, select, canClick } = props
showExtend: boolean
ulRef: React.RefObject<HTMLUListElement> const { t } = useTranslation()
const [expand, setExpand] = useState(false)
const ulRef = useRef<HTMLUListElement>()
const showExtend = useMemo(() => ulRef.current.offsetHeight > 30, [ulRef])
const rowHeight = this.state.expand ? 'auto' : this.props.rowHeight
const handleClick = canClick ? onClick : noop
function toggleExtend () {
setExpand(!expand)
}
const tags = data
.map(t => {
const tagClass = classnames({ 'tags-selected': select === t, 'can-click': canClick })
return (
<li className={tagClass} key={t} onClick={() => handleClick(t)}>
{ t }
</li>
)
})
return (
<div className={classnames('tags-container', className)} style={{ height: rowHeight }}>
<ul ref={ulRef} className={classnames('tags', { expand })}>
{ tags }
</ul>
{
showExtend &&
<span className="tags-expand" onClick={toggleExtend}>{ expand ? t('collapseText') : t('expandText') }</span>
}
</div>
)
} }
class TagsClass extends React.Component<TagsProps, TagsState> {
state: TagsState = {
expand: false,
showExtend: true,
ulRef: React.createRef<HTMLUListElement>()
}
toggleExtend = () => {
this.setState({ expand: !this.state.expand })
}
componentDidMount () {
this.setState({ showExtend: this.state.ulRef.current.offsetHeight > 30 })
}
render () {
const { t, className, data, onClick, select, canClick } = this.props
const { expand } = this.state
const rowHeight = this.state.expand ? 'auto' : this.props.rowHeight
const handleClick = canClick ? onClick : noop
const tags = data
.map(t => {
const tagClass = classnames({ 'tags-selected': select === t, 'can-click': canClick })
return (
<li className={tagClass} key={t} onClick={() => handleClick(t)}>
{ t }
</li>
)
})
return (
<div className={classnames('tags-container', className)} style={{ height: rowHeight }}>
<ul ref={this.state.ulRef} className={classnames('tags', { expand })}>
{ tags }
</ul>
{
this.state.showExtend &&
<span className="tags-expand" onClick={this.toggleExtend}>{ this.state.expand ? t('collapseText') : t('expandText') }</span>
}
</div>
)
}
}
export const Tags = translate(['Proxies'])(TagsClass)

View File

@ -1,5 +1,5 @@
import * as React from 'react' import * as React from 'react'
import { translate } from 'react-i18next' import { withTranslation } from 'react-i18next'
import { inject, observer } from 'mobx-react' import { inject, observer } from 'mobx-react'
import { storeKeys } from '@lib/createStore' import { storeKeys } from '@lib/createStore'
import { Modal, Input, Row, Col, Alert } from '@components' import { Modal, Input, Row, Col, Alert } from '@components'
@ -93,4 +93,4 @@ class ExternalController extends React.Component<ExternalControllerModalProps, E
} }
} }
export default translate(['Settings'])(ExternalController) export default withTranslation(['Settings'])(ExternalController)

View File

@ -1,4 +1,4 @@
import * as EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
export enum Action { export enum Action {
SPEED_NOTIFY = 'speed-notify' SPEED_NOTIFY = 'speed-notify'

View File

@ -1,5 +1,5 @@
import { to } from '@lib/helper' import { to } from '@lib/helper'
import * as EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
export interface Config { export interface Config {
url: string url: string

View File

@ -1,4 +1,4 @@
import { CSSProperties } from 'react' import { CSSProperties, ReactNode } from 'react'
import { RouteComponentProps } from 'react-router' import { RouteComponentProps } from 'react-router'
import { RouterStore, ConfigStore } from '@stores' import { RouterStore, ConfigStore } from '@stores'
@ -19,5 +19,6 @@ export interface BaseProps extends BaseComponentProps {
export interface BaseComponentProps { export interface BaseComponentProps {
className?: string className?: string
children?: ReactNode
style?: CSSProperties style?: CSSProperties
} }

View File

@ -2,6 +2,7 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./dist/", "outDir": "./dist/",
"sourceMap": true, "sourceMap": true,
"esModuleInterop": true,
"noImplicitAny": false, "noImplicitAny": false,
"noUnusedLocals": true, "noUnusedLocals": true,
"module": "commonjs", "module": "commonjs",