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

(react-switch) - Adding dragging functionality #19864

Merged
merged 16 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 13 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
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Adding dragging to the Switch component.",
"packageName": "@fluentui/react-switch",
"email": "czearing@outlook.com",
"dependentChangeType": "patch"
}
3 changes: 2 additions & 1 deletion packages/react-switch/etc/react-switch.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface SwitchCommons {
checked?: boolean;
defaultChecked?: boolean;
disabled?: boolean;
onChange?: (ev: React_2.ChangeEvent<HTMLInputElement>, data: {
onChange?: (ev: React_2.PointerEvent<HTMLDivElement> | React_2.KeyboardEvent<HTMLDivElement>, data: {
checked: boolean;
}) => void;
}
Expand All @@ -39,6 +39,7 @@ export type SwitchSlots = {
thumbWrapper: IntrinsicShorthandProps<'div'>;
thumb: IntrinsicShorthandProps<'div'>;
input: IntrinsicShorthandProps<'input'>;
activeRail: IntrinsicShorthandProps<'div'>;
};

// @public (undocumented)
Expand Down
98 changes: 67 additions & 31 deletions packages/react-switch/src/Switch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,35 @@ const useIosStyles = makeStyles({
height: '30px',

':hover .ms-Switch-thumb': {
background: 'white',
':before': {
background: 'white',
},
},

':active .ms-Switch-thumb': {
background: 'white',
':before': {
background: 'white',
},
},

':hover .ms-Switch-track': {
borderColor: 'none',
},
// Unchecked
':before': {
borderColor: 'none',
},

'&.checked': {
':hover .ms-Switch-track': {
// Checked
':after': {
background: '#4cd964',
},
},

'& .ms-Switch-track': {
':active .ms-Switch-track': {
':before': {
borderColor: 'none',
},
':after': {
background: '#4cd964',
border: 'none',
},
},
},
Expand All @@ -51,16 +61,29 @@ const useIosStyles = makeStyles({
thumb: {
width: '27px',
height: '27px',
background: 'white',
boxShadow: `
0px 3px 8px 0px rgba(0, 0, 0, 0.15),
0px 3px 1px 0px rgba(0, 0, 0, 0.06)
`,
':before': {
opacity: 1,
background: 'white',
},
':after': {
opacity: 1,
background: 'white',
},
},

track: {
background: 'white',
border: '1px solid #e0e0e0',
':after': {
background: '#4cd964',
border: 'none',
},
':before': {
background: 'white',
border: '1px solid #e0e0e0',
},
},
});

Expand All @@ -70,28 +93,29 @@ const useMaterialStyles = makeStyles({
height: '14px',

':hover .ms-Switch-thumb': {
background: 'white',
},

':hover .ms-Switch-track': {
background: '#9f9f9f',
},

'&.checked': {
'& .ms-Switch-thumb': {
background: '#f50057',
':before': {
background: 'white',
},

':hover .ms-Switch-thumb': {
':after': {
background: '#f50057',
},
},

'& .ms-Switch-track': {
':hover .ms-Switch-track': {
':before': {
background: '#9f9f9f',
},
':after': {
background: '#fa80ab',
border: 'none',
},
},

':hover .ms-Switch-track': {
':active .ms-Switch-track': {
':before': {
background: '#9f9f9f',
},
':after': {
background: '#fa80ab',
border: 'none',
},
Expand All @@ -107,23 +131,35 @@ const useMaterialStyles = makeStyles({
thumb: {
width: '20px',
height: '20px',
background: 'white',
boxShadow: '0px 2px 1px -1px rgb(0 0 0 / 20%), 0px 1px 1px 0px rgb(0 0 0 / 14%), 0px 1px 3px 0px rgb(0 0 0 / 12%)',
':before': {
background: 'white',
},
':after': {
background: '#f50057',
},
},

track: {
background: '#9f9f9f',
border: 'none',
':before': {
background: '#9f9f9f',
border: 'none',
},
':after': {
background: '#fa80ab',
border: 'none',
},
},
});

export const BasicSwitchExample = (props: SwitchProps) => {
const styles = useStyles();
const [switchValue, setSwitchValue] = React.useState(false);

const switchOnChange = (ev: React.ChangeEvent<HTMLInputElement>, data: { checked: boolean }) => {
setSwitchValue(data.checked);
};
const switchOnChange = (
ev: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
data: { checked: boolean },
) => setSwitchValue(data.checked);

return (
<div className={styles.root}>
Expand Down
20 changes: 12 additions & 8 deletions packages/react-switch/src/components/Switch/Switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ describe('Switch', () => {
const inputRef = React.createRef<HTMLInputElement>();
const eventHandler = jest.fn();

render(<Switch checked={false} onChange={eventHandler} input={{ ref: inputRef }} />);
const inputElement = screen.getByRole('switch');
render(<Switch data-testid="root-element" checked={false} onChange={eventHandler} input={{ ref: inputRef }} />);
const rootElement = screen.getByTestId('root-element');

fireEvent.click(inputElement);
fireEvent.pointerDown(rootElement);
fireEvent.pointerUp(rootElement);

expect(eventHandler).toBeCalledTimes(1);
expect(eventHandler.mock.calls[0][1]).toEqual({ checked: true });
Expand All @@ -72,14 +73,17 @@ describe('Switch', () => {
it('calls onChange with the correct value', () => {
const eventHandler = jest.fn();

render(<Switch onChange={eventHandler} />);
render(<Switch data-testid="root-element" onChange={eventHandler} />);

const input = screen.getByRole('switch');
const rootElement = screen.getByTestId('root-element');
expect(eventHandler).toBeCalledTimes(0);

fireEvent.click(input);
fireEvent.click(input);
fireEvent.click(input);
fireEvent.pointerDown(rootElement);
fireEvent.pointerUp(rootElement);
fireEvent.pointerDown(rootElement);
fireEvent.pointerUp(rootElement);
fireEvent.pointerDown(rootElement);
fireEvent.pointerUp(rootElement);

expect(eventHandler).toBeCalledTimes(3);
expect(eventHandler.mock.calls[2][1]).toEqual({ checked: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export type SwitchSlots = {
* The hidden input that handles the Switch's internal functionality.
*/
input: IntrinsicShorthandProps<'input'>;

/**
* The area in which the Switch's rail allows for the thumb to be dragged.
*/
activeRail: IntrinsicShorthandProps<'div'>;
};

export interface SwitchCommons {
Expand Down Expand Up @@ -54,7 +59,7 @@ export interface SwitchCommons {
* Callback to be called when the `checked` value changes.
*/
onChange?: (
ev: React.ChangeEvent<HTMLInputElement>,
ev: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>,
data: {
checked: boolean;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exports[`Switch Snapshot Tests renders a basic Switch checked 1`] = `
/>
<div
class=""
style="transform: translate(100%);"
style="transform: translate(100%); transition: transform .1s cubic-bezier(0.33, 0.0, 0.67, 1), opacity .1s cubic-bezier(0.33, 0.0, 0.67, 1);"
>
<div
class="ms-Switch-thumb"
Expand All @@ -20,9 +20,13 @@ exports[`Switch Snapshot Tests renders a basic Switch checked 1`] = `
<input
checked=""
class=""
readonly=""
role="switch"
type="checkbox"
/>
<div
class=""
/>
</div>
</div>
`;
Expand All @@ -38,17 +42,21 @@ exports[`Switch Snapshot Tests renders a basic Switch unchecked 1`] = `
/>
<div
class=""
style="transform: translate(0%);"
style="transform: translate(0%); transition: transform .1s cubic-bezier(0.33, 0.0, 0.67, 1), opacity .1s cubic-bezier(0.33, 0.0, 0.67, 1);"
>
<div
class="ms-Switch-thumb"
/>
</div>
<input
class=""
readonly=""
role="switch"
type="checkbox"
/>
<div
class=""
/>
</div>
</div>
`;
Expand All @@ -64,7 +72,7 @@ exports[`Switch Snapshot Tests renders a disabled Switch checked 1`] = `
/>
<div
class=""
style="transform: translate(100%);"
style="transform: translate(100%); transition: transform .1s cubic-bezier(0.33, 0.0, 0.67, 1), opacity .1s cubic-bezier(0.33, 0.0, 0.67, 1);"
>
<div
class="ms-Switch-thumb"
Expand All @@ -73,9 +81,13 @@ exports[`Switch Snapshot Tests renders a disabled Switch checked 1`] = `
<input
checked=""
class=""
readonly=""
role="switch"
type="checkbox"
/>
<div
class=""
/>
</div>
</div>
`;
Expand All @@ -91,17 +103,21 @@ exports[`Switch Snapshot Tests renders a disabled Switch unchecked 1`] = `
/>
<div
class=""
style="transform: translate(0%);"
style="transform: translate(0%); transition: transform .1s cubic-bezier(0.33, 0.0, 0.67, 1), opacity .1s cubic-bezier(0.33, 0.0, 0.67, 1);"
>
<div
class="ms-Switch-thumb"
/>
</div>
<input
class=""
readonly=""
role="switch"
type="checkbox"
/>
<div
class=""
/>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const renderSwitch = (state: SwitchState) => {
<slots.thumb {...slotProps.thumb} />
</slots.thumbWrapper>
<slots.input {...slotProps.input} />
<slots.activeRail {...slotProps.activeRail} />
</slots.root>
);
};
13 changes: 11 additions & 2 deletions packages/react-switch/src/components/Switch/useSwitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import type { SwitchProps, SwitchSlots, SwitchState } from './Switch.types';
/**
* Array of all shorthand properties listed in SwitchSlots
*/
export const switchShorthandProps: (keyof SwitchSlots)[] = ['root', 'track', 'thumbWrapper', 'thumb', 'input'];
export const switchShorthandProps: (keyof SwitchSlots)[] = [
'root',
'track',
'thumbWrapper',
'thumb',
'activeRail',
'input',
];

/**
* Given user props, returns state and render function for a Switch.
*/
export const useSwitch = (props: SwitchProps, ref: React.Ref<HTMLElement>): SwitchState => {
const { track, thumbWrapper, thumb, input, defaultChecked, checked, disabled, onChange } = props;
const { track, thumbWrapper, thumb, activeRail, input, defaultChecked, checked, disabled, onChange } = props;
const state: SwitchState = {
defaultChecked,
checked,
Expand All @@ -28,11 +35,13 @@ export const useSwitch = (props: SwitchProps, ref: React.Ref<HTMLElement>): Swit
track: 'div',
thumbWrapper: 'div',
thumb: 'div',
activeRail: 'div',
input: 'input',
},
track: resolveShorthand(track, { required: true }),
thumbWrapper: resolveShorthand(thumbWrapper, { required: true }),
thumb: resolveShorthand(thumb, { required: true }),
activeRail: resolveShorthand(activeRail, { required: true }),
input: resolveShorthand(input, {
required: true,
defaultProps: {
Expand Down
Loading