diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c858d6753..ea37e39175 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add single search flavor for `Dropdown` component @Bugaa92 ([#839](https://github.com/stardust-ui/react/pull/839))
- Add multiple selection flavor for `Dropdown` component @Bugaa92 ([#845](https://github.com/stardust-ui/react/pull/845))
- Add `black` and `white` options for the `color` prop of the `Label` component @mnajdova ([#855](https://github.com/stardust-ui/react/pull/855))
+- Add `Flex` component @kuzhelov ([#802](https://github.com/stardust-ui/react/pull/802))
## [v0.20.0](https://github.com/stardust-ui/react/tree/v0.20.0) (2019-02-06)
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleColumns.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleColumns.shorthand.tsx
new file mode 100644
index 0000000000..f74c052376
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleColumns.shorthand.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react'
+import { Flex, Segment } from '@stardust-ui/react'
+
+const FlexExampleColumns = () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+)
+
+export default FlexExampleColumns
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleInput.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleInput.shorthand.tsx
new file mode 100644
index 0000000000..093d13b00b
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleInput.shorthand.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react'
+import { Flex, Input, Button, Label } from '@stardust-ui/react'
+
+const FlexExampleInput = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default FlexExampleInput
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleItemsAlignment.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleItemsAlignment.shorthand.tsx
new file mode 100644
index 0000000000..b729bb2892
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleItemsAlignment.shorthand.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react'
+import { Flex, Segment } from '@stardust-ui/react'
+
+const FlexExampleItemsAlignment = () => (
+
+ {[
+ [
+ { hAlign: 'start', vAlign: 'start' },
+ { hAlign: 'start', vAlign: 'center' },
+ { hAlign: 'start', vAlign: 'end' },
+ ],
+ [
+ { hAlign: 'center', vAlign: 'start' },
+ { hAlign: 'center', vAlign: 'center' },
+ { hAlign: 'center', vAlign: 'end' },
+ ],
+ [
+ { hAlign: 'end', vAlign: 'start' },
+ { hAlign: 'end', vAlign: 'center' },
+ { hAlign: 'end', vAlign: 'end' },
+ ],
+ ].map(rowOfAlignmentProps => (
+
+ {rowOfAlignmentProps.map((alignmentProps: any) => (
+
+
+
+ ))}
+
+ ))}
+
+)
+
+export default FlexExampleItemsAlignment
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleMediaCard.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleMediaCard.shorthand.tsx
new file mode 100644
index 0000000000..8bb6c7a8b8
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleMediaCard.shorthand.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react'
+import { Flex, Image, Text, Header } from '@stardust-ui/react'
+
+const FlexExampleMediaCard = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default FlexExampleMediaCard
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleMixedAlignment.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleMixedAlignment.shorthand.tsx
new file mode 100644
index 0000000000..5fc0c353e5
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleMixedAlignment.shorthand.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react'
+import { Flex, Segment } from '@stardust-ui/react'
+
+const FlexExampleMixedAlignment = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default FlexExampleMixedAlignment
diff --git a/docs/src/examples/components/Flex/Types/FlexExampleNavMenu.shorthand.tsx b/docs/src/examples/components/Flex/Types/FlexExampleNavMenu.shorthand.tsx
new file mode 100644
index 0000000000..1d5127f2f4
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/FlexExampleNavMenu.shorthand.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react'
+import { Flex, Button } from '@stardust-ui/react'
+
+const FlexExampleNavMenu = () => (
+
+
+
+
+
+
+
+
+
+
+)
+
+export default FlexExampleNavMenu
diff --git a/docs/src/examples/components/Flex/Types/index.tsx b/docs/src/examples/components/Flex/Types/index.tsx
new file mode 100644
index 0000000000..52d481fa18
--- /dev/null
+++ b/docs/src/examples/components/Flex/Types/index.tsx
@@ -0,0 +1,40 @@
+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/Flex/index.tsx b/docs/src/examples/components/Flex/index.tsx
new file mode 100644
index 0000000000..eae87ca08f
--- /dev/null
+++ b/docs/src/examples/components/Flex/index.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react'
+import Types from './Types'
+
+const FlexExamples = () => (
+ <>
+
+ >
+)
+
+export default FlexExamples
diff --git a/packages/react/src/components/Flex/Flex.tsx b/packages/react/src/components/Flex/Flex.tsx
new file mode 100644
index 0000000000..285a4ed5ca
--- /dev/null
+++ b/packages/react/src/components/Flex/Flex.tsx
@@ -0,0 +1,112 @@
+import * as PropTypes from 'prop-types'
+import * as React from 'react'
+import * as _ from 'lodash'
+import cx from 'classnames'
+
+import { UIComponent, commonPropTypes } from '../../lib'
+import { ReactProps } from '../../types'
+import FlexItem from './FlexItem'
+import FlexGap from './FlexGap'
+
+export interface FlexProps {
+ /** Defines if container should be inline element. */
+ inline?: boolean
+
+ /** Sets vertical flow direction. */
+ column?: boolean
+
+ /** Allows overflow items to wrap on the next container's line. */
+ wrap?: boolean
+
+ /** Controls items alignment in horizontal direction. */
+ hAlign?: 'start' | 'center' | 'end' | 'stretch'
+
+ /** Controls items alignment in vertical direction. */
+ vAlign?: 'start' | 'center' | 'end' | 'stretch'
+
+ /** Defines strategy for distributing remaining space between items. */
+ space?: 'around' | 'between' | 'evenly'
+
+ /** Defines gap between each two adjacent child items. */
+ gap?: 'gap.small' | 'gap.medium' | 'gap.large'
+
+ /** Defines container's padding. */
+ padding?: 'padding.medium'
+
+ /** Enables debug mode. */
+ debug?: boolean
+
+ /** Orders container to fill all parent's space available. */
+ fill?: boolean
+}
+
+/**
+ * Arrange group of items aligned towards common direction.
+ */
+class Flex extends UIComponent> {
+ static Item = FlexItem
+
+ static Gap = FlexGap
+
+ static displayName = 'Flex'
+ static className = 'ui-flex'
+
+ static defaultProps = {
+ as: 'div',
+ }
+
+ public static propTypes = {
+ ...commonPropTypes.createCommon({
+ content: false,
+ }),
+
+ inline: PropTypes.bool,
+
+ column: PropTypes.bool,
+
+ wrap: PropTypes.bool,
+
+ hAlign: PropTypes.oneOf(['start', 'center', 'end', 'stretch']),
+ vAlign: PropTypes.oneOf(['start', 'center', 'end', 'stretch']),
+
+ space: PropTypes.oneOf(['around', 'between', 'evenly']),
+
+ gap: PropTypes.oneOf(['gap.small', 'gap.medium', 'gap.large']),
+
+ padding: PropTypes.oneOf(['padding.medium']),
+ fill: PropTypes.bool,
+
+ debug: PropTypes.bool,
+ }
+
+ renderComponent({ ElementType, classes, unhandledProps }): React.ReactNode {
+ return (
+
+ {this.renderChildren(classes.gap)}
+
+ )
+ }
+
+ renderChildren = (gapClasses: string) => {
+ const { column, gap, children } = this.props
+
+ return React.Children.map(children, (child: React.ReactElement, index) => {
+ const childElement =
+ child.type && ((child.type as any) as typeof FlexItem).__isFlexItem
+ ? React.cloneElement(child, {
+ flexDirection: column ? 'column' : 'row',
+ })
+ : child
+
+ const renderGap = index !== 0
+ return (
+ <>
+ {renderGap && gap && }
+ {childElement}
+ >
+ )
+ })
+ }
+}
+
+export default Flex
diff --git a/packages/react/src/components/Flex/FlexGap.tsx b/packages/react/src/components/Flex/FlexGap.tsx
new file mode 100644
index 0000000000..2847895207
--- /dev/null
+++ b/packages/react/src/components/Flex/FlexGap.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react'
+
+const FlexGap: React.FC = ({ className }) =>
+FlexGap.displayName = 'FlexGap'
+
+export default FlexGap
diff --git a/packages/react/src/components/Flex/FlexItem.tsx b/packages/react/src/components/Flex/FlexItem.tsx
new file mode 100644
index 0000000000..e6f43466b5
--- /dev/null
+++ b/packages/react/src/components/Flex/FlexItem.tsx
@@ -0,0 +1,107 @@
+import * as React from 'react'
+import * as PropTypes from 'prop-types'
+import cx from 'classnames'
+import { UIComponent, commonPropTypes } from '../../lib'
+import { mergeStyles } from '../../lib/mergeThemes'
+import { Extendable } from '../../types'
+
+export interface FlexItemProps {
+ /** Controls item's alignment. */
+ align?: 'auto' | 'start' | 'end' | 'center' | 'baseline' | 'stretch'
+
+ /** Defines size of the item. */
+ size?: 'size.half' | 'size.quarter' | 'size.small' | 'size.medium' | 'size.large'
+
+ /**
+ * Item can fill remaining space of the container.
+ * If numeric value is provided, remaining space will be distributed proportionally between all the items.
+ * */
+ grow?: boolean | number
+
+ /**
+ * Controls item's ability to shrink.
+ * */
+ shrink?: boolean | number
+
+ /**
+ * Item can be pushed towards opposite side in the container's direction.
+ */
+ push?: boolean
+
+ /**
+ * IGNORE (will be refactored and not exposed via API).
+ * Value is automatically set by parent Flex component.
+ */
+ flexDirection?: 'row' | 'column'
+}
+
+class FlexItem extends UIComponent> {
+ static className = 'ui-flex__item'
+
+ static displayName = 'FlexItem'
+
+ static propTypes = {
+ ...commonPropTypes.createCommon({
+ content: false,
+ }),
+ align: PropTypes.oneOf(['auto', 'start', 'end', 'center', 'baseline', 'stretch']),
+ size: PropTypes.oneOf(['size.half', 'size.quarter', 'size.small', 'size.medium', 'size.large']),
+
+ stretch: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
+ shrink: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
+
+ push: PropTypes.bool,
+
+ /**
+ * Will be automatically set by parent Flex component
+ */
+ flexDirection: PropTypes.oneOf(['row', 'column']),
+ }
+
+ displayName: 'FlexItem'
+
+ static create: Function
+
+ // Boolean flag for now, Symbol-based approach may be used instead.
+ // However, there are concerns related to browser compatibility if Symbols will be used.
+ // Completely alternative approach - check class name of React element (and generalize this logic).
+ static __isFlexItem = true
+
+ renderComponent({ styles, classes }) {
+ const { children } = this.props
+
+ // pass calculated bits using Render Props pattern
+ if (typeof children === 'function') {
+ return children({
+ styles: styles.root,
+ classes: classes.root,
+ })
+ }
+
+ return applyStyles(React.Children.only(children), styles, classes)
+ }
+}
+
+export default FlexItem
+
+const applyStyles = (
+ element: React.ReactElement,
+ styles,
+ classes,
+): React.ReactElement => {
+ if (!styles) {
+ return element
+ }
+
+ // if element is DOM element
+ if (typeof element.type === 'string') {
+ return React.cloneElement(element, {
+ className: cx(element.props.className, classes.root),
+ })
+ }
+
+ // assuming element is Stardust element
+ return React.cloneElement(element, {
+ styles: mergeStyles(styles.root || {}, element.props.styles),
+ })
+}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index b4bb0187ef..aceb5e58b5 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -58,6 +58,9 @@ export {
DropdownSearchInputProps,
} from './components/Dropdown/DropdownSearchInput'
+export { default as Flex, FlexProps } from './components/Flex/Flex'
+export { default as FlexItem, FlexItemProps } from './components/Flex/FlexItem'
+
export { default as Form, FormProps } from './components/Form/Form'
export { default as FormField, FormFieldProps } from './components/Form/FormField'
diff --git a/packages/react/src/themes/teams/componentStyles.ts b/packages/react/src/themes/teams/componentStyles.ts
index b0d43027de..aab3c89b45 100644
--- a/packages/react/src/themes/teams/componentStyles.ts
+++ b/packages/react/src/themes/teams/componentStyles.ts
@@ -20,6 +20,9 @@ export { default as DropdownSearchInput } from './components/Dropdown/dropdownSe
export { default as DropdownSelectedItem } from './components/Dropdown/dropdownSelectedItemStyles'
export { default as DropdownItem } from './components/Dropdown/dropdownItemStyles'
+export { default as Flex } from './components/Flex/flexStyles'
+export { default as FlexItem } from './components/Flex/flexItemStyles'
+
export { default as Form } from './components/Form/formStyles'
export { default as FormField } from './components/Form/formFieldStyles'
diff --git a/packages/react/src/themes/teams/componentVariables.ts b/packages/react/src/themes/teams/componentVariables.ts
index 3f2073e6bc..4ef2f8fc2f 100644
--- a/packages/react/src/themes/teams/componentVariables.ts
+++ b/packages/react/src/themes/teams/componentVariables.ts
@@ -15,6 +15,9 @@ export { default as Divider } from './components/Divider/dividerVariables'
export { default as Dropdown } from './components/Dropdown/dropdownVariables'
+export { default as Flex } from './components/Flex/flexVariables'
+export { default as FlexItem } from './components/Flex/flexItemVariables'
+
export { default as Grid } from './components/Grid/gridVariables'
export { default as Header } from './components/Header/headerVariables'
diff --git a/packages/react/src/themes/teams/components/Flex/flexItemStyles.ts b/packages/react/src/themes/teams/components/Flex/flexItemStyles.ts
new file mode 100644
index 0000000000..404cd4a76d
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Flex/flexItemStyles.ts
@@ -0,0 +1,27 @@
+import { ComponentSlotStylesInput } from '../../../types'
+import { FlexItemProps } from '../../../../components/Flex/FlexItem'
+
+import { toFlexAlignment, toFlexItemSizeValues } from './utils'
+import { FlexItemVariables } from './flexItemVariables'
+
+const flexItemStyles: ComponentSlotStylesInput = {
+ root: ({ props: p, variables: v }) => {
+ return {
+ ...(p.align && { alignSelf: toFlexAlignment(p.align) }),
+
+ ...(p.size && toFlexItemSizeValues(v[p.size])),
+
+ ...(p.shrink && { flexShrink: p.shrink }),
+ ...(p.shrink === false && { flexShrink: 0 }),
+
+ ...(p.grow && { flexGrow: p.grow }),
+ ...(p.grow === true && { flexGrow: 1 }),
+
+ ...p.itemStyles,
+ ...(p.push &&
+ (p.flexDirection === 'column' ? { marginTop: 'auto' } : { marginLeft: 'auto' })),
+ }
+ },
+}
+
+export default flexItemStyles
diff --git a/packages/react/src/themes/teams/components/Flex/flexItemVariables.ts b/packages/react/src/themes/teams/components/Flex/flexItemVariables.ts
new file mode 100644
index 0000000000..10e7650816
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Flex/flexItemVariables.ts
@@ -0,0 +1,16 @@
+import { pxToRem } from '../../../../lib'
+
+import { FlexItemProps } from '../../../../components/Flex/FlexItem'
+
+type SizeValues = Record
+
+export type FlexItemVariables = SizeValues
+
+export default (): FlexItemVariables => ({
+ 'size.half': '50%',
+ 'size.quarter': '25%',
+
+ 'size.small': pxToRem(150),
+ 'size.medium': pxToRem(200),
+ 'size.large': pxToRem(300),
+})
diff --git a/packages/react/src/themes/teams/components/Flex/flexStyles.ts b/packages/react/src/themes/teams/components/Flex/flexStyles.ts
new file mode 100644
index 0000000000..df0cf15ab1
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Flex/flexStyles.ts
@@ -0,0 +1,45 @@
+import { ComponentSlotStylesInput } from '../../../types'
+import { FlexProps } from '../../../../components/Flex/Flex'
+
+import { toFlexAlignment } from './utils'
+import { FlexVariables } from './flexVariables'
+
+const flexStyles: ComponentSlotStylesInput = {
+ root: ({ props: p, variables: v }) => ({
+ display: 'flex',
+ ...(p.debug && { border: '1px dotted grey', background: 'lightgrey' }),
+
+ ...(p.inline && { display: 'inline-flex' }),
+
+ ...(p.column && { flexDirection: 'column' }),
+
+ ...(p.hAlign &&
+ (p.column
+ ? { alignItems: toFlexAlignment(p.hAlign) }
+ : { justifyContent: toFlexAlignment(p.hAlign) })),
+ ...(p.vAlign &&
+ (p.column
+ ? { justifyContent: toFlexAlignment(p.vAlign) }
+ : { alignItems: toFlexAlignment(p.vAlign) })),
+
+ ...(p.space && { justifyContent: `space-${p.space}` }),
+
+ ...(p.wrap && { flexWrap: 'wrap' }),
+
+ ...(p.fill && {
+ height: '100%',
+ }),
+
+ ...(p.padding && { padding: v[p.padding] }),
+ }),
+
+ gap: ({ props: p, variables: v }) => {
+ const gapValue = v[p.gap]
+
+ return {
+ ...(p.column ? { height: gapValue } : { width: gapValue }),
+ }
+ },
+}
+
+export default flexStyles
diff --git a/packages/react/src/themes/teams/components/Flex/flexVariables.ts b/packages/react/src/themes/teams/components/Flex/flexVariables.ts
new file mode 100644
index 0000000000..dd440b1b89
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Flex/flexVariables.ts
@@ -0,0 +1,17 @@
+import { pxToRem } from '../../../../lib'
+import { FlexProps } from '../../../../components/Flex/Flex'
+
+type GapValues = Record
+type PaddingValues = Record
+
+export type FlexVariables = GapValues & PaddingValues
+
+export default (): FlexVariables => ({
+ // GAP VALUES
+ 'gap.small': pxToRem(10),
+ 'gap.medium': pxToRem(15),
+ 'gap.large': pxToRem(30),
+
+ // PADDING VALUES
+ 'padding.medium': pxToRem(10),
+})
diff --git a/packages/react/src/themes/teams/components/Flex/utils.ts b/packages/react/src/themes/teams/components/Flex/utils.ts
new file mode 100644
index 0000000000..7e09d509c9
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Flex/utils.ts
@@ -0,0 +1,13 @@
+export const toFlexAlignment = (propValue: string) => {
+ const trimmedValue = propValue.trim()
+
+ if (trimmedValue === 'start' || trimmedValue === 'end') {
+ return `flex-${trimmedValue}`
+ }
+
+ return trimmedValue
+}
+
+export const toFlexItemSizeValues = (sizeValue: string) => ({
+ flexBasis: sizeValue,
+})