mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Add: setting page
This commit is contained in:
parent
c7ce6a1bca
commit
e8bde550a3
@ -2,7 +2,10 @@
|
|||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-standard",
|
||||||
"rules": {
|
"rules": {
|
||||||
"indentation": 4,
|
"indentation": 4,
|
||||||
"font-family-no-missing-generic-family-keyword": null
|
"font-family-no-missing-generic-family-keyword": null,
|
||||||
|
"at-rule-no-unknown": [true, {
|
||||||
|
"ignoreAtRules": ["for", "function", "if", "each", "include", "mixin"]
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
"ignoreFiles": [
|
"ignoreFiles": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -2888,6 +2888,11 @@
|
|||||||
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=",
|
"integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.7.5.tgz",
|
||||||
|
"integrity": "sha512-OzkAcosqOgWgQF+dQTXO/iaSGa3hMs/sSkfzkxwWpZXqJEbaA0V6O1V+Ew2tGBlTz1r7Rb7opU3w8ympWb9d2Q=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"dayjs": "^1.7.5",
|
||||||
"i18next": "^11.9.0",
|
"i18next": "^11.9.0",
|
||||||
"i18next-browser-languagedetector": "^2.2.3",
|
"i18next-browser-languagedetector": "^2.2.3",
|
||||||
"ini": "^1.3.5",
|
"ini": "^1.3.5",
|
||||||
|
43
src/components/ButtonSelect/index.tsx
Normal file
43
src/components/ButtonSelect/index.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { BaseComponentProps } from '@models/BaseProps'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
export interface ButtonSelectOptions {
|
||||||
|
label: string,
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonSelectProps extends BaseComponentProps {
|
||||||
|
// options
|
||||||
|
options: ButtonSelectOptions[]
|
||||||
|
|
||||||
|
// active value
|
||||||
|
value: any
|
||||||
|
|
||||||
|
// select callback
|
||||||
|
onSelect?: (value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ButtonSelect extends React.Component<ButtonSelectProps, {}> {
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
src/components/ButtonSelect/style.scss
Normal file
41
src/components/ButtonSelect/style.scss
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
.button-select {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.button-select-options {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 15px;
|
||||||
|
color: $color-primary-darken;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 30px;
|
||||||
|
background: $color-white;
|
||||||
|
border: 1px solid $color-primary-lightly;
|
||||||
|
border-right: none;
|
||||||
|
transition: all 300ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-select-options:first-child {
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-select-options:last-child {
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
border-right: 1px solid $color-primary-lightly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-select-options.actived {
|
||||||
|
background: $color-primary;
|
||||||
|
color: $color-white;
|
||||||
|
border-color: $color-primary;
|
||||||
|
box-shadow: 0 2px 5px rgba($color: $color-primary, $alpha: 0.5);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/components/Col/index.tsx
Normal file
37
src/components/Col/index.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { BaseComponentProps } from '@models/BaseProps'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
interface ColProps extends BaseComponentProps {
|
||||||
|
// left offset
|
||||||
|
offset?: number
|
||||||
|
|
||||||
|
// flex order
|
||||||
|
order?: number
|
||||||
|
|
||||||
|
span?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Col: React.SFC<ColProps> = props => {
|
||||||
|
const {
|
||||||
|
offset = 0,
|
||||||
|
order = 0,
|
||||||
|
span = 1,
|
||||||
|
className,
|
||||||
|
style: s,
|
||||||
|
children
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const style = Object.assign({}, { order }, s)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames(
|
||||||
|
'column',
|
||||||
|
`column-offset-${offset}`,
|
||||||
|
`column-span-${span}`,
|
||||||
|
className
|
||||||
|
)} style={style}>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
38
src/components/Row/index.tsx
Normal file
38
src/components/Row/index.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { BaseComponentProps } from '@models/BaseProps'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
interface RowProps extends BaseComponentProps {
|
||||||
|
// grid column
|
||||||
|
gutter?: number
|
||||||
|
|
||||||
|
// row align
|
||||||
|
align?: 'top' | 'middle' | 'bottom'
|
||||||
|
|
||||||
|
// column justify
|
||||||
|
justify?: 'start' | 'end' | 'center' | 'space-around' | 'space-between'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Row: React.SFC<RowProps> = props => {
|
||||||
|
const {
|
||||||
|
gutter = 24,
|
||||||
|
align = 'top',
|
||||||
|
justify = 'start',
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
children
|
||||||
|
} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames(
|
||||||
|
'row',
|
||||||
|
`row-gutter-${gutter}`,
|
||||||
|
`row-align-${align}`,
|
||||||
|
`row-justify-${justify}`,
|
||||||
|
className
|
||||||
|
)} style={style}>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
65
src/components/Row/style.scss
Normal file
65
src/components/Row/style.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
$padding: 12px;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gutter
|
||||||
|
@for $i from 1 through 24 {
|
||||||
|
.row-gutter-#{$i} {
|
||||||
|
padding: $padding $padding / 2;
|
||||||
|
|
||||||
|
.column {
|
||||||
|
padding: 0 $padding / 2;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $c from 1 through 24 {
|
||||||
|
.column-span-#{$c} {
|
||||||
|
width: (100% / $i) * $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-offset-#{$c} {
|
||||||
|
margin-left: (100% / $i) * $c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// align
|
||||||
|
.row-align-top {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-align-middle {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-align-bottom {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// justify
|
||||||
|
.row-justify-start {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-justify-space-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-justify-space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
@ -28,7 +28,7 @@ export class Switch extends React.Component<SwitchProps, {}> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('switch', { checked, disabled }, className)} onClick={this.handleClick}>
|
<div className={classnames('switch', { checked, disabled }, className)} onClick={this.handleClick}>
|
||||||
<Icon className="switch-icon" type="check" size={8} />
|
<Icon className="switch-icon" type="check" size={8} style={{ fontWeight: 'bold' }} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
@import '~@styles/variables';
|
@import '~@styles/variables';
|
||||||
|
|
||||||
$height: 14px;
|
$height: 16px;
|
||||||
$switch-radius: 16px;
|
$switch-radius: 18px;
|
||||||
$switch-offset: 2px;
|
$switch-offset: 2px;
|
||||||
$width: 28px;
|
$width: 32px;
|
||||||
|
|
||||||
.switch {
|
.switch {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -43,7 +43,7 @@ $width: 28px;
|
|||||||
width: $switch-radius;
|
width: $switch-radius;
|
||||||
border-radius: $switch-radius / 2;
|
border-radius: $switch-radius / 2;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 0 8px rgba($color-primary-dark, 0.25);
|
box-shadow: 0 0 8px rgba($color-primary-dark, 0.4);
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
transform: translateX($width - $switch-radius + $switch-offset);
|
transform: translateX($width - $switch-radius + $switch-offset);
|
||||||
}
|
}
|
||||||
|
@ -2,3 +2,6 @@ export * from './Header'
|
|||||||
export * from './Icon'
|
export * from './Icon'
|
||||||
export * from './Switch'
|
export * from './Switch'
|
||||||
export * from './Card'
|
export * from './Card'
|
||||||
|
export * from './Row'
|
||||||
|
export * from './Col'
|
||||||
|
export * from './ButtonSelect'
|
||||||
|
@ -1,21 +1,77 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Header, Card } from '@components'
|
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions } from '@components'
|
||||||
import { translate } from 'react-i18next'
|
import { translate } from 'react-i18next'
|
||||||
import { I18nProps } from '@i18n'
|
import { I18nProps } from '@i18n'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
class Settings extends React.Component<I18nProps, {}> {
|
class Settings extends React.Component<I18nProps, {}> {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
startAtLogin: false
|
startAtLogin: false,
|
||||||
|
language: 'en',
|
||||||
|
setAsSystemProxy: true,
|
||||||
|
allowConnectFromLan: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
languageOptions: ButtonSelectOptions[] = [
|
||||||
|
{ label: '中文', value: 'zh' },
|
||||||
|
{ label: 'English', value: 'en' }
|
||||||
|
]
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { t } = this.props
|
const { t } = this.props
|
||||||
|
const {
|
||||||
|
startAtLogin,
|
||||||
|
language,
|
||||||
|
setAsSystemProxy,
|
||||||
|
allowConnectFromLan
|
||||||
|
} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Header title={t('title')} />
|
<Header title={t('title')} />
|
||||||
<Card style={{ marginTop: 25 }}>
|
<Card className="settings-card">
|
||||||
|
<Row gutter={24} align="middle">
|
||||||
|
<Col span={6} offset={1}>
|
||||||
|
<span className="label">{t('labels.startAtLogin')}</span>
|
||||||
|
</Col>
|
||||||
|
<Col span={4} className="value-column">
|
||||||
|
<Switch
|
||||||
|
checked={startAtLogin}
|
||||||
|
onChange={startAtLogin => this.setState({ startAtLogin })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={4} offset={1}>
|
||||||
|
<span className="label">{t('labels.language')}</span>
|
||||||
|
</Col>
|
||||||
|
<Col span={7} className="value-column">
|
||||||
|
<ButtonSelect
|
||||||
|
options={this.languageOptions}
|
||||||
|
value={language}
|
||||||
|
onSelect={language => this.setState({ language })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24} align="middle">
|
||||||
|
<Col span={6} offset={1}>
|
||||||
|
<span className="label">{t('labels.setAsSystemProxy')}</span>
|
||||||
|
</Col>
|
||||||
|
<Col span={4} className="value-column">
|
||||||
|
<Switch
|
||||||
|
checked={setAsSystemProxy}
|
||||||
|
onChange={setAsSystemProxy => this.setState({ setAsSystemProxy })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={7} offset={1}>
|
||||||
|
<span className="label">{t('labels.allowConnectFromLan')}</span>
|
||||||
|
</Col>
|
||||||
|
<Col span={4} className="value-column">
|
||||||
|
<Switch
|
||||||
|
checked={allowConnectFromLan}
|
||||||
|
onChange={allowConnectFromLan => this.setState({ allowConnectFromLan })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
@import '~@styles/variables';
|
@import '~@styles/variables';
|
||||||
|
|
||||||
.proxies-list {
|
.settings-card {
|
||||||
margin: 10px 0;
|
margin-top: 25px;
|
||||||
display: flex;
|
padding: 20px 0;
|
||||||
flex-wrap: wrap;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
li {
|
.column {
|
||||||
margin: 20px 15px 20px 0;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-column {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $color-primary-darken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ $color-primary: #57befc;
|
|||||||
$color-primary-dark: #2c8af8;
|
$color-primary-dark: #2c8af8;
|
||||||
$color-primary-darken: #54759a;
|
$color-primary-darken: #54759a;
|
||||||
$color-primary-light: #7fcae4;
|
$color-primary-light: #7fcae4;
|
||||||
$color-primary-lightly: #b4ddf5;
|
$color-primary-lightly: #e4eaef;
|
||||||
|
|
||||||
// common colors
|
// common colors
|
||||||
$color-gray: #d8dee2;
|
$color-gray: #d8dee2;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user