Commit d9f14025 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: [DetailsV2] Add Activity Chart Time Period Switcher (#6653)

* add endButton and new TimePeriodSwitcher component

* add snapshot testing

* add test file

* remove switcher from TabbedComponent

* update snapshots

* update describe

* update padding

* extra return

* as const notation

* update snapshots

* no more abs positioning

* cleanup tests and add dropdown test

* add divider line to tabbed component

* update design to match new specs

---------
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent d81cb280
import { TableContentContainer } from './shared' import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { Row } from 'nft/components/Flex'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { SupportedTimePeriodsType, TimePeriodSwitcher } from './TimePeriodSwitcher'
const TableContentContainer = styled(Row)`
height: 568px;
justify-content: space-between;
align-items: flex-start;
`
export const ActivityTableContent = () => { export const ActivityTableContent = () => {
return <TableContentContainer>Activity Content</TableContentContainer> const [timePeriod, setTimePeriod] = useState<SupportedTimePeriodsType>(HistoryDuration.Week)
return (
<TableContentContainer>
<span>Activity Content</span>
<TimePeriodSwitcher activeTimePeriod={timePeriod} setTimePeriod={setTimePeriod} />
</TableContentContainer>
)
} }
...@@ -11,8 +11,10 @@ const TabbedComponentContainer = styled.div` ...@@ -11,8 +11,10 @@ const TabbedComponentContainer = styled.div`
const TabsRow = styled(Row)` const TabsRow = styled(Row)`
gap: 32px; gap: 32px;
margin-bottom: 12px;
width: 100; width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
` `
const Tab = styled(ThemedText.SubHeader)<{ isActive: boolean; numTabs: number }>` const Tab = styled(ThemedText.SubHeader)<{ isActive: boolean; numTabs: number }>`
......
import userEvent from '@testing-library/user-event'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { act, render, screen } from 'test-utils/render'
import { TimePeriodSwitcher } from './TimePeriodSwitcher'
describe('NFT Details Activity Time Period Switcher', () => {
const mockSetTimePeriod = jest.fn()
it('renders when week is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Week} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 week')
})
it('renders when month is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Month} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 month')
})
it('renders when year is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Year} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 year')
})
it('renders when all time is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Max} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('All time')
})
it('renders dropdown when clicked', async () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Max} setTimePeriod={mockSetTimePeriod} />)
await act(() => userEvent.click(screen.getByTestId('activity-time-period-switcher')))
expect(screen.queryByTestId('activity-time-period-switcher-dropdown')).toBeTruthy()
})
})
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Dispatch, ReactNode, SetStateAction, useReducer, useRef } from 'react'
import { Check, ChevronDown } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
const SwitcherAndDropdownWrapper = styled.div`
position: relative;
`
const SwitcherWrapper = styled(Row)`
gap: 4px;
padding: 8px;
cursor: pointer;
border-radius: 12px;
width: 92px;
justify-content: space-between;
user-select: none;
background: ${({ theme }) => theme.backgroundInteractive};
${OpacityHoverState}
`
const Chevron = styled(ChevronDown)<{ $isOpen: boolean }>`
height: 16px;
width: 16px;
color: ${({ theme }) => theme.textSecondary};
transform: ${({ $isOpen }) => ($isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
transition: transform ${({ theme }) => theme.transition.duration.fast};
`
const TimeDropdownMenu = styled(Column)`
background-color: ${({ theme }) => theme.backgroundSurface};
box-shadow: ${({ theme }) => theme.deepShadow};
border: 0.5px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
padding: 10px 8px;
gap: 8px;
position: absolute;
top: 42px;
z-index: ${Z_INDEX.dropdown}};
right: 0px;
width: 240px;
`
const DropdownContent = styled(Row)`
gap: 4px;
padding: 10px 8px;
width: 100%;
justify-content: space-between;
border-radius: 8px;
cursor: pointer;
&:hover {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
`
const supportedTimePeriods = [
HistoryDuration.Week,
HistoryDuration.Month,
HistoryDuration.Year,
HistoryDuration.Max,
] as const
export type SupportedTimePeriodsType = typeof supportedTimePeriods[number]
const supportedTimePeriodsData: Record<SupportedTimePeriodsType, ReactNode> = {
[HistoryDuration.Week]: <Trans>1 week</Trans>,
[HistoryDuration.Month]: <Trans>1 month</Trans>,
[HistoryDuration.Year]: <Trans>1 year</Trans>,
[HistoryDuration.Max]: <Trans>All time</Trans>,
}
export const TimePeriodSwitcher = ({
activeTimePeriod,
setTimePeriod,
}: {
activeTimePeriod: SupportedTimePeriodsType
setTimePeriod: Dispatch<SetStateAction<SupportedTimePeriodsType>>
}) => {
const theme = useTheme()
const [isOpen, toggleIsOpen] = useReducer((isOpen) => !isOpen, false)
const menuRef = useRef<HTMLDivElement>(null)
useOnClickOutside(menuRef, () => {
isOpen && toggleIsOpen()
})
return (
<SwitcherAndDropdownWrapper ref={menuRef}>
<SwitcherWrapper onClick={toggleIsOpen} data-testid="activity-time-period-switcher">
<ThemedText.LabelSmall lineHeight="16px" color="textPrimary">
{supportedTimePeriodsData[activeTimePeriod]}
</ThemedText.LabelSmall>
<Chevron $isOpen={isOpen} />
</SwitcherWrapper>
{isOpen && (
<TimeDropdownMenu data-testid="activity-time-period-switcher-dropdown">
{supportedTimePeriods.map((timePeriod) => (
<DropdownContent
key={timePeriod}
onClick={() => {
setTimePeriod(timePeriod)
toggleIsOpen()
}}
>
<ThemedText.BodyPrimary lineHeight="24px">{supportedTimePeriodsData[timePeriod]}</ThemedText.BodyPrimary>
<Check size="16px" color={theme.accentActive} opacity={activeTimePeriod === timePeriod ? 1 : 0} />
</DropdownContent>
))}
</TimeDropdownMenu>
)}
</SwitcherAndDropdownWrapper>
)
}
...@@ -136,10 +136,6 @@ exports[`data page loads with header showing 1`] = ` ...@@ -136,10 +136,6 @@ exports[`data page loads with header showing 1`] = `
justify-content: flex-start; justify-content: flex-start;
} }
.c27 {
height: 568px;
}
.c20 { .c20 {
background: #FFFFFF; background: #FFFFFF;
border: 1px solid #D2D9EE; border: 1px solid #D2D9EE;
...@@ -153,8 +149,10 @@ exports[`data page loads with header showing 1`] = ` ...@@ -153,8 +149,10 @@ exports[`data page loads with header showing 1`] = `
.c21 { .c21 {
gap: 32px; gap: 32px;
margin-bottom: 12px;
width: 100; width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #D2D9EE;
} }
.c22 { .c22 {
...@@ -287,6 +285,61 @@ exports[`data page loads with header showing 1`] = ` ...@@ -287,6 +285,61 @@ exports[`data page loads with header showing 1`] = `
margin-right: auto; margin-right: auto;
} }
.c28 {
position: relative;
}
.c29 {
gap: 4px;
padding: 8px;
cursor: pointer;
border-radius: 12px;
width: 92px;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: #E8ECFB;
-webkit-transition: opacity 250ms ease;
transition: opacity 250ms ease;
}
.c29:hover {
opacity: 0.6;
}
.c29:active {
opacity: 0.4;
}
.c30 {
height: 16px;
width: 16px;
color: #7780A0;
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transition: -webkit-transform 125ms;
-webkit-transition: transform 125ms;
transition: transform 125ms;
}
.c27 {
height: 568px;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: flex-start;
-webkit-box-align: flex-start;
-ms-flex-align: flex-start;
align-items: flex-start;
}
.c0 { .c0 {
padding: 24px 64px; padding: 24px 64px;
height: 100vh; height: 100vh;
...@@ -505,9 +558,41 @@ exports[`data page loads with header showing 1`] = ` ...@@ -505,9 +558,41 @@ exports[`data page loads with header showing 1`] = `
</div> </div>
</div> </div>
<div <div
class="c27" class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez4ry sprinkles_flexDirection_row_sm__rgw6ez4uh sprinkles_alignItems_center_sm__rgw6ez3k c27"
> >
<span>
Activity Content Activity Content
</span>
<div
class="c28"
>
<div
class="c4 c5 c29"
data-testid="activity-time-period-switcher"
>
<div
class="c11 css-1adn0z6"
>
1 week
</div>
<svg
class="c30"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="6 9 12 15 18 9"
/>
</svg>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -652,10 +737,6 @@ exports[`data page loads without header showing 1`] = ` ...@@ -652,10 +737,6 @@ exports[`data page loads without header showing 1`] = `
justify-content: flex-start; justify-content: flex-start;
} }
.c27 {
height: 568px;
}
.c20 { .c20 {
background: #FFFFFF; background: #FFFFFF;
border: 1px solid #D2D9EE; border: 1px solid #D2D9EE;
...@@ -669,8 +750,10 @@ exports[`data page loads without header showing 1`] = ` ...@@ -669,8 +750,10 @@ exports[`data page loads without header showing 1`] = `
.c21 { .c21 {
gap: 32px; gap: 32px;
margin-bottom: 12px;
width: 100; width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #D2D9EE;
} }
.c22 { .c22 {
...@@ -803,6 +886,61 @@ exports[`data page loads without header showing 1`] = ` ...@@ -803,6 +886,61 @@ exports[`data page loads without header showing 1`] = `
margin-right: auto; margin-right: auto;
} }
.c28 {
position: relative;
}
.c29 {
gap: 4px;
padding: 8px;
cursor: pointer;
border-radius: 12px;
width: 92px;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background: #E8ECFB;
-webkit-transition: opacity 250ms ease;
transition: opacity 250ms ease;
}
.c29:hover {
opacity: 0.6;
}
.c29:active {
opacity: 0.4;
}
.c30 {
height: 16px;
width: 16px;
color: #7780A0;
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transition: -webkit-transform 125ms;
-webkit-transition: transform 125ms;
transition: transform 125ms;
}
.c27 {
height: 568px;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-align-items: flex-start;
-webkit-box-align: flex-start;
-ms-flex-align: flex-start;
align-items: flex-start;
}
.c0 { .c0 {
padding: 24px 64px; padding: 24px 64px;
height: 100vh; height: 100vh;
...@@ -1021,9 +1159,41 @@ exports[`data page loads without header showing 1`] = ` ...@@ -1021,9 +1159,41 @@ exports[`data page loads without header showing 1`] = `
</div> </div>
</div> </div>
<div <div
class="c27" class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez4ry sprinkles_flexDirection_row_sm__rgw6ez4uh sprinkles_alignItems_center_sm__rgw6ez3k c27"
> >
<span>
Activity Content Activity Content
</span>
<div
class="c28"
>
<div
class="c4 c5 c29"
data-testid="activity-time-period-switcher"
>
<div
class="c11 css-1adn0z6"
>
1 week
</div>
<svg
class="c30"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<polyline
points="6 9 12 15 18 9"
/>
</svg>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -78,8 +78,10 @@ exports[`data page trait component does not load with asset with no traits 1`] = ...@@ -78,8 +78,10 @@ exports[`data page trait component does not load with asset with no traits 1`] =
.c3 { .c3 {
gap: 32px; gap: 32px;
margin-bottom: 12px;
width: 100; width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #D2D9EE;
} }
.c5 { .c5 {
......
...@@ -10,10 +10,6 @@ export const containerStyles = css` ...@@ -10,10 +10,6 @@ export const containerStyles = css`
align-self: flex-start; align-self: flex-start;
` `
export const TableContentContainer = styled.div`
height: 568px;
`
// Scrim that fades out the top and bottom of the scrollable container, isBottom changes the direction and placement of the fade // Scrim that fades out the top and bottom of the scrollable container, isBottom changes the direction and placement of the fade
export const Scrim = styled.div<{ isBottom?: boolean }>` export const Scrim = styled.div<{ isBottom?: boolean }>`
position: absolute; position: absolute;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment