Skip to content

Commit 423d568

Browse files
authoredOct 14, 2020
ColorSlider (adobe#1142)
1 parent db9b022 commit 423d568

File tree

25 files changed

+1104
-42
lines changed

25 files changed

+1104
-42
lines changed
 

‎packages/@adobe/spectrum-css-temp/components/colorloupe/skin.css

+7
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,10 @@
22
fill: var(--spectrum-colorloupe-inner-border-color);
33
stroke: var(--spectrum-colorloupe-outer-border-color);
44
}
5+
6+
.spectrum-ColorLoupe-inner-background {
7+
fill: var(--spectrum-global-color-static-white);
8+
}
9+
.spectrum-ColorLoupe-inner-checker {
10+
fill: var(--spectrum-global-color-static-gray-500);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
:root {
2+
--spectrum-colorslider-handle-hitarea-border-radius: 0%;
3+
--spectrum-colorslider-handle-hitarea-width: var(
4+
--spectrum-global-dimension-size-300
5+
);
6+
--spectrum-colorslider-handle-hitarea-height: var(
7+
--spectrum-global-dimension-size-300
8+
);
9+
}
10+
11+
%spectrum-ColorControl-handle--focused {
12+
/* Bigger handle when focused */
13+
width: calc(var(--spectrum-colorhandle-size) * 2);
14+
height: calc(var(--spectrum-colorhandle-size) * 2);
15+
16+
margin-left: calc(-1 * var(--spectrum-colorhandle-size));
17+
margin-top: calc(-1 * var(--spectrum-colorhandle-size));
18+
}
19+
20+
%spectrum-ColorControl-hiddenField {
21+
opacity: 0.0001;
22+
position: absolute;
23+
top: 0;
24+
left: 0;
25+
width: 100%;
26+
height: 100%;
27+
z-index: 0;
28+
margin: 0;
29+
pointer-events: none;
30+
}
31+
32+
.spectrum-ColorSlider {
33+
position: relative;
34+
display: block;
35+
width: var(--spectrum-colorslider-default-length);
36+
height: var(--spectrum-colorslider-height);
37+
38+
/* Otherwise we randomly drag a file icon */
39+
user-select: none;
40+
touch-action: none;
41+
42+
cursor: default;
43+
44+
&.is-focused {
45+
z-index: 2;
46+
47+
.spectrum-ColorSlider-handle {
48+
@extend %spectrum-ColorControl-handle--focused;
49+
}
50+
}
51+
52+
&.is-disabled {
53+
pointer-events: none;
54+
}
55+
}
56+
57+
.spectrum-ColorSlider--vertical {
58+
display: inline-block;
59+
60+
width: var(--spectrum-colorslider-vertical-width);
61+
height: var(--spectrum-colorslider-vertical-default-length);
62+
63+
.spectrum-ColorSlider-handle {
64+
left: 50%;
65+
top: 0;
66+
}
67+
}
68+
69+
.spectrum-ColorSlider-handle {
70+
left: 0;
71+
top: 50%;
72+
73+
&:after {
74+
border-radius: var(--spectrum-colorslider-handle-hitarea-border-radius);
75+
width: var(--spectrum-colorslider-handle-hitarea-width);
76+
height: var(--spectrum-colorslider-handle-hitarea-height);
77+
}
78+
}
79+
80+
.spectrum-ColorSlider-checkerboard {
81+
background-size: var(--spectrum-global-dimension-static-size-200)
82+
var(--spectrum-global-dimension-static-size-200);
83+
background-position: 0 0, 0 var(--spectrum-global-dimension-static-size-100),
84+
var(--spectrum-global-dimension-static-size-100)
85+
calc(-1 * var(--spectrum-global-dimension-static-size-100)),
86+
calc(-1 * var(--spectrum-global-dimension-static-size-100)) 0;
87+
88+
/* the floating inset box shadow must be a separate element since <canvas> won't take it */
89+
&:before {
90+
content: "";
91+
z-index: 1;
92+
position: absolute;
93+
top: 0;
94+
left: 0;
95+
bottom: 0;
96+
right: 0;
97+
border-radius: var(--spectrum-colorslider-border-radius);
98+
}
99+
}
100+
101+
.spectrum-ColorSlider-gradient,
102+
.spectrum-ColorSlider-checkerboard {
103+
width: 100%;
104+
height: 100%;
105+
border-radius: var(--spectrum-colorslider-border-radius);
106+
}
107+
108+
.spectrum-ColorSlider-slider {
109+
@extend %spectrum-ColorControl-hiddenField;
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
:root {
2+
/* todo: fix this in DNA */
3+
--spectrum-colorslider-border-color: var(--spectrum-colorarea-border-color);
4+
}
5+
6+
.spectrum-ColorSlider-checkerboard {
7+
background-color: var(--spectrum-global-color-static-white);
8+
9+
/* Add a half-percent to fix diagonal line issue in Chrome on non-retina displays */
10+
background-image:
11+
linear-gradient(-45deg, transparent 75.5%, var(--spectrum-global-color-static-gray-500) 75.5%),
12+
linear-gradient(45deg, transparent 75.5%, var(--spectrum-global-color-static-gray-500) 75.5%),
13+
linear-gradient(-45deg, var(--spectrum-global-color-static-gray-500) 25.5%, transparent 25.5%),
14+
linear-gradient(45deg, var(--spectrum-global-color-static-gray-500) 25.5%, transparent 25.5%);
15+
16+
&:before {
17+
box-shadow: inset 0 0 0 var(--spectrum-colorslider-border-size) var(--spectrum-colorslider-border-color);
18+
}
19+
}
20+
21+
.spectrum-ColorSlider {
22+
&.is-disabled {
23+
.spectrum-ColorSlider-checkerboard {
24+
background: var(--spectrum-colorslider-fill-color-disabled);
25+
}
26+
27+
.spectrum-ColorSlider-checkerboard {
28+
&:before {
29+
box-shadow: 0 0 0 var(--spectrum-colorslider-border-size) var(--spectrum-colorslider-border-color-disabled);
30+
}
31+
}
32+
33+
.spectrum-ColorSlider-gradient {
34+
display: none
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
@import './index.css';
14+
@import './skin.css';

‎packages/@react-aria/color/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
},
2020
"dependencies": {
2121
"@babel/runtime": "^7.6.2",
22+
"@react-aria/i18n": "3.1.2",
2223
"@react-aria/interactions": "^3.2.0",
24+
"@react-aria/slider": "^3.0.0-alpha.2",
2325
"@react-aria/utils": "^3.2.1",
2426
"@react-stately/color": "^3.0.0-alpha.1",
25-
"@react-types/color": "^3.0.0-alpha.1"
27+
"@react-types/color": "^3.0.0-alpha.1",
28+
"@react-types/shared": "3.2.1",
29+
"@react-types/slider": "^3.0.0-alpha.1"
2630
},
2731
"peerDependencies": {
2832
"react": "^16.8.0 || ^17.0.0-rc.1"

‎packages/@react-aria/color/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
export * from './useColorSlider';
1314
export * from './useColorWheel';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {ColorChannel} from '@react-types/color';
14+
import {ColorSliderState} from '@react-stately/color';
15+
import {SliderThumbOptions, useSlider, useSliderThumb} from '@react-aria/slider';
16+
import {useLocale} from '@react-aria/i18n';
17+
18+
export interface ColorSliderAriaOptions extends Omit<SliderThumbOptions, 'index'> {
19+
channel: ColorChannel
20+
}
21+
22+
export function useColorSlider(props: ColorSliderAriaOptions, state: ColorSliderState) {
23+
let {trackRef, orientation, channel} = props;
24+
25+
let {direction} = useLocale();
26+
27+
let {containerProps, trackProps, labelProps} = useSlider(props, state, trackRef);
28+
let {inputProps, thumbProps} = useSliderThumb({
29+
...props,
30+
index: 0
31+
}, state);
32+
33+
return {
34+
containerProps,
35+
trackProps,
36+
inputProps,
37+
thumbProps,
38+
labelProps,
39+
generateBackground() {
40+
let value = state.getDisplayColor();
41+
let to: string;
42+
if (orientation === 'vertical') {
43+
to = 'top';
44+
} else if (direction === 'ltr') {
45+
to = 'right';
46+
} else {
47+
to = 'left';
48+
}
49+
switch (channel) {
50+
case 'hue':
51+
return `linear-gradient(to ${to}, rgb(255, 0, 0) 0%, rgb(255, 255, 0) 17%, rgb(0, 255, 0) 33%, rgb(0, 255, 255) 50%, rgb(0, 0, 255) 67%, rgb(255, 0, 255) 83%, rgb(255, 0, 0) 100%)`;
52+
case 'saturation':
53+
case 'lightness':
54+
case 'brightness':
55+
case 'red':
56+
case 'green':
57+
case 'blue':
58+
case 'alpha': {
59+
let start = value.withChannelValue(channel, state.getThumbMinValue(0)).toString('css');
60+
let end = value.withChannelValue(channel, state.getThumbMaxValue(0)).toString('css');
61+
return `linear-gradient(to ${to}, ${start}, ${end})`;
62+
}
63+
default:
64+
throw new Error('Unknown color channel: ' + channel);
65+
}
66+
}
67+
};
68+
}

‎packages/@react-aria/color/src/useColorWheel.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface ColorWheelAriaResult {
2323
thumbPosition: {x: number, y: number}
2424
}
2525

26-
const PAGE_STEP_SIZE = 6;
26+
const PAGE_MIN_STEP_SIZE = 6;
2727

2828
function degToRad(deg: number) {
2929
return deg * Math.PI / 180;
@@ -33,7 +33,7 @@ function radToDeg(rad: number) {
3333
return rad * 180 / Math.PI;
3434
}
3535

36-
// 0deg = 3 o'clock. increses clockwise
36+
// 0deg = 3 o'clock. increases clockwise
3737
function angleToCartesian(angle: number, radius: number): {x: number, y: number} {
3838
let rad = degToRad(360 - angle + 90);
3939
let x = Math.sin(rad) * (radius);
@@ -175,11 +175,11 @@ export function useColorWheel(props: ColorWheelAriaProps, state: ColorWheelState
175175
switch (e.key) {
176176
case 'PageUp':
177177
e.preventDefault();
178-
state.increment(PAGE_STEP_SIZE);
178+
state.increment(PAGE_MIN_STEP_SIZE);
179179
break;
180180
case 'PageDown':
181181
e.preventDefault();
182-
state.decrement(PAGE_STEP_SIZE);
182+
state.decrement(PAGE_MIN_STEP_SIZE);
183183
break;
184184
}
185185
}

‎packages/@react-aria/slider/src/useSlider.ts

+25-14
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {useLabel} from '@react-aria/label';
1919
import {useLocale} from '@react-aria/i18n';
2020
import {useMove} from '@react-aria/interactions';
2121

22-
interface SliderAria {
22+
export interface SliderAria {
2323
/** Props for the label element. */
2424
labelProps: HTMLAttributes<HTMLElement>,
2525

@@ -45,7 +45,9 @@ export function useSlider(
4545
state: SliderState,
4646
trackRef: React.RefObject<HTMLElement>
4747
): SliderAria {
48-
const {labelProps, fieldProps} = useLabel(props);
48+
let {labelProps, fieldProps} = useLabel(props);
49+
50+
let isVertical = props.orientation === 'vertical';
4951

5052
// Attach id of the label to the state so it can be accessed by useSliderThumb.
5153
sliderIds.set(state, labelProps.id ?? fieldProps.id);
@@ -68,14 +70,22 @@ export function useSlider(
6870
onMoveStart() {
6971
currentPosition.current = null;
7072
},
71-
onMove({deltaX}) {
73+
onMove({deltaX, deltaY}) {
74+
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth;
75+
7276
if (currentPosition.current == null) {
73-
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * trackRef.current.offsetWidth;
77+
currentPosition.current = stateRef.current.getThumbPercent(realTimeTrackDraggingIndex.current) * size;
7478
}
75-
currentPosition.current += reverseX ? -deltaX : deltaX;
79+
80+
let delta = isVertical ? deltaY : deltaX;
81+
if (isVertical || reverseX) {
82+
delta = -delta;
83+
}
84+
85+
currentPosition.current += delta;
7686

7787
if (realTimeTrackDraggingIndex.current != null && trackRef.current) {
78-
const percent = clamp(currentPosition.current / trackRef.current.offsetWidth, 0, 1);
88+
const percent = clamp(currentPosition.current / size, 0, 1);
7989
stateRef.current.setThumbPercent(realTimeTrackDraggingIndex.current, percent);
8090
}
8191
},
@@ -88,15 +98,16 @@ export function useSlider(
8898
});
8999

90100
let currentPointer = useRef<number | null | undefined>(undefined);
91-
let onDownTrack = (e: React.UIEvent, id: number, clientX: number) => {
101+
let onDownTrack = (e: React.UIEvent, id: number, clientX: number, clientY: number) => {
92102
// We only trigger track-dragging if the user clicks on the track itself and nothing is currently being dragged.
93103
if (trackRef.current && !props.isDisabled && state.values.every((_, i) => !state.isThumbDragging(i))) {
104+
let size = isVertical ? trackRef.current.offsetHeight : trackRef.current.offsetWidth;
94105
// Find the closest thumb
95-
const trackPosition = trackRef.current.getBoundingClientRect().left;
96-
const clickPosition = clientX;
106+
const trackPosition = trackRef.current.getBoundingClientRect()[isVertical ? 'top' : 'left'];
107+
const clickPosition = isVertical ? clientY : clientX;
97108
const offset = clickPosition - trackPosition;
98-
let percent = offset / trackRef.current.offsetWidth;
99-
if (direction === 'rtl') {
109+
let percent = offset / size;
110+
if (direction === 'rtl' || isVertical) {
100111
percent = 1 - percent;
101112
}
102113
let value = state.getPercentValue(percent);
@@ -148,9 +159,9 @@ export function useSlider(
148159
...fieldProps
149160
},
150161
trackProps: mergeProps({
151-
onMouseDown(e: React.MouseEvent<HTMLElement>) { onDownTrack(e, undefined, e.clientX); },
152-
onPointerDown(e: React.PointerEvent<HTMLElement>) { onDownTrack(e, e.pointerId, e.clientX); },
153-
onTouchStart(e: React.TouchEvent<HTMLElement>) { onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX); }
162+
onMouseDown(e: React.MouseEvent<HTMLElement>) { onDownTrack(e, undefined, e.clientX, e.clientY); },
163+
onPointerDown(e: React.PointerEvent<HTMLElement>) { onDownTrack(e, e.pointerId, e.clientX, e.clientY); },
164+
onTouchStart(e: React.TouchEvent<HTMLElement>) { onDownTrack(e, e.changedTouches[0].identifier, e.changedTouches[0].clientX, e.changedTouches[0].clientY); }
154165
}, moveProps)
155166
};
156167
}

0 commit comments

Comments
 (0)
Failed to load comments.