index.tsx 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. 'use client'
  2. import React, { useEffect, useState } from 'react'
  3. import { Switch as OriginalSwitch } from '@headlessui/react'
  4. import classNames from '@/utils/classnames'
  5. type SwitchProps = {
  6. onChange?: (value: boolean) => void
  7. beforeChange?: () => void
  8. size?: 'xs' | 'sm' | 'md' | 'lg' | 'l'
  9. defaultValue?: boolean
  10. disabled?: boolean
  11. className?: string
  12. }
  13. const Switch = (
  14. {
  15. ref: propRef,
  16. onChange,
  17. beforeChange,
  18. size = 'md',
  19. defaultValue = false,
  20. disabled = false,
  21. className,
  22. }: SwitchProps & {
  23. ref: React.RefObject<HTMLButtonElement>;
  24. },
  25. ) => {
  26. const [enabled, setEnabled] = useState(defaultValue)
  27. useEffect(() => {
  28. setEnabled(defaultValue)
  29. }, [defaultValue])
  30. const wrapStyle = {
  31. lg: 'h-6 w-11',
  32. l: 'h-5 w-9',
  33. md: 'h-4 w-7',
  34. sm: 'h-3 w-5',
  35. xs: 'h-2.5 w-3.5',
  36. }
  37. const circleStyle = {
  38. lg: 'h-5 w-5',
  39. l: 'h-4 w-4',
  40. md: 'h-3 w-3',
  41. sm: 'h-2 w-2',
  42. xs: 'h-1.5 w-1',
  43. }
  44. const translateLeft = {
  45. lg: 'translate-x-5',
  46. l: 'translate-x-4',
  47. md: 'translate-x-3',
  48. sm: 'translate-x-2',
  49. xs: 'translate-x-1.5',
  50. }
  51. const handleBeforeChange = (checked: boolean) => {
  52. if (beforeChange) {
  53. const result: any = beforeChange()
  54. if (result) {
  55. setEnabled(checked)
  56. onChange?.(checked)
  57. }
  58. }
  59. else {
  60. setEnabled(checked)
  61. onChange?.(checked)
  62. }
  63. }
  64. return (
  65. <OriginalSwitch
  66. ref={propRef}
  67. checked={enabled}
  68. onChange={(checked: boolean) => {
  69. if (disabled)
  70. return
  71. handleBeforeChange(checked)
  72. }}
  73. className={classNames(
  74. wrapStyle[size],
  75. enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
  76. 'relative inline-flex flex-shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out',
  77. disabled ? '!opacity-50 !cursor-not-allowed' : '',
  78. size === 'xs' && 'rounded-sm',
  79. className,
  80. )}
  81. >
  82. <span
  83. aria-hidden="true"
  84. className={classNames(
  85. circleStyle[size],
  86. enabled ? translateLeft[size] : 'translate-x-0',
  87. size === 'xs' && 'rounded-[1px]',
  88. 'pointer-events-none inline-block transform rounded-[3px] bg-components-toggle-knob shadow ring-0 transition duration-200 ease-in-out',
  89. )}
  90. />
  91. </OriginalSwitch>
  92. )
  93. }
  94. Switch.displayName = 'Switch'
  95. export default React.memo(Switch)