Skip to content

Commit 67fd360

Browse files
Created SwitchButton component (#656)
1 parent bbc6c42 commit 67fd360

File tree

13 files changed

+528
-10
lines changed

13 files changed

+528
-10
lines changed

packages/private/test-utils/src/__mocks__/@monkvision/common.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
toCamelCase,
1010
getRGBAFromString,
1111
getHexFromRGBA,
12+
changeAlpha,
1213
shadeColor,
1314
InteractiveVariation,
1415
getInteractiveVariants,
@@ -30,6 +31,7 @@ export = {
3031
toCamelCase,
3132
getRGBAFromString,
3233
getHexFromRGBA,
34+
changeAlpha,
3335
shadeColor,
3436
InteractiveVariation,
3537
getInteractiveVariants,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './expects';
22
export * from './dom';
3+
export * from './styles';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function getNumberFromCSSProperty(prop: string | undefined | null): number {
2+
expect(typeof prop).toBe('string');
3+
const reg = /^(\d+)\D*$/;
4+
expect(prop).toMatch(reg);
5+
const result = (prop as string).match(reg);
6+
return Number((result as string[])[1]);
7+
}

packages/public/common-ui-web/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,35 @@ function MyComponent() {
157157
| sight | Sight | The sight to display the SVG overlay of. | ✔️ | |
158158
| getAttributes | <code>(element: Element, groupIds: string[]) => SVGProps<SVGSVGElement> &#124; null</code> | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
159159
| getInnerText | <code>(element: Element, groupIds: string[]) => string &#124; null</code> | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
160+
161+
## SwitchButton
162+
### Description
163+
Switch button component that can be used to turn ON or OFF a feature.
164+
165+
### Example
166+
```tsx
167+
import { SwitchButton } from '@monkvision/common-ui-web';
168+
169+
export function MyComponent() {
170+
const [checked, setChecked] = useState(false);
171+
return (
172+
<div>
173+
<SwitchButton checked={checked} onSwitch={(value) => setChecked(value)} />
174+
</div>
175+
);
176+
}
177+
```
178+
179+
### Props
180+
| Prop | Type | Description | Required | Default Value |
181+
|-------------------------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------|
182+
| size | `'normal' &#124; 'small'` | The size of the button. Normal buttons are bigger and have their icon and labels inside the button. Small buttons are smaller, accept no label and have their icon inside the knob. | | 'normal' |
183+
| checked | boolean | Boolean used to control the SwitchButton. Set to `true` to make the Button switched on and `false` for off. | | false |
184+
| onSwitch | `(value: boolean) => void` | Callback called when the SwitchButton is switched. The value passed as the first parameter is the result `checked` value. | | |
185+
| disabled | boolean | Boolean indicating if the button is disabled or not. | | false |
186+
| checkedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is checked. | | 'primary' |
187+
| checkedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is checked. | | 'text-white' |
188+
| uncheckedPrimaryColor | ColorProp | Primary color (background and knob overlay color) of the button when it is unchecked. | | 'text-tertiary' |
189+
| uncheckedSecondaryColor | ColorProp | Secondary color (knob, labels and icons color) of the button when it is unchecked. | | 'text-white' |
190+
| checkedLabel | ColorProp | Custom label that can be displayed instead of the check icon when the button is checked. This prop is ignored for small buttons. | | |
191+
| uncheckedLabel | ColorProp | Custom label that can be displayed when the button is unchecked. This prop is ignored for small buttons. | | |
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { Styles } from '@monkvision/types';
2+
3+
export const slideTransitionFrames = '0.3s ease-out';
4+
5+
export const sizes = {
6+
normal: {
7+
width: 65,
8+
height: 30,
9+
knobPadding: 2,
10+
labelPadding: 10,
11+
knobOverlayExpansion: 3,
12+
iconSize: 18,
13+
},
14+
small: {
15+
width: 36,
16+
height: 22,
17+
knobPadding: 2,
18+
knobOverlayExpansion: 2,
19+
iconSize: 10,
20+
},
21+
};
22+
23+
export const styles: Styles = {
24+
button: {
25+
width: sizes.normal.width,
26+
height: sizes.normal.height,
27+
padding: `0 ${sizes.normal.labelPadding}px`,
28+
overflow: 'visible',
29+
borderRadius: 9999999,
30+
position: 'relative',
31+
display: 'flex',
32+
alignItems: 'center',
33+
justifyContent: 'space-between',
34+
border: 'none',
35+
cursor: 'pointer',
36+
transition: `background-color ${slideTransitionFrames}`,
37+
},
38+
buttonDisabled: {
39+
opacity: 0.37,
40+
cursor: 'default',
41+
},
42+
buttonSmall: {
43+
padding: 0,
44+
width: sizes.small.width,
45+
height: sizes.small.height,
46+
},
47+
knobOverlay: {
48+
width: sizes.normal.height + 2 * sizes.normal.knobOverlayExpansion,
49+
height: sizes.normal.height + 2 * sizes.normal.knobOverlayExpansion,
50+
borderRadius: '50%',
51+
position: 'absolute',
52+
top: -sizes.normal.knobOverlayExpansion,
53+
left: -sizes.normal.knobOverlayExpansion,
54+
transition: `left ${slideTransitionFrames}`,
55+
},
56+
knobOverlaySmall: {
57+
width: sizes.small.height + 2 * sizes.small.knobOverlayExpansion,
58+
height: sizes.small.height + 2 * sizes.small.knobOverlayExpansion,
59+
top: -sizes.small.knobOverlayExpansion,
60+
left: -sizes.small.knobOverlayExpansion,
61+
},
62+
knobOverlayChecked: {
63+
left: sizes.normal.width - sizes.normal.height - sizes.normal.knobOverlayExpansion,
64+
},
65+
knobOverlaySmallChecked: {
66+
left: sizes.small.width - sizes.small.height - sizes.small.knobOverlayExpansion,
67+
},
68+
knob: {
69+
width: sizes.normal.height - 2 * sizes.normal.knobPadding,
70+
height: sizes.normal.height - 2 * sizes.normal.knobPadding,
71+
borderRadius: '50%',
72+
position: 'absolute',
73+
top: sizes.normal.knobPadding,
74+
left: sizes.normal.knobPadding,
75+
display: 'flex',
76+
alignItems: 'center',
77+
justifyContent: 'center',
78+
transition: `left ${slideTransitionFrames}`,
79+
},
80+
knobSmall: {
81+
width: sizes.small.height - 2 * sizes.small.knobPadding,
82+
height: sizes.small.height - 2 * sizes.small.knobPadding,
83+
top: sizes.small.knobPadding,
84+
left: sizes.small.knobPadding,
85+
},
86+
knobChecked: {
87+
left: sizes.normal.width - sizes.normal.height + sizes.normal.knobPadding,
88+
},
89+
knobSmallChecked: {
90+
left: sizes.small.width - sizes.small.height + sizes.small.knobPadding,
91+
},
92+
label: {
93+
fontSize: 12,
94+
fontWeight: 400,
95+
lineHeight: '18px',
96+
letterSpacing: '0.4px',
97+
transition: `opacity ${slideTransitionFrames}`,
98+
},
99+
icon: {
100+
transition: `opacity ${slideTransitionFrames}`,
101+
},
102+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useInteractiveStatus } from '@monkvision/common';
2+
import { SwitchButtonProps, useSwitchButtonStyles } from './hooks';
3+
import { Icon } from '../../icons';
4+
5+
/**
6+
* Switch button component that can be used to turn ON or OFF a feature.
7+
*
8+
* @see https://uxplanet.org/checkbox-vs-toggle-switch-7fc6e83f10b8
9+
* @example
10+
* export function MyComponent() {
11+
* const [checked, setChecked] = useState(false);
12+
* return (
13+
* <div>
14+
* <SwitchButton checked={checked} onSwitch={(value) => setChecked(value)} />
15+
* </div>
16+
* );
17+
* }
18+
*/
19+
export function SwitchButton({
20+
size = 'normal',
21+
checked = false,
22+
onSwitch,
23+
disabled = false,
24+
checkedPrimaryColor = 'primary',
25+
checkedSecondaryColor = 'text-white',
26+
uncheckedPrimaryColor = 'text-tertiary',
27+
uncheckedSecondaryColor = 'text-white',
28+
checkedLabel,
29+
uncheckedLabel,
30+
}: SwitchButtonProps) {
31+
const { status, eventHandlers } = useInteractiveStatus({ disabled });
32+
const switchButtonStyles = useSwitchButtonStyles({
33+
size,
34+
checked,
35+
checkedPrimaryColor,
36+
checkedSecondaryColor,
37+
uncheckedPrimaryColor,
38+
uncheckedSecondaryColor,
39+
status,
40+
});
41+
42+
const handleSwitch = () => {
43+
if (onSwitch) {
44+
onSwitch(!checked);
45+
}
46+
};
47+
48+
return (
49+
<button
50+
style={switchButtonStyles.buttonStyle}
51+
onClick={handleSwitch}
52+
disabled={disabled}
53+
{...eventHandlers}
54+
data-testid='switch-btn'
55+
>
56+
{size === 'normal' && !checkedLabel && (
57+
<Icon
58+
icon='check'
59+
primaryColor={switchButtonStyles.icon.color}
60+
size={switchButtonStyles.icon.size}
61+
style={switchButtonStyles.icon.style}
62+
/>
63+
)}
64+
{size === 'normal' && checkedLabel && (
65+
<div style={switchButtonStyles.checkedLabelStyle}>{checkedLabel ?? ''}</div>
66+
)}
67+
{size === 'normal' && (
68+
<div style={switchButtonStyles.uncheckedLabelStyle}>{uncheckedLabel ?? ''}</div>
69+
)}
70+
<div style={switchButtonStyles.knobStyle} data-testid='switch-knob'>
71+
{size === 'small' && checked && (
72+
<Icon
73+
icon='check'
74+
primaryColor={switchButtonStyles.iconSmall.color}
75+
size={switchButtonStyles.iconSmall.size}
76+
style={switchButtonStyles.iconSmall.style}
77+
/>
78+
)}
79+
</div>
80+
<div style={switchButtonStyles.knobOverlayStyle}></div>
81+
</button>
82+
);
83+
}

0 commit comments

Comments
 (0)