Skip to content

Commit

Permalink
feat(Navigation): Complete controlled component related props
Browse files Browse the repository at this point in the history
  • Loading branch information
xinyao27 committed Jun 26, 2019
1 parent a867d24 commit b387416
Show file tree
Hide file tree
Showing 16 changed files with 474 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"linebreak-style": ["error", "unix"],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-non-null-assertion": "off"
"@typescript-eslint/no-non-null-assertion": "off",
"no-dupe-class-members": "off"
}
}
4 changes: 2 additions & 2 deletions packages/fluent-ui-icons/src/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const SvgIconStyled = styled.svg`
`

const Icon = forwardRef<{}, IconProps>(
({ type }: IconProps, ref: Ref<any>): ReactElement => {
({ type, ...rest }: IconProps, ref: Ref<any>): ReactElement => {
const vdom = (Icons as any)[type]
return (
<SvgIconStyled ref={ref} {...omit(vdom.props, ['children'])}>
<SvgIconStyled ref={ref} {...omit(vdom.props, ['children'])} {...rest}>
<path {...vdom.props.children.props} />
</SvgIconStyled>
)
Expand Down
52 changes: 52 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Navigation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Navigation
components: Navigation
---

# Navigation

<p class="description">The vertical navigation view control provides a collapsible upright navigation menu for top-level sections within the app.</p>

## Controlled

```jsx
() => {
const [activeId, setActiveId] = React.useState(1)
function handleActiveId(id) {
setActiveId(id)
}
return (
<Navigation height={600} value={activeId} onChange={handleActiveId}>
<Navigation.Header>
<Navigation.Item>
<Icon type="GlobalNavigationButton" />
</Navigation.Item>
</Navigation.Header>

<Navigation.Item id={1}>
<Icon type="Connected" />
<span>Option 1</span>
</Navigation.Item>
<Navigation.Item id={2}>
<Icon type="Connected" />
<span>Option 2</span>
</Navigation.Item>
<Navigation.Item id={3}>
<Icon type="Connected" />
<span>Option 3</span>
</Navigation.Item>
<Navigation.Item id={4}>
<Icon type="Connected" />
<span>Option 4</span>
</Navigation.Item>

<Navigation.Footer>
<Navigation.Item>
<Icon type="Settings" />
<span>Settings</span>
</Navigation.Item>
</Navigation.Footer>
</Navigation>
)
}
```
23 changes: 18 additions & 5 deletions packages/fluent-ui/src/components/Command/Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import React, {
useEffect,
createContext,
useState,
MouseEvent
MouseEvent,
useRef,
RefObject
} from 'react'
import {
StyledContent,
Expand All @@ -22,16 +24,17 @@ import { omit } from '../../utils'
import { BoxProps } from '../Box/Box'
import { ThemeProps } from '../../theme'
import Portal from '../Portal'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'

type Children =
type Child =
| ReactComponentElement<typeof CommandButton>
| ReactComponentElement<typeof Content>
| ReactComponentElement<typeof Secondary>
| any

interface CommandProps extends BoxProps, ThemeProps {
reveal: boolean
children: Children[]
children: Child[]
}

interface Container {
Expand All @@ -57,7 +60,7 @@ const Command: CommandType = forwardRef<HTMLDivElement, CommandProps>(
}
Children.forEach(
children,
(child: Children): void => {
(child: Child): void => {
if (child.type.name! === 'Content') {
container.content.push(child)
} else if (child.type.name === 'Secondary') {
Expand Down Expand Up @@ -99,13 +102,22 @@ const Command: CommandType = forwardRef<HTMLDivElement, CommandProps>(
left: `${rect.left + window.scrollX}px`,
top: `${rect.top + window.scrollY + rect.height}px`
}
setSecondaryVisible(!secondaryVisible)
setSecondaryVisible((visible): boolean => !visible)
setPortalStyle(position)
}

// 当使用 acrylic 时 reveal 不生效
reveal = acrylic ? false : reveal

// 点击 More 菜单之外的区域关闭 More 菜单
const secondaryRef = useRef<HTMLDivElement>(null)
useOnClickOutside(
secondaryRef,
(): void => {
setSecondaryVisible((visible): boolean => !visible)
}
)

const otherProps = omit(rest, ['display'])
return (
<Box display="flex" ref={ref} acrylic={acrylic} {...otherProps}>
Expand Down Expand Up @@ -137,6 +149,7 @@ const Command: CommandType = forwardRef<HTMLDivElement, CommandProps>(
{secondaryVisible && (
<Portal style={portalStyle}>
<Box
ref={secondaryRef}
width={130}
display="flex"
flexDirection="column"
Expand Down
40 changes: 20 additions & 20 deletions packages/fluent-ui/src/components/CommandButton/CommandButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React, {
ReactElement,
MouseEventHandler,
forwardRef,
ButtonHTMLAttributes
ButtonHTMLAttributes,
useContext
} from 'react'
import styled from '@xstyled/styled-components'
import { th } from '@xstyled/system'
Expand Down Expand Up @@ -53,25 +54,24 @@ const CommandButton = forwardRef<HTMLButtonElement, CommandButtonProps>(
(
{ icon, onClick, children, ...rest }: CommandButtonProps,
ref
): ReactElement => (
<CommandContext.Consumer>
{(reveal): ReactElement => (
<CommandButtonStyled
onClick={onClick}
ref={ref}
reveal={reveal}
{...rest}
>
{icon && <Icon type={icon} />}
{children && (
<CommandButtonTextStyled icon={!!icon}>
{children}
</CommandButtonTextStyled>
)}
</CommandButtonStyled>
)}
</CommandContext.Consumer>
)
): ReactElement => {
const reveal = useContext(CommandContext)
return (
<CommandButtonStyled
onClick={onClick}
ref={ref}
reveal={reveal}
{...rest}
>
{icon && <Icon type={icon} />}
{children && (
<CommandButtonTextStyled icon={!!icon}>
{children}
</CommandButtonTextStyled>
)}
</CommandButtonStyled>
)
}
)

CommandButton.displayName = 'FCommandButton'
Expand Down
12 changes: 12 additions & 0 deletions packages/fluent-ui/src/components/Navigation/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { ReactElement, ReactNode } from 'react'
import Box from '../Box'

interface ContentProps {
children: ReactNode
}

const Content = ({ children }: ContentProps): ReactElement => (
<Box>{children}</Box>
)

export default Content
10 changes: 10 additions & 0 deletions packages/fluent-ui/src/components/Navigation/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { ReactElement, ReactNode } from 'react'
import Box from '../Box'

interface ItemProps {
children: ReactNode
}

const Footer = ({ children }: ItemProps): ReactElement => <Box>{children}</Box>

export default Footer
10 changes: 10 additions & 0 deletions packages/fluent-ui/src/components/Navigation/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { ReactElement, ReactNode } from 'react'
import Box from '../Box'

interface ItemProps {
children: ReactNode
}

const Header = ({ children }: ItemProps): ReactElement => <Box>{children}</Box>

export default Header
112 changes: 112 additions & 0 deletions packages/fluent-ui/src/components/Navigation/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, {
ReactElement,
ReactNode,
Children,
ReactComponentElement,
ReactChild,
cloneElement,
useContext,
useEffect,
useState
} from 'react'
import Box from '../Box'
import { Icon } from '@fluent-ui/icons'
import styled from '@xstyled/styled-components'
import { th } from '@xstyled/system'
import { NavigationContext } from './Navigation'
import { useDispatch } from '../../hooks/useAction'

export type ID = string | number

interface ItemProps {
id?: ID
title?: string
children: ReactNode
}

type Child = ReactComponentElement<typeof Icon> | ReactChild | any

const StyledItemWrapper = styled.div`
position: relative;
max-height: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
padding: 12;
box-sizing: border-box;
transition: ${th.transition('navigation')};
&:hover {
background-color: secondary;
}
&:active {
background-color: primary;
}
`
interface StyledItemActiveBarProps {
active: boolean
}
const StyledItemActiveBar = styled.div<StyledItemActiveBarProps>`
position: absolute;
left: 0;
top: 50%;
background-color: accent;
width: 6px;
height: 24px;
margin-top: -12px;
transform: ${({ active }): string =>
active ? 'scale3d(1,1,1)' : 'scale3d(0,0,0)'};
transition: ${th.transition('navigation')};
`

const Item = ({ id, children }: ItemProps): ReactElement => {
const container: {
icon: any
content: any
} = {
icon: null,
content: null
}
Children.forEach(
children,
(child: Child): void => {
if (child.type && child.type.displayName === 'FIcon') {
container.icon = cloneElement(child, {
style: { width: 16, height: 16 }
})
} else {
container.content = cloneElement(child, {
style: { fontSize: 14 }
})
}
}
)

// handle active item
const activeID = useContext(NavigationContext)
const dispatch = useDispatch({ type: 'navigation/handleActive', payload: id })
function handleItemClick(): void {
dispatch()
}
const [active, setActive] = useState(false)
useEffect((): void => {
if (id) {
if (activeID === id) setActive(true)
else if (activeID && activeID !== id) setActive(false)
}
}, [activeID, id])

return (
<StyledItemWrapper onClick={handleItemClick}>
{id && <StyledItemActiveBar active={active} />}
<Box marginRight={12} height="100%">
{container.icon}
</Box>
<Box flex={1} display="flex" alignItems="center" overflow="hidden">
{container.content}
</Box>
</StyledItemWrapper>
)
}

export default Item
17 changes: 17 additions & 0 deletions packages/fluent-ui/src/components/Navigation/Navigation.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import styled from '@xstyled/styled-components'

export const StyledHeader = styled.div`
display: flex;
flex-direction: column;
`

export const StyledFooter = styled.div`
display: flex;
flex-direction: column;
`

export const StyledContent = styled.div`
flex: 1;
display: flex;
flex-direction: column;
`
Loading

0 comments on commit b387416

Please sign in to comment.