switchButton.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import * as React from 'react';
  2. import styled from '@emotion/styled';
  3. type Props = {
  4. toggle: React.HTMLProps<HTMLButtonElement>['onClick'];
  5. className?: string;
  6. /**
  7. * Toggle color is always active.
  8. */
  9. forceActiveColor?: boolean;
  10. forwardRef?: React.Ref<HTMLButtonElement>;
  11. id?: string;
  12. isActive?: boolean;
  13. isDisabled?: boolean;
  14. isLoading?: boolean;
  15. name?: string;
  16. size?: 'sm' | 'lg';
  17. };
  18. const Switch = ({
  19. forwardRef,
  20. size = 'sm',
  21. isActive,
  22. forceActiveColor,
  23. isLoading,
  24. isDisabled,
  25. toggle,
  26. id,
  27. name,
  28. className,
  29. }: Props) => (
  30. <SwitchButton
  31. ref={forwardRef}
  32. id={id}
  33. name={name}
  34. type="button"
  35. className={className}
  36. onClick={isDisabled ? undefined : toggle}
  37. role="checkbox"
  38. aria-checked={isActive}
  39. isLoading={isLoading}
  40. disabled={isDisabled}
  41. isActive={isActive}
  42. size={size}
  43. data-test-id="switch"
  44. >
  45. <Toggle
  46. isDisabled={isDisabled}
  47. isActive={isActive}
  48. forceActiveColor={forceActiveColor}
  49. size={size}
  50. />
  51. </SwitchButton>
  52. );
  53. type StyleProps = Partial<Props>;
  54. const getSize = (p: StyleProps) => (p.size === 'sm' ? 16 : 24);
  55. const getToggleSize = (p: StyleProps) => getSize(p) - (p.size === 'sm' ? 4 : 8);
  56. const getToggleTop = (p: StyleProps) => (p.size === 'sm' ? 1 : 3);
  57. const getTranslateX = (p: StyleProps) =>
  58. p.isActive ? getToggleTop(p) + getSize(p) * 0.875 : getToggleTop(p);
  59. const SwitchButton = styled('button')<StyleProps>`
  60. display: inline-block;
  61. background: none;
  62. padding: 0;
  63. border: 1px solid ${p => p.theme.border};
  64. position: relative;
  65. box-shadow: inset ${p => p.theme.dropShadowLight};
  66. height: ${getSize}px;
  67. width: ${p => getSize(p) * 1.875}px;
  68. border-radius: ${getSize}px;
  69. transition: border 0.1s, box-shadow 0.1s;
  70. &[disabled] {
  71. cursor: not-allowed;
  72. }
  73. &:focus,
  74. &.focus-visible {
  75. outline: none;
  76. border-color: ${p => p.theme.focusBorder};
  77. box-shadow: ${p => p.theme.focusBorder} 0 0 0 1px;
  78. }
  79. `;
  80. const Toggle = styled('span')<StyleProps>`
  81. display: block;
  82. position: absolute;
  83. border-radius: 50%;
  84. transition: 0.25s all ease;
  85. top: ${getToggleTop}px;
  86. transform: translateX(${getTranslateX}px);
  87. width: ${getToggleSize}px;
  88. height: ${getToggleSize}px;
  89. background: ${p =>
  90. p.isActive || p.forceActiveColor ? p.theme.active : p.theme.border};
  91. opacity: ${p => (p.isDisabled ? 0.4 : null)};
  92. `;
  93. export default React.forwardRef<HTMLButtonElement, Props>((props, ref) => (
  94. <Switch {...props} forwardRef={ref} />
  95. ));