Skip to content

Commit

Permalink
feat(swatch-picker): Added ColorSwatch sub component and selection lo…
Browse files Browse the repository at this point in the history
…gic (#30544)

Co-authored-by: Oleksandr Fediashov <alexander.mcgarret@gmail.com>
  • Loading branch information
ValentinaKozlova and layershifter committed Feb 21, 2024
1 parent be0c9ec commit ae14927
Show file tree
Hide file tree
Showing 25 changed files with 685 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "feat(swatch-picker): Added plain ColorSwatch sub component.",
"packageName": "@fluentui/react-shared-contexts",
"email": "vkozlova@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,91 @@

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { EventData } from '@fluentui/react-utilities';
import type { EventHandler } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const renderSwatchPicker_unstable: (state: SwatchPickerState) => JSX.Element;
export const ColorSwatch: ForwardRefComponent<ColorSwatchProps>;

// @public (undocumented)
export const colorSwatchClassNames: SlotClassNames<ColorSwatchSlots>;

// @public
export type ColorSwatchProps = Omit<ComponentProps<Partial<ColorSwatchSlots>, 'button'>, 'children'> & Pick<SwatchPickerProps, 'size' | 'shape'> & {
color: string;
value: string;
};

// @public (undocumented)
export type ColorSwatchSlots = {
root: NonNullable<Slot<'div'>>;
button: NonNullable<Slot<'button'>>;
};

// @public
export type ColorSwatchState = ComponentState<ColorSwatchSlots> & Pick<ColorSwatchProps, 'color' | 'size' | 'shape' | 'value'> & {
selected: boolean;
};

// @public
export const renderColorSwatch_unstable: (state: ColorSwatchState) => JSX.Element;

// @public
export const renderSwatchPicker_unstable: (state: SwatchPickerState, contextValues: SwatchPickerContextValues) => JSX.Element;

// @public (undocumented)
export const swatchCSSVars: {
color: string;
};

// @public
export const SwatchPicker: ForwardRefComponent<SwatchPickerProps>;

// @public (undocumented)
export const swatchPickerClassNames: SlotClassNames<SwatchPickerSlots>;

// @public (undocumented)
export const swatchPickerCSSVars: {
columnCountGrid: string;
cellSize: string;
gridGap: string;
};

// @public (undocumented)
export type SwatchPickerOnSelectEventHandler = EventHandler<SwatchPickerOnSelectionChangeData>;

// @public (undocumented)
export type SwatchPickerOnSelectionChangeData = EventData<'click', React_2.MouseEvent<HTMLButtonElement>> & {
selectedValue: string;
selectedColor: string;
};

// @public
export type SwatchPickerProps = ComponentProps<SwatchPickerSlots> & {};
export type SwatchPickerProps = ComponentProps<SwatchPickerSlots> & {
defaultSelectedValue?: string;
onSelectionChange?: EventHandler<SwatchPickerOnSelectionChangeData>;
selectedValue?: string;
size?: 'extraSmall' | 'small' | 'medium' | 'large';
shape?: 'rounded' | 'square' | 'circular';
};

// @public (undocumented)
export type SwatchPickerSlots = {
root: Slot<'div'>;
};

// @public
export type SwatchPickerState = ComponentState<SwatchPickerSlots>;
export type SwatchPickerState = ComponentState<SwatchPickerSlots> & SwatchPickerContextValue & Pick<SwatchPickerProps, 'size' | 'shape'>;

// @public
export const useColorSwatch_unstable: (props: ColorSwatchProps, ref: React_2.Ref<HTMLButtonElement>) => ColorSwatchState;

// @public
export const useColorSwatchStyles_unstable: (state: ColorSwatchState) => ColorSwatchState;

// @public
export const useSwatchPicker_unstable: (props: SwatchPickerProps, ref: React_2.Ref<HTMLDivElement>) => SwatchPickerState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
"@fluentui/scripts-tasks": "*"
},
"dependencies": {
"@fluentui/react-context-selector": "^9.1.52",
"@fluentui/react-jsx-runtime": "^9.0.30",
"@fluentui/react-shared-contexts": "^9.14.0",
"@fluentui/react-tabster": "^9.19.1",
"@fluentui/react-theme": "^9.1.16",
"@fluentui/react-utilities": "^9.18.1",
"@griffel/react": "^1.5.14",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/ColorSwatch/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { ColorSwatch } from './ColorSwatch';
import { colorSwatchClassNames } from './useColorSwatchStyles.styles';

describe('ColorSwatch', () => {
isConformant({
Component: ColorSwatch,
displayName: 'ColorSwatch',
primarySlot: 'button',
testOptions: {
'has-static-classnames': [
{
props: {},
expectedClassNames: {
button: colorSwatchClassNames.button,
},
},
],
},
});

it('renders a default state', () => {
const result = render(<ColorSwatch color="#f09" value="#f09" />);
expect(result.container).toMatchInlineSnapshot(`
<div>
<div
aria-selected="false"
class="fui-ColorSwatch"
role="radio"
style="--fui-SwatchPicker--color: #f09;"
>
<button
class="fui-ColorSwatch__button"
type="button"
/>
</div>
</div>
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
// import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts';
import { useColorSwatch_unstable } from './useColorSwatch';
import { renderColorSwatch_unstable } from './renderColorSwatch';
import { useColorSwatchStyles_unstable } from './useColorSwatchStyles.styles';
import type { ColorSwatchProps } from './ColorSwatch.types';

/**
* ColorSwatch component - TODO: add more docs
*/
export const ColorSwatch: ForwardRefComponent<ColorSwatchProps> = React.forwardRef((props, ref) => {
const state = useColorSwatch_unstable(props, ref);

useColorSwatchStyles_unstable(state);
// TODO uncomment when SwatchPicker is stable
// useCustomStyleHook_unstable('useColorSwatchStyles_unstable')(state);

return renderColorSwatch_unstable(state);
});

ColorSwatch.displayName = 'ColorSwatch';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { SwatchPickerProps } from '../SwatchPicker/SwatchPicker.types';

export type ColorSwatchSlots = {
root: NonNullable<Slot<'div'>>;
button: NonNullable<Slot<'button'>>;
};

/**
* ColorSwatch Props
*/
export type ColorSwatchProps = Omit<ComponentProps<Partial<ColorSwatchSlots>, 'button'>, 'children'> &
Pick<SwatchPickerProps, 'size' | 'shape'> & {
/**
* Swatch color
*/
color: string;

/**
* Swatch value
*/
value: string;
};

/**
* State used in rendering ColorSwatch
*/
export type ColorSwatchState = ComponentState<ColorSwatchSlots> &
Pick<ColorSwatchProps, 'color' | 'size' | 'shape' | 'value'> & {
selected: boolean;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './ColorSwatch';
export * from './ColorSwatch.types';
export * from './renderColorSwatch';
export * from './useColorSwatch';
export * from './useColorSwatchStyles.styles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';
import type { ColorSwatchState, ColorSwatchSlots } from './ColorSwatch.types';

/**
* Render the final JSX of ColorSwatch
*/
export const renderColorSwatch_unstable = (state: ColorSwatchState) => {
assertSlots<ColorSwatchSlots>(state);

return (
<state.root>
<state.button />
</state.root>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import { slot, useEventCallback, getPartitionedNativeProps } from '@fluentui/react-utilities';
import type { ColorSwatchProps, ColorSwatchState } from './ColorSwatch.types';
import { useFocusWithin } from '@fluentui/react-tabster';
import { useSwatchPickerContextValue_unstable } from '../../contexts/swatchPicker';
import { swatchCSSVars } from './useColorSwatchStyles.styles';

/**
* Create the state required to render ColorSwatch.
*
* The returned state can be modified with hooks such as useColorSwatchStyles_unstable,
* before being passed to renderColorSwatch_unstable.
*
* @param props - props from this instance of ColorSwatch
* @param ref - reference to root HTMLButtonElement of ColorSwatch
*/
export const useColorSwatch_unstable = (
props: ColorSwatchProps,
ref: React.Ref<HTMLButtonElement>,
): ColorSwatchState => {
const { color, value } = props;
const size = useSwatchPickerContextValue_unstable(ctx => ctx.size);
const shape = useSwatchPickerContextValue_unstable(ctx => ctx.shape);

const requestSelectionChange = useSwatchPickerContextValue_unstable(ctx => ctx.requestSelectionChange);
const selected = useSwatchPickerContextValue_unstable(ctx => ctx.selectedValue === value);

const onClick = useEventCallback((event: React.MouseEvent<HTMLButtonElement>) =>
requestSelectionChange(event, {
selectedValue: value,
selectedColor: color,
}),
);

const nativeProps = getPartitionedNativeProps({
props,
primarySlotTagName: 'button',
excludedPropNames: ['value', 'color'],
});

const rootVariables = {
[swatchCSSVars.color]: color,
};

const root = slot.always(props.root, {
defaultProps: {
ref: useFocusWithin<HTMLDivElement>(),
role: props.role ?? 'radio',
'aria-selected': selected,
...nativeProps.root,
},
elementType: 'div',
});

const button = slot.always(props.button, {
defaultProps: {
ref,
type: 'button',
onClick,
...nativeProps.primary,
},
elementType: 'button',
});

const state: ColorSwatchState = {
components: {
root: 'div',
button: 'button',
},
root,
button,
size,
shape,
selected,
color,
value,
};

// Root props
state.root.style = {
...rootVariables,
...state.root.style,
};

return state;
};

0 comments on commit ae14927

Please sign in to comment.