feat(color-picker): make it work

This commit is contained in:
07akioni 2021-04-13 12:44:53 +08:00
parent c236653f01
commit 049b00448d
13 changed files with 291 additions and 35 deletions

View File

@ -114,7 +114,7 @@
"evtd": "^0.2.0",
"highlight.js": "^10.7.1",
"lodash-es": "^4.17.21",
"seemly": "^0.1.28",
"seemly": "^0.1.29",
"treemate": "^0.2.4",
"vdirs": "^0.1.2",
"vfonts": "^0.1.0",

View File

@ -21,7 +21,8 @@ export default defineComponent({
onUpdateAlpha: {
type: Function as PropType<(value: number) => void>,
required: true
}
},
onComplete: Function as PropType<() => void>
},
setup (props) {
const railRef = ref<HTMLElement | null>(null)
@ -41,6 +42,7 @@ export default defineComponent({
function handleMouseUp (): void {
off('mousemove', document, handleMouseMove)
off('mouseup', document, handleMouseUp)
props.onComplete?.()
}
return {
railRef,

View File

@ -6,7 +6,10 @@ import {
PropType,
toRef,
watchEffect,
VNode
VNode,
withDirectives,
Transition,
CSSProperties
} from 'vue'
import {
hsv2rgb,
@ -31,18 +34,41 @@ import Pallete from './Pallete'
import type { PalleteInst } from './Pallete'
import ColorInput from './ColorInput'
import style from './styles/index.cssr'
import { useStyle } from '../../_mixins'
import { useMergedState } from 'vooks'
import { call, MaybeArray } from '../../_utils'
import type { ThemeProps } from '../../_mixins'
import { useConfig, useTheme } from '../../_mixins'
import { useIsMounted, useMergedState } from 'vooks'
import { call, MaybeArray, useAdjustedTo } from '../../_utils'
import { getModeFromValue } from './utils'
import type { ColorPickerMode } from './utils'
import { VBinder, VFollower, VTarget } from 'vueuc'
import ColorPickerTrigger from './ColorPickerTrigger'
import { clickoutside } from 'vdirs'
import { colorPickerLight } from '../styles'
import type { ColorPickerTheme } from '../styles'
export const colorPickerPanelProps = {
...(useTheme.props as ThemeProps<ColorPickerTheme>),
value: String,
show: {
type: Boolean,
default: undefined
},
defaultShow: {
type: Boolean,
default: false
},
defaultValue: {
type: String as PropType<string | null>,
default: null
},
to: useAdjustedTo.propTo,
onComplete: Function as PropType<(value: string) => void>,
'onUpdate:show': [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,
onUpdateShow: [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: string) => void>
>,
@ -56,9 +82,28 @@ export default defineComponent({
props: colorPickerPanelProps,
setup (props) {
const palleteInstRef = ref<PalleteInst | null>(null)
const selfRef = ref<HTMLElement | null>(null)
let upcomingValue: string | null = null
useStyle('ColorPicker', style)
const themeRef = useTheme(
'ColorPicker',
'ColorPicker',
style,
colorPickerLight,
props
)
const uncontrolledShowRef = ref(props.defaultShow)
const mergedShowRef = useMergedState(
toRef(props, 'show'),
uncontrolledShowRef
)
function doUpdateShow (value: boolean): void {
const { onUpdateShow, 'onUpdate:show': _onUpdateShow } = props
if (onUpdateShow) call(onUpdateShow, value)
if (_onUpdateShow) call(_onUpdateShow, value)
uncontrolledShowRef.value = value
}
const uncontrolledValueRef = ref(props.defaultValue)
const mergedValueRef = useMergedState(
toRef(props, 'value'),
@ -242,6 +287,15 @@ export default defineComponent({
function handleInputUpdateValue (value: string): void {
doUpdateValue(value, 'input')
handleComplete()
}
function handleComplete (): void {
const { value } = mergedValueRef
// no value & only hue changes will complete with no value
if (value) {
props.onComplete?.(value)
}
}
watchEffect(() => {
@ -258,6 +312,22 @@ export default defineComponent({
upcomingValue = null
})
const cssVarsRef = computed(() => {
const {
common: { cubicBezierEaseInOut },
self: { textColor, color, fontSize, boxShadow, border, borderRadius }
} = themeRef.value
return {
'--bezier': cubicBezierEaseInOut,
'--text-color': textColor,
'--color': color,
'--font-size': fontSize,
'--box-shadow': boxShadow,
'--border': border,
'--border-radius': borderRadius
}
})
function renderPanel (): VNode {
const { value: rgba } = rgbaRef
const { value: displayedMode } = displayedModeRef
@ -265,26 +335,29 @@ export default defineComponent({
return (
<div
class="n-color-picker-panel"
style={{
width: '240px',
border: '1px solid rgba(0, 0, 0, .08)'
}}
onDragstart={(e) => {
e.preventDefault()
}}
style={cssVarsRef.value as CSSProperties}
>
<Pallete
ref={palleteInstRef}
rgba={rgba}
displayedHue={displayedHue}
onUpdateSV={handleUpdateSv}
onComplete={handleComplete}
/>
<div class="n-color-picker-control">
<HueSlider hue={displayedHue} onUpdateHue={handleUpdateHue} />
<HueSlider
hue={displayedHue}
onUpdateHue={handleUpdateHue}
onComplete={handleComplete}
/>
<AlphaSlider
rgba={rgba}
alpha={displayedAlphaRef.value}
onUpdateAlpha={handleUpdateAlpha}
onComplete={handleComplete}
/>
<ColorInput
mode={displayedMode}
@ -298,11 +371,72 @@ export default defineComponent({
}
return {
...useConfig(props),
selfRef,
hsla: hslaRef,
rgba: rgbaRef,
renderPanel
mergedShow: mergedShowRef,
isMounted: useIsMounted(),
adjustedTo: useAdjustedTo(props),
mergedValue: mergedValueRef,
handleTriggerClick () {
doUpdateShow(true)
},
handleClickOutside (e: MouseEvent) {
if (selfRef.value?.contains(e.target as Node)) return
doUpdateShow(false)
},
renderPanel,
cssVars: cssVarsRef
}
},
render () {
return this.renderPanel()
return (
<div class="n-color-picker" ref="selfRef">
<VBinder>
{{
default: () => [
<VTarget>
{{
default: () => (
<ColorPickerTrigger
value={this.mergedValue}
style={this.cssVars as CSSProperties}
hsla={this.hsla}
onClick={this.handleTriggerClick}
/>
)
}}
</VTarget>,
<VFollower
placement="bottom-start"
show={this.mergedShow}
containerClass={this.namespace}
teleportDisabled={this.adjustedTo === useAdjustedTo.tdkey}
to={this.adjustedTo}
>
{{
default: () => (
<Transition
name="n-fade-in-scale-up-transition"
appear={this.isMounted}
>
{{
default: () =>
this.mergedShow
? withDirectives(this.renderPanel(), [
[clickoutside, this.handleClickOutside]
])
: null
}}
</Transition>
)
}}
</VFollower>
]
}}
</VBinder>
</div>
)
}
})

View File

@ -1,25 +1,40 @@
import { RGBA, toRgbaString } from 'seemly'
import { HSLA, toHslaString } from 'seemly'
import { defineComponent, PropType, h } from 'vue'
export default defineComponent({
name: 'ColorPickerTrigger',
props: {
rgba: {
type: (Array as unknown) as PropType<RGBA | null>,
value: {
type: String as PropType<string | null>,
default: null
},
hsla: {
type: (Array as unknown) as PropType<HSLA | null>,
default: null
},
onClick: Function as PropType<() => void>
},
render () {
const { rgba } = this
const { hsla, value } = this
return (
<div class="n-color-picker-trigger">
<div class="n-color-picker-trigger" onClick={this.onClick}>
<div
class="n-color-picker-trigger__fill"
style={{
color: rgba ? toRgbaString(rgba) : ''
backgroundColor: hsla ? toHslaString(hsla) : ''
}}
/>
>
{value && hsla ? (
<div
class="n-color-picker-trigger__value"
style={{
color: hsla[2] > 50 ? 'black' : 'white'
}}
>
{value}
</div>
) : null}
</div>
</div>
)
}

View File

@ -20,7 +20,8 @@ export default defineComponent({
onUpdateHue: {
type: Function as PropType<(value: number) => void>,
required: true
}
},
onComplete: Function as PropType<() => void>
},
setup (props) {
const railRef = ref<HTMLElement | null>(null)
@ -42,6 +43,7 @@ export default defineComponent({
function handleMouseUp (): void {
off('mousemove', document, handleMouseMove)
off('mouseup', document, handleMouseUp)
props.onComplete?.()
}
return {
railRef,

View File

@ -24,7 +24,8 @@ export default defineComponent({
onUpdateSV: {
type: Function as PropType<(s: number, v: number) => void>,
required: true
}
},
onComplete: Function as PropType<() => void>
},
setup (props) {
const palleteRef = ref<HTMLElement | null>(null)
@ -54,6 +55,7 @@ export default defineComponent({
function handleMouseUp (): void {
off('mousemove', document, handleMouseMove)
off('mouseup', document, handleMouseUp)
props.onComplete?.()
}
return {
palleteRef,

View File

@ -1,16 +1,38 @@
import { c, cB, cE, cM } from '../../../_utils/cssr'
import fadeInScaleUpTransition from '../../../_styles/transitions/fade-in-scale-up.cssr'
// vars:
// --color
// --text-color
// --border-radius
// --font-size
export default c([
cB('color-picker', `
display: inline-block;
box-sizing: border-box;
height: 34px;
width: 100%;
position: relative;
`),
cB('color-picker-panel', `
border-radius: 4px;
margin: 4px 0;
width: 240px;
font-size: var(--font-size);
color: var(--text-color);
background-color: var(--color);
transition:
box-shadow .3s var(--bezier),
color .3s var(--bezier),
background-color .3s var(--bezier);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 12px;
`, [
fadeInScaleUpTransition(),
cB('input', `
text-align: center;
`)
]),
cB('color-picker-control', `
`),
cB('color-picker-slider', `
margin-bottom: 8px;
position: relative;
@ -19,7 +41,7 @@ export default c([
`, [
cE('grid', `
height: 6px;
background-image: linear-gradient(to right,#eee 6px,transparent 6px);
background-image: linear-gradient(to right, rgba(0, 0, 0, .12) 6px,transparent 6px);
background-size: 12px 6px;
background-repeat: repeat;
position: relative;
@ -73,18 +95,24 @@ export default c([
`)
]),
cB('color-picker-trigger', `
display: inline-block;
height: 34px;
border: var(--border);
height: 100%;
box-sizing: border-box;
width: 100%;
position: relative;
border-radius: var(--border-radius);
transition: border-color .3s var(--bezier);
cursor: pointer;
`, [
cE('value', `
transition: color .3s var(--bezier);
`),
cE('fill', `
text-align: center;
border-radius: var(--border-radius);
position: absolute;
left: 4px;
right: 4px;
top: 4px;
bottom: 4px;
left: 6px;
right: 6px;
top: 6px;
bottom: 6px;
`)
])
])

View File

@ -0,0 +1,27 @@
import { commonDark } from '../../_styles/common'
import type { ColorPickerTheme } from './light'
const colorPickerDark: ColorPickerTheme = {
name: 'ColorPicker',
common: commonDark,
self (vars) {
const {
fontSize,
boxShadow2,
popoverColor,
textColor2,
borderRadius,
borderColor
} = vars
return {
fontSize,
boxShadow: boxShadow2,
color: popoverColor,
textColor: textColor2,
borderRadius,
border: `1px solid ${borderColor}`
}
}
}
export default colorPickerDark

View File

@ -0,0 +1,3 @@
export { default as colorPickerDark } from './dark'
export { default as colorPickerLight } from './light'
export type { ColorPickerThemeVars, ColorPickerTheme } from './light'

View File

@ -0,0 +1,37 @@
import { commonLight } from '../../_styles/common'
import type { ThemeCommonVars } from '../../_styles/common'
import { createTheme } from '../../_mixins/use-theme'
import { inputLight } from '../../input/styles'
const self = (vars: ThemeCommonVars) => {
const {
fontSize,
boxShadow2,
popoverColor,
textColor2,
borderRadius,
borderColor
} = vars
return {
fontSize,
boxShadow: boxShadow2,
color: popoverColor,
textColor: textColor2,
borderRadius,
border: `1px solid ${borderColor}`
}
}
export type ColorPickerThemeVars = ReturnType<typeof self>
const colorPickerLight = createTheme({
name: 'ColorPicker',
common: commonLight,
peers: {
Input: inputLight
},
self
})
export default colorPickerLight
export type ColorPickerTheme = typeof colorPickerLight

View File

@ -13,6 +13,7 @@ import type { CascaderTheme } from '../../cascader/styles'
import type { CheckboxTheme } from '../../checkbox/styles'
import type { CodeTheme } from '../../code/styles'
import type { CollapseTheme } from '../../collapse/styles'
import type { ColorPickerTheme } from '../../color-picker/styles'
import type { DataTableTheme } from '../../data-table/styles'
import type { DatePickerTheme } from '../../date-picker/styles'
import type { DescriptionsTheme } from '../../descriptions/styles'
@ -96,6 +97,7 @@ export interface GlobalThemeWithoutCommon {
Checkbox?: CheckboxTheme
Code?: CodeTheme
Collapse?: CollapseTheme
ColorPicker?: ColorPickerTheme
DataTable?: DataTableTheme
DatePicker?: DatePickerTheme
Descriptions?: DescriptionsTheme

View File

@ -13,6 +13,7 @@ import { cascaderDark } from '../cascader/styles'
import { checkboxDark } from '../checkbox/styles'
import { codeDark } from '../code/styles'
import { collapseDark } from '../collapse/styles'
import { colorPickerDark } from '../color-picker/styles'
import { dataTableDark } from '../data-table/styles'
import { datePickerDark } from '../date-picker/styles'
import { descriptionsDark } from '../descriptions/styles'
@ -87,6 +88,7 @@ export const darkTheme: BuiltInGlobalTheme = {
Checkbox: checkboxDark,
Code: codeDark,
Collapse: collapseDark,
ColorPicker: colorPickerDark,
DataTable: dataTableDark,
DatePicker: datePickerDark,
Descriptions: descriptionsDark,

View File

@ -15,6 +15,7 @@ import { cascaderLight } from '../cascader/styles'
import { checkboxLight } from '../checkbox/styles'
import { codeLight } from '../code/styles'
import { collapseLight } from '../collapse/styles'
import { colorPickerLight } from '../color-picker/styles'
import { dataTableLight } from '../data-table/styles'
import { datePickerLight } from '../date-picker/styles'
import { descriptionsLight } from '../descriptions/styles'
@ -89,6 +90,7 @@ export const lightTheme: BuiltInGlobalTheme = {
Checkbox: checkboxLight,
Code: codeLight,
Collapse: collapseLight,
ColorPicker: colorPickerLight,
DataTable: dataTableLight,
DatePicker: datePickerLight,
Descriptions: descriptionsLight,