Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-color-picker) Combined components of the ColorPicker #33273

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ export const AlphaSlider: ForwardRefComponent<AlphaSliderProps>;
export const alphaSliderClassNames: SlotClassNames<AlphaSliderSlots>;

// @public
export type AlphaSliderProps = Omit<ComponentProps<Partial<AlphaSliderSlots>, 'input'>, 'defaultValue' | 'onChange' | 'value'> & ColorSliderProps & {
overlayColor?: string;
};
export type AlphaSliderProps = ColorSliderProps;

// @public (undocumented)
export type AlphaSliderSlots = ColorSliderSlots;
Expand Down Expand Up @@ -61,8 +59,8 @@ export const ColorPicker: ForwardRefComponent<ColorPickerProps>;
export const colorPickerClassNames: SlotClassNames<ColorPickerSlots>;

// @public
export type ColorPickerProps = ComponentProps<ColorPickerSlots> & {
color: string;
export type ColorPickerProps = Omit<ComponentProps<Partial<ColorPickerSlots>>, 'color'> & {
color: HsvColor;
onColorChange?: EventHandler<ColorPickerOnChangeData>;
};

Expand All @@ -81,11 +79,12 @@ export const ColorSlider: ForwardRefComponent<ColorSliderProps>;
export const colorSliderClassNames: SlotClassNames<ColorSliderSlots>;

// @public
export type ColorSliderProps = Omit<ComponentProps<Partial<ColorSliderSlots>, 'input'>, 'defaultValue' | 'onChange' | 'value'> & {
export type ColorSliderProps = Omit<ComponentProps<Partial<ColorSliderSlots>, 'input'>, 'defaultValue' | 'onChange' | 'value' | 'color'> & {
channel?: string;
onChange?: EventHandler<SliderOnChangeData>;
vertical?: boolean;
color?: string;
color?: HsvColor;
defaultColor?: HsvColor;
};

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ describe('AlphaSlider', () => {
});

it('renders a default state', () => {
const result = render(<AlphaSlider overlayColor="red" />);
const result = render(<AlphaSlider color={{ h: 0, s: 1, v: 1 }} />);
expect(result.container).toMatchInlineSnapshot(`
<div>
<div
class="fui-ColorSlider fui-AlphaSlider"
style="--fui-AlphaSlider--direction: 90deg; --fui-AlphaSlider--progress: 100%; --fui-AlphaSlider__thumb--color: transparent; --fui-AlphaSlider__rail--color: hsl(0 0%, 0%);"
style="--fui-AlphaSlider--direction: 90deg; --fui-AlphaSlider--progress: 100%; --fui-AlphaSlider__thumb--color: transparent; --fui-AlphaSlider__rail--color: hsl(0 100%, 50%);"
>
<input
class="fui-ColorSlider__input fui-AlphaSlider__input"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import type { ComponentProps, ComponentState } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ColorSliderSlots, ColorSliderProps, ColorSliderState } from '../ColorSlider/ColorSlider.types';

export type AlphaSliderSlots = ColorSliderSlots;

/**
* AlphaSlider Props
*/
export type AlphaSliderProps = Omit<
ComponentProps<Partial<AlphaSliderSlots>, 'input'>,
'defaultValue' | 'onChange' | 'value'
> &
ColorSliderProps & {
/**
* The color to overlay on the alpha slider.
*/
overlayColor?: string;
};
export type AlphaSliderProps = ColorSliderProps;

/**
* State used in rendering AlphaSlider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const useAlphaSlider_unstable = (
const nativeProps = getPartitionedNativeProps({
props,
primarySlotTagName: 'input',
excludedPropNames: ['onChange'],
excludedPropNames: ['onChange', 'color'],
});

const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts
import { alphaSliderCSSVars } from './useAlphaSliderStyles.styles';
import type { AlphaSliderState, AlphaSliderProps } from './AlphaSlider.types';
import { useColorPickerContextValue_unstable } from '../../contexts/colorPicker';

const { sliderProgressVar, sliderDirectionVar, thumbColorVar, railColorVar } = alphaSliderCSSVars;

const MIN = 0;
const MAX = 100;

const getPercent = (value: number, min: number, max: number) => {
return max === min ? 0 : ((value - min) / (max - min)) * 100;
};
import { MIN, MAX } from '../../utils/constants';
import { getPercent } from '../../utils/getPercent';
import type { HsvColor } from '../ColorPicker/ColorPicker.types';

export const useAlphaSliderState_unstable = (state: AlphaSliderState, props: AlphaSliderProps) => {
'use no memo';
Expand All @@ -22,12 +16,13 @@ export const useAlphaSliderState_unstable = (state: AlphaSliderState, props: Alp
const onChangeFromContext = useColorPickerContextValue_unstable(ctx => ctx.requestChange);
const colorFromContext = useColorPickerContextValue_unstable(ctx => ctx.color);
const { color, onChange = onChangeFromContext } = props;
const _color = colorFromContext || color;
const hslColor = tinycolor(_color).toHsl();
const hsvColor = color || colorFromContext;
const hslColor = tinycolor(hsvColor).toHsl();

const [currentValue, setCurrentValue] = useControllableState({
state: hslColor.a * 100,
initialState: 0,
defaultState: props.defaultColor?.a ? props.defaultColor.a * 100 : undefined,
state: hsvColor?.a ? hsvColor.a * 100 : undefined,
initialState: 100,
});
const clampedValue = clamp(currentValue, MIN, MAX);
const valuePercent = getPercent(clampedValue, MIN, MAX);
Expand All @@ -36,8 +31,8 @@ export const useAlphaSliderState_unstable = (state: AlphaSliderState, props: Alp

const _onChange: React.ChangeEventHandler<HTMLInputElement> = useEventCallback(event => {
const newValue = Number(event.target.value);
const newColor = tinycolor({ ...hslColor, a: newValue / 100 }).toRgbString();
setCurrentValue(clamp(newValue, MIN, MAX));
const newColor: HsvColor = { ...hsvColor, a: newValue / 100 };
setCurrentValue(newValue);
inputOnChange?.(event);
onChange?.(event, { type: 'change', event, color: newColor });
onChangeFromContext(event, {
Expand All @@ -46,10 +41,10 @@ export const useAlphaSliderState_unstable = (state: AlphaSliderState, props: Alp
});

const rootVariables = {
[sliderDirectionVar]: state.vertical ? '0deg' : dir === 'ltr' ? '90deg' : '-90deg',
[sliderProgressVar]: `${valuePercent}%`,
[thumbColorVar]: `transparent`,
[railColorVar]: `hsl(${hslColor.h} ${hslColor.s * 100}%, ${hslColor.l * 100}%)`,
[alphaSliderCSSVars.sliderDirectionVar]: state.vertical ? '0deg' : dir === 'ltr' ? '90deg' : '-90deg',
[alphaSliderCSSVars.sliderProgressVar]: `${valuePercent}%`,
[alphaSliderCSSVars.thumbColorVar]: `transparent`,
[alphaSliderCSSVars.railColorVar]: `hsl(${hslColor.h} ${hslColor.s * 100}%, ${hslColor.l * 100}%)`,
};

// Root props
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ export const alphaSliderClassNames: SlotClassNames<AlphaSliderSlots> = {
export const alphaSliderCSSVars = {
sliderDirectionVar: `--fui-AlphaSlider--direction`,
sliderProgressVar: `--fui-AlphaSlider--progress`,
sliderStepsPercentVar: `--fui-AlphaSlider--steps-percent`,
thumbColorVar: `--fui-AlphaSlider__thumb--color`,
railColorVar: `--fui-AlphaSlider__rail--color`,
};

const { sliderDirectionVar, railColorVar, sliderProgressVar, thumbColorVar } = alphaSliderCSSVars;

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
rail: {
border: `1px solid ${tokens.colorNeutralStroke1}`,
backgroundImage: `linear-gradient(var(${sliderDirectionVar}), transparent, var(${railColorVar})), url(${TRANSPARENT_IMAGE_URL})`,
backgroundImage: `linear-gradient(var(${alphaSliderCSSVars.sliderDirectionVar}), transparent, var(${alphaSliderCSSVars.railColorVar})), url(${TRANSPARENT_IMAGE_URL})`,
},
});

Expand All @@ -38,15 +35,15 @@ const useStyles = makeStyles({
*/
const useThumbStyles = makeStyles({
thumb: {
backgroundColor: `var(${thumbColorVar})`,
backgroundColor: `var(${alphaSliderCSSVars.thumbColorVar})`,
},
horizontal: {
transform: 'translateX(-50%)',
left: `var(${sliderProgressVar})`,
left: `var(${alphaSliderCSSVars.sliderProgressVar})`,
},
vertical: {
transform: 'translateY(50%)',
bottom: `var(${sliderProgressVar})`,
bottom: `var(${alphaSliderCSSVars.sliderProgressVar})`,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import type { ComponentState, Slot, EventHandler, EventData, ComponentProps } from '@fluentui/react-utilities';
import type { HsvColor } from '../ColorPicker/ColorPicker.types';

export type ColorAreaOnColorChangeData = EventData<'change', React.SyntheticEvent | MouseEvent> & {
color: HsvColor;
Expand All @@ -12,13 +13,6 @@ export type ColorAreaSlots = {
inputY?: NonNullable<Slot<'input'>>;
};

export type HsvColor = {
h: number;
s: number;
v: number;
a?: number;
};

/**
* ColorArea Props
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as React from 'react';
import { useId, slot, useMergedRefs, mergeCallbacks, getIntrinsicElementProps } from '@fluentui/react-utilities';
import type { ColorAreaProps, ColorAreaState, HsvColor } from './ColorArea.types';
import type { ColorAreaProps, ColorAreaState } from './ColorArea.types';
import type { HsvColor } from '../ColorPicker/ColorPicker.types';
import { colorAreaCSSVars } from './useColorAreaStyles.styles';
import { useEventCallback, useControllableState } from '@fluentui/react-utilities';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { useFocusWithin } from '@fluentui/react-tabster';
import { INITIAL_COLOR_HSV } from '../../utils/constants';
import { getCoordinates } from '../../utils/getCoordinates';
import { useColorPickerContextValue_unstable } from '../../contexts/colorPicker';

/**
* Create the state required to render ColorArea.
Expand All @@ -23,10 +25,11 @@ export const useColorArea_unstable = (props: ColorAreaProps, ref: React.Ref<HTML
const xRef = React.useRef<HTMLInputElement>(null);
const yRef = React.useRef<HTMLInputElement>(null);
const focusWithinRef = useFocusWithin();
const onChangeFromContext = useColorPickerContextValue_unstable(ctx => ctx.requestChange);
const colorFromContext = useColorPickerContextValue_unstable(ctx => ctx.color);

const {
onChange,

onChange = onChangeFromContext as unknown as ColorAreaProps['onChange'],
// Slots
inputX,
inputY,
Expand All @@ -37,7 +40,7 @@ export const useColorArea_unstable = (props: ColorAreaProps, ref: React.Ref<HTML

const [hsvColor, setColor] = useControllableState<HsvColor>({
defaultState: props.defaultColor,
state: color,
state: color || colorFromContext,
initialState: INITIAL_COLOR_HSV,
});
const saturation = Math.round(hsvColor.s * 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('ColorPicker', () => {
});

it('renders a default state', () => {
const result = render(<ColorPicker color="red" />);
const result = render(<ColorPicker color={{ h: 0, s: 1, v: 1 }} />);
expect(result.container).toMatchInlineSnapshot(`
<div>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@ import type { ComponentProps, ComponentState, Slot, EventHandler, EventData } fr
import { ColorPickerContextValue } from '../../contexts/colorPicker';

export type ColorPickerOnChangeData = EventData<'change', React.ChangeEvent<HTMLInputElement>> & {
color: string;
color: HsvColor;
};

export type ColorPickerSlots = {
root: Slot<'div'>;
};

export type HsvColor = {
h: number;
s: number;
v: number;
a?: number;
};

/**
* ColorPicker Props
*/
export type ColorPickerProps = ComponentProps<ColorPickerSlots> & {
export type ColorPickerProps = Omit<ComponentProps<Partial<ColorPickerSlots>>, 'color'> & {
/**
* Selected color.
*/
color: string;
color: HsvColor;

/**
* Callback for when the user changes the color.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ describe('ColorSlider', () => {
<input
class="fui-ColorSlider__input"
id="slider-9"
max="360"
min="0"
type="range"
value="0"
/>
Expand All @@ -42,7 +44,7 @@ describe('ColorSlider', () => {
});

it('applies the color prop', () => {
render(<ColorSlider color="#f09" />);
render(<ColorSlider color={{ h: 324, s: 1, v: 1 }} />);
expect(screen.getByRole('slider').getAttribute('value')).toEqual('324');
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import type { ComponentProps, ComponentState, Slot, EventHandler, EventData } from '@fluentui/react-utilities';
import type { HsvColor } from '../ColorPicker/ColorPicker.types';

export type SliderOnChangeData = EventData<'change', React.ChangeEvent<HTMLInputElement>> & {
color: string;
color: HsvColor;
};

export type ColorSliderSlots = {
Expand All @@ -17,7 +18,7 @@ export type ColorSliderSlots = {
*/
export type ColorSliderProps = Omit<
ComponentProps<Partial<ColorSliderSlots>, 'input'>,
'defaultValue' | 'onChange' | 'value'
'defaultValue' | 'onChange' | 'value' | 'color'
> & {
channel?: string;

Expand All @@ -35,7 +36,12 @@ export type ColorSliderProps = Omit<
/**
* Color of the COlorPicker
*/
color?: string;
color?: HsvColor;

/**
* The starting color for an uncontrolled ColorSlider.
*/
defaultColor?: HsvColor;
};

/**
Expand Down
Loading
Loading