wip(input-number): new theme

This commit is contained in:
07akioni 2021-01-05 01:29:57 +08:00
parent 45e4a7483f
commit d348f34095
8 changed files with 257 additions and 584 deletions

View File

@ -1,79 +1,47 @@
<template>
<div
class="n-input-number"
:class="{
[`n-input-number--${mergedSize}-size`]: true,
'n-input-number--disabled': disabled,
'n-input-number--invalid': invalid,
[`n-${mergedTheme}-theme`]: mergedTheme
}"
:class="[
`n-input-number--${mergedSize}-size`,
{
'n-input-number--disabled': disabled,
'n-input-number--invalid': invalid
}
]"
>
<button
tabindex="-1"
type="button"
class="n-input-number__minus-button n-input-number__button"
:class="{
[`n-input-number__button--disabled`]: !minusable
}"
@mousedown="handleMouseDown"
@click="minus"
>
<div class="n-input-number-button-boundary" />
<div class="n-input-number-button-body">
<n-icon>
<remove-icon />
</n-icon>
</div>
<div class="n-input-number-button-border-mask" />
</button>
<button
tabindex="-1"
type="button"
class="n-input-number__add-button n-input-number__button"
:class="{
[`n-input-number__button--disabled`]: !addable
}"
@mousedown="handleMouseDown"
@click="add"
>
<div class="n-input-number-button-body">
<n-icon>
<add-icon />
</n-icon>
</div>
<div class="n-input-number-button-boundary" />
<div class="n-input-number-button-border-mask" />
</button>
<input
ref="input"
class="n-input-number__input"
type="text"
<n-input
:placeholder="mergedPlaceholder"
:value="mergedValue"
:disabled="disabled ? 'disabled' : false"
:value="stringifiedValue"
:builtin-theme-overrides="inputThemeOverrides"
@focus="doFocus"
@blur="doBlur"
@keyup.enter="handleEnter"
>
<div class="n-input-number__border" />
<div class="n-input-number__border-mask" />
<template #prefix>
<n-icon :configurable="false" @mousedown.prevent>
<remove-icon @click="minus" />
</n-icon>
</template>
<template #suffix>
<n-icon :configurable="false" @mousedown.prevent>
<add-icon @click="add" />
</n-icon>
</template>
</n-input>
</div>
</template>
<script>
import { ref, toRef } from 'vue'
import { defineComponent, ref, toRef, computed } from 'vue'
import { useMergedState } from 'vooks'
import { NInput } from '../../input'
import { NIcon } from '../../icon'
import { RemoveIcon, AddIcon } from '../../_base/icons'
import {
configurable,
themeable,
useFormItem,
withCssr,
locale
} from '../../_mixins'
import { useTheme, useFormItem, useLocale } from '../../_mixins'
import { warn, call } from '../../_utils'
import styles from './styles'
// import { inputNumberLight } from '../styles'
// import style from './styles/index.cssr.js'
function parseNumber (number) {
if (number === null) return null
@ -88,15 +56,143 @@ function parseNumber (number) {
}
}
export default {
function useMethods (
props,
formItem,
{
uncontrolledValueRef,
mergedValueRef,
addableRef,
minusableRef,
mergedMaxRef,
mergedMinRef,
inputRef,
mergedStepRef
}
) {
const doUpdateValue = (value) => {
const { value: mergedValue } = mergedValueRef
if (value === mergedValue) return
const { 'onUpdate:value': onUpdateValue, onChange } = props
const { nTriggerFormInput, nTriggerFormChange } = formItem
if (onChange) call(onChange, value)
if (onUpdateValue) call(onUpdateValue, value)
uncontrolledValueRef.value = value
nTriggerFormInput()
nTriggerFormChange()
}
const doFocus = (e) => {
const { onFocus } = props
const { nTriggerFormFocus } = formItem
if (onFocus) call(onFocus, e)
nTriggerFormFocus()
}
const doBlur = (e) => {
const value = sanitizeValue(e.target.value)
e.target.value = value
doUpdateValue(value)
const { onBlur } = props
const { nTriggerFormBlur } = formItem
if (onBlur) call(onBlur, e)
nTriggerFormBlur()
}
const createValidValue = () => {
if (props.validator) return null
const { value: mergedMin } = mergedMinRef
const { value: mergedMax } = mergedMaxRef
if (mergedMin !== null) {
return Math.max(0, mergedMin)
} else if (mergedMax !== null) {
return Math.min(0, mergedMax)
} else {
return 0
}
}
const handleMouseDown = (e) => {
if (document.activeElement !== inputRef.value) {
inputRef.value.focus()
}
e.preventDefault()
}
const add = () => {
const { value: addable } = addableRef
if (!addable) return
const { value: mergedValue } = mergedValueRef
if (mergedValue === null) {
doUpdateValue(createValidValue())
} else {
const { value: mergedStep } = mergedStepRef
const valueAfterChange = sanitizeValue(mergedValue + mergedStep)
doUpdateValue(valueAfterChange)
}
}
const minus = () => {
const { value: minusable } = minusableRef
if (!minusable) return
const { value: mergedValue } = mergedValueRef
if (mergedValue === null) {
doUpdateValue(createValidValue())
} else {
const { value: mergedStep } = mergedStepRef
const valueAfterChange = sanitizeValue(mergedValue - mergedStep)
doUpdateValue(valueAfterChange)
}
}
const handleEnter = (e) => {
const value = sanitizeValue(inputRef.value.value)
inputRef.value.value = value
doUpdateValue(value)
}
const sanitizeValue = (value) => {
value = String(value).trim() || ''
if (value.trim() === '') {
value = null
} else if (Number.isNaN(Number(value))) {
value = this.mergedValue
} else {
value = Number(value)
}
if (value === null) {
return null
}
const { validator } = props
if (validator) {
if (validator(value)) {
return value
} else {
return null
}
} else {
const { value: mergedMin } = mergedMinRef
const { value: mergedMax } = mergedMaxRef
if (mergedMin !== null && value < mergedMin) {
value = mergedMin
} else if (mergedMax !== null && value > mergedMax) {
value = mergedMax
}
}
return value
}
return {
handleEnter,
handleMouseDown,
add,
minus,
doFocus,
doBlur
}
}
export default defineComponent({
name: 'InputNumber',
components: {
NIcon,
NInput,
RemoveIcon,
AddIcon
},
mixins: [configurable, themeable, locale('InputNumber'), withCssr(styles)],
props: {
...useTheme.props,
placeholder: {
type: String,
default: undefined
@ -163,180 +259,116 @@ export default {
}
},
setup (props) {
// const themeRef = useTheme('InputNumber', 'InputNumber', style, inputNumberLight, props)
const { locale } = useLocale('InputNumber')
const formItem = useFormItem(props)
// dom ref
const inputRef = ref(null)
// value
const uncontrolledValueRef = ref(props.defaultValue)
const controlledValueRef = toRef(props, 'value')
const mergedValueRef = useMergedState(
controlledValueRef,
uncontrolledValueRef
)
return {
uncontrolledValue: uncontrolledValueRef,
mergedValue: mergedValueRef,
...useFormItem(props)
}
},
computed: {
mergedPlaceholder () {
const { placeholder } = this
const mergedPlaceholderRef = computed(() => {
const { placeholder } = props
if (placeholder !== undefined) return placeholder
return this.locale.placeholder
},
mergedStep () {
const parsedNumber = parseNumber(this.step)
return locale.placeholder
})
const mergedStepRef = computed(() => {
const parsedNumber = parseNumber(props.step)
if (parsedNumber !== null) {
return parsedNumber === 0 ? 1 : Math.abs(parsedNumber)
}
return 1
},
mergedMin () {
const parsedNumber = parseNumber(this.min)
})
const mergedMinRef = computed(() => {
const parsedNumber = parseNumber(props.min)
if (parsedNumber !== null) return parsedNumber
else return null
},
mergedMax () {
const parsedNumber = parseNumber(this.max)
})
const mergedMaxRef = computed(() => {
const parsedNumber = parseNumber(props.max)
if (parsedNumber !== null) return parsedNumber
else return null
},
invalid () {
const { mergedValue } = this
})
const invalidRef = computed(() => {
const { value: mergedValue } = mergedValueRef
if (mergedValue === null) return false
const { validator } = this
const { validator } = props
if (validator && !validator(mergedValue)) return true
const { mergedMin } = this
const { value: mergedMin } = mergedMinRef
if (mergedMin !== null && mergedValue < mergedMin) return true
const { mergedMax } = this
const { value: mergedMax } = mergedMaxRef
if (mergedMax !== null && mergedValue > mergedMax) return true
return false
},
minusable () {
const { mergedValue, validator } = this
})
const minusableRef = computed(() => {
const { value: mergedValue } = mergedValueRef
const { value: mergedMin } = mergedMinRef
const { validator } = props
if (validator) {
if (mergedValue !== null) return validator(mergedValue - this.step)
if (mergedValue !== null) return validator(mergedValue - props.step)
else return false
} else {
return !(
mergedValue !== null &&
this.mergedMin !== null &&
mergedValue <= this.mergedMin
mergedMin !== null &&
mergedValue <= mergedMin
)
}
},
addable () {
const { mergedValue, validator } = this
})
const addableRef = computed(() => {
const { value: mergedValue } = mergedValueRef
const { value: mergedMax } = mergedMaxRef
const { validator } = props
if (validator) {
if (mergedValue !== null) return validator(mergedValue + this.step)
if (mergedValue !== null) return validator(mergedValue + props.step)
else return false
} else {
return !(
mergedValue !== null &&
this.mergedMax !== null &&
mergedValue >= this.mergedMax
mergedMax !== null &&
mergedValue >= mergedMax
)
}
}
},
methods: {
doUpdateValue (value) {
const { mergedValue } = this
if (value === mergedValue) return
const {
'onUpdate:value': onUpdateValue,
onChange,
nTriggerFormInput,
nTriggerFormChange
} = this
if (onChange) call(onChange, value)
if (onUpdateValue) call(onUpdateValue, value)
this.uncontrolledValue = value
nTriggerFormInput()
nTriggerFormChange()
},
doFocus (e) {
const { onFocus, nTriggerFormFocus } = this
if (onFocus) call(onFocus, e)
nTriggerFormFocus()
},
doBlur (e) {
const value = this.sanitizeValue(e.target.value)
e.target.value = value
this.doUpdateValue(value)
const { onBlur, nTriggerFormBlur } = this
if (onBlur) call(onBlur, e)
nTriggerFormBlur()
},
createValidValue () {
if (this.validator) return null
if (this.mergedMin !== null) {
return Math.max(0, this.mergedMin)
} else if (this.mergedMax !== null) {
return Math.min(0, this.mergedMax)
} else {
return 0
})
return {
uncontrolledValue: uncontrolledValueRef,
mergedValue: mergedValueRef,
mergedPlaceholder: mergedPlaceholderRef,
invalid: invalidRef,
stringifiedValue: computed(() => {
const { value } = mergedValueRef
return value === undefined || value === null ? value : String(value)
}),
mergedSize: formItem.mergedSize,
...useMethods(props, formItem, {
uncontrolledValueRef,
mergedValueRef,
addableRef,
minusableRef,
mergedMaxRef,
mergedMinRef,
inputRef,
mergedStepRef
}),
cssVars () {
// const {
// common {
// },
// self {
// }
// } = themeRef.value
// return {
// }
},
inputThemeOverrides: {
paddingLeft: '8px',
paddingRight: '8px'
}
},
handleMouseDown (e) {
if (document.activeElement !== this.$refs.input) {
this.$refs.input.focus()
}
e.preventDefault()
},
add () {
if (!this.addable) return
const { mergedValue } = this
if (mergedValue === null) {
this.doUpdateValue(this.createValidValue())
} else {
const valueAfterChange = this.sanitizeValue(
mergedValue + this.mergedStep
)
this.doUpdateValue(valueAfterChange)
}
},
minus () {
if (!this.minusable) return
const { mergedValue } = this
if (mergedValue === null) {
this.doUpdateValue(this.createValidValue())
} else {
const valueAfterChange = this.sanitizeValue(
mergedValue - this.mergedStep
)
this.doUpdateValue(valueAfterChange)
}
},
handleEnter (e) {
const value = this.sanitizeValue(this.$refs.input.value)
this.$refs.input.value = value
this.doUpdateValue(value)
},
sanitizeValue (value) {
value = String(value).trim() || ''
if (value.trim() === '') {
value = null
} else if (Number.isNaN(Number(value))) {
value = this.mergedValue
} else {
value = Number(value)
}
if (value === null) {
return null
}
if (this.validator) {
if (this.validator(value)) {
return value
} else {
return null
}
} else {
if (this.mergedMin !== null && value < this.mergedMin) {
value = this.mergedMin
} else if (this.mergedMax !== null && value > this.mergedMax) {
value = this.mergedMax
}
}
return value
}
}
}
})
</script>

View File

@ -0,0 +1,8 @@
import { cB } from '../../../_utils/cssr'
// vars:
// --bezier
export default cB('input-number', `
text-align: center;
width: 132px;
`)

View File

@ -1,9 +0,0 @@
import baseStyle from './themed-base.cssr.js'
export default [
{
key: 'mergedTheme',
watch: ['mergedTheme'],
CNode: baseStyle
}
]

View File

@ -1,360 +0,0 @@
import { cTB, c, cB, cE, cM, createKey, insideFormItem } from '../../../_utils/cssr'
export default c([
({ props }) => {
const {
$global: {
cubicBezierEaseInOut
},
$local
} = props
const {
buttonColorDisabled,
buttonTextColorDisabled,
placeholderColorDisabled,
colorDisabled,
textColorDisabled,
textColor,
border,
borderHover,
borderFocus,
buttonColor,
buttonColorHover,
buttonColorActive,
buttonTextColor,
buttonTextColorHover,
buttonTextColorPressed,
caretColor,
color: backgroundColor,
colorFocus,
boxShadowFocus,
placeholderColor,
borderRadius
} = $local
return cTB('input-number', {
raw: `
position: relative;
box-sizing: border-box;
display: inline-block;
outline: none;
z-index: auto;
`,
borderRadius
}, [
['small', 'medium', 'large'].map(size => {
const {
[createKey('height', size)]: height,
[createKey('width', size)]: width,
[createKey('fontSize', size)]: fontSize,
[createKey('buttonIconSize', size)]: buttonIconSize,
[createKey('buttonWidth', size)]: buttonWidth
} = $local
return cM(size + '-size', {
height,
width,
lineHeight: height
}, [
cE('button, input', {
height,
lineHeight: height
}),
cE('button', {
width: buttonWidth,
fontSize: buttonIconSize
}),
cE('input', {
padding: `0 ${buttonWidth}`,
fontSize
})
])
}),
cE('border', {
raw: `
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: ${border};
border-radius: ${borderRadius};
transition: border-color .3s ${cubicBezierEaseInOut};
pointer-events: none;
`
}),
cE('border-mask', {
raw: `
position: absolute;
z-index: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: ${border};
border-color: transparent;
border-radius: ${borderRadius};
transition:
box-shadow .3s ${cubicBezierEaseInOut},
border-color .3s ${cubicBezierEaseInOut};
pointer-events: none;
`
}),
cE('minus-button', {
left: 0,
borderTopLeftRadius: borderRadius,
borderBottomLeftRadius: borderRadius
}, [
cB('input-number-button-border-mask', {
borderTopLeftRadius: borderRadius,
borderBottomLeftRadius: borderRadius
}),
cB('input-number-button-body', {
left: '1px',
right: 0
}, [
cB('icon', {
transform: 'translateX(-1px)'
})
]),
cB('input-number-button-boundary', {
left: 0
})
]),
cE('add-button', {
right: 0,
borderTopRightRadius: borderRadius,
borderBottomRightRadius: borderRadius
}, [
cB('input-number-button-border-mask', {
borderTopRightRadius: borderRadius,
borderBottomRightRadius: borderRadius
}),
cB('input-number-button-body', {
right: '1px',
left: 0
}, [
cB('icon', {
transform: 'translateX(1px)'
})
]),
cB('input-number-button-boundary', {
right: 0
})
]),
cM('disabled', {
cursor: 'not-allowed'
}, [
cE('button', {
pointerEvents: 'none'
}, [
cB('input-number-button-body', {
backgroundColor: buttonColorDisabled
}),
cB('input-number-button-boundary', {
backgroundColor: buttonColorDisabled
}),
cB('icon', {
color: buttonTextColorDisabled
})
]),
cE('input', {
backgroundColor: colorDisabled,
color: textColorDisabled,
pointerEvents: 'none'
}, [
c('&::placeholder', {
color: placeholderColorDisabled
})
])
]),
cM('invalid', [
cE('input', {
textDecoration: 'line-through',
textDecorationColor: textColor
})
]),
cE('button', {
raw: `
background-color: transparent;
overflow: hidden;
outline: none;
position: absolute;
cursor: pointer;
z-index: auto;
top: 0;
padding: 0;
border: none;
`
}, [
cB('input-number-button-border-mask', {
raw: `
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`,
transition: `
border-color .3s ${cubicBezierEaseInOut},
box-shadow .3s ${cubicBezierEaseInOut}
`,
border
}),
cB('input-number-button-body', {
raw: `
position: absolute;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
transition: background-color .3s ${cubicBezierEaseInOut};
`,
backgroundColor: buttonColor
}),
cB('input-number-button-boundary', {
raw: `
width: 1px;
position: absolute;
top: 0;
bottom: 0;
transition: background-color .3s ${cubicBezierEaseInOut};
`,
backgroundColor: buttonColor
}),
cB('icon', {
transition: `color .3s ${cubicBezierEaseInOut}`,
color: buttonTextColor
}),
c('&:hover ~', [
cE('border-mask', {
border: borderHover
})
]),
c('&:hover', [
cB('input-number-button-body', {
backgroundColor: buttonColorHover
}),
cB('input-number-button-boundary', {
backgroundColor: buttonColorHover
}),
cB('icon', {
color: buttonTextColorHover
})
]),
c('&:active', [
cB('input-number-button-body', {
backgroundColor: buttonColorActive
}),
cB('input-number-button-boundary', {
backgroundColor: buttonColorActive
}),
cB('icon', {
color: buttonTextColorPressed
})
]),
cM('disabled', {
cursor: 'not-allowed'
}, [
cB('input-number-button-body', {
backgroundColor: buttonColorDisabled
}),
cB('input-number-button-boundary', {
backgroundColor: buttonColorDisabled
}),
cB('icon', {
color: buttonTextColorDisabled
})
])
]),
cE('input', {
raw: `
outline: none;
box-sizing: border-box;
border-radius: ${borderRadius};
transition:
color .3s ${cubicBezierEaseInOut},
caret-color .3s ${cubicBezierEaseInOut},
background-color .3s ${cubicBezierEaseInOut},
box-shadow .3s ${cubicBezierEaseInOut},
text-decoration-color .3s ${cubicBezierEaseInOut};
border: none;
width: 100%;
text-align: center;
`,
backgroundColor,
color: textColor,
caretColor: caretColor
}, [
c('&::placeholder', {
transition: `color .3s ${cubicBezierEaseInOut}`,
color: placeholderColor
}),
c('&:hover ~', [
cE('border-mask', {
border: borderHover
})
]),
c('&:focus', {
backgroundColor: colorFocus
}, [
c('& ~', [
cE('border-mask', {
border: borderFocus,
boxShadow: boxShadowFocus
})
])
])
])
])
},
({ props }) => ['warning', 'error'].map(status => {
const local = props.$local
const border = local[createKey('border', status)]
const borderHover = local[createKey('borderHover', status)]
const borderFocus = local[createKey('borderFocus', status)]
const boxShadowFocus = local[createKey('boxShadowFocus', status)]
const colorFocus = local[createKey('colorFocus', status)]
const caretColor = local[createKey('caretColor', status)]
const buttonTextColorHover = local[createKey('buttonTextColorHover', status)]
const buttonTextColorPressed = local[createKey('buttonTextColorPressed', status)]
return insideFormItem(
status,
cTB('input-number', [
cE('border-mask', {
border
}),
cE('input', {
caretColor: caretColor
}, [
c('&:hover ~', [
cE('border-mask', {
border: borderHover
})
]),
c('&:focus', {
backgroundColor: colorFocus
}, [
c('& ~', [
cE('border-mask', {
border: borderFocus,
boxShadow: boxShadowFocus
})
])
])
]),
cE('button', [
c('&:hover', [
cB('icon', {
color: buttonTextColorHover
})
]),
c('&:active', [
cB('icon', {
color: buttonTextColorPressed
})
])
])
])
)
})
])

View File

@ -1,14 +1,15 @@
import create from '../../_styles/utils/create-component-base'
import { changeColor } from 'seemly'
import { baseDark } from '../../_styles/base'
import { iconDark } from '../../icon/styles'
import commonVars from './_common'
import { commonDark } from '../../_styles/new-common'
export default create({
export default {
name: 'InputNumber',
theme: 'dark',
peer: [baseDark, iconDark],
getLocalVars (vars) {
common: commonDark,
peers: {
Icon: iconDark
},
self (vars) {
const {
primaryColor,
primaryColorHover,
@ -94,4 +95,4 @@ export default create({
buttonTextColorPressedError: errorColorPressed
}
}
})
}

View File

@ -1,14 +1,13 @@
import create from '../../_styles/utils/create-component-base'
import { changeColor } from 'seemly'
import { baseLight } from '../../_styles/base'
import { iconLight } from '../../icon/styles'
import commonVars from './_common'
export default create({
export default {
name: 'InputNumber',
theme: 'light',
peer: [baseLight, iconLight],
getLocalVars (vars) {
peers: {
Icon: iconLight
},
self (vars) {
const {
primaryColor,
primaryColorHover,
@ -94,4 +93,4 @@ export default create({
buttonTextColorPressedError: errorColorPressed
}
}
})
}

View File

@ -47,7 +47,7 @@ export { baseDark, baseLight } from './_styles/base'
// export { gridDark, gridLight } from './grid/styles'
// export { iconDark, iconLight } from './icon/styles'
// export { inputDark, inputLight } from './input/styles'
export { inputNumberDark, inputNumberLight } from './input-number/styles'
// export { inputNumberDark, inputNumberLight } from './input-number/styles'
export { layoutDark, layoutLight } from './layout/styles'
export { listDark, listLight } from './list/styles'
export { loadingBarDark, loadingBarLight } from './loading-bar/styles'

View File

@ -29,6 +29,7 @@ import { gradientTextDark } from './gradient-text/styles'
import { gridDark } from './grid/styles'
import { iconDark } from './icon/styles'
import { inputDark } from './input/styles'
import { inputNumberDark } from './input-number/styles'
export const darkTheme = {
common: commonDark,
@ -61,5 +62,6 @@ export const darkTheme = {
GradientText: gradientTextDark,
Grid: gridDark,
Icon: iconDark,
Input: inputDark
Input: inputDark,
InputNumber: inputNumberDark
}