Skip to content

Commit

Permalink
feat(mobile): mobile menus: horizontal, select, level
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Yuferev committed Mar 20, 2019
1 parent 6b07bb5 commit bda9f54
Show file tree
Hide file tree
Showing 12 changed files with 529 additions and 0 deletions.
47 changes: 47 additions & 0 deletions packages/mobile/src/horizontal-menu/HorizontalMenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Horizontal menu

Горизонтальное меню с прокруткой через touch-события. Используется как первый уровень меню.

```jsx
class HorizontalMenuDemo extends React.Component {
constructor() {
super()
this.state = {
activeId: 'requests',
}
}

render() {
return (
<HorizontalMenu
id="kassa-menu-first"
activeId={this.state.activeId}
onClick={item => this.setState({activeId: item.id})}
items={[
{
title: 'Услуги',
id: 'merchants',
},
{
title: 'Заявки',
id: 'requests',
},

{
title: 'Пользователи',
id: 'users',
},

{
title: 'Поддержка',
id: 'support',
},
]}
/>
)
}
}
;<Spacer size="xl">
<HorizontalMenuDemo />
</Spacer>
```
91 changes: 91 additions & 0 deletions packages/mobile/src/horizontal-menu/HorizontalMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {BaseMenuItem, ButtonControl, styled} from '@qiwi/pijma-core'
import React, {ReactNode} from 'react'

import {HorizontalMenuItem} from './HorizontalMenuItem'

export interface HorizontalMenuRenderItemProps<Item extends BaseMenuItem> {
/**
* Html item id
*/
id: string
item: Item
active: boolean
onClick: () => void
}

const horizontalMenuRenderItemDefault = <Item extends BaseMenuItem>({
item,
active,
id,
onClick,
}: HorizontalMenuRenderItemProps<Item>): ReactNode => (
<ButtonControl
key={item.id}
onClick={onClick}
children={renderProps => (
<HorizontalMenuItem
active={active}
onClick={renderProps.onClick}
id={id}
children={item.title}
/>
)}
/>
)

// @see https://iamsteve.me/blog/entry/using-flexbox-for-horizontal-scrolling-navigation
export const HorizontalMenuContainer = styled('nav')(({theme}) => ({
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
webkitOverflowScrolling: 'auto',
msOverflowStyle: '-ms-autohiding-scrollbar',
'&::-webkit-scrollbar': {
display: 'none',
},
'&:last-child': {
marginRight: 0,
},
paddingLeft: '16px',
paddingRight: '16px',
borderBottom: '1px solid ' + theme.color.gray.light,
}))

export interface HorizontalMenuProps<Item extends BaseMenuItem = BaseMenuItem> {
/**
* Menu html id and item ids prefix
*/
id: string
/**
* Active menu item id
*/
activeId: string
items: Item[]
onClick: (item: Item) => void
renderItem: (props: HorizontalMenuRenderItemProps<Item>) => ReactNode
}

// Do not use React.FC
// See https://github.com/sw-yx/react-typescript-cheatsheet#typing-defaultprops
export const HorizontalMenu = <Item extends BaseMenuItem = BaseMenuItem>({
items,
renderItem,
onClick,
activeId,
id,
}: HorizontalMenuProps<Item>) => (
<HorizontalMenuContainer
id={id}
children={items.map(item =>
renderItem({
item,
id: `${id}-${item.id}`,
active: item.id === activeId,
onClick: onClick.bind(null, item),
})
)}
/>
)
HorizontalMenu.defaultProps = {
renderItem: horizontalMenuRenderItemDefault,
}
28 changes: 28 additions & 0 deletions packages/mobile/src/horizontal-menu/HorizontalMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {styled, Typo} from '@qiwi/pijma-core'

export interface HorizontalMenuItemProps {
active: boolean
id: string
href?: string
target?: string
reverse?: boolean
onClick?: (e: React.MouseEvent) => void
}

export const HorizontalMenuItem = styled(Typo.withComponent('a'), {
shouldForwardProp: prop => prop !== 'active',
})<HorizontalMenuItemProps>(({active, theme}) => ({
cursor: 'pointer',
fontWeight: theme.font.weight.normal,
flexShrink: 0,
flexGrow: 0,
whiteSpace: 'nowrap',
paddingBottom: '4px',
marginRight: '24px',
borderBottomWidth: '4px',
borderBottomStyle: 'solid',
borderBottomColor: active ? theme.color.brand : 'transparent',
'&:hover, &:active, &:focus': {
borderBottomColor: theme.color.brand,
},
}))
2 changes: 2 additions & 0 deletions packages/mobile/src/horizontal-menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './HorizontalMenu'
export * from './HorizontalMenuItem'
2 changes: 2 additions & 0 deletions packages/mobile/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './modal'
export * from './checkbox-field'
export * from './radio-field'
export * from './typography'
export * from './horizontal-menu'
export * from './select-menu'

export {TextField as MaskTextField} from './text-field'
export {PasswordField as MaskPasswordField} from './password-field'
81 changes: 81 additions & 0 deletions packages/mobile/src/level-menu/LevelMenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## Level menu

Горизонтальное меню + вертикальное меню.

```jsx
class LevelMenuDemo extends React.Component {
constructor() {
super()
this.state = {
activeId: 't2',
}
}

render() {
return (
<LevelMenu
id="kassa-menus"
activeId={this.state.activeId}
onClick={item => this.setState({activeId: item.id})}
items={[
{
title: 'Услуги',
id: 'merchants',
sub: [
{
title: 'Транзакции',
id: 'transactions',
sub: [
{
title: 'Что-то там 1',
id: 't1',
},
{
title: 'Что-то там 2',
id: 't2',
},
],
},
{
title: 'Акты',
id: 'reports',
},
{
title: 'Реестры',
id: 'registry',
},
],
},
{
title: 'Заявки',
id: 'requests',
sub: [
{
title: 'Список 1',
id: 'list1',
},
{
title: 'Список 2',
id: 'list2',
},
],
},

{
title: 'Пользователи',
id: 'users',
},

{
title: 'Поддержка',
id: 'support',
},
]}
/>
)
}
}
;<Spacer size="xl">
<LevelMenuDemo />
</Spacer>
```
58 changes: 58 additions & 0 deletions packages/mobile/src/level-menu/LevelMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {BaseMenuItem, Box, MenuLevelBuilder} from '@qiwi/pijma-core'
import React, {Component} from 'react'

import {HorizontalMenu} from '../horizontal-menu'
import {SelectMenu} from '../select-menu'

export interface LevelMenuProps<Item extends BaseMenuItem> {
id: string
activeId: string
items: Item[]
onClick: (item: Item) => void
}

export class LevelMenu<Item extends BaseMenuItem> extends Component<
LevelMenuProps<Item>
> {
render() {
const {
props: {id, onClick, activeId, items},
} = this
const levels = new MenuLevelBuilder(items).levels(activeId)
const level0 = levels[0]
const level1 = levels[1]
const level2 = levels[2]
return (
<div id={id}>
{level0 && (
<HorizontalMenu
id={`${id}-0`}
items={level0.items}
onClick={onClick}
activeId={level0.activeId}
/>
)}
{level1 && (
<Box id={`${id}-1-wrapper`} mt={4}>
<SelectMenu
id={`${id}-1`}
items={level1.items}
onClick={onClick}
activeId={level1.activeId}
/>
</Box>
)}
{level2 && (
<Box id={`${id}-2-wrapper`} mt={4}>
<SelectMenu
id={`${id}-2`}
items={level2.items}
onClick={onClick}
activeId={level2.activeId}
/>
</Box>
)}
</div>
)
}
}
1 change: 1 addition & 0 deletions packages/mobile/src/level-menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './LevelMenu'
42 changes: 42 additions & 0 deletions packages/mobile/src/select-menu/SelectMenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Select menu

Вертикальное меню, похожее на dropdown. Используется как второй уровень.

```jsx
class SelectMenuDemo extends React.Component {
constructor() {
super()
this.state = {
activeId: 'transactions',
}
}

render() {
return (
<SelectMenu
id="kassa-menu-second"
activeId={this.state.activeId}
onClick={item => this.setState({activeId: item.id})}
items={[
{
title: 'Транзакции и отчеты',
id: 'transactions',
},
{
title: 'Реестры',
id: 'registry',
},

{
title: 'Акты',
id: 'reports',
},
]}
/>
)
}
}
;<Spacer size="xl">
<SelectMenuDemo />
</Spacer>
```
Loading

0 comments on commit bda9f54

Please sign in to comment.