chore: init read-star monorepo
This commit is contained in:
180
packages/color/src/palette/antd.ts
Normal file
180
packages/color/src/palette/antd.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import type { AnyColor, HsvColor } from 'colord'
|
||||
import type { ColorIndex } from '../types'
|
||||
import { getHex, getHsv, isValidColor, mixColor } from '../shared'
|
||||
|
||||
/** Hue step */
|
||||
const hueStep = 2
|
||||
/** Saturation step, light color part */
|
||||
const saturationStep = 16
|
||||
/** Saturation step, dark color part */
|
||||
const saturationStep2 = 5
|
||||
/** Brightness step, light color part */
|
||||
const brightnessStep1 = 5
|
||||
/** Brightness step, dark color part */
|
||||
const brightnessStep2 = 15
|
||||
/** Light color count, main color up */
|
||||
const lightColorCount = 5
|
||||
/** Dark color count, main color down */
|
||||
const darkColorCount = 4
|
||||
|
||||
/**
|
||||
* Get AntD palette color by index
|
||||
*
|
||||
* @param color - Color
|
||||
* @param index - The color index of color palette (the main color index is 6)
|
||||
* @returns Hex color
|
||||
*/
|
||||
export function getAntDPaletteColorByIndex(color: AnyColor, index: ColorIndex): string {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('invalid input color value')
|
||||
}
|
||||
|
||||
if (index === 6) {
|
||||
return getHex(color)
|
||||
}
|
||||
|
||||
const isLight = index < 6
|
||||
const hsv = getHsv(color)
|
||||
const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1
|
||||
|
||||
const newHsv: HsvColor = {
|
||||
h: getHue(hsv, i, isLight),
|
||||
s: getSaturation(hsv, i, isLight),
|
||||
v: getValue(hsv, i, isLight),
|
||||
}
|
||||
|
||||
return getHex(newHsv)
|
||||
}
|
||||
|
||||
/** Map of dark color index and opacity */
|
||||
const darkColorMap = [
|
||||
{ index: 7, opacity: 0.15 },
|
||||
{ index: 6, opacity: 0.25 },
|
||||
{ index: 5, opacity: 0.3 },
|
||||
{ index: 5, opacity: 0.45 },
|
||||
{ index: 5, opacity: 0.65 },
|
||||
{ index: 5, opacity: 0.85 },
|
||||
{ index: 5, opacity: 0.9 },
|
||||
{ index: 4, opacity: 0.93 },
|
||||
{ index: 3, opacity: 0.95 },
|
||||
{ index: 2, opacity: 0.97 },
|
||||
{ index: 1, opacity: 0.98 },
|
||||
]
|
||||
|
||||
/**
|
||||
* Get AntD color palette
|
||||
*
|
||||
* @param color - Color
|
||||
* @param darkTheme - Dark theme
|
||||
* @param darkThemeMixColor - Dark theme mix color (default: #141414)
|
||||
*/
|
||||
export function getAntDColorPalette(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
|
||||
const patterns = indexes.map(index => getAntDPaletteColorByIndex(color, index))
|
||||
|
||||
if (darkTheme) {
|
||||
const darkPatterns = darkColorMap.map(({ index, opacity }) => {
|
||||
const darkColor = mixColor(darkThemeMixColor, patterns[index], opacity)
|
||||
|
||||
return darkColor
|
||||
})
|
||||
|
||||
return darkPatterns.map(item => getHex(item))
|
||||
}
|
||||
|
||||
return patterns
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hue
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getHue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let hue: number
|
||||
|
||||
const hsvH = Math.round(hsv.h)
|
||||
|
||||
if (hsvH >= 60 && hsvH <= 240) {
|
||||
hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i
|
||||
}
|
||||
else {
|
||||
hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i
|
||||
}
|
||||
|
||||
if (hue < 0) {
|
||||
hue += 360
|
||||
}
|
||||
|
||||
if (hue >= 360) {
|
||||
hue -= 360
|
||||
}
|
||||
|
||||
return hue
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saturation
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getSaturation(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
if (hsv.h === 0 && hsv.s === 0) {
|
||||
return hsv.s
|
||||
}
|
||||
|
||||
let saturation: number
|
||||
|
||||
if (isLight) {
|
||||
saturation = hsv.s - saturationStep * i
|
||||
}
|
||||
else if (i === darkColorCount) {
|
||||
saturation = hsv.s + saturationStep
|
||||
}
|
||||
else {
|
||||
saturation = hsv.s + saturationStep2 * i
|
||||
}
|
||||
|
||||
if (saturation > 100) {
|
||||
saturation = 100
|
||||
}
|
||||
|
||||
if (isLight && i === lightColorCount && saturation > 10) {
|
||||
saturation = 10
|
||||
}
|
||||
|
||||
if (saturation < 6) {
|
||||
saturation = 6
|
||||
}
|
||||
|
||||
return saturation
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of hsv
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getValue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let value: number
|
||||
|
||||
if (isLight) {
|
||||
value = hsv.v + brightnessStep1 * i
|
||||
}
|
||||
else {
|
||||
value = hsv.v - brightnessStep2 * i
|
||||
}
|
||||
|
||||
if (value > 100) {
|
||||
value = 100
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
46
packages/color/src/palette/index.ts
Normal file
46
packages/color/src/palette/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { AnyColor } from 'colord'
|
||||
import type { ColorPaletteNumber } from '../types'
|
||||
import { getHex } from '../shared'
|
||||
import { getAntDColorPalette } from './antd'
|
||||
import { getRecommendedColorPalette } from './recommend'
|
||||
|
||||
/**
|
||||
* get color palette by provided color
|
||||
*
|
||||
* @param color
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, recommended = false) {
|
||||
const colorMap = new Map<ColorPaletteNumber, string>()
|
||||
|
||||
if (recommended) {
|
||||
const colorPalette = getRecommendedColorPalette(getHex(color))
|
||||
colorPalette.palettes.forEach((palette) => {
|
||||
colorMap.set(palette.number, palette.hex)
|
||||
})
|
||||
}
|
||||
else {
|
||||
const colors = getAntDColorPalette(color)
|
||||
|
||||
const colorNumbers: ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
|
||||
|
||||
colorNumbers.forEach((number, index) => {
|
||||
colorMap.set(number, colors[index])
|
||||
})
|
||||
}
|
||||
|
||||
return colorMap
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette color by number
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getPaletteColorByNumber(color: AnyColor, number: ColorPaletteNumber, recommended = false) {
|
||||
const colorMap = getColorPalette(color, recommended)
|
||||
|
||||
return colorMap.get(number as ColorPaletteNumber)!
|
||||
}
|
||||
152
packages/color/src/palette/recommend.ts
Normal file
152
packages/color/src/palette/recommend.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import type {
|
||||
ColorPalette,
|
||||
ColorPaletteFamily,
|
||||
ColorPaletteFamilyWithNearestPalette,
|
||||
ColorPaletteMatch,
|
||||
ColorPaletteNumber,
|
||||
} from '../types'
|
||||
import { colorPalettes } from '../constant'
|
||||
import { getColorName, getDeltaE, getHsl, isValidColor, transformHslToHex } from '../shared'
|
||||
|
||||
/**
|
||||
* get recommended color palette by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPalette(color: string) {
|
||||
const colorPaletteFamily = getRecommendedColorPaletteFamily(color)
|
||||
|
||||
const colorMap = new Map<ColorPaletteNumber, ColorPalette>()
|
||||
|
||||
colorPaletteFamily.palettes.forEach((palette) => {
|
||||
colorMap.set(palette.number, palette)
|
||||
})
|
||||
|
||||
const mainColor = colorMap.get(500)!
|
||||
const matchColor = colorPaletteFamily.palettes.find(palette => palette.hex === color)!
|
||||
|
||||
const colorPalette: ColorPaletteMatch = {
|
||||
...colorPaletteFamily,
|
||||
colorMap,
|
||||
main: mainColor,
|
||||
match: matchColor,
|
||||
}
|
||||
|
||||
return colorPalette
|
||||
}
|
||||
|
||||
/**
|
||||
* get recommended palette color by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
*/
|
||||
export function getRecommendedPaletteColorByNumber(color: string, number: ColorPaletteNumber) {
|
||||
const colorPalette = getRecommendedColorPalette(color)
|
||||
|
||||
const { hex } = colorPalette.colorMap.get(number)!
|
||||
|
||||
return hex
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette family by provided color and color name
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPaletteFamily(color: string) {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('Invalid color, please check color value!')
|
||||
}
|
||||
|
||||
let colorName = getColorName(color)
|
||||
|
||||
colorName = colorName.toLowerCase().replace(/\s/g, '-')
|
||||
|
||||
const { h: h1, s: s1 } = getHsl(color)
|
||||
|
||||
const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(color, colorPalettes)
|
||||
|
||||
const { number, hex } = nearestLightnessPalette
|
||||
|
||||
const { h: h2, s: s2 } = getHsl(hex)
|
||||
|
||||
const deltaH = h1 - h2
|
||||
|
||||
const sRatio = s1 / s2
|
||||
|
||||
const colorPaletteFamily: ColorPaletteFamily = {
|
||||
name: colorName,
|
||||
palettes: palettes.map((palette) => {
|
||||
let hexValue = color
|
||||
|
||||
const isSame = number === palette.number
|
||||
|
||||
if (!isSame) {
|
||||
const { h: h3, s: s3, l } = getHsl(palette.hex)
|
||||
|
||||
const newH = deltaH < 0 ? h3 + deltaH : h3 - deltaH
|
||||
const newS = s3 * sRatio
|
||||
|
||||
hexValue = transformHslToHex({
|
||||
h: newH,
|
||||
s: newS,
|
||||
l,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
hex: hexValue,
|
||||
number: palette.number,
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
return colorPaletteFamily
|
||||
}
|
||||
|
||||
/**
|
||||
* get nearest color palette family
|
||||
*
|
||||
* @param color color
|
||||
* @param families color palette families
|
||||
*/
|
||||
function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
|
||||
const familyWithConfig = families.map((family) => {
|
||||
const palettes = family.palettes.map((palette) => {
|
||||
return {
|
||||
...palette,
|
||||
delta: getDeltaE(color, palette.hex),
|
||||
}
|
||||
})
|
||||
|
||||
const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr))
|
||||
|
||||
return {
|
||||
...family,
|
||||
palettes,
|
||||
nearestPalette,
|
||||
}
|
||||
})
|
||||
|
||||
const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
|
||||
prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr,
|
||||
)
|
||||
|
||||
const { l } = getHsl(color)
|
||||
|
||||
const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
|
||||
...nearestPaletteFamily,
|
||||
nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
|
||||
const { l: prevLightness } = getHsl(prev.hex)
|
||||
const { l: currLightness } = getHsl(curr.hex)
|
||||
|
||||
const deltaPrev = Math.abs(prevLightness - l)
|
||||
const deltaCurr = Math.abs(currLightness - l)
|
||||
|
||||
return deltaPrev < deltaCurr ? prev : curr
|
||||
}),
|
||||
}
|
||||
|
||||
return paletteFamily
|
||||
}
|
||||
Reference in New Issue
Block a user