diff --git a/components/src/components/molecules/CountdownCircle/CountdownCircle.test.tsx b/components/src/components/molecules/CountdownCircle/CountdownCircle.test.tsx index 9335e31d..55d3b20a 100644 --- a/components/src/components/molecules/CountdownCircle/CountdownCircle.test.tsx +++ b/components/src/components/molecules/CountdownCircle/CountdownCircle.test.tsx @@ -2,53 +2,76 @@ import * as React from 'react' import { ThemeProvider } from 'styled-components' -import { cleanup, render, screen, waitFor } from '@/test' +import { act, cleanup, render, screen } from '@/test' import { CountdownCircle } from './CountdownCircle' import { lightTheme } from '@/src/tokens' +const advanceTime = (ms: number) => { + act(() => { + jest.advanceTimersByTime(ms) + }) +} + describe('', () => { + beforeAll(() => { + jest.useFakeTimers() + }) + afterAll(() => { + jest.useRealTimers() + }) afterEach(cleanup) - it('renders', async () => { + it('renders', () => { render( - + , ) + advanceTime(10000) expect(screen.getByTestId('countdown-circle')).toBeInTheDocument() }) - it('should countdown starting from supplied value', async () => { + it('should countdown starting from supplied value', () => { render( - + , ) - await waitFor(() => { - expect(screen.queryByText('9')).toBeInTheDocument() - }) + advanceTime(1000) + expect(screen.queryByText('9')).toBeInTheDocument() }) - it('should not countdown if disabled', async () => { + it('should not countdown if disabled', () => { render( - + , ) - await new Promise((r) => setTimeout(r, 2000)) + advanceTime(1000) expect(screen.queryByText('10')).toBeInTheDocument() }) - it('should call callback on 0', async () => { + it('should call callback on 0', () => { const mockCallback = jest.fn() render( - + + , + ) + advanceTime(1000) + expect(mockCallback).toHaveBeenCalled() + }) + it('should use startTimestamp if provided', () => { + render( + + , ) - await waitFor(() => { - expect(mockCallback).toHaveBeenCalled() - }) + advanceTime(5000) + expect(screen.queryByTestId('countdown-complete-check')).toBeInTheDocument() }) }) diff --git a/components/src/components/molecules/CountdownCircle/CountdownCircle.tsx b/components/src/components/molecules/CountdownCircle/CountdownCircle.tsx index 98d67819..fc1ce87b 100644 --- a/components/src/components/molecules/CountdownCircle/CountdownCircle.tsx +++ b/components/src/components/molecules/CountdownCircle/CountdownCircle.tsx @@ -4,6 +4,7 @@ import styled, { css } from 'styled-components' import { VisuallyHidden } from '../..' import { Colors } from '@/src/tokens' import { getTestId } from '../../../utils/utils' +import { CheckSVG } from '@/src/icons' const CountDownContainer = styled.div( () => css` @@ -31,6 +32,12 @@ const NumberBox = styled.div( color: ${theme.colors.textPlaceholder}; `} + #countdown-complete-check { + stroke-width: ${theme.borderWidths['1.5']}; + overflow: visible; + display: block; + } + ${() => { switch ($size) { case 'small': @@ -109,8 +116,9 @@ type NativeDivProps = React.HTMLAttributes type Props = { accessibilityLabel?: string - countdownAmount: number color?: Colors + startTimestamp?: number + countdownSeconds: number disabled?: boolean callback?: () => void size?: 'small' | 'large' @@ -122,32 +130,43 @@ export const CountdownCircle = React.forwardRef( accessibilityLabel, color = 'textSecondary', size = 'small', - countdownAmount, + countdownSeconds, + startTimestamp, disabled, callback, ...props }: Props, ref: React.Ref, ) => { - const [totalCount, setTotalCount] = React.useState(0) - const [currentCount, setCurrentCount] = React.useState(0) + const _startTimestamp = React.useMemo( + () => Math.ceil((startTimestamp || Date.now()) / 1000), + [startTimestamp], + ) + const endTimestamp = React.useMemo( + () => _startTimestamp + countdownSeconds, + [_startTimestamp, countdownSeconds], + ) + const calculateCurrentCount = React.useCallback( + () => Math.max(endTimestamp - Math.ceil(Date.now() / 1000), 0), + [endTimestamp], + ) + + const [currentCount, setCurrentCount] = React.useState(countdownSeconds) React.useEffect(() => { - setTotalCount(countdownAmount) if (!disabled) { - setCurrentCount(countdownAmount) + setCurrentCount(calculateCurrentCount()) const countInterval = setInterval(() => { - setCurrentCount((prevCount) => { - if (prevCount === 1) { - clearInterval(countInterval) - callback && callback() - } - return prevCount - 1 ? prevCount - 1 : 0 - }) + const currentSeconds = calculateCurrentCount() + if (currentSeconds === 0) { + clearInterval(countInterval) + callback && callback() + } + setCurrentCount(currentSeconds) }, 1000) return () => clearInterval(countInterval) } - }, [callback, countdownAmount, disabled]) + }, [calculateCurrentCount, callback, countdownSeconds, disabled]) return ( - {disabled ? totalCount : currentCount} + {disabled && countdownSeconds} + {!disabled && + (currentCount > 0 ? ( + currentCount + ) : ( + + ))} {accessibilityLabel && ( @@ -170,7 +198,7 @@ export const CountdownCircle = React.forwardRef( cy="12" fill="none" r="9" - strokeDasharray={`${48 * (currentCount / totalCount)}, 56`} + strokeDasharray={`${48 * (currentCount / countdownSeconds)}, 56`} strokeLinecap="round" /> + ``` ## Props @@ -19,9 +19,9 @@ import { CountdownCircle } from '@ensdomains/thorin' ```tsx expand=true live=true - - - + + + ``` @@ -29,13 +29,13 @@ import { CountdownCircle } from '@ensdomains/thorin' ```tsx expand=true live=true - - + + ``` ## Disabled ```tsx expand=true live=true - + ``` diff --git a/docs/src/reference/snippets/molecules/CountdownCircle.snippets.tsx b/docs/src/reference/snippets/molecules/CountdownCircle.snippets.tsx index 33786b17..8e98dc7b 100644 --- a/docs/src/reference/snippets/molecules/CountdownCircle.snippets.tsx +++ b/docs/src/reference/snippets/molecules/CountdownCircle.snippets.tsx @@ -7,6 +7,6 @@ import { Snippet } from '../../../types' export const snippets: Snippet[] = [ { name: 'Basic', - code: , + code: , }, ]