Skip to content

Commit

Permalink
[Feat] add color picker to single color selector (#2699)
Browse files Browse the repository at this point in the history
* [Feat] add color picker to single color selector

Signed-off-by: Ihor Dykhta <[email protected]>
  • Loading branch information
igorDykhta authored Oct 22, 2024
1 parent b258e8a commit 2d8161e
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"moment-timezone": "^0.5.35",
"prop-types": "^15.6.0",
"react": "^18.2.0",
"react-color": "^2.17.3",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.2",
"react-date-picker": "^10.2.0",
"react-dom": "^18.2.0",
Expand Down
77 changes: 77 additions & 0 deletions src/components/src/side-panel/layer-panel/color-palette-preset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import React from 'react';
import styled from 'styled-components';
import {range} from 'd3-array';

import {ColorsByTheme} from '@kepler.gl/constants';
import {HexColor} from '@kepler.gl/types';

const PALETTE_HEIGHT = '8px';
const ROWS = 22;

const StyledColorPalette = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 12px;
:hover {
cursor: pointer;
}
`;

const StyledColorColumn = styled.div`
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-between;
`;

type StyledColorBlockProps = {
selected?: boolean;
};

const StyledColorBlock = styled.div<StyledColorBlockProps>`
flex-grow: 1;
height: ${PALETTE_HEIGHT};
border-width: 1px;
border-style: solid;
`;

export type PresetColorPaletteProps = {
themes: string[];
selectedColor: HexColor;
onSelectColor: (c: HexColor, e?: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
};

const PresetColorPalette: React.FC<PresetColorPaletteProps> = ({
themes,
onSelectColor,
selectedColor
}: PresetColorPaletteProps) => (
<StyledColorPalette>
{themes.map(theme => (
<StyledColorColumn key={theme} className="single-color-palette__column">
{range(1, ROWS + 1, 1).map((key, i) => (

Check warning on line 57 in src/components/src/side-panel/layer-panel/color-palette-preset.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

'i' is defined but never used
<StyledColorBlock
className="single-color-palette__block"
style={{
backgroundColor: ColorsByTheme[theme][key],
borderColor:
selectedColor === ColorsByTheme[theme][key].toUpperCase()
? 'white'
: ColorsByTheme[theme][key]
}}
key={`${theme}_${key}`}
selected={selectedColor === ColorsByTheme[theme][key].toUpperCase()}
onClick={e => onSelectColor(ColorsByTheme[theme][key], e)}
/>
))}
</StyledColorColumn>
))}
</StyledColorPalette>
);

export default PresetColorPalette;
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function ColorSelectorFactory(RangeSlider): ComponentType<ColorSelectorProps> {

const onSelectColor = useCallback(
(color: RGBColor | ColorRange, e: MouseEvent) => {
e.stopPropagation();
if (e) e.stopPropagation();
const colorSet = typeof editing === 'number' && colorSets[editing];
if (colorSet) {
onSetColor(colorSet, color, colorSet.selectedColor[3]);
Expand Down
46 changes: 38 additions & 8 deletions src/components/src/side-panel/layer-panel/custom-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
import React, {useMemo} from 'react';
import styled, {withTheme} from 'styled-components';
import {SketchPicker, ColorChangeHandler} from 'react-color';

import {HexColor} from '@kepler.gl/types';

import useOnClickOutside from '../../hooks/use-on-click-outside';

// This was put in because 3rd party library react-color doesn't yet cater for customized color of child component <SketchField> which contains HEX/RGB input text box
// Issue raised: https://github.com/casesandberg/react-color/issues/631

type CustomPickerProps = {
color: string;
theme: {
panelBackground: string;
};
onChange: ColorChangeHandler;
onSwatchClose: () => void;
type StyledPickerProps = {
type?: string;
active?: boolean;
};

const StyledPicker = styled.div`
const StyledPicker = styled.div<StyledPickerProps>`
.sketch-picker {
span {
color: ${props => props.theme.labelColor} !important;
Expand All @@ -31,15 +30,46 @@ const StyledPicker = styled.div`
border-color: ${props => props.theme.secondaryInputBgd} !important;
box-shadow: none !important;
background-color: ${props => props.theme.inputBgdHover} !important;
:hover {
cursor: ${props => (props.type === 'number' || props.type === 'text' ? 'text' : 'pointer')};
background-color: ${props =>
props.active ? props.theme.inputBgdActive : props.theme.inputBgdHover};
border-color: ${props =>
props.active ? props.theme.inputBorderActiveColor : props.theme.inputBorderHoverColor};
}
:active,
:focus,
&.focus,
&.active {
outline: 0;
background-color: ${props => props.theme.inputBgdActive};
border-color: ${props => props.theme.inputBorderActiveColor};
box-shadow: ${props => props.theme.inputBoxShadowActive};
}
}
label {
color: ${props => props.theme.subtextColor} !important;
}
}
`;

const PRESET_COLORS = [];

type CustomPickerProps = {
color: HexColor;
theme: {
panelBackground: string;
};
onChange: ColorChangeHandler;
onSwatchClose: () => void;
};

const CustomPicker: React.FC<CustomPickerProps> = props => {
const {color, onChange, theme} = props;
const ref = useOnClickOutside<HTMLDivElement>(props.onSwatchClose);

const pickerStyle = useMemo(
() => ({
picker: {
Expand Down
127 changes: 76 additions & 51 deletions src/components/src/side-panel/layer-panel/single-color-palette.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,95 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import React, {MouseEvent} from 'react';
import {range} from 'd3-array';
import React, {MouseEvent, useCallback, useState} from 'react';
import styled from 'styled-components';
import classnames from 'classnames';

import {hexToRgb} from '@kepler.gl/utils';
import {FormattedMessage} from '@kepler.gl/localization';
import {Themes, ColorRange} from '@kepler.gl/constants';
import {RGBColor, HexColor} from '@kepler.gl/types';

import CustomPicker from './custom-picker';
import PresetColorPalette from './color-palette-preset';

import {ColorsByTheme, Themes, ColorRange} from '@kepler.gl/constants';
import {RGBColor} from '@kepler.gl/types';
const MODE = {
preset: 'preset',
picker: 'picker'
};

export type SingleColorPaletteProps = {
selectedColor: HexColor;
onSelectColor: (color: RGBColor | ColorRange, e: MouseEvent) => void;
// hex value
selectedColor: string;
};

const PALETTE_HEIGHT = '8px';
const ROWS = 22;

const StyledColorPalette = styled.div`
const StyledColorPickerTop = styled.div`
border-bottom: 1px solid ${({theme}) => theme.dropdownListBorderTop};
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 12px;
:hover {
cursor: pointer;
padding-top: 2px 4px 0 4px;
.color-palette-tab {
padding: 8px 0;
margin: 0 8px;
color: ${({theme}) => theme.subtextColor};
border-bottom: 2px;
border-bottom-style: solid;
border-bottom-color: transparent;
&.active {
color: ${({theme}) => theme.textColorHl};
border-bottom-color: ${({theme}) => theme.panelToggleBorderColor};
}
:hover {
cursor: pointer;
color: ${props => props.theme.textColorHl};
}
}
`;

const StyledColorColumn = styled.div`
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: space-between;
`;

const StyledColorBlock = styled.div<{selected: boolean}>`
flex-grow: 1;
height: ${PALETTE_HEIGHT};
border-width: 1px;
border-style: solid;
`;

const SingleColorPalette: React.FC<SingleColorPaletteProps> = ({selectedColor, onSelectColor}) => (
<StyledColorPalette className="single-color-palette">
{Themes.map(theme => (
<StyledColorColumn key={theme} className="single-color-palette__column">
{range(1, ROWS + 1, 1).map(key => (
<StyledColorBlock
className="single-color-palette__block"
style={{
backgroundColor: ColorsByTheme[theme][key],
borderColor:
selectedColor === ColorsByTheme[theme][key].toUpperCase()
? 'white'
: ColorsByTheme[theme][key]
}}
key={`${theme}_${key}`}
selected={selectedColor === ColorsByTheme[theme][key].toUpperCase()}
onClick={e => onSelectColor(hexToRgb(ColorsByTheme[theme][key]), e)}
/>
))}
</StyledColorColumn>
const ColorPickerTop = ({setMode, mode}) => (
<StyledColorPickerTop>
{Object.keys(MODE).map(modeId => (
<div
onClick={() => setMode(modeId)}
key={modeId}
className={classnames('color-palette-tab', {active: mode === modeId})}
>
<FormattedMessage id={`color.${modeId}`} />
</div>
))}
</StyledColorPalette>
</StyledColorPickerTop>
);

// eslint-disable-next-line @typescript-eslint/no-empty-function
const nop = () => {};

const SingleColorPalette: React.FC<SingleColorPaletteProps> = ({
selectedColor,
onSelectColor
}: SingleColorPaletteProps) => {
const [mode, setMode] = useState(MODE.preset);
const onSetColor = useCallback(
(color, e) => {
// color picker return an object, with color.hex
const hex = color.hex || color;
onSelectColor(hexToRgb(hex), e);
},
[onSelectColor]
);
return (
<div className="single-color-palette">
<ColorPickerTop mode={mode} setMode={setMode} />
{mode === MODE.preset ? (
<PresetColorPalette
themes={Themes}
onSelectColor={onSetColor}
selectedColor={selectedColor}
/>
) : null}
{mode === MODE.picker ? (
<CustomPicker color={selectedColor} onChange={onSetColor} onSwatchClose={nop} />
) : null}
</div>
);
};

export default SingleColorPalette;
4 changes: 3 additions & 1 deletion src/localization/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ ${'```'}
steps: 'steps',
type: 'type',
reversed: 'reversed',
opacity: 'Opacity'
opacity: 'Opacity',
preset: 'Preset Colors',
picker: 'Color Picker'
},
scale: {
colorScale: 'Color Scale',
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2909,7 +2909,7 @@ __metadata:
moment-timezone: "npm:^0.5.35"
prop-types: "npm:^15.6.0"
react: "npm:^18.2.0"
react-color: "npm:^2.17.3"
react-color: "npm:^2.19.3"
react-copy-to-clipboard: "npm:^5.0.2"
react-date-picker: "npm:^10.2.0"
react-dom: "npm:^18.2.0"
Expand Down Expand Up @@ -19268,7 +19268,7 @@ __metadata:
languageName: node
linkType: hard

"react-color@npm:^2.17.3":
"react-color@npm:^2.19.3":
version: 2.19.3
resolution: "react-color@npm:2.19.3"
dependencies:
Expand Down

0 comments on commit 2d8161e

Please sign in to comment.