Skip to content
This repository was archived by the owner on Apr 4, 2022. It is now read-only.

Commit

Permalink
Create dropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
henrypalacios committed Sep 23, 2021
1 parent ea521ce commit 30f505b
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 2 deletions.
68 changes: 66 additions & 2 deletions src/apps/explorer/components/OrdersTableWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useMemo, useState } from 'react'
import styled from 'styled-components'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
Expand All @@ -8,6 +8,7 @@ import { OrdersTableWithData } from './OrdersTableWithData'
import { OrdersTableContext } from './context/OrdersTableContext'
import { useGetOrders } from './useGetOrders'
import { TabItemInterface } from 'components/common/Tabs/Tabs'
import { Dropdown } from '../../components/common/Dropdown'

const StyledTabLoader = styled.span`
padding-left: 4px;
Expand All @@ -28,6 +29,69 @@ const tabItems = (isLoadingOrders: boolean): TabItemInterface[] => {
]
}

const PaginationDropdownButton = styled.div`
font-size: 13px;
font-weight: normal;
white-space: nowrap;
cursor: pointer;
`
const PaginationWrapper = styled.div`
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 50px;
padding: 0 15px;
`

const PaginationText = styled.p`
cursor: pointer;
white-space: nowrap;
font-size: ${({ theme }): string => theme.fontSizeDefault};
`

const DropdownPagination = styled(Dropdown)`
.dropdownItems {
min-width: 70px;
}
`
const PaginationItem = styled.option`
align-items: center;
cursor: pointer;
`

const Pagination: React.FC = () => {
const [pageSize, setPageSize] = useState(20)

const onSetPageSize = (pageOption: number): void => setPageSize(pageOption)

return (
<PaginationWrapper>
<PaginationText>Rows per page:</PaginationText>
<DropdownPagination
dropdownButtonContent={<PaginationDropdownButton>{pageSize}</PaginationDropdownButton>}
items={[10, 20, 30, 50].map((pageOption) => (
<PaginationItem key={pageOption} onClick={(): void => onSetPageSize(pageOption)}>
{pageOption}
</PaginationItem>
))}
/>
</PaginationWrapper>
)
}

const WrapperExtraComponents = styled.div`
align-items: center;
display: flex;
justify-content: flex-end;
height: 100%;
`

const ExtraComponentNode: React.ReactNode = (
<WrapperExtraComponents>
<Pagination />
</WrapperExtraComponents>
)
interface Props {
ownerAddress: string
}
Expand All @@ -43,7 +107,7 @@ const OrdersTableWidget: React.FC<Props> = ({ ownerAddress }) => {

return (
<OrdersTableContext.Provider value={{ orders, error, isFirstLoading }}>
<ExplorerTabs tabItems={tabItems(isLoading)} />
<ExplorerTabs tabItems={tabItems(isLoading)} extra={ExtraComponentNode} />
</OrdersTableContext.Provider>
)
}
Expand Down
48 changes: 48 additions & 0 deletions src/apps/explorer/components/common/Dropdown/Drodown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import styled, { css } from 'styled-components'

import { Meta, Story } from '@storybook/react'
import { GlobalStyles, ThemeToggler } from 'storybook/decorators'

import { Dropdown, DropdownProps, DropdownOption } from './index'

export default {
title: 'ExplorerApp/Dropdown',
component: Dropdown,
decorators: [GlobalStyles, ThemeToggler],
} as Meta

const itemsPerPage = [15, 30, 50, 100]
let pageSize = itemsPerPage[0]

const onSetPageSize = (pageOption: number): void => {
pageSize = pageOption
console.info(`Selected option ${pageSize}`)
}

const PaginationTextCSS = css`
color: ${({ theme }): string => theme.textPrimary1};
font-size: 13px;
font-weight: normal;
white-space: nowrap;
`

const PaginationDropdownButton = styled.div`
${PaginationTextCSS}
cursor: pointer;
white-space: nowrap;
`

const dropdownOptions = itemsPerPage.map((pageOption) => (
<DropdownOption key={pageOption} onClick={(): void => onSetPageSize(pageOption)}>
{pageOption}
</DropdownOption>
))

const Template: Story<DropdownProps> = (args) => <Dropdown {...args} />

export const DefaultTabs = Template.bind({})
DefaultTabs.args = {
items: dropdownOptions,
dropdownButtonContent: <PaginationDropdownButton>{pageSize}</PaginationDropdownButton>,
}
111 changes: 111 additions & 0 deletions src/apps/explorer/components/common/Dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { DOMAttributes, useState, useCallback, createRef, ReactElement } from 'react'
import styled from 'styled-components'

import { BaseCard } from './styled'
import useOnClickOutside from './useOnClickOutside'

const Wrapper = styled.div<{ isOpen: boolean; disabled: boolean }>`
outline: none;
pointer-events: ${(props): string => (props.disabled ? 'none' : 'initial')};
position: relative;
z-index: ${(props): string => (props.isOpen ? '100' : '50')};
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
`
const ButtonContainer = styled.div`
background-color: transparent;
border: none;
display: block;
outline: none;
padding: 0;
user-select: none;
width: 100%;
`
export interface DropdownProps extends DOMAttributes<HTMLDivElement> {
activeItemHighlight?: boolean | undefined
className?: string
closeOnClick?: boolean
currentItem?: number | undefined
disabled?: boolean
items: Array<ReactElement>
triggerClose?: boolean
dropdownButtonContent?: React.ReactNode | string
}

const Items = styled(BaseCard)<{
fullWidth?: boolean
isOpen: boolean
}>`
border-radius: 5px;
border: ${({ theme }): string => `1px solid ${theme.borderPrimary}`};
display: ${(props): string => (props.isOpen ? 'block' : 'none')};
min-width: 160px;
position: absolute;
white-space: nowrap;
`

export const DropdownOption = styled.li`
align-items: center;
cursor: pointer;
`

export const Dropdown: React.FC<DropdownProps> = (props) => {
const {
activeItemHighlight = true,
closeOnClick = true,
className = '',
currentItem = 0,
disabled = false,
items,
dropdownButtonContent = '▼',
} = props
const [isOpen, setIsOpen] = useState<boolean>(false)
const dropdownContainerRef = createRef<HTMLDivElement>()
useOnClickOutside(dropdownContainerRef, () => setIsOpen(!isOpen))

const onButtonClick = useCallback(
(e) => {
e.stopPropagation()
if (disabled) return
setIsOpen(!isOpen)
},
[disabled, isOpen],
)

return (
<Wrapper
className={`dropdown-container ${isOpen ? 'dropdown-open' : ''} ${className}`}
disabled={disabled}
isOpen={isOpen}
ref={dropdownContainerRef}
>
<ButtonContainer onClick={onButtonClick}>{dropdownButtonContent}</ButtonContainer>
<Items className="dropdown-options" isOpen={isOpen}>
{items.map((item: ReactElement, index: number) => {
const isActive = activeItemHighlight && index === currentItem

return React.cloneElement(item, {
className: `dropdown-item ${isActive && 'active'}`,
key: item.key ? item.key : index,
onClick: (e: Event) => {
e.stopPropagation()

if (closeOnClick) {
setIsOpen(false)
}

if (!item.props.onClick) {
return
}

item.props.onClick()
},
})
})}
</Items>
</Wrapper>
)
}
11 changes: 11 additions & 0 deletions src/apps/explorer/components/common/Dropdown/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components'

export const BaseCard = styled.div<{ noPadding?: boolean }>`
display: flex;
flex-direction: column;
position: relative;
`

BaseCard.defaultProps = {
noPadding: false,
}
29 changes: 29 additions & 0 deletions src/apps/explorer/components/common/Dropdown/useOnClickOutside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, RefObject } from 'react'

type Event = MouseEvent | TouchEvent

const useOnClickOutside = <T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,
handler: (event: Event) => void,
): void => {
useEffect(() => {
const listener = (event: Event): void => {
const el = ref?.current
if (!el || el.contains((event?.target as Node) || null)) {
return
}

handler(event) // Call the handler only if the click is outside of the element passed.
}

document.addEventListener('mousedown', listener)
document.addEventListener('touchstart', listener)

return (): void => {
document.removeEventListener('mousedown', listener)
document.removeEventListener('touchstart', listener)
}
}, [ref, handler]) // Reload only if ref or handler changes
}

export default useOnClickOutside

0 comments on commit 30f505b

Please sign in to comment.