Skip to content

Commit

Permalink
v0.2.12
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoo0122 authored Oct 27, 2020
2 parents 33cc145 + 9e60f88 commit d898816
Show file tree
Hide file tree
Showing 28 changed files with 792 additions and 39 deletions.
40 changes: 40 additions & 0 deletions src/components/List/ListItem/ListItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* External dependencies */
import React from 'react'
import base from 'paths.macro'

/* Internal dependencies */
import { Navigation } from '../../../layout/Navigation'
import { getTitle } from '../../../utils/utils'
import ListItem from './ListItem'

export default {
title: getTitle(base),
component: ListItem,
argTypes: {
onClick: { control: { action: 'onClick' } },
active: { control: { type: 'boolean' } },
},
}

const SIDEBAR_WIDTH = 240

const Template = ({ ...otherListItemProps }) => (
<Navigation
withScroll
disableResize
title="사이드바"
minWidth={SIDEBAR_WIDTH}
>
<ListItem
optionKey="menu-item-0"
{...otherListItemProps}
/>
</Navigation>
)

export const Primary = Template.bind({})

Primary.args = {
content: '전체 상태',
active: false,
}
32 changes: 32 additions & 0 deletions src/components/List/ListItem/ListItem.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* Internal dependencies */
import { css, styled } from '../../../styling/Theme'
import Palette from '../../../styling/Palette'
import { StyledWrapperProps } from './ListItem.types'

const ActiveItemStyle = css<StyledWrapperProps>`
color: ${Palette.blue500};
background-color: ${Palette.blue100};
`

export const Wrapper = styled.div<StyledWrapperProps>`
display: flex;
align-items: center;
height: 32px;
padding: 0 8px;
margin-right: 6px;
margin-left: 6px;
font-size: 14px;
font-weight: normal;
color: ${props => props.theme?.colors?.text7};
text-decoration: none;
cursor: pointer;
border-radius: 6px;
&:hover {
${props => (props.active ? '' : `
background-color: ${props.theme?.colors?.background3};
`)}
}
${props => (props.active && ActiveItemStyle)}
`
45 changes: 45 additions & 0 deletions src/components/List/ListItem/ListItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* External dependencies */
import React from 'react'
import { render } from '@testing-library/react'

/* Internal dependencies */
import ListItem, { SIDEBAR_MENU_ITEM_TEST_ID } from './ListItem'
import ListItemProps from './ListItem.types'

describe('ListItem', () => {
let props: ListItemProps

beforeEach(() => {
props = {
content: 'this is content',
optionKey: 'menu-item',
active: false,
}
})

const renderComponent = (optionProps?: Partial<ListItemProps>) => render(
<ListItem {...props} {...optionProps} />,
)

it('should have "optionKey" value on "data-option-key" ', () => {
const { getByTestId } = renderComponent({ optionKey: 'my-menu-item' })
const rendered = getByTestId(SIDEBAR_MENU_ITEM_TEST_ID)

expect(rendered).toHaveAttribute('data-option-key', 'my-menu-item')
})

it('should have "data-active" attribute when "active" prop is "true', () => {
const { getByTestId } = renderComponent({ active: true })
const rendered = getByTestId(SIDEBAR_MENU_ITEM_TEST_ID)

expect(rendered).toHaveAttribute('data-active', 'true')
})

it('should have "a tag" related attributes when "href" prop is string', () => {
const { getByTestId } = renderComponent({ href: 'https://naver.com' })
const rendered = getByTestId(SIDEBAR_MENU_ITEM_TEST_ID)
expect(rendered).toHaveAttribute('href', 'https://naver.com')
expect(rendered).toHaveAttribute('rel', 'noopener noreferer')
expect(rendered).toHaveAttribute('target', '_blank')
})
})
91 changes: 91 additions & 0 deletions src/components/List/ListItem/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* External dependencies */
import React, { Ref, forwardRef, useCallback, useMemo } from 'react'
import { get, noop, isNil } from 'lodash-es'

/* Internal dependencies */
import { mergeClassNames } from '../../../utils/stringUtils'
import ListItemProps from './ListItem.types'
import { Wrapper } from './ListItem.styled'

export const SIDEBAR_MENU_ITEM_COMPONENT_NAME = 'ListItem'
export const SIDEBAR_MENU_ITEM_TEST_ID = 'ch-design-system-sidebar-menu-item'

export function isListItem(element: any): element is React.ReactElement<ListItemProps> {
return React.isValidElement(element) &&
get(element, 'type.displayName') === SIDEBAR_MENU_ITEM_COMPONENT_NAME
}

function ListItemComponent({
as,
testId = SIDEBAR_MENU_ITEM_TEST_ID,
content,
href,
hide,
/* OptionItem Props */
optionKey,
/* Activable Element Props */
active = false,
activeClassName,
/* HTMLAttribute Props */
onClick = noop,
className,
...othreProps
}: ListItemProps, forwardedRef: Ref<any>) {
const clazzName = useMemo(() => (
mergeClassNames(className, ((active && activeClassName) || undefined))
), [
className,
activeClassName,
active,
])

const handleClick = useCallback((e) => {
if (!active) {
onClick(e)
}
}, [active, onClick])

if (hide) return null

if (!isNil(href)) {
return (
<Wrapper
ref={forwardedRef}
as="a"
className={clazzName}
draggable={false}
href={href}
target="_blank"
rel="noopener noreferer"
onClick={handleClick}
active={active}
data-active={active}
data-option-key={optionKey}
data-testid={testId}
{...othreProps}
>
{ content }
</Wrapper>
)
}

return (
<Wrapper
as={as}
className={clazzName}
onClick={handleClick}
active={active}
data-active={active}
data-option-key={optionKey}
data-testid={testId}
{...othreProps}
>
{ content }
</Wrapper>
)
}

const ListItem = forwardRef(ListItemComponent)
ListItem.displayName = SIDEBAR_MENU_ITEM_COMPONENT_NAME

export default ListItem
11 changes: 11 additions & 0 deletions src/components/List/ListItem/ListItem.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Internal dependencies */
import ActivableElement from '../../../types/ActivatableElement'
import { ContentComponentProps, UIComponentProps } from '../../../types/ComponentProps'
import OptionItem from '../../../types/OptionItem'

export default interface ListItemProps extends ContentComponentProps, OptionItem, ActivableElement {
href?: string
hide?: boolean
}

export interface StyledWrapperProps extends UIComponentProps, OptionItem, ActivableElement {}
10 changes: 10 additions & 0 deletions src/components/List/ListItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import ListItem from './ListItem'
import type ListItemProps from './ListItem.types'

export type {
ListItemProps,
}

export {
ListItem,
}
54 changes: 54 additions & 0 deletions src/components/List/ListMenuGroup/ListMenuGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* External dependencies */
import React from 'react'
import base from 'paths.macro'
import { v4 as uuid } from 'uuid'
import { range } from 'lodash-es'

/* Internal dependencies */
import { Navigation } from '../../../layout/Navigation'
import { getTitle } from '../../../utils/utils'
import { ListItem } from '../ListItem'
import ListMenuGroup from './ListMenuGroup'

export default {
title: getTitle(base),
component: ListMenuGroup,
argTypes: {
open: {
control: {
type: 'boolean',
},
},
},
}

const SIDEBAR_WIDTH = 240

const Template = ({ ...otherListMenuGroupProps }) => (
<Navigation
withScroll
disableResize
title="사이드바"
minWidth={SIDEBAR_WIDTH}
>
<ListMenuGroup
{...otherListMenuGroupProps}
>
{ range(0, 4).map(n => (
<ListItem
key={uuid()}
optionKey={`menu-item-${n}`}
content={`아이템 ${n}`}
/>
)) }
</ListMenuGroup>
</Navigation>
)

export const Primary = Template.bind({})

Primary.args = {
content: '전체 상태',
leftIcon: 'sent',
selectedOptionIndex: null,
}
42 changes: 42 additions & 0 deletions src/components/List/ListMenuGroup/ListMenuGroup.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* External dependencies */
import { isNil } from 'lodash-es'

/* Internal dependencies */
import { styled } from '../../../styling/Theme'
import { StyledWrapperProps } from './ListMenuGroup.types'

export const GroupItemWrapper = styled.div<StyledWrapperProps>`
display: flex;
align-items: center;
height: 32px;
padding: 0 8px;
margin-right: 6px;
margin-left: 6px;
font-size: 14px;
font-weight: normal;
color: ${props => props.theme?.colors?.text7};
text-decoration: none;
cursor: pointer;
border-radius: 6px;
&:hover {
background-color: ${props => props.theme?.colors?.background3};
}
${props => !isNil(props.currentMenuItemIndex) && `
color: ${props.theme?.colors?.focus5};
background-color: ${props.theme?.colors?.background2};
`}
`

export const GroupItemContentWrapper = styled.div`
display: flex;
flex: 1;
align-items: center;
`

export const ChildrenWrapper = styled.div`
& > * {
padding-left: 42px;
}
`
61 changes: 61 additions & 0 deletions src/components/List/ListMenuGroup/ListMenuGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* External dependencies */
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { v4 as uuid } from 'uuid'
import { range } from 'lodash-es'

/* Internal dependencies */
import { ListItem } from '../ListItem'
import ListMenuGroup, { SIDEBAR_MENU_GROUP_TEST_ID } from './ListMenuGroup'
import ListMenuGroupProps from './ListMenuGroup.types'

describe('ListMenuGroup', () => {
let props: ListMenuGroupProps

beforeEach(() => {
props = {
open: true,
selectedOptionIndex: 0,
content: 'campaigns',
}
})

const renderComponent = (optionProps?: Partial<ListMenuGroupProps>) => render(
<ListMenuGroup {...props} {...optionProps}>
{ range(0, 4).map(n => (
<ListItem
key={uuid()}
optionKey={`menu-item-${n}`}
content={`item ${n}`}
/>
)) }
</ListMenuGroup>,
)

it('should have default styles', () => {
const { getByTestId } = renderComponent()
const rendered = getByTestId(SIDEBAR_MENU_GROUP_TEST_ID)

expect(rendered).toHaveStyle('display: flex;')
expect(rendered).toHaveStyle('align-items: center;')
expect(rendered).toHaveStyle('height: 32px;')
})

it(
'should have index on "data-active-index" attr when "selectedOptionIndex" given',
() => {
const { getByTestId } = renderComponent({ selectedMenuItemIndex: 2 })
const rendered = getByTestId(SIDEBAR_MENU_GROUP_TEST_ID)

expect(rendered).toHaveAttribute('data-active-index', '2')
})

it('should change "data-active-index"', () => {
const { getByTestId } = renderComponent({ selectedMenuItemIndex: 1 })
const rendered = getByTestId(SIDEBAR_MENU_GROUP_TEST_ID)

expect(rendered).toHaveAttribute('data-active-index', '1')
fireEvent.click(screen.getByText('item 3'))
expect(rendered).toHaveAttribute('data-active-index', '3')
})
})
Loading

0 comments on commit d898816

Please sign in to comment.