Skip to content

Commit ff11fcc

Browse files
authored
feature: introduce direction and autoHideColorPicker props (#34)
1 parent aedf47d commit ff11fcc

File tree

16 files changed

+307
-59
lines changed

16 files changed

+307
-59
lines changed

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,17 @@ const App = () => {
4848

4949
### Accepted props
5050

51-
| Name | Type | Default Value | Required? | Description
52-
|-|-|-|-|-
53-
| `palette` | `PaletteColor[]` | `undefined` | Yes | The gradient pickers color palette, Each palette color struct is described below
54-
| `onPaletteChange` | `Function` | `undefined` | Yes | The function to trigger upon palette change (Can be either from stop drag or color select).
55-
| `paletteHeight` | `Number` | `32` | No | The stops palette display area height
56-
| `width` | `Number` | `400` | No | Determines the width of the gradient picker
57-
| `stopRemovalDrop` | `Number` | `50` | No | Sets the Y stop drop removal offset, If the user will drag the color stop further than specified, Color will be removed
58-
| `maxStops` | `Number` | `5` | No | The max gradient picker palette length can have
59-
| `minStops` | `Number` | `2` | No | The min gradient picker palette length can have
51+
| Name | Type | Default Value | Required? | Description
52+
|-------------------|--------------------------|-----------------------|-|-
53+
| `palette` | `PaletteColor[]` | `undefined` | Yes | The gradient pickers color palette, Each palette color struct is described below
54+
| `onPaletteChange` | `Function` | `undefined` | Yes | The function to trigger upon palette change (Can be either from stop drag or color select).
55+
| `paletteHeight` | `Number` | `32` | No | The stops palette display area height
56+
| `width` | `Number` | `400` | No | Determines the width of the gradient picker
57+
| `stopRemovalDrop` | `Number` | `50` | No | Sets the Y stop drop removal offset, If the user will drag the color stop further than specified, Color will be removed
58+
| `maxStops` | `Number` | `5` | No | The max gradient picker palette length can have
59+
| `minStops` | `Number` | `2` | No | The min gradient picker palette length can have
60+
| `colorPickerMode` | `String` | `static` \| `popover` | No | Determines the mode of the color picker picker component, by default `static` will always present otherwise will act as popover which will appear upon interaction.
61+
| `direction` | `horizontal`\|`vertical` | `horizontal` | No | Determines the direction of the gradient picker.
6062

6163
|> Palette Color
6264
| Name | Type | Default Value | Required? | Description

src/components/ColorStop/hooks/useStopDragging.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
22
import useDragging from '../../hooks/useDragging';
3+
import { DIRECTIONS } from '../../GradientPicker/constants';
34

45
/**
56
* Limits a client drag movement within given min / max
@@ -10,35 +11,43 @@ import useDragging from '../../hooks/useDragging';
1011
*/
1112
const limitPos = (offset, min, max) => Math.max(Math.min(offset, max), min);
1213

13-
const getColorStopRefTop = (ref) => {
14+
const getColorStopRefCoordinate = (ref, direction) => {
1415
if (!ref.current) return 0;
15-
return ref.current.getBoundingClientRect().top;
16+
const rect = ref.current.getBoundingClientRect();
17+
18+
return direction === DIRECTIONS.HORIZONTAL
19+
? rect.top
20+
: rect.left;
21+
};
22+
23+
const getDeleteDistanceFromColorStopToCoordinate = (direction, coordinates, colorStopRef) => {
24+
const start = direction === DIRECTIONS.HORIZONTAL ? coordinates.clientY : coordinates.clientX;
25+
const end = getColorStopRefCoordinate(colorStopRef, direction);
26+
27+
return Math.abs(start - end);
1628
};
1729

18-
const useStopDragging = ({ limits, stop, initialPos, colorStopRef, onPosChange, onDragStart, onDragEnd, onDeleteColor}) => {
30+
const useStopDragging = ({ limits, stop, initialPos, colorStopRef, onPosChange, onDragStart, onDragEnd, onDeleteColor, direction}) => {
1931
const [posStart, setPosStart] = useState(initialPos);
2032

21-
const handleDrag = ({ clientX, clientY }) => {
33+
const handleDrag = (coordinates) => {
2234
const { id, offset } = stop;
2335
const { min, max } = limits;
36+
const dragOffset = offset - posStart;
2437

25-
// Removing if out of drop limit on Y axis.
26-
const top = getColorStopRefTop(colorStopRef);
27-
if (Math.abs(clientY - top) > limits.drop) {
28-
//deactivate();
38+
if (getDeleteDistanceFromColorStopToCoordinate(direction, coordinates, colorStopRef) > limits.drop) {
2939
return onDeleteColor(id);
3040
}
31-
3241
// Limit movements
33-
const dragOffset = offset - posStart;
34-
const limitedPos = limitPos(dragOffset + clientX, min, max);
42+
const clientCoordinate = direction === DIRECTIONS.HORIZONTAL ? coordinates.clientX : coordinates.clientY;
43+
const limitedPos = limitPos(dragOffset + clientCoordinate, min, max);
3544

3645
onPosChange({ id, offset: limitedPos });
3746
};
3847

3948
const [drag] = useDragging({
40-
onDragStart: ({ clientX }) => {
41-
setPosStart(clientX);
49+
onDragStart: ({ clientX, clientY }) => {
50+
setPosStart(direction === DIRECTIONS.HORIZONTAL ? clientX : clientY );
4251

4352
onDragStart(stop.id);
4453
},

src/components/ColorStop/index.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import React, { useState, useRef, useEffect } from 'react';
22
import { noop } from '../../lib';
33
import { STOP_PROP_TYPES } from '../propTypes';
4+
import { DIRECTIONS } from '../GradientPicker/constants';
45
import useStopDragging from './hooks/useStopDragging';
56
import './index.scss';
67

7-
const ColorStop = ({ stop, limits, onPosChange, onDeleteColor, onDragStart = noop, onDragEnd = noop}) => {
8+
const ColorStop = ({ stop, limits, onPosChange, onDeleteColor, onDragStart = noop, onDragEnd = noop, direction}) => {
89
const colorStopRef = useRef();
9-
const [allowRemoveOnDoubleClick, setAllowRemoveOnDoubleClick] = useState(false)
10+
const [allowRemoveOnDoubleClick, setAllowRemoveOnDoubleClick] = useState(false);
1011
const [drag] = useStopDragging({
1112
stop,
1213
limits,
1314
onPosChange,
1415
onDragStart,
1516
onDragEnd,
1617
onDeleteColor,
17-
colorStopRef
18+
colorStopRef,
19+
direction,
1820
});
1921

2022
useEffect(() => {
@@ -24,12 +26,13 @@ const ColorStop = ({ stop, limits, onPosChange, onDeleteColor, onDragStart = noo
2426
const { offset, color, isActive, opacity } = stop;
2527

2628
return (
27-
<div className={isActive ? 'cs active' : 'cs'}
29+
<div
30+
className={`cs ${direction} ${isActive ? 'active' : ''}`}
2831
ref={colorStopRef}
29-
style={{ left: offset }}
32+
style={direction === DIRECTIONS.HORIZONTAL ? { left: offset } : { top: offset }}
3033
onMouseDown={drag}
3134
onDoubleClick={() => {
32-
allowRemoveOnDoubleClick && onDeleteColor(stop.id)
35+
allowRemoveOnDoubleClick && onDeleteColor(stop.id);
3336
}}
3437
onTouchStart={drag}>
3538
<div style={{ backgroundColor: color, opacity }}/>

src/components/ColorStop/index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@
1616
.active {
1717
background-position: left center;
1818
}
19+
20+
&.vertical {
21+
transform: rotate(-90deg) translate(4px, 2px);
22+
}
1923
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const COLOR_STOP_HOLDER_CLASSNAME = 'csh';

src/components/ColorStopsHolder/index.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
import React from 'react';
22
import ColorStop from '../ColorStop/index';
33
import { STOPS_HOLDER_PROP_TYPES } from '../propTypes';
4+
import { DIRECTIONS } from '../GradientPicker/constants';
5+
import { COLOR_STOP_HOLDER_CLASSNAME } from './constants';
46

5-
const getStopsHolderStyle = (width, disabled) => ({
6-
width,
7-
height: 17,
7+
8+
const getStopsHolderStyle = (width, disabled, direction) => ({
9+
width: direction === DIRECTIONS.HORIZONTAL ? width : 17,
10+
height: direction === DIRECTIONS.HORIZONTAL ? 17 : width,
811
position: 'relative',
9-
cursor: disabled ? 'default' : 'crosshair'
12+
cursor: disabled ? 'default' : 'crosshair',
1013
});
1114

12-
const ColorStopsHolder = ({ width, stops, disabled = false, onAddColor, ...rest }) => {
15+
const ColorStopsHolder = ({ width, direction, stops, disabled = false, onAddColor, ...rest }) => {
1316

1417
const handleColorAdd = (e) => {
1518
e.preventDefault();
1619

1720
if (e.button) return;
21+
const offset = direction === DIRECTIONS.HORIZONTAL
22+
? e.clientX - e.target.getBoundingClientRect().left
23+
: e.clientY - e.target.getBoundingClientRect().top;
1824

19-
const offset = e.clientX - e.target.getBoundingClientRect().left;
2025
onAddColor({ offset });
2126
};
2227

2328
return (
24-
<div className="csh" style={getStopsHolderStyle(width, disabled)} onMouseDown={handleColorAdd}>
29+
<div className={COLOR_STOP_HOLDER_CLASSNAME} style={getStopsHolderStyle(width, disabled, direction)} onMouseDown={handleColorAdd}>
2530
{stops.map(stop =>
26-
<ColorStop key={stop.id} stop={stop} {...rest} />
31+
<ColorStop key={stop.id} stop={stop} direction={direction} {...rest} />
2732
)}
2833
</div>
2934
);

src/components/GradientPicker/constants.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { COLOR_STOP_HOLDER_CLASSNAME } from '../ColorStopsHolder/constants';
2+
13
const STOP_WIDTH = 10;
24

35
export const HALF_STOP_WIDTH = STOP_WIDTH / 2;
@@ -10,4 +12,24 @@ export const DEFAULT_HEIGHT = 32;
1012

1113
export const DEFAULT_MAX_STOPS = 5;
1214

13-
export const DEFAULT_MIN_STOPS = 2;
15+
export const DEFAULT_MIN_STOPS = 2;
16+
17+
export const DIRECTIONS = {
18+
HORIZONTAL: 'horizontal',
19+
VERTICAL: 'vertical',
20+
};
21+
22+
export const DEFAULT_DIRECTION = DIRECTIONS.HORIZONTAL;
23+
24+
export const GRADIENT_PICKER_CLASSNAME = 'gp';
25+
26+
export const COLOR_PICKER_CLASSNAME = 'color-picker';
27+
28+
export const IGNORED_CLICK_OUTSIDE_SELECTORS = [`.${COLOR_PICKER_CLASSNAME}`, `.${GRADIENT_PICKER_CLASSNAME} .${COLOR_STOP_HOLDER_CLASSNAME}`];
29+
30+
export const COLOR_PICKER_MODS = {
31+
STATIC: 'static',
32+
POPOVER: 'popover',
33+
};
34+
35+
export const DEFAULT_COLOR_PICKER_MOD = COLOR_PICKER_MODS.STATIC;

src/components/GradientPicker/index.js

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import React, { useState, useMemo } from 'react';
1+
import React, { useState, useMemo, useRef, useCallback } from 'react';
22
import ColorStopsHolder from '../ColorStopsHolder/index';
33
import Palette from '../Palette/index';
44
import ColorPicker from '../ColorPicker/index';
55
import { GRADIENT_PICKER_PROP_TYPES } from '../propTypes/index';
66
import { sortPalette, noop } from '../../lib/index';
7+
import { useClickOutside } from '../hooks/useClickOutside';
78
import {
89
HALF_STOP_WIDTH,
910
DEFAULT_HEIGHT,
1011
DEFAULT_WIDTH,
1112
DEFAULT_STOP_REMOVAL_DROP,
1213
DEFAULT_MAX_STOPS,
13-
DEFAULT_MIN_STOPS
14+
DEFAULT_MIN_STOPS,
15+
DEFAULT_DIRECTION,
16+
DEFAULT_COLOR_PICKER_MOD,
17+
GRADIENT_PICKER_CLASSNAME,
18+
COLOR_PICKER_CLASSNAME,
19+
IGNORED_CLICK_OUTSIDE_SELECTORS,
20+
COLOR_PICKER_MODS
1421
} from './constants';
1522
import './index.scss';
1623

@@ -44,12 +51,16 @@ const GradientPicker = ({
4451
children,
4552
flatStyle = false,
4653
onPaletteChange,
47-
onColorStopSelect = noop
54+
onColorStopSelect = noop,
55+
direction = DEFAULT_DIRECTION,
56+
colorPickerMode = DEFAULT_COLOR_PICKER_MOD
4857
}) => {
4958
palette = mapIdToPalette(palette);
50-
59+
const [showColorPicker, setShowColorPicker] = React.useState(colorPickerMode === COLOR_PICKER_MODS.STATIC);
5160
const [defaultActiveColor] = palette;
5261
const [activeColorId, setActiveColorId] = useState(defaultActiveColor.id);
62+
const pickerRef = useRef(null);
63+
const isPopoverColorPicker = colorPickerMode === COLOR_PICKER_MODS.POPOVER;
5364

5465
const limits = useMemo(() => {
5566
const min = -HALF_STOP_WIDTH;
@@ -58,8 +69,20 @@ const GradientPicker = ({
5869
return { min, max, drop: stopRemovalDrop };
5970
}, [width]);
6071

72+
const closePicker = useCallback(() => setShowColorPicker(false), [setShowColorPicker]);
73+
74+
useClickOutside({
75+
pickerRef,
76+
callback: closePicker,
77+
ignoredSelectors: IGNORED_CLICK_OUTSIDE_SELECTORS,
78+
enabled: isPopoverColorPicker,
79+
});
80+
6181
const handleColorAdd = ({ offset }) => {
6282
if (palette.length >= maxStops) return;
83+
if (isPopoverColorPicker) {
84+
setShowColorPicker(true);
85+
}
6386

6487
const { color } = getPaletteColor(palette, activeColorId);
6588
const entry = { id: nextColorId(palette), offset: offset / width, color };
@@ -81,6 +104,10 @@ const GradientPicker = ({
81104
};
82105

83106
const onStopDragStart = (id) => {
107+
if (isPopoverColorPicker) {
108+
setShowColorPicker(true);
109+
}
110+
84111
if (id !== activeColorId) {
85112
setActiveColorId(id);
86113

@@ -123,11 +150,12 @@ const GradientPicker = ({
123150
const props = {
124151
color,
125152
opacity,
153+
className: COLOR_PICKER_CLASSNAME,
126154
...(flatStyle && {
127155
width,
128-
className: 'gp-flat',
156+
className: `gp-flat ${COLOR_PICKER_CLASSNAME}`,
129157
}),
130-
onSelect: handleColorSelect
158+
onSelect: handleColorSelect,
131159
};
132160

133161
if (!children) {
@@ -142,8 +170,16 @@ const GradientPicker = ({
142170
const stopsHolderDisabled = palette.length >= maxStops;
143171

144172
return (
145-
<div className="gp">
146-
<Palette width={paletteWidth} height={paletteHeight} palette={palette}/>
173+
<div
174+
ref={pickerRef}
175+
className={`${GRADIENT_PICKER_CLASSNAME} color-picker-mode-${colorPickerMode} ${direction}`}
176+
>
177+
<Palette
178+
width={paletteWidth}
179+
height={paletteHeight}
180+
palette={palette}
181+
direction={direction}
182+
/>
147183
<ColorStopsHolder
148184
width={paletteWidth}
149185
disabled={stopsHolderDisabled}
@@ -157,8 +193,11 @@ const GradientPicker = ({
157193
onAddColor={handleColorAdd}
158194
onDeleteColor={handleColorDelete}
159195
onDragStart={onStopDragStart}
196+
direction={direction}
160197
/>
161-
{colorPicker()}
198+
<div className="color-picker-wrapper">
199+
<div className="color-picker-wrapper-inner">{showColorPicker && colorPicker()}</div>
200+
</div>
162201
</div>
163202
);
164203
};

src/components/GradientPicker/index.scss

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,38 @@
33
flex-direction: column;
44
align-items: center;
55

6+
&.vertical {
7+
flex-direction: row;
8+
}
9+
610
.gp-flat {
711
margin: 0 auto;
812
padding: 10px 0 0!important;
913
box-shadow: none!important;
1014
transform: none!important;
1115
}
16+
17+
&.color-picker-mode-popover {
18+
.color-picker-wrapper {
19+
position: relative;
20+
width: 100%;
21+
flex: 0;
22+
23+
.color-picker-wrapper-inner {
24+
position: absolute;
25+
display: flex;
26+
justify-content: center;
27+
}
28+
}
29+
30+
&.vertical .color-picker-wrapper .color-picker-wrapper-inner {
31+
flex-direction: column;
32+
height: 100%;
33+
}
34+
35+
&.horizontal .color-picker-wrapper .color-picker-wrapper-inner {
36+
flex-direction: row;
37+
width: 100%;
38+
}
39+
}
1240
}

0 commit comments

Comments
 (0)