diff --git a/CHANGELOG.md b/CHANGELOG.md
index e83c6d9c98..7e3c5c0420 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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](https://github.com/stardust-ui/react/tree/v0.17.0) (2019-01-17)
diff --git a/docs/src/examples/components/Indicator/Types/IndicatorExample.shorthand.tsx b/docs/src/examples/components/Indicator/Types/IndicatorExample.shorthand.tsx
new file mode 100644
index 0000000000..5e5dd0572a
--- /dev/null
+++ b/docs/src/examples/components/Indicator/Types/IndicatorExample.shorthand.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react'
+import { Indicator } from '@stardust-ui/react'
+
+const IndicatorExample = () =>
+
+export default IndicatorExample
diff --git a/docs/src/examples/components/Indicator/Types/IndicatorExampleDirection.shorthand.tsx b/docs/src/examples/components/Indicator/Types/IndicatorExampleDirection.shorthand.tsx
new file mode 100644
index 0000000000..5a0df21481
--- /dev/null
+++ b/docs/src/examples/components/Indicator/Types/IndicatorExampleDirection.shorthand.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react'
+import { Indicator } from '@stardust-ui/react'
+
+const IndicatorExampleDirection = () => (
+ <>
+ {' '}
+ {' '}
+ >
+)
+
+export default IndicatorExampleDirection
diff --git a/docs/src/examples/components/Indicator/Types/IndicatorExampleIcon.shorthand.tsx b/docs/src/examples/components/Indicator/Types/IndicatorExampleIcon.shorthand.tsx
new file mode 100644
index 0000000000..a2a611cf92
--- /dev/null
+++ b/docs/src/examples/components/Indicator/Types/IndicatorExampleIcon.shorthand.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react'
+import { Indicator } from '@stardust-ui/react'
+
+const IndicatorExampleIcon = () => (
+ <>
+ {' '}
+ {' '}
+ {' '}
+ {' '}
+ >
+)
+export default IndicatorExampleIcon
diff --git a/docs/src/examples/components/Indicator/Types/index.tsx b/docs/src/examples/components/Indicator/Types/index.tsx
new file mode 100644
index 0000000000..0f540d4608
--- /dev/null
+++ b/docs/src/examples/components/Indicator/Types/index.tsx
@@ -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 = () => (
+
+
+
+
+
+)
+
+export default Types
diff --git a/docs/src/examples/components/Indicator/index.tsx b/docs/src/examples/components/Indicator/index.tsx
new file mode 100644
index 0000000000..8c27ea542f
--- /dev/null
+++ b/docs/src/examples/components/Indicator/index.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react'
+import Types from './Types'
+
+const IndicatorExamples = () => (
+ <>
+
+ >
+)
+
+export default IndicatorExamples
diff --git a/src/components/Accordion/AccordionTitle.tsx b/src/components/Accordion/AccordionTitle.tsx
index 7e5d024310..db5a604703 100644
--- a/src/components/Accordion/AccordionTitle.tsx
+++ b/src/components/Accordion/AccordionTitle.tsx
@@ -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,
@@ -30,6 +31,9 @@ export interface AccordionTitleProps
* @param {object} data - All props.
*/
onClick?: ComponentEventHandler
+
+ /** Shorthand for the active indicator. */
+ indicator?: ShorthandValue
}
/**
@@ -47,26 +51,32 @@ class AccordionTitle extends UIComponent, 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 (
-
- {children}
-
- )
- }
+ const contentElement = (
+ <>
+ {Indicator.create(indicatorWithDefaults, {
+ defaultProps: {
+ direction: active ? 'bottom' : 'end',
+ styles: styles.indicator,
+ },
+ })}
+ {content}
+ >
+ )
return (
- {content}
+ {childrenExist(children) ? children : contentElement}
)
}
diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx
index e554e3ad91..74f42616d4 100644
--- a/src/components/Icon/Icon.tsx
+++ b/src/components/Icon/Icon.tsx
@@ -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
@@ -65,6 +68,7 @@ class Icon extends UIComponent, 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']),
}
@@ -73,6 +77,7 @@ class Icon extends UIComponent, any> {
as: 'span',
size: 'medium',
accessibility: iconBehavior,
+ rotate: 0,
}
private renderFontIcon(ElementType, classes, unhandledProps, accessibility): React.ReactNode {
diff --git a/src/components/Indicator/Indicator.tsx b/src/components/Indicator/Indicator.tsx
new file mode 100644
index 0000000000..f0b93896dc
--- /dev/null
+++ b/src/components/Indicator/Indicator.tsx
@@ -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, 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 (
+
+ {icon
+ ? Icon.create(icon, {
+ defaultProps: { color },
+ overrideProps: ({ rotate }) => ({
+ rotate: (Indicator.directionMap[direction].rotation || 0) + (rotate || 0),
+ }),
+ })
+ : hexUnicode}
+
+ )
+ }
+
+ 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
diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx
index 414b76b00a..6dc9ffc374 100644
--- a/src/components/Menu/Menu.tsx
+++ b/src/components/Menu/Menu.tsx
@@ -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'
@@ -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 {
@@ -103,6 +106,7 @@ class Menu extends AutoControlledComponent, MenuState> {
underlined: PropTypes.bool,
vertical: PropTypes.bool,
submenu: PropTypes.bool,
+ indicator: customPropTypes.itemShorthand,
}
static defaultProps = {
@@ -145,6 +149,7 @@ class Menu extends AutoControlledComponent, MenuState> {
underlined,
vertical,
submenu,
+ indicator,
} = this.props
const { activeIndex } = this.state
@@ -177,6 +182,7 @@ class Menu extends AutoControlledComponent, MenuState> {
index,
active,
inSubmenu: submenu,
+ indicator,
},
overrideProps: this.handleItemOverrides,
})
diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx
index ba8ef2e875..96357f87fa 100644
--- a/src/components/Menu/MenuItem.tsx
+++ b/src/components/Menu/MenuItem.tsx
@@ -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,
@@ -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 {
@@ -144,6 +148,7 @@ class MenuItem extends AutoControlledComponent, MenuIt
defaultMenuOpen: PropTypes.bool,
onActiveChanged: PropTypes.func,
inSubmenu: PropTypes.bool,
+ indicator: customPropTypes.itemShorthand,
}
static defaultProps = {
@@ -181,8 +186,11 @@ class MenuItem extends AutoControlledComponent, MenuIt
primary,
secondary,
active,
+ vertical,
+ indicator,
disabled,
} = this.props
+ const indicatorWithDefaults = indicator === undefined ? {} : indicator
const { menuOpen } = this.state
@@ -205,6 +213,13 @@ class MenuItem extends AutoControlledComponent, MenuIt
defaultProps: { xSpacing: !!content ? 'after' : 'none' },
})}
{content}
+ {menu &&
+ Indicator.create(indicatorWithDefaults, {
+ defaultProps: {
+ direction: vertical ? 'end' : 'bottom',
+ styles: styles.indicator,
+ },
+ })}
)
@@ -219,6 +234,7 @@ class MenuItem extends AutoControlledComponent, MenuIt
secondary,
styles: styles.menu,
submenu: true,
+ indicator,
},
})}
diff --git a/src/index.ts b/src/index.ts
index eb5489f13c..c86b4b12b6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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
//
diff --git a/src/themes/teams/components/Accordion/accordionTitleStyles.ts b/src/themes/teams/components/Accordion/accordionTitleStyles.ts
index ca642e8ee1..d368fb80d5 100644
--- a/src/themes/teams/components/Accordion/accordionTitleStyles.ts
+++ b/src/themes/teams/components/Accordion/accordionTitleStyles.ts
@@ -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
diff --git a/src/themes/teams/components/Icon/iconStyles.ts b/src/themes/teams/components/Icon/iconStyles.ts
index a717a0ebf4..888871e41f 100644
--- a/src/themes/teams/components/Icon/iconStyles.ts
+++ b/src/themes/teams/components/Icon/iconStyles.ts
@@ -92,11 +92,12 @@ const getIconColor = (colorProp: string, variables: IconVariables) =>
const iconStyles: ComponentSlotStylesInput = {
root: ({
- props: { disabled, name, size, bordered, circular, color, xSpacing },
+ props: { disabled, name, size, bordered, circular, color, xSpacing, rotate },
variables: v,
theme,
}): ICSSInJSStyle => {
const iconSpec = theme.icons[name]
+ const rtl = theme.rtl
const isFontBased = !iconSpec || !iconSpec.isSvg
return {
@@ -120,6 +121,14 @@ const iconStyles: ComponentSlotStylesInput = {
...((bordered || v.borderColor || circular) &&
getBorderedStyles(circular, v.borderColor || getIconColor(color, v))),
+
+ ...(rtl && {
+ transform: `scaleX(-1) rotate(${-1 * rotate}deg)`,
+ }),
+
+ ...(!rtl && {
+ transform: `rotate(${rotate}deg)`,
+ }),
}
},
diff --git a/src/themes/teams/components/Menu/menuItemStyles.ts b/src/themes/teams/components/Menu/menuItemStyles.ts
index a7d5309b8f..d559fe1e5a 100644
--- a/src/themes/teams/components/Menu/menuItemStyles.ts
+++ b/src/themes/teams/components/Menu/menuItemStyles.ts
@@ -1,4 +1,3 @@
-import { getSideArrow } from '../../utils'
import { pxToRem } from '../../../../lib'
import { ComponentSlotStyleFunction, ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { MenuVariables } from './menuVariables'
@@ -279,8 +278,6 @@ const menuItemStyles: ComponentSlotStylesInput ({
+ position: 'relative',
+ float: 'right',
+ left: pxToRem(10),
+ userSelect: 'none',
+ }),
}
export default menuItemStyles
diff --git a/src/themes/teams/siteVariables.ts b/src/themes/teams/siteVariables.ts
index 43c0e2d8a7..96228fbc2e 100644
--- a/src/themes/teams/siteVariables.ts
+++ b/src/themes/teams/siteVariables.ts
@@ -98,10 +98,3 @@ export const bodyFontSize = '1.4rem'
export const bodyBackground = white
export const bodyColor = black
export const bodyLineHeight = lineHeightBase
-
-//
-// UNICODE CHARACTERS
-//
-export const arrowRight = '\u25B8'
-export const arrowDown = '\u25BE'
-export const arrowLeft = '\u25C2'
diff --git a/src/themes/teams/utils/index.ts b/src/themes/teams/utils/index.ts
deleted file mode 100644
index e818cf91b3..0000000000
--- a/src/themes/teams/utils/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { ThemeInput } from '../../types'
-
-export const getSideArrow = (theme: ThemeInput) => {
- const { rtl, siteVariables } = theme
- const { arrowLeft, arrowRight } = siteVariables
- return rtl ? arrowLeft : arrowRight
-}
diff --git a/test/specs/components/Indicator/Indicator-test.ts b/test/specs/components/Indicator/Indicator-test.ts
new file mode 100644
index 0000000000..dd21346825
--- /dev/null
+++ b/test/specs/components/Indicator/Indicator-test.ts
@@ -0,0 +1,7 @@
+import { isConformant } from 'test/specs/commonTests'
+
+import Indicator from 'src/components/Indicator/Indicator'
+
+describe('Indicator', () => {
+ isConformant(Indicator)
+})