diff --git a/src/components/__tests__/switch.test.tsx b/src/components/__tests__/switch.test.tsx new file mode 100644 index 00000000..4b108c02 --- /dev/null +++ b/src/components/__tests__/switch.test.tsx @@ -0,0 +1,69 @@ +import '@testing-library/jest-dom' + +import { fireEvent, render, screen } from '@testing-library/react' +import React from 'react' + +import { Switch } from '@/components/switch' + +describe('Switch', () => { + it('renders without crashing', () => { + render() + expect(screen.getByRole('checkbox')).toBeInTheDocument() + }) + + it('applies default classes', () => { + render() + const switchElement = screen.getByRole('checkbox').nextSibling + expect(switchElement).toHaveClass('w-[42px] h-[26px] before:h-5 before:w-5') + }) + + it('applies small size classes when size prop is small', () => { + render() + const switchElement = screen.getByRole('checkbox').nextSibling + expect(switchElement).toHaveClass('w-9 h-[22px] before:h-4 before:w-4 before:left-[3px]') + }) + + it('forwards ref to input element', () => { + const ref = React.createRef() + render() + expect(ref.current).toBeInstanceOf(HTMLInputElement) + }) + + it('forwards checked prop to input element', () => { + render() + const inputElement = screen.getByRole('checkbox') + expect(inputElement).toBeChecked() + }) + + it('handles additional props', () => { + render() + const inputElement = screen.getByRole('checkbox') + expect(inputElement).toHaveAttribute('aria-label', 'Custom Switch') + }) + + it('applies custom class names', () => { + const customClass = 'custom-class' + render() + const switchElement = screen.getByRole('checkbox').nextSibling + expect(switchElement).toHaveClass(customClass) + }) + + it('toggles switch state when clicked', () => { + render() + const inputElement = screen.getByRole('checkbox') + expect(inputElement).not.toBeChecked() + + fireEvent.click(inputElement) + expect(inputElement).toBeChecked() + + fireEvent.click(inputElement) + expect(inputElement).not.toBeChecked() + }) + + it('handles additional HTML attributes', () => { + const testId = 'switch-test' + render() + const switchElement = screen.getByTestId(testId) + expect(switchElement).toBeInTheDocument() + }) +}) diff --git a/src/components/switch.tsx b/src/components/switch.tsx new file mode 100644 index 00000000..bc046b61 --- /dev/null +++ b/src/components/switch.tsx @@ -0,0 +1,47 @@ +import { type VariantProps, cva } from 'class-variance-authority' +import React, { forwardRef } from 'react' + +import { cn } from '@/utils/cn' + +const switchVariants = cva( + [ + 'rounded-full bg-disabled-strong relative cursor-pointer transition-all duration-300 ease-in-out', + 'before:content-[""] before:absolute before:bg-white before:rounded-full', + 'before:top-1/2 before:transform before:-translate-y-1/2 before:left-[4px]', + 'before:transition-all before:duration-300 before:ease-in-out', + 'peer-checked:before:left-[18px] peer-checked:bg-switch-base', + ], + + { + variants: { + size: { + default: 'w-[42px] h-[26px] before:h-5 before:w-5', + small: [ + 'w-9 h-[22px] before:h-4 before:w-4 before:left-[3px]', + 'peer-checked:before:left-4', + ], + }, + }, + defaultVariants: { + size: 'default', + }, + }, +) + +export interface SwitchProps + extends VariantProps, + React.HTMLAttributes { + checked?: boolean +} + +export const Switch = forwardRef(function Switch( + { size, className, ...props }, + ref, +) { + return ( +