-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PB-3786] feature/Create media component for premeet modal (#47)
* Added new transparent modal and modified input props * Added circle button component
- Loading branch information
Showing
8 changed files
with
1,565 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { CaretUp, Warning } from '@phosphor-icons/react'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
type ButtonVariant = 'default' | 'warning' | 'cancel'; | ||
|
||
export interface CircleButtonProps { | ||
children?: JSX.Element | JSX.Element[]; | ||
variant?: ButtonVariant; | ||
active?: boolean; | ||
onClick?: () => void; | ||
onClickToggleButton?: () => void; | ||
className?: string; | ||
dropdown?: React.ReactNode; | ||
indicator?: { | ||
icon?: JSX.Element; | ||
className?: string; | ||
}; | ||
} | ||
|
||
const CircleButton = ({ | ||
children, | ||
variant = 'default', | ||
active = false, | ||
onClick, | ||
onClickToggleButton, | ||
className = '', | ||
dropdown, | ||
indicator, | ||
}: CircleButtonProps): JSX.Element => { | ||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
useEffect(() => { | ||
const handleClickOutside = (event: MouseEvent) => { | ||
if (isOpen) { | ||
const target = event.target as HTMLElement; | ||
const circleButton = document.querySelector(`[data-circle-button="${variant}"]`); | ||
if (circleButton && !circleButton.contains(target)) { | ||
setIsOpen(false); | ||
} | ||
} | ||
}; | ||
|
||
document.addEventListener('click', handleClickOutside); | ||
return () => { | ||
document.removeEventListener('click', handleClickOutside); | ||
}; | ||
}, [isOpen, variant]); | ||
|
||
const handleToggle = (e: React.MouseEvent) => { | ||
e.stopPropagation(); | ||
if (dropdown) { | ||
onClickToggleButton?.(); | ||
setIsOpen(!isOpen); | ||
} | ||
}; | ||
|
||
const handleMainClick = () => { | ||
onClick?.(); | ||
}; | ||
|
||
const getButtonStyles = () => { | ||
switch (variant) { | ||
case 'cancel': | ||
return 'bg-red hover:bg-red/85'; | ||
case 'warning': | ||
return active ? 'bg-white/85' : 'bg-white/25 hover:bg-white/35'; | ||
default: | ||
return active ? 'bg-white/85' : 'bg-white/25 hover:bg-white/35'; | ||
} | ||
}; | ||
|
||
const renderIndicator = () => { | ||
if (!indicator) return null; | ||
|
||
if (variant === 'warning') { | ||
return ( | ||
<div className="absolute -top-1 -right-1 h-5 w-5 bg-orange border border-black/35 rounded-full flex items-center justify-center"> | ||
<Warning size={12} color="black" weight="bold" /> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div | ||
className={`absolute -top-1 -right-1 h-5 w-5 flex items-center justify-center rounded-full ${indicator.className || ''}`} | ||
> | ||
{indicator.icon} | ||
</div> | ||
); | ||
}; | ||
|
||
const renderDropdownButton = () => { | ||
if (!dropdown || variant === 'cancel' || variant === 'warning') return null; | ||
|
||
return ( | ||
<button | ||
onClick={handleToggle} | ||
className="absolute -top-1 -right-1 h-5 w-5 border bg-white border-black/35 rounded-full flex items-center justify-center hover:bg-gray-50" | ||
> | ||
<CaretUp size={10} color="black" weight="fill" /> | ||
</button> | ||
); | ||
}; | ||
|
||
return ( | ||
<div className="relative w-12 h-12" data-circle-button={variant}> | ||
<button | ||
onClick={handleMainClick} | ||
className={` | ||
h-11 w-11 | ||
flex items-center justify-center | ||
rounded-full | ||
transition-all duration-200 | ||
${getButtonStyles()} | ||
${className} | ||
`} | ||
> | ||
{children} | ||
</button> | ||
{renderDropdownButton()} | ||
{renderIndicator()} | ||
{isOpen && dropdown && variant !== 'cancel' && <div className="absolute bottom-full mb-2 left-0">{dropdown}</div>} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CircleButton; |
117 changes: 117 additions & 0 deletions
117
src/components/buttonCircle/__test__/CircleButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { CaretUp } from '@phosphor-icons/react'; | ||
import { fireEvent, render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import CircleButton from '../CircleButton'; | ||
|
||
describe('CircleButton component', () => { | ||
it('should render default button correctly', () => { | ||
const button = render(<CircleButton />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render active default button correctly', () => { | ||
const button = render(<CircleButton active />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render warning button correctly', () => { | ||
const button = render(<CircleButton variant="warning" />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render active warning button correctly', () => { | ||
const button = render(<CircleButton variant="warning" active />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render cancel button correctly', () => { | ||
const button = render(<CircleButton variant="cancel" />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render button with children correctly', () => { | ||
const button = render( | ||
<CircleButton> | ||
<span>Test Child</span> | ||
</CircleButton>, | ||
); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render button with custom className correctly', () => { | ||
const button = render(<CircleButton className="custom-class" />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render button with dropdown correctly', () => { | ||
const dropdown = <div>Dropdown Content</div>; | ||
const button = render(<CircleButton dropdown={dropdown} />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render button with warning indicator correctly', () => { | ||
const button = render(<CircleButton variant="warning" indicator={{}} />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should render button with custom indicator correctly', () => { | ||
const indicator = { | ||
icon: <CaretUp size={12} />, | ||
className: 'custom-indicator', | ||
}; | ||
const button = render(<CircleButton indicator={indicator} />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should handle main button click correctly', () => { | ||
const onClick = vi.fn(); | ||
render(<CircleButton onClick={onClick} />); | ||
const button = screen.getByRole('button'); | ||
fireEvent.click(button); | ||
expect(onClick).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
it('should handle dropdown toggle click correctly', () => { | ||
const onClickToggleButton = vi.fn(); | ||
const dropdown = <div>Dropdown Content</div>; | ||
render(<CircleButton dropdown={dropdown} onClickToggleButton={onClickToggleButton} />); | ||
const toggleButton = screen.getAllByRole('button')[1]; // Get the second button (dropdown toggle) | ||
fireEvent.click(toggleButton); | ||
expect(onClickToggleButton).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
it('should show dropdown content when toggle is clicked', () => { | ||
const dropdown = <div>Dropdown Content</div>; | ||
render(<CircleButton dropdown={dropdown} />); | ||
const toggleButton = screen.getAllByRole('button')[1]; | ||
fireEvent.click(toggleButton); | ||
expect(screen.getByText('Dropdown Content')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should handle dropdown visibility correctly', () => { | ||
const dropdown = <div>Dropdown Content</div>; | ||
render(<CircleButton dropdown={dropdown} />); | ||
const toggleButton = screen.getAllByRole('button')[1]; | ||
|
||
// Open dropdown | ||
fireEvent.click(toggleButton); | ||
expect(screen.getByText('Dropdown Content')).toBeInTheDocument(); | ||
|
||
// Simulate click outside - in this case, we just dispatch a global click event | ||
fireEvent.click(document.body); | ||
expect(screen.queryByText('Dropdown Content')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('should not render dropdown toggle for cancel variant', () => { | ||
const dropdown = <div>Dropdown Content</div>; | ||
const button = render(<CircleButton variant="cancel" dropdown={dropdown} />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
|
||
it('should not render dropdown toggle for warning variant', () => { | ||
const dropdown = <div>Dropdown Content</div>; | ||
const button = render(<CircleButton variant="warning" dropdown={dropdown} />); | ||
expect(button).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.