Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

Commit

Permalink
feat(MenuItem|AccordionTitle): add ability for removing and customizi…
Browse files Browse the repository at this point in the history
…ng the (active|submenu) indicator (#721)

* -added option for hiding the submenu indicators as well as shorthands for the vertical and horizontal menus
-TODO rethink the names used

* -refactored menu item styles

* -added rotate prop to the Icon
-supporting rtl for the icons
-added submenu and active indicators in the Menu and AccordionTitle components

* -removed boolean from the types of the indicators

* -added UnicodeCharacter component

* -replaced style unicode characters with UnicodeCharacter usages

* -fixed tests

* -replaced UnicodeCharacter with Indicator

* -renamed indicator* props to indicator
-refactored icon styles to be more readable

* -removed color prop in the indicator
-added examples

* -renamed forward and back direction to start and end
-moved the rotate icon prop from default to override props in the Indicator component

* -updated changelog

* -remove default props in AccordionTitle

* -removed arrow unicode chars from the teams theme
-remove helper methods for getting the mapping arrow in rtl

* -addressed comments on the PR

* -replaced div with fragment
  • Loading branch information
mnajdova authored Jan 21, 2019
1 parent c66f30e commit 519fda8
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Features
- Rename `Slot` component to `Box` and export it @Bugaa92 ([#713](https://github.com/stardust-ui/react/pull/713))
- Add `Indicator` component and used it in `MenuItem` and `AccordionTitle` @mnajdova ([#721](https://github.com/stardust-ui/react/pull/721))

<!--------------------------------[ v0.17.0 ]------------------------------- -->
## [v0.17.0](https://github.com/stardust-ui/react/tree/v0.17.0) (2019-01-17)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react'
import { Indicator } from '@stardust-ui/react'

const IndicatorExample = () => <Indicator />

export default IndicatorExample
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react'
import { Indicator } from '@stardust-ui/react'

const IndicatorExampleDirection = () => (
<>
<Indicator direction="end" /> <Indicator direction="bottom" /> <Indicator direction="start" />{' '}
<Indicator direction="top" />{' '}
</>
)

export default IndicatorExampleDirection
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react'
import { Indicator } from '@stardust-ui/react'

const IndicatorExampleIcon = () => (
<>
<Indicator icon="chevron down" direction="end" />{' '}
<Indicator icon="chevron down" direction="bottom" />{' '}
<Indicator icon="chevron down" direction="start" />{' '}
<Indicator icon="chevron down" direction="top" />{' '}
</>
)
export default IndicatorExampleIcon
25 changes: 25 additions & 0 deletions docs/src/examples/components/Indicator/Types/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react'
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

const Types = () => (
<ExampleSection title="Types">
<ComponentExample
title="Default"
description="A default Indicator ."
examplePath="components/Indicator/Types/IndicatorExample"
/>
<ComponentExample
title="Direction"
description="An Indicator may show towards different direction."
examplePath="components/Indicator/Types/IndicatorExampleDirection"
/>
<ComponentExample
title="Icon"
description="An Indicator may show icon instead of character."
examplePath="components/Indicator/Types/IndicatorExampleIcon"
/>
</ExampleSection>
)

export default Types
10 changes: 10 additions & 0 deletions docs/src/examples/components/Indicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react'
import Types from './Types'

const IndicatorExamples = () => (
<>
<Types />
</>
)

export default IndicatorExamples
34 changes: 22 additions & 12 deletions src/components/Accordion/AccordionTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
ContentComponentProps,
ChildrenComponentProps,
commonPropTypes,
customPropTypes,
} from '../../lib'
import { ReactProps, ComponentEventHandler } from '../../../types/utils'

import { ReactProps, ComponentEventHandler, ShorthandValue } from '../../../types/utils'
import Indicator from '../Indicator/Indicator'
export interface AccordionTitleProps
extends UIComponentProps,
ContentComponentProps,
Expand All @@ -30,6 +31,9 @@ export interface AccordionTitleProps
* @param {object} data - All props.
*/
onClick?: ComponentEventHandler<AccordionTitleProps>

/** Shorthand for the active indicator. */
indicator?: ShorthandValue
}

/**
Expand All @@ -47,26 +51,32 @@ class AccordionTitle extends UIComponent<ReactProps<AccordionTitleProps>, any> {
active: PropTypes.bool,
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onClick: PropTypes.func,
indicator: customPropTypes.itemShorthand,
}

handleClick = e => {
_.invoke(this.props, 'onClick', e, this.props)
}

renderComponent({ ElementType, classes, unhandledProps }) {
const { children, content } = this.props
renderComponent({ ElementType, classes, unhandledProps, styles }) {
const { children, content, indicator, active } = this.props
const indicatorWithDefaults = indicator === undefined ? {} : indicator

if (childrenExist(children)) {
return (
<ElementType {...unhandledProps} className={classes.root} onClick={this.handleClick}>
{children}
</ElementType>
)
}
const contentElement = (
<>
{Indicator.create(indicatorWithDefaults, {
defaultProps: {
direction: active ? 'bottom' : 'end',
styles: styles.indicator,
},
})}
{content}
</>
)

return (
<ElementType {...unhandledProps} className={classes.root} onClick={this.handleClick}>
{content}
{childrenExist(children) ? children : contentElement}
</ElementType>
)
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export interface IconProps extends UIComponentProps, ColorComponentProps {
/** Name of the icon. */
name?: string

/** An icon can be rotated by the degree specified as number. */
rotate?: number

/** Size of the icon. */
size?: IconSize

Expand Down Expand Up @@ -65,6 +68,7 @@ class Icon extends UIComponent<ReactProps<IconProps>, any> {
circular: PropTypes.bool,
disabled: PropTypes.bool,
name: PropTypes.string,
rotate: PropTypes.number,
size: PropTypes.oneOf(['smallest', 'smaller', 'small', 'medium', 'large', 'larger', 'largest']),
xSpacing: PropTypes.oneOf(['none', 'before', 'after', 'both']),
}
Expand All @@ -73,6 +77,7 @@ class Icon extends UIComponent<ReactProps<IconProps>, any> {
as: 'span',
size: 'medium',
accessibility: iconBehavior,
rotate: 0,
}

private renderFontIcon(ElementType, classes, unhandledProps, accessibility): React.ReactNode {
Expand Down
79 changes: 79 additions & 0 deletions src/components/Indicator/Indicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from 'react'
import * as PropTypes from 'prop-types'

import {
createShorthandFactory,
UIComponent,
UIComponentProps,
commonPropTypes,
customPropTypes,
} from '../../lib'
import { ReactProps, ShorthandValue } from '../../../types/utils'
import Icon from '../Icon/Icon'

export interface IndicatorProps extends UIComponentProps {
/** The indicator can point towards different directions. */
direction?: 'start' | 'end' | 'top' | 'bottom'

/** The indicator can show specific icon if provided. */
icon?: ShorthandValue
}

/**
* An indicator is suggesting additional content next to the element it is used in.
*/
class Indicator extends UIComponent<ReactProps<IndicatorProps>, any> {
static displayName = 'Indicator'

static create: Function

static className = 'ui-indicator'

static directionMap = {
end: { unicode: '\u25B8', rotation: -90 },
start: { unicode: '\u25C2', rotation: 90 },
top: { unicode: '\u25B4', rotation: 180 },
bottom: { unicode: '\u25BE', rotation: 0 },
}

static propTypes = {
...commonPropTypes.createCommon({ children: false, content: false }),
direction: PropTypes.oneOf(['start', 'end', 'top', 'bottom']),
icon: customPropTypes.itemShorthand,
}

static defaultProps = {
as: 'span',
direction: 'bottom',
}

renderComponent({ ElementType, classes, unhandledProps, rtl }) {
const { direction, icon, color } = this.props
const hexUnicode =
direction && Indicator.directionMap[this.getDirectionBasedOnRtl(rtl, direction)].unicode

return (
<ElementType {...unhandledProps} className={classes.root}>
{icon
? Icon.create(icon, {
defaultProps: { color },
overrideProps: ({ rotate }) => ({
rotate: (Indicator.directionMap[direction].rotation || 0) + (rotate || 0),
}),
})
: hexUnicode}
</ElementType>
)
}

private getDirectionBasedOnRtl = (rtl: boolean, direction) => {
if (!rtl) return direction
if (direction === 'start') return 'end'
if (direction === 'end') return 'start'
return direction
}
}

Indicator.create = createShorthandFactory(Indicator, 'hex')

export default Indicator
8 changes: 7 additions & 1 deletion src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { menuBehavior } from '../../lib/accessibility'
import { Accessibility } from '../../lib/accessibility/types'

import { ComponentVariablesObject } from '../../themes/types'
import { ReactProps, ShorthandCollection } from '../../../types/utils'
import { ReactProps, ShorthandCollection, ShorthandValue } from '../../../types/utils'
import MenuDivider from './MenuDivider'

export type MenuShorthandKinds = 'divider' | 'item'
Expand Down Expand Up @@ -68,6 +68,9 @@ export interface MenuProps extends UIComponentProps, ChildrenComponentProps {

/** Indicates whether the menu is submenu. */
submenu?: boolean

/** Shorthand for the submenu indicator. */
indicator?: ShorthandValue
}

export interface MenuState {
Expand Down Expand Up @@ -103,6 +106,7 @@ class Menu extends AutoControlledComponent<ReactProps<MenuProps>, MenuState> {
underlined: PropTypes.bool,
vertical: PropTypes.bool,
submenu: PropTypes.bool,
indicator: customPropTypes.itemShorthand,
}

static defaultProps = {
Expand Down Expand Up @@ -145,6 +149,7 @@ class Menu extends AutoControlledComponent<ReactProps<MenuProps>, MenuState> {
underlined,
vertical,
submenu,
indicator,
} = this.props
const { activeIndex } = this.state

Expand Down Expand Up @@ -177,6 +182,7 @@ class Menu extends AutoControlledComponent<ReactProps<MenuProps>, MenuState> {
index,
active,
inSubmenu: submenu,
indicator,
},
overrideProps: this.handleItemOverrides,
})
Expand Down
16 changes: 16 additions & 0 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibil
import { ComponentEventHandler, ReactProps, ShorthandValue } from '../../../types/utils'
import { focusAsync } from '../../lib/accessibility/FocusZone'
import Ref from '../Ref/Ref'
import Indicator from '../Indicator/Indicator'

export interface MenuItemProps
extends UIComponentProps,
Expand Down Expand Up @@ -105,6 +106,9 @@ export interface MenuItemProps

/** Indicates whether the menu item is part of submenu. */
inSubmenu?: boolean

/** Shorthand for the submenu indicator. */
indicator?: ShorthandValue
}

export interface MenuItemState {
Expand Down Expand Up @@ -144,6 +148,7 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
defaultMenuOpen: PropTypes.bool,
onActiveChanged: PropTypes.func,
inSubmenu: PropTypes.bool,
indicator: customPropTypes.itemShorthand,
}

static defaultProps = {
Expand Down Expand Up @@ -181,8 +186,11 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
primary,
secondary,
active,
vertical,
indicator,
disabled,
} = this.props
const indicatorWithDefaults = indicator === undefined ? {} : indicator

const { menuOpen } = this.state

Expand All @@ -205,6 +213,13 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
defaultProps: { xSpacing: !!content ? 'after' : 'none' },
})}
{content}
{menu &&
Indicator.create(indicatorWithDefaults, {
defaultProps: {
direction: vertical ? 'end' : 'bottom',
styles: styles.indicator,
},
})}
</ElementType>
</Ref>
)
Expand All @@ -219,6 +234,7 @@ class MenuItem extends AutoControlledComponent<ReactProps<MenuItemProps>, MenuIt
secondary,
styles: styles.menu,
submenu: true,
indicator,
},
})}
</Ref>
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export { default as Animation, AnimationProps } from './components/Animation/Ani

export { default as Tree } from './components/Tree'

export { default as Indicator, IndicatorProps } from './components/Indicator/Indicator'

//
// Accessibility
//
Expand Down
27 changes: 9 additions & 18 deletions src/themes/teams/components/Accordion/accordionTitleStyles.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { ICSSInJSStyle } from '../../../types'
import { getSideArrow } from '../../utils'

const accordionTitleStyles = {
root: ({ props, theme }): ICSSInJSStyle => {
const { active } = props
const { arrowDown } = theme.siteVariables
const sideArrow = getSideArrow(theme)
return {
display: 'inline-block',
verticalAlign: 'middle',
padding: '.5rem 0',
cursor: 'pointer',
'::before': {
userSelect: 'none',
content: active ? `"${arrowDown}"` : `"${sideArrow}"`,
},
}
},
root: () => ({
display: 'inline-block',
verticalAlign: 'middle',
padding: '.5rem 0',
cursor: 'pointer',
}),
indicator: () => ({
userSelect: 'none',
}),
}

export default accordionTitleStyles
Loading

0 comments on commit 519fda8

Please sign in to comment.