Skip to content

Commit

Permalink
feat: forward ref for <Switch> (#2152)
Browse files Browse the repository at this point in the history
* feat: forward ref for `<Switch>``

Ref #2145

* Create brave-meals-hammer.md
  • Loading branch information
sebald authored Jun 8, 2022
1 parent 8f3c3e1 commit 8980b64
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 90 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-meals-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marigold/components": patch
---

feat: forward ref for `<Switch>`
11 changes: 11 additions & 0 deletions packages/components/src/Switch/Switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,14 @@ test('supports controlled component usage', () => {
expect(onChange).toHaveBeenCalledWith(false);
expect(input).toHaveAttribute('aria-checked', 'false');
});

test('forwards ref', () => {
const ref = React.createRef<HTMLInputElement>();
render(
<ThemeProvider theme={theme}>
<Switch ref={ref}>Label</Switch>
</ThemeProvider>
);

expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
187 changes: 97 additions & 90 deletions packages/components/src/Switch/Switch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useRef } from 'react';
import React, { forwardRef } from 'react';
import { useFocusRing } from '@react-aria/focus';
import { useSwitch } from '@react-aria/switch';
import { useObjectRef } from '@react-aria/utils';
import { useToggleState } from '@react-stately/toggle';
import { AriaSwitchProps } from '@react-types/switch';

Expand Down Expand Up @@ -40,110 +41,116 @@ export interface SwitchProps
size?: string;
width?: string;
}

// Component
// ---------------
export const Switch = ({
variant,
size,
width = '100%',
checked,
disabled,
readOnly,
defaultChecked,
...rest
}: SwitchProps) => {
const ref = useRef<HTMLInputElement>(null);
// Adjust props to the react-aria API
const props = {
isSelected: checked,
isDisabled: disabled,
isReadOnly: readOnly,
defaultSelected: defaultChecked,
...rest,
};
export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
(
{
variant,
size,
width = '100%',
checked,
disabled,
readOnly,
defaultChecked,
...rest
},
ref
) => {
const inputRef = useObjectRef(ref);
// Adjust props to the react-aria API
const props = {
isSelected: checked,
isDisabled: disabled,
isReadOnly: readOnly,
defaultSelected: defaultChecked,
...rest,
};

const state = useToggleState(props);
const { inputProps } = useSwitch(props, state, ref);
const { isFocusVisible, focusProps } = useFocusRing();
const stateProps = useStateProps({
checked: state.isSelected,
disabled: disabled,
readOnly: readOnly,
focus: isFocusVisible,
});
const state = useToggleState(props);
const { inputProps } = useSwitch(props, state, inputRef);
const { isFocusVisible, focusProps } = useFocusRing();
const stateProps = useStateProps({
checked: state.isSelected,
disabled: disabled,
readOnly: readOnly,
focus: isFocusVisible,
});

const styles = useComponentStyles(
'Switch',
{ variant, size },
{ parts: ['container', 'label', 'track', 'thumb'] }
);
const styles = useComponentStyles(
'Switch',
{ variant, size },
{ parts: ['container', 'label', 'track', 'thumb'] }
);

return (
<Box
as="label"
__baseCSS={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '1ch',
position: 'relative',
width,
}}
css={styles.container}
>
<Box
as="input"
ref={ref}
css={{
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
zIndex: 1,
opacity: 0.0001,
cursor: inputProps.disabled ? 'not-allowed' : 'pointer',
}}
{...inputProps}
{...focusProps}
/>
{props.children && <Box css={styles.label}>{props.children}</Box>}
return (
<Box
as="label"
__baseCSS={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '1ch',
position: 'relative',
width: 48,
height: 24,
bg: '#dee2e6',
borderRadius: 20,
width,
}}
css={styles.track}
{...stateProps}
css={styles.container}
>
<Box
__baseCSS={{
display: 'block',
as="input"
ref={inputRef}
css={{
position: 'absolute',
top: 1,
width: '100%',
height: '100%',
top: 0,
left: 0,
zIndex: 1,
opacity: 0.0001,
cursor: inputProps.disabled ? 'not-allowed' : 'pointer',
}}
{...inputProps}
{...focusProps}
/>
{props.children && <Box css={styles.label}>{props.children}</Box>}
<Box
__baseCSS={{
position: 'relative',
width: 48,
height: 24,
bg: '#dee2e6',
borderRadius: 20,
}}
css={styles.track}
{...stateProps}
>
<Box
__baseCSS={{
display: 'block',
position: 'absolute',
top: 1,
left: 0,

willChange: 'transform',
transform: 'translateX(1px)',
transition: 'all 0.1s cubic-bezier(.7, 0, .3, 1)',
willChange: 'transform',
transform: 'translateX(1px)',
transition: 'all 0.1s cubic-bezier(.7, 0, .3, 1)',

height: 22,
width: 22,
height: 22,
width: 22,

borderRadius: 9999,
bg: '#fff',
borderRadius: 9999,
bg: '#fff',

'&:checked': {
transform: 'translateX(calc(47px - 100%))',
},
}}
css={styles.thumb}
{...stateProps}
/>
'&:checked': {
transform: 'translateX(calc(47px - 100%))',
},
}}
css={styles.thumb}
{...stateProps}
/>
</Box>
</Box>
</Box>
);
};
);
}
);

0 comments on commit 8980b64

Please sign in to comment.