diff --git a/src/a11y/a11y.js.flow b/src/a11y/a11y.js.flow deleted file mode 100644 index d434330073..0000000000 --- a/src/a11y/a11y.js.flow +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -/* global document cancelIdleCallback requestIdleCallback */ - -import * as React from 'react'; -import axe from 'axe-core'; - -import { Layer, TetherBehavior, TETHER_PLACEMENT } from '../layer/index.js'; -import { ParagraphSmall, ParagraphXSmall } from '../typography/index.js'; -import { styled } from '../styles/index.js'; -import { ThemeContext } from '../styles/theme-provider.js'; - -import type { ViolationPropsT } from './types.js'; - -function validateNode(node) { - return new Promise((resolve, reject) => { - axe.run(node, { reporter: 'v2' }, (error, results) => { - if (error) reject(error); - resolve(results.violations); - }); - }); -} - -function segmentViolationsByNode(violations) { - const nodes = violations.reduce((map, violation) => { - violation.nodes.forEach((node) => { - if (!map[node.target]) { - map[node.target] = [violation]; - } else { - map[node.target] = map[node.target].push(violation); - } - }); - return map; - }, {}); - return Object.entries(nodes); -} - -const ViolationContainer = styled<{ $top: string, $left: string }>( - 'div', - ({ $theme, $top, $left }) => { - return { - backgroundColor: $theme.colors.backgroundPrimary, - boxShadow: $theme.lighting.shadow600, - position: 'absolute', - padding: $theme.sizing.scale400, - top: $top, - left: $left, - }; - } -); - -function Violation(props: ViolationPropsT) { - const [offset, setOffset] = React.useState({ top: 0, left: 0 }); - const [anchor, setAnchor] = React.useState(null); - const [popper, setPopper] = React.useState(null); - const [isHovered, setIsHovered] = React.useState(false); - const theme = React.useContext(ThemeContext); - - const handleMouseEnter = () => setIsHovered(true); - const handleMouseLeave = () => setIsHovered(false); - - React.useEffect(() => { - const node = document.querySelector(props.target); - if (node) { - setAnchor(node); - - node.setAttribute('style', `border: solid 1px ${theme.colors.borderNegative};`); - - node.addEventListener('mouseenter', handleMouseEnter); - node.addEventListener('mouseleave', handleMouseLeave); - } - - return () => { - if (node) { - node.removeEventListener('mouseenter', handleMouseEnter); - node.removeEventListener('mouseleave', handleMouseLeave); - } - }; - }, [props.target]); - - if (!isHovered) return null; - - return ( - - setOffset(update.popper)} - placement={TETHER_PLACEMENT.bottom} - > - - {props.target} - {props.violations.map((violation, index) => ( - {violation.description} - ))} - - - - ); -} - -export default function A11y(props: { children: React.Node }) { - const [violations, setViolations] = React.useState([]); - const [idleID, setIdleID] = React.useState(null); - const child = React.useRef(null); - React.useEffect(() => { - if (child.current) { - if (idleID) { - cancelIdleCallback(idleID); - setIdleID(null); - } - - const id = requestIdleCallback(() => { - validateNode(child.current).then(setViolations); - }); - setIdleID(id); - } - }, [props.children]); - - const violationsByNode = segmentViolationsByNode(violations); - - return ( - <> - {props.children} -
- {violationsByNode.map(([node, violations], index) => ( - // flowlint-next-line unclear-type:off - - ))} -
- - ); -} diff --git a/src/a11y/index.js.flow b/src/a11y/index.js.flow deleted file mode 100644 index bc20ff34e1..0000000000 --- a/src/a11y/index.js.flow +++ /dev/null @@ -1,9 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { default as Unstable_A11y } from './a11y.js'; diff --git a/src/a11y/types.js.flow b/src/a11y/types.js.flow deleted file mode 100644 index cbfb1d92de..0000000000 --- a/src/a11y/types.js.flow +++ /dev/null @@ -1,15 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -type NodeT = { target: string }; -type ViolationT = { description: string, nodes: NodeT }; - -export type ViolationPropsT = { - target: string, - violations: Array, -}; diff --git a/src/accordion/accordion.js.flow b/src/accordion/accordion.js.flow deleted file mode 100644 index 39c47f1f61..0000000000 --- a/src/accordion/accordion.js.flow +++ /dev/null @@ -1,154 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { getOverrides } from '../helpers/overrides.js'; -import { Root as StyledRoot } from './styled-components.js'; -import { STATE_CHANGE_TYPE } from './constants.js'; -import type { AccordionPropsT, AccordionStateT, StateChangeTypeT } from './types.js'; - -export default class Accordion extends React.Component { - static defaultProps: $Shape = { - accordion: true, - disabled: false, - initialState: { - expanded: [], - }, - onChange: () => {}, - overrides: {}, - renderAll: false, - stateReducer: (type, newState) => newState, - }; - - state = { - expanded: [], - ...this.props.initialState, - }; - - itemRefs = []; - - //flowlint-next-line unclear-type:off - onPanelChange(key: React.Key, onChange: () => {}, ...args: Array) { - let activeKeys = this.state.expanded; - const { accordion } = this.props; - if (accordion) { - activeKeys = activeKeys[0] === key ? [] : [key]; - } else { - activeKeys = [...activeKeys]; - const index = activeKeys.indexOf(key); - const wasExpanded = index > -1; - if (wasExpanded) { - // remove active state - activeKeys.splice(index, 1); - } else { - activeKeys.push(key); - } - } - const newState = { expanded: activeKeys }; - this.internalSetState(STATE_CHANGE_TYPE.expand, newState); - // Call individual panel's onChange handler - if (typeof onChange === 'function') onChange(...args); - } - - internalSetState(type: StateChangeTypeT, changes: AccordionStateT) { - const { stateReducer, onChange } = this.props; - const newState = stateReducer(type, changes, this.state); - this.setState(newState); - typeof onChange === 'function' && onChange(newState); - } - - handleKeyDown(e: KeyboardEvent) { - if (this.props.disabled) { - return; - } - - const itemRefs = this.itemRefs; - - const HOME = 36; - const END = 35; - const ARROW_UP = 38; - const ARROW_DOWN = 40; - - if (e.keyCode === HOME) { - e.preventDefault(); - const firstItem = itemRefs[0]; - firstItem.current && firstItem.current.focus(); - } - if (e.keyCode === END) { - e.preventDefault(); - const lastItem = itemRefs[itemRefs.length - 1]; - lastItem.current && lastItem.current.focus(); - } - if (e.keyCode === ARROW_UP) { - e.preventDefault(); - const activeItemIdx = itemRefs.findIndex((item) => item.current === document.activeElement); - if (activeItemIdx > 0) { - const prevItem = itemRefs[activeItemIdx - 1]; - prevItem.current && prevItem.current.focus(); - } - } - if (e.keyCode === ARROW_DOWN) { - e.preventDefault(); - const activeItemIdx = itemRefs.findIndex((item) => item.current === document.activeElement); - if (activeItemIdx < itemRefs.length - 1) { - const nextItem = itemRefs[activeItemIdx + 1]; - nextItem.current && nextItem.current.focus(); - } - } - } - - getItems() { - const { expanded } = this.state; - const { accordion, disabled, children, renderAll, overrides } = this.props; - // flowlint-next-line unclear-type:off - return React.Children.map(children, (child: any, index) => { - if (!child) return; - - const itemRef = React.createRef(); - this.itemRefs.push(itemRef); - - // If there is no key provided use the panel order as a default key - const key = child.key || String(index); - let isExpanded = false; - if (accordion) { - isExpanded = expanded[0] === key; - } else { - isExpanded = expanded.includes(key); - } - - const props = { - key, - ref: itemRef, - expanded: isExpanded || child.props.expanded, - accordion, - renderAll, - overrides: child.props.overrides || overrides, - disabled: child.props.disabled || disabled, - onChange: (...args) => this.onPanelChange(key, child.props.onChange, ...args), - }; - return React.cloneElement(child, props); - }); - } - - render() { - const { overrides = {} } = this.props; - const { Root: RootOverride } = overrides; - const [Root, rootProps] = getOverrides(RootOverride, StyledRoot); - return ( - - {this.getItems()} - - ); - } -} diff --git a/src/accordion/constants.js.flow b/src/accordion/constants.js.flow deleted file mode 100644 index d9c49a49f9..0000000000 --- a/src/accordion/constants.js.flow +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const STATE_CHANGE_TYPE = { - expand: 'expand', -}; diff --git a/src/accordion/index.js.flow b/src/accordion/index.js.flow deleted file mode 100644 index 9857ca1320..0000000000 --- a/src/accordion/index.js.flow +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -export { default as Accordion } from './accordion.js'; -export { default as Panel } from './panel.js'; -export { default as StatefulPanel } from './stateful-panel.js'; -export { default as StatefulPanelContainer } from './stateful-panel-container.js'; -export { default as StatelessAccordion } from './stateless-accordion.js'; -// Constants -export { STATE_CHANGE_TYPE } from './constants.js'; -// Styled elements -export { - Root as StyledRoot, - PanelContainer as StyledPanelContainer, - Header as StyledHeader, - Content as StyledContent, - ContentAnimationContainer as StyledContentAnimationContainer, - ToggleIcon as StyledToggleIcon, - ToggleIconGroup as StyledToggleIconGroup, -} from './styled-components.js'; -// Flow -export type * from './types.js'; diff --git a/src/accordion/locale.js.flow b/src/accordion/locale.js.flow deleted file mode 100644 index 60458bcec7..0000000000 --- a/src/accordion/locale.js.flow +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export type AccordionLocaleT = {| - collapse: string, - expand: string, -|}; - -const locale = { - collapse: 'Collapse', - expand: 'Expand', -}; - -export default locale; diff --git a/src/accordion/panel.js.flow b/src/accordion/panel.js.flow deleted file mode 100644 index 397e0892e6..0000000000 --- a/src/accordion/panel.js.flow +++ /dev/null @@ -1,232 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { LocaleContext } from '../locale/index.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { - PanelContainer as StyledPanelContainer, - Header as StyledHeader, - Content as StyledContent, - ToggleIcon as StyledToggleIcon, - ToggleIconGroup as StyledToggleIconGroup, - ContentAnimationContainer as StyledContentAnimationContainer, -} from './styled-components.js'; -import { isFocusVisible, forkFocus, forkBlur } from '../utils/focusVisible.js'; - -import type { PanelPropsT } from './types.js'; - -const Panel = ( - { - 'aria-controls': ariaControls, - children, - disabled = false, - expanded = false, - onChange = () => {}, - onClick = () => {}, - onKeyDown = () => {}, - overrides = {}, - title = '', - renderAll = false, - }: PanelPropsT, - ref -) => { - const [localState, setLocalState] = React.useState({ - expanded, - isFocusVisible: false, - elementHeight: 0, - animationInProgress: false, - }); - const handleFocus = React.useCallback( - (event: SyntheticEvent<>) => { - if (isFocusVisible(event)) { - setLocalState({ ...localState, isFocusVisible: true }); - } - }, - [localState] - ); - const handleBlur = React.useCallback( - (event: SyntheticEvent<>) => { - if (localState.isFocusVisible) { - setLocalState({ ...localState, isFocusVisible: false }); - } - }, - [localState] - ); - const handleClick = React.useCallback( - (e: Event) => { - if (disabled) { - return; - } - typeof onChange === 'function' && onChange({ expanded: !expanded }); - typeof onClick === 'function' && onClick(e); - }, - [expanded, disabled, onChange, onClick] - ); - const handleKeyDown = React.useCallback( - (e: KeyboardEvent) => { - if (disabled) { - return; - } - - const ENTER = 13; - const SPACE = 32; - - if (e.keyCode === ENTER || e.keyCode === SPACE) { - typeof onChange === 'function' && onChange({ expanded: !expanded }); - if (e.keyCode === SPACE) { - e.preventDefault(); // prevent jumping scroll when using Space - } - } - typeof onKeyDown === 'function' && onKeyDown(e); - }, - [expanded, disabled, onChange, onKeyDown] - ); - // flowlint-next-line unclear-type:off - const _animateRef = React.useRef(null); - - React.useEffect(() => { - if (_animateRef.current) { - const height = _animateRef.current.getBoundingClientRect().height; - // After the first render, when everything is in the DOM, update the local - //state to indicate an animation is in progress. - if (expanded !== localState.expanded) { - setLocalState({ - ...localState, - expanded, - animationInProgress: true, - }); - } else if (parseInt(localState.elementHeight) !== height) { - // After the second render (where child elements were added to the Content) - //the Content height now reflects the true height. This kicks off the actual - //animation. - setLocalState({ - ...localState, - elementHeight: height ? `${height}px` : 0, - }); - } - } - }, [_animateRef.current, expanded, localState.elementHeight, localState.expanded, setLocalState]); - - const contentHeight = React.useMemo(() => { - // When closing, the first render will re-query the content element for the new - //height and set the height of the animation container from auto to a px value. - if (!expanded && localState.expanded) { - const height = _animateRef.current.getBoundingClientRect().height; - setLocalState({ - ...localState, - elementHeight: height ? `${height}px` : 0, - }); - return localState.elementHeight; - } - if (!localState.expanded) { - return 0; - } - // When no longer animating, set the height to auto to accommodate dynamic nested components. - return localState.animationInProgress ? localState.elementHeight : 'auto'; - }, [expanded, localState.expanded, localState.animationInProgress, localState.elementHeight]); - - const sharedProps = { - $disabled: disabled, - $expanded: expanded, - $isFocusVisible: localState.isFocusVisible, - }; - - const { - PanelContainer: PanelContainerOverride, - Header: HeaderOverride, - Content: ContentOverride, - ContentAnimationContainer: ContentAnimationContainerOverride, - ToggleIcon: ToggleIconOverride, - ToggleIconGroup: ToggleIconGroupOverride, - } = overrides; - - const [PanelContainer, panelContainerProps] = getOverrides( - PanelContainerOverride, - StyledPanelContainer - ); - const [Header, headerProps] = getOverrides(HeaderOverride, StyledHeader); - const [Content, contentProps] = getOverrides(ContentOverride, StyledContent); - const [ContentAnimationContainer, contentAnimationProps] = getOverrides( - ContentAnimationContainerOverride, - StyledContentAnimationContainer - ); - const [ToggleIconGroup, toggleIconGroupProps] = getOverrides( - ToggleIconGroupOverride, - StyledToggleIconGroup - ); - - const [ToggleIcon, toggleIconProps] = getOverrides(ToggleIconOverride, StyledToggleIcon); - - return ( - - {(locale) => ( - -
- {title} - - - - - - -
- { - if (localState.animationInProgress) { - setLocalState({ ...localState, animationInProgress: false }); - } - }} - > - - {localState.expanded || renderAll || localState.animationInProgress ? children : null} - - -
- )} -
- ); -}; - -const ForwardedPanel = React.forwardRef(Panel); -ForwardedPanel.displayName = 'Panel'; -export default ForwardedPanel; diff --git a/src/accordion/stateful-panel-container.js.flow b/src/accordion/stateful-panel-container.js.flow deleted file mode 100644 index ab62a22548..0000000000 --- a/src/accordion/stateful-panel-container.js.flow +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { STATE_CHANGE_TYPE } from './constants.js'; -import type { - PanelStateT, - StatefulPanelContainerPropsT, - PanelStateReducerT, - StateChangeTypeT, - OnChangeHandlerT, -} from './types.js'; - -const defaultStateReducer: PanelStateReducerT = (type, nextState) => nextState; - -class StatefulPanelContainer extends React.Component { - static defaultProps = { - initialState: { expanded: false }, - stateReducer: defaultStateReducer, - onChange: () => {}, - }; - - state = { - expanded: false, - ...this.props.initialState, - }; - - onChange: OnChangeHandlerT = () => { - if (typeof this.props.onChange === 'function') { - this.props.onChange({ expanded: !this.state.expanded }); - } - this.internalSetState(STATE_CHANGE_TYPE.expand, { - expanded: !this.state.expanded, - }); - }; - - internalSetState(type: StateChangeTypeT, changes: PanelStateT) { - const { stateReducer } = this.props; - this.setState((prevState) => (stateReducer ? stateReducer(type, changes, prevState) : changes)); - } - - render() { - const { children, initialState, stateReducer, ...restProps } = this.props; - - return this.props.children({ - ...restProps, - ...this.state, - onChange: this.onChange, - }); - } -} - -export default StatefulPanelContainer; diff --git a/src/accordion/stateful-panel.js.flow b/src/accordion/stateful-panel.js.flow deleted file mode 100644 index 721e968cda..0000000000 --- a/src/accordion/stateful-panel.js.flow +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import StatefulContainer from './stateful-panel-container.js'; -import Panel from './panel.js'; -import type { StatefulPanelPropsT } from './types.js'; - -export default function StatefulPanel(props: StatefulPanelPropsT) { - const { children, ...restProps } = props; - return ( - - {(componentProps) => {children}} - - ); -} diff --git a/src/accordion/stateless-accordion.js.flow b/src/accordion/stateless-accordion.js.flow deleted file mode 100644 index 41a69fac13..0000000000 --- a/src/accordion/stateless-accordion.js.flow +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { getOverrides } from '../helpers/overrides.js'; -import { Root as StyledRoot } from './styled-components.js'; -import type { StatelessAccordionPropsT } from './types.js'; - -function StatelessAccordion({ - accordion = true, - children, - disabled, - expanded, - onChange, - overrides = {}, - renderAll, -}: StatelessAccordionPropsT) { - const { Root: RootOverrides, ...PanelOverrides } = overrides; - const [Root, rootProps] = getOverrides(RootOverrides, StyledRoot); - return ( - - {React.Children.map(children, (child, index) => { - const key = child.key || String(index); - return React.cloneElement(child, { - disabled: child.props.disabled || disabled, - expanded: expanded.includes(key), - key, - onChange: - // Don't bother constructing the wrapper function if no one is listening - onChange && typeof onChange === 'function' - ? () => { - let next; - if (accordion) { - if (expanded.includes(key)) { - next = []; - } else { - next = [key]; - } - } else { - if (expanded.includes(key)) { - next = expanded.filter((k) => k !== key); - } else { - next = [...expanded, key]; - } - } - onChange({ key, expanded: next }); - } - : onChange, - overrides: child.props.overrides || PanelOverrides, - renderAll, - }); - })} - - ); -} - -export default StatelessAccordion; diff --git a/src/accordion/styled-components.js.flow b/src/accordion/styled-components.js.flow deleted file mode 100644 index 59b998ddcc..0000000000 --- a/src/accordion/styled-components.js.flow +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import { styled } from '../styles/index.js'; -import { getSvgStyles } from '../icon/styled-components.js'; -import type { SharedStylePropsArgT } from './types.js'; - -/** - * Main component container element - */ -export const Root = styled('ul', { - listStyleType: 'none', - marginBottom: 0, - marginTop: 0, - paddingLeft: 0, - paddingRight: 0, - width: '100%', -}); - -export const PanelContainer = styled('li', (props) => { - const { - $expanded, - $theme: { colors }, - } = props; - return { - listStyleType: 'none', - width: '100%', - borderBottomWidth: '1px', - borderBottomStyle: 'solid', - borderBottomColor: $expanded ? colors.borderOpaque : colors.borderOpaque, - }; -}); - -export const Header = styled('div', (props) => { - const { - $disabled, - $isFocusVisible, - $theme: { colors, sizing, typography }, - } = props; - return { - ...typography.font350, - color: colors.contentPrimary, - cursor: $disabled ? 'not-allowed' : 'pointer', - backgroundColor: colors.listHeaderFill, - paddingTop: sizing.scale600, - paddingBottom: sizing.scale600, - paddingLeft: sizing.scale700, - paddingRight: sizing.scale700, - marginTop: 0, - marginBottom: 0, - marginLeft: 0, - marginRight: 0, - display: 'flex', - alignItems: 'center', - outline: $isFocusVisible ? `3px solid ${colors.borderAccent}` : 'none', - outlineOffset: '-3px', - justifyContent: 'space-between', - ':hover': { - color: colors.contentPrimary, - }, - }; -}); - -export const ToggleIcon = styled('svg', (props) => { - const { $theme, $disabled, $color } = props; - return { - ...getSvgStyles(props), - flexShrink: 0, - color: $color || $theme.colors.contentPrimary, - cursor: $disabled ? 'not-allowed' : 'pointer', - }; -}); - -export const ToggleIconGroup = styled('g', (props) => { - const { $theme, $expanded } = props; - return { - transform: $expanded ? 'rotate(0)' : 'rotate(-90deg)', - transformOrigin: 'center', - transitionProperty: 'transform', - transitionDuration: $theme.animation.timing500, - transitionTimingFunction: $theme.animation.easeOutQuinticCurve, - }; -}); - -export const Content = styled('div', (props) => { - const { - $theme: { animation, colors, sizing, typography }, - $expanded, - } = props; - return { - ...typography.font200, - backgroundColor: colors.listBodyFill, - color: colors.contentPrimary, - paddingTop: sizing.scale800, - paddingBottom: sizing.scale1000, - paddingLeft: sizing.scale800, - paddingRight: sizing.scale800, - marginTop: 0, - marginBottom: 0, - marginLeft: 0, - marginRight: 0, - boxSizing: 'border-box', - overflow: 'hidden', - opacity: $expanded ? 1 : 0, - visibility: $expanded ? 'visible' : 'hidden', - transitionProperty: 'opacity,visibility', - transitionDuration: animation.timing500, - transitionDelay: animation.timing200, - transitionTimingFunction: animation.easeOutQuinticCurve, - }; -}); - -export const ContentAnimationContainer = styled< - { $height: string | number } & SharedStylePropsArgT ->('div', (props) => { - const { - $height, - $theme: { animation }, - } = props; - return { - height: `${$height}`, - overflow: 'hidden', - transitionProperty: 'height', - transitionDuration: animation.timing500, - transitionTimingFunction: animation.easeOutQuinticCurve, - }; -}); diff --git a/src/accordion/types.js.flow b/src/accordion/types.js.flow deleted file mode 100644 index 21fdb65a0b..0000000000 --- a/src/accordion/types.js.flow +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -/* eslint-disable flowtype/generic-spacing */ -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { STATE_CHANGE_TYPE } from './constants.js'; - -export type AccordionStateT = { - expanded: Array, -}; - -export type PanelStateT = { - expanded: boolean, -}; - -export type StateChangeTypeT = $Keys; - -export type StateReducerT = ( - stateChangeType: StateChangeTypeT, - nextState: AccordionStateT, - currentState: AccordionStateT -) => AccordionStateT; - -export type PanelStateReducerT = ( - stateChangeType: StateChangeTypeT, - nextState: PanelStateT, - currentState: PanelStateT -) => PanelStateT; - -export type AccordionOverridesT = { - Content?: OverrideT, - ContentAnimationContainer?: OverrideT, - Header?: OverrideT, - PanelContainer?: OverrideT, - Root?: OverrideT, - ToggleIcon?: OverrideT, - ToggleIconGroup?: OverrideT, -}; - -export type PanelOverridesT = { - PanelContainer?: OverrideT, - Header?: OverrideT, - ToggleIcon?: OverrideT, - ToggleIconGroup?: OverrideT, - Content?: OverrideT, - ContentAnimationContainer?: OverrideT, -}; - -export type OnChangeHandlerT = ({ expanded: boolean }) => mixed; - -export type AccordionOnChangeHandlerT = ({ - expanded: Array, -}) => mixed; - -//flowlint-next-line unclear-type:off -type ChildrenT = React.ChildrenArray>; - -export type AccordionPropsT = { - /** Determines how many panels may be expanded at a time. If set to - * true it will collapse a current panel when a new panel is expanded. - * If set to false more than one panel may be expanded at a time. */ - accordion?: boolean, - /** Accordion expandable items. See Panel API below for reference. */ - children: ChildrenT, - /** If set to true all its children panels will be disabled from toggling. */ - disabled?: boolean, - initialState?: AccordionStateT, - /** Handler called each time a panel is toggled. expanded prop is an array - * of Panel keys that are currently expanded. */ - onChange?: AccordionOnChangeHandlerT, - overrides?: AccordionOverridesT, - /** Handler called each time the component state changes. - * Used to override default state-change functionality. */ - stateReducer: StateReducerT, - /** - * Allows users to render all child content whether a panel is expanded or not - * for SEO purposed - */ - renderAll?: boolean, -}; - -export type StatelessAccordionOnChangeHandlerT = ({ - expanded: Array, - key: React.Key, -}) => mixed; - -export type StatelessAccordionPropsT = { - /** Determines how many panels may be expanded at a time. If set to - * true it will collapse a current panel when a new panel is expanded. - * If set to false more than one panel may be expanded at a time. */ - accordion?: boolean, - /** Accordion expandable items. See Panel API below for reference. */ - children: ChildrenT, - /** If set to true all its children panels will be disabled from toggling. */ - disabled?: boolean, - /** List of Panel keys which are expanded. */ - expanded: Array, - /** Handler called each time a panel is toggled. */ - onChange?: StatelessAccordionOnChangeHandlerT, - overrides?: AccordionOverridesT & PanelOverridesT, - /** - * Allows users to render all child content whether a panel is expanded or not - * for SEO purposed - */ - renderPanelContent?: boolean, - /** - * Allows users to render all child content whether a panel is expanded or not - * for SEO purposed - */ - renderAll?: boolean, -}; - -type SharedPanelPropsT = { - /** The content visible when Panel is expanded. */ - children: React.Node, - /** Defaults to the disabled value provided by the parent Accordion component. */ - disabled?: boolean, - /** Id for a panel, when provided populates aria-controls - * attribute for panel button and content - * */ - 'aria-controls'?: string, - /** The key of a Panel. Used to maintain list of expanded panels. - * Must be unique across children of the Accordion. */ - key?: React.Key, - /** Handler for individual Panel change events. */ - onChange?: OnChangeHandlerT, - /** Handler for the Header's click events. */ - onClick?: (e: Event) => mixed, - /** Handler for the Header's keyDown events. */ - onKeyDown?: (e: KeyboardEvent) => mixed, - overrides?: PanelOverridesT, - /** The title of an accordion panel. */ - title?: React.Node, - /** - * Allows users to render all child content whether a panel is expanded or not - * for SEO purposed - */ - renderPanelContent?: boolean, - /** - * Allows users to render all child content whether a panel is expanded or not - * for SEO purposed - */ - renderAll?: boolean, -}; - -export type PanelPropsT = SharedPanelPropsT & { - /** Defines if the panel is expanded. If set to true the panel is rendered expanded. */ - expanded?: boolean, -}; - -// Props for panel stateful container -type SharedStatefulPanelContainerPropsT = { - /** Initial state of a stateful panel component. - * The expanded prop indicates if the panel is initially expanded. - * If set to true the panel will be expanded initially */ - initialState?: PanelStateT, - onChange?: OnChangeHandlerT, - stateReducer?: PanelStateReducerT, -}; -export type StatefulPanelContainerPropsT = SharedStatefulPanelContainerPropsT & { - children: (props: $Diff) => React.Node, -}; - -// Props for stateful panel -export type StatefulPanelPropsT = SharedStatefulPanelContainerPropsT & SharedPanelPropsT; - -export type SharedStylePropsArgT = { - $color?: string, - $disabled: ?boolean, - $expanded?: ?boolean, - $size?: string | number, - $isFocusVisible: boolean, -}; diff --git a/src/app-nav-bar/app-nav-bar.js.flow b/src/app-nav-bar/app-nav-bar.js.flow deleted file mode 100644 index 1046ee63d8..0000000000 --- a/src/app-nav-bar/app-nav-bar.js.flow +++ /dev/null @@ -1,238 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { getOverrides } from '../helpers/overrides.js'; -import { useStyletron } from '../styles/index.js'; -import { isFocusVisible } from '../utils/focusVisible.js'; - -import { KIND, POSITION } from './constants.js'; -import MobileNav from './mobile-menu.js'; -import UserMenu from './user-menu.js'; -import { - StyledRoot, - StyledSpacing, - StyledPrimaryMenuContainer, - StyledSecondaryMenuContainer, - StyledAppName, - StyledMainMenuItem, - StyledDesktopMenuContainer, - StyledDesktopMenu, -} from './styled-components.js'; -import type { AppNavBarPropsT } from './types.js'; -import { defaultMapItemToNode, mapItemsActive } from './utils.js'; - -function MainMenuItem(props) { - const { item, kind = KIND.primary, mapItemToNode, onSelect, overrides = {} } = props; - const [focusVisible, setFocusVisible] = React.useState(false); - - function handleFocus(event) { - if (isFocusVisible(event)) { - setFocusVisible(true); - } - } - - function handleBlur(event) { - if (focusVisible) { - setFocusVisible(false); - } - } - - function handleClick(event) { - if (onSelect) { - onSelect(item); - } - } - - function handleKeyDown(event) { - if (event.key === 'Enter' && onSelect) { - onSelect(item); - } - } - - const [MainMenuItemElement, mainMenuItemElementProps] = getOverrides( - overrides.MainMenuItem, - StyledMainMenuItem - ); - - return ( - - {mapItemToNode(item)} - - ); -} - -function SecondaryMenu(props) { - const { items = [], mapItemToNode, onSelect, overrides = {} } = props; - - const [SecondaryMenuContainer, secondaryMenuContainerProps] = getOverrides( - overrides.SecondaryMenuContainer, - StyledSecondaryMenuContainer - ); - - return ( - - {items.map((item, index) => ( - // Replace with a menu item renderer - - ))} - - ); -} - -export default function AppNavBar(props: AppNavBarPropsT) { - const [css, theme] = useStyletron(); - const { - title, - mapItemToNode = defaultMapItemToNode, - onMainItemSelect = (item) => {}, - onUserItemSelect = (item) => {}, - overrides = {}, - userItems = [], - username, - usernameSubtitle, - userImgUrl, - } = props; - - const mainItems = React.useMemo(() => { - if (props.isMainItemActive) { - return mapItemsActive(props.mainItems || [], props.isMainItemActive); - } - return props.mainItems || []; - }, [props.mainItems, props.isMainItemActive]); - - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [Spacing, spacingProps] = getOverrides(overrides.Spacing, StyledSpacing); - const [AppName, appNameProps] = getOverrides(overrides.AppName, StyledAppName); - const [PrimaryMenuContainer, primaryMenuContainerProps] = getOverrides( - overrides.PrimaryMenuContainer, - StyledPrimaryMenuContainer - ); - const [DesktopMenuContainer, desktopMenuContainerProps] = getOverrides( - overrides.DesktopMenuContainer, - StyledDesktopMenuContainer - ); - const [DesktopMenu, desktopMenuProps] = getOverrides(overrides.DesktopMenu, StyledDesktopMenu); - - let secondaryMenu; - let desktopSubNavPosition = POSITION.horizontal; - let mobileSubNavPosition = POSITION.vertical; - - return ( - - {/* Mobile Nav Experience */} -
- - {mainItems.length || userItems.length ? : null} - {title} - - - {secondaryMenu && mobileSubNavPosition === POSITION.horizontal && ( - - )} -
- - {/* Desktop Nav Experience */} -
- - - {/* Replace with a Logo renderer */} - {title} - - - {mainItems.map((item, index) => { - // For an active top level menu get the secondary navigation and its positioning - if (item.active && item.children && item.children.length) { - secondaryMenu = item.children; - if (item.navPosition) { - desktopSubNavPosition = item.navPosition.desktop || desktopSubNavPosition; - mobileSubNavPosition = item.navPosition.mobile || mobileSubNavPosition; - } - } - return ( - - ); - })} - - - {userItems.length ? ( - - ) : null} - - - - {secondaryMenu && desktopSubNavPosition === POSITION.horizontal && ( - - )} -
-
- ); -} diff --git a/src/app-nav-bar/constants.js.flow b/src/app-nav-bar/constants.js.flow deleted file mode 100644 index 826440093a..0000000000 --- a/src/app-nav-bar/constants.js.flow +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const POSITION = { - horizontal: 'horizontal', - vertical: 'vertical', -}; - -export const KIND = { - primary: 'primary', - secondary: 'secondary', -}; diff --git a/src/app-nav-bar/index.js.flow b/src/app-nav-bar/index.js.flow deleted file mode 100644 index 218421c71f..0000000000 --- a/src/app-nav-bar/index.js.flow +++ /dev/null @@ -1,14 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { default as AppNavBar } from './app-nav-bar.js'; -export { POSITION } from './constants.js'; -export * from './styled-components.js'; -export * from './types.js'; - -export { setItemActive } from './utils.js'; diff --git a/src/app-nav-bar/mobile-menu.js.flow b/src/app-nav-bar/mobile-menu.js.flow deleted file mode 100644 index 008fe9bc71..0000000000 --- a/src/app-nav-bar/mobile-menu.js.flow +++ /dev/null @@ -1,207 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Button } from '../button/index.js'; -import { Drawer, ANCHOR } from '../drawer/index.js'; -import { getOverrides, mergeOverrides } from '../helpers/overrides.js'; -import ArrowLeft from '../icon/arrow-left.js'; -import MenuIcon from '../icon/menu.js'; -import { MenuAdapter, ListItemLabel, ARTWORK_SIZES } from '../list/index.js'; -import { StatefulMenu } from '../menu/index.js'; - -import { StyledSideMenuButton, StyledUserMenuProfileListItem } from './styled-components.js'; -import type { AppNavBarPropsT } from './types.js'; -import UserProfileTile from './user-profile-tile.js'; -import { defaultMapItemToNode } from './utils.js'; - -const USER_TITLE_ITEM = 'USER_TITLE_ITEM'; -const USER_MENU_ITEM = 'USER_MENU_ITEM'; -const PARENT_MENU_ITEM = 'PARENT_MENU_ITEM'; - -// eslint-disable-next-line react/display-name -const MobileNavMenuItem = React.forwardRef((props, ref) => { - const { item, mapItemToNode = defaultMapItemToNode, overrides = {}, ...restProps } = props; - - const [UserMenuProfileListItem, userMenuProfileListItemProps] = getOverrides( - overrides.UserMenuProfileListItem, - StyledUserMenuProfileListItem - ); - - if (item.PARENT_MENU_ITEM) { - return ( - - {item.label} - - ); - } - if (item.USER_TITLE_ITEM) { - // Replace with a user menu item renderer - return ( - - - - ); - } - return ( - // Replace with a main menu item renderer - - {mapItemToNode(item)} - - ); -}); - -export default function MobileMenu(props: AppNavBarPropsT) { - const { mainItems = [], userItems = [], mapItemToNode, overrides = {}, ...rest } = props; - - const items = [ - ...(userItems.length - ? [ - { - item: { ...rest }, - label: props.username, - [USER_TITLE_ITEM]: true, - children: userItems.map((item) => { - return { - ...item, - [USER_MENU_ITEM]: true, - }; - }), - }, - ] - : []), - ...mainItems, - ]; - - const [isOpen, setIsOpen] = React.useState(false); - const [currentNavItems, setCurrentNavItems] = React.useState(items); - const [ancestorNavItems, setAncestorNavItems] = React.useState([]); - - const toggleMenu = () => { - setIsOpen(!isOpen); - }; - - const [SideMenuButton, sideMenuButtonProps] = getOverrides(overrides.SideMenuButton, Button); - sideMenuButtonProps.overrides = mergeOverrides( - { BaseButton: { component: StyledSideMenuButton } }, - sideMenuButtonProps.overrides - ); - - const [MobileDrawer, drawerProps] = getOverrides(overrides.MobileDrawer, Drawer); - drawerProps.overrides = mergeOverrides( - { - DrawerBody: { - style: ({ $theme }) => { - return { - marginTop: '0px', - marginBottom: '0px', - marginLeft: '0px', - marginRight: '0px', - }; - }, - }, - // Removes the close icon from the drawer - Close: () => null, - }, - drawerProps.overrides - ); - - const [MobileMenu, menuProps] = getOverrides(overrides.MobileMenu, StatefulMenu); - menuProps.overrides = mergeOverrides( - { - List: { - style: { - paddingTop: '0', - paddingBottom: '0', - minHeight: '100vh', - boxShadow: 'none', - }, - }, - // eslint-disable-next-line react/display-name - ListItem: React.forwardRef((listItemProps, ref) => { - return ( - - ); - }), - }, - menuProps.overrides - ); - - return ( - <> - - - - - { - if (item.PARENT_MENU_ITEM) { - // Remove current parent item selected to return to - // from the ancestors list (`ancestorNavItems[ancestorArrLength - 1]`) - const updatedAncestorNavItems = ancestorNavItems.slice( - 0, - ancestorNavItems.length - 1 - ); - const isTopLevel = !updatedAncestorNavItems.length; - if (isTopLevel) { - // Set to the initial `navItems` value - setCurrentNavItems(items); - } else { - const newParentItem = { - ...updatedAncestorNavItems[updatedAncestorNavItems.length - 1], - [PARENT_MENU_ITEM]: true, - }; - setCurrentNavItems([newParentItem, ...newParentItem.children]); - } - setAncestorNavItems(updatedAncestorNavItems); - return; - } - - if (item.USER_MENU_ITEM && props.onUserItemSelect) { - props.onUserItemSelect(item); - } else if (!item.USER_TITLE_ITEM && props.onMainItemSelect) { - props.onMainItemSelect(item); - } - - if (item.children && item.children.length) { - const parentItem = { ...item, [PARENT_MENU_ITEM]: true }; - setAncestorNavItems([...ancestorNavItems, item]); - setCurrentNavItems([parentItem, ...item.children]); - return; - } - toggleMenu(); - }} - {...menuProps} - /> - - - ); -} diff --git a/src/app-nav-bar/styled-components.js.flow b/src/app-nav-bar/styled-components.js.flow deleted file mode 100644 index f3a6c8ee5a..0000000000 --- a/src/app-nav-bar/styled-components.js.flow +++ /dev/null @@ -1,220 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import { styled, withStyle } from '../styles/index.js'; -import { getMediaQueryPageMargins, getMinimumPageMargins } from '../helpers/responsive-helpers.js'; -import { StyledListItem } from '../menu/index.js'; -import { KIND } from './constants.js'; -import { type StyleObject } from 'styletron-react'; - -const StyledButton = styled<{ $isFocusVisible: boolean }>( - 'button', - ({ $theme, $isFocusVisible }) => ({ - boxSizing: 'border-box', - display: 'flex', - flexDirection: 'row', - flexWrap: 'nowrap', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'transparent', - color: $theme.colors.contentPrimary, - borderLeftWidth: 0, - borderTopWidth: 0, - borderRightWidth: 0, - borderBottomWidth: 0, - paddingTop: '0', - paddingBottom: '0', - paddingLeft: '0', - paddingRight: '0', - marginLeft: 0, - marginTop: 0, - marginRight: 0, - marginBottom: 0, - outline: $isFocusVisible ? `3px solid ${$theme.colors.borderAccent}` : 'none', - outlineOffset: '-3px', - WebkitAppearance: 'none', - cursor: 'pointer', - }) -); - -export const StyledRoot = styled<{}>('div', (props): StyleObject => { - const { $theme } = props; - return { - ...getMediaQueryPageMargins($theme), - ...$theme.typography.font300, - ...getMinimumPageMargins($theme.grid.margins), - boxSizing: 'border-box', - backgroundColor: $theme.colors.backgroundPrimary, - borderBottomWidth: '1px', - borderBottomStyle: 'solid', - borderBottomColor: `${$theme.colors.borderOpaque}`, - }; -}); - -export const StyledSubnavContainer = styled('div', {}); - -export const StyledSpacing = styled<{}>('div', (props) => { - const { $theme } = props; - return { - boxSizing: 'border-box', - height: '100%', - display: 'flex', - alignItems: 'center', - paddingTop: $theme.sizing.scale400, - paddingBottom: $theme.sizing.scale400, - [$theme.mediaQuery.medium]: { - paddingTop: $theme.sizing.scale700, - paddingBottom: $theme.sizing.scale700, - }, - }; -}); - -export const StyledAppName = styled<{}>('div', ({ $theme }) => ({ - ...$theme.typography.font550, - color: $theme.colors.contentPrimary, - textDecoration: 'none', - [$theme.mediaQuery.medium]: { - ...$theme.typography.font650, - }, -})); - -export const StyledSideMenuButton = withStyle( - StyledButton, - ({ $theme }) => ({ - ...($theme.direction === 'rtl' - ? { marginLeft: $theme.sizing.scale600 } - : { marginRight: $theme.sizing.scale600 }), - paddingTop: $theme.sizing.scale100, - paddingBottom: $theme.sizing.scale100, - paddingLeft: $theme.sizing.scale100, - paddingRight: $theme.sizing.scale100, - }) -); - -export const StyledPrimaryMenuContainer = styled<{}>('div', ({ $theme }) => { - return { - boxSizing: 'border-box', - height: '100%', - display: 'flex', - flexDirection: 'row', - flexGrow: 1, - flexWrap: 'nowrap', - justifyContent: 'flex-end', - alignItems: 'stretch', - paddingInlineEnd: $theme.sizing.scale1000, - }; -}); - -export const StyledMainMenuItem = styled<{ - $active?: boolean, - $isFocusVisible: boolean, - $kind: $Values, -}>('div', (props) => { - const { - $active, - $isFocusVisible, - $kind, - $theme: { colors, sizing, direction }, - } = props; - return { - boxSizing: 'border-box', - display: 'flex', - alignItems: 'center', - color: $active ? colors.contentPrimary : colors.contentTertiary, - marginLeft: sizing.scale700, - marginRight: sizing.scale700, - paddingTop: $kind === KIND.secondary ? sizing.scale750 : '0', - paddingBottom: $kind === KIND.secondary ? sizing.scale750 : '0', - outline: $isFocusVisible ? `3px solid ${colors.borderAccent}` : 'none', - outlineOffset: '-3px', - borderBottomWidth: '2px', - borderBottomStyle: 'solid', - borderBottomColor: $active && !$isFocusVisible ? colors.borderSelected : 'transparent', - cursor: $active ? 'default' : 'pointer', - whiteSpace: $kind === KIND.secondary ? 'nowrap' : 'initial', - ':first-child': { - ...(direction === 'rtl' ? { marginRight: '0' } : { marginLeft: '0' }), - }, - ':last-child': { - ...(direction === 'rtl' ? { marginLeft: '0' } : { marginRight: '0' }), - }, - ':hover': { - color: colors.contentPrimary, - }, - }; -}); - -export const StyledSecondaryMenuContainer = styled<{}>('div', ({ $theme }) => { - return { - boxSizing: 'border-box', - height: '100%', - display: 'flex', - flexDirection: 'row', - flexWrap: 'nowrap', - justifyContent: 'flex-start', - margin: 'auto', - maxWidth: `${$theme.grid.maxWidth}px`, - alignItems: 'stretch', - overflow: 'auto', - }; -}); - -export const StyledUserMenuButton = StyledButton; - -export const StyledUserMenuProfileListItem = withStyle( - StyledListItem, - ({ $theme }) => ({ - paddingTop: '0', - paddingBottom: '0', - ...($theme.direction === 'rtl' ? { paddingLeft: '0' } : { paddingRight: '0' }), - }) -); - -export const StyledUserProfileTileContainer = styled<{}>('div', ({ $theme }) => { - return { - boxSizing: 'border-box', - height: '100%', - display: 'flex', - flexDirection: 'row', - flexWrap: 'nowrap', - justifyContent: 'flex-start', - paddingTop: $theme.sizing.scale650, - paddingBottom: $theme.sizing.scale650, - }; -}); - -export const StyledUserProfilePictureContainer = styled<{}>('div', ({ $theme }) => { - return { - ...($theme.direction === 'rtl' - ? { marginLeft: $theme.sizing.scale600 } - : { marginRight: $theme.sizing.scale600 }), - }; -}); - -export const StyledUserProfileInfoContainer = styled<{}>('div', ({ $theme }) => { - return { - boxSizing: 'border-box', - alignSelf: 'center', - }; -}); - -export const StyledDesktopMenuContainer = styled<{}>('div', ({ $theme }) => { - return {}; -}); - -export const StyledDesktopMenu = styled<{}>('div', ({ $theme }) => { - return { - alignItems: 'center', - display: 'flex', - justifyContent: 'space-between', - margin: 'auto', - maxWidth: `${$theme.grid.maxWidth}px`, - paddingBlockStart: '18px', - paddingBlockEnd: '18px', - }; -}); diff --git a/src/app-nav-bar/types.js.flow b/src/app-nav-bar/types.js.flow deleted file mode 100644 index 846bff1e5e..0000000000 --- a/src/app-nav-bar/types.js.flow +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { POSITION } from './constants.js'; - -import type { OverrideT } from '../helpers/overrides.js'; - -export type OverridesT = { - Root?: OverrideT, - AppName?: OverrideT, - DesktopMenu?: OverrideT, - DesktopMenuContainer?: OverrideT, - MainMenuItem?: OverrideT, - PrimaryMenuContainer?: OverrideT, - ProfileTileContainer?: OverrideT, - SecondaryMenuContainer?: OverrideT, - Spacing?: OverrideT, - SubnavContainer?: OverrideT, - UserMenuProfileListItem?: OverrideT, - UserProfileInfoContainer?: OverrideT, - UserProfilePictureContainer?: OverrideT, - UserProfileTileContainer?: OverrideT, - - // nested overrides - MobileDrawer?: OverrideT, - MobileMenu?: OverrideT, - SideMenuButton?: OverrideT, - UserMenuButton?: OverrideT, - UserMenu?: OverrideT, -}; - -export type NavItemT = {| - active?: boolean, - // flowlint-next-line unclear-type:off - icon?: React.AbstractComponent, - // flowlint-next-line unclear-type:off - info?: any, - label: string, - children?: NavItemT[], - // flowlint-next-line unclear-type:off - navExitIcon?: React.AbstractComponent, - navPosition?: { - desktop?: $Values, - mobile?: $Values, - }, -|}; - -export type UserMenuPropsT = {| - userItems?: NavItemT[], - username?: string, - usernameSubtitle?: React.Node, - userImgUrl?: string, - onUserItemSelect?: (NavItemT) => mixed, -|}; - -export type AppNavBarPropsT = {| - ...UserMenuPropsT, - isMainItemActive?: (NavItemT) => boolean, - mainItems?: NavItemT[], - mapItemToNode?: (NavItemT) => React.Node, - onMainItemSelect?: (NavItemT) => mixed, - overrides?: OverridesT, - title?: React.Node, -|}; diff --git a/src/app-nav-bar/user-menu.js.flow b/src/app-nav-bar/user-menu.js.flow deleted file mode 100644 index 4af99bf959..0000000000 --- a/src/app-nav-bar/user-menu.js.flow +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Avatar } from '../avatar/index.js'; -import { Button } from '../button/index.js'; -import { getOverrides, mergeOverrides } from '../helpers/overrides.js'; -import ChevronDownSmallFilled from '../icon/chevron-down.js'; -import ChevronUpSmallFilled from '../icon/chevron-up.js'; -import { MenuAdapter, ListItemLabel, ARTWORK_SIZES } from '../list/index.js'; -import { StatefulMenu, StyledList } from '../menu/index.js'; -import { StatefulPopover, PLACEMENT, TRIGGER_TYPE } from '../popover/index.js'; - -import { StyledUserMenuButton, StyledUserMenuProfileListItem } from './styled-components.js'; -import type { UserMenuPropsT, NavItemT, OverridesT } from './types.js'; -import UserProfileTile from './user-profile-tile.js'; -import { defaultMapItemToNode } from './utils.js'; - -const MENU_ITEM_WIDTH = '275px'; - -// eslint-disable-next-line react/display-name -const UserMenuListItem = React.forwardRef((props, ref) => { - const { item, mapItemToNode = defaultMapItemToNode } = props; - // Replace with a user menu item renderer - return ( - - {mapItemToNode(item)} - - ); -}); - -const svgStyleOverride = ({ $theme }) => ({ paddingLeft: $theme.sizing.scale200 }); - -export default function UserMenuComponent(props: {| - ...UserMenuPropsT, - mapItemToNode: (NavItemT) => React.Node, - onItemSelect: (NavItemT) => mixed, - overrides: OverridesT, -|}) { - // isOpen is used for displaying different arrow icons in open or closed state - const [isOpen, setIsOpen] = React.useState(false); - const { userItems = [], username, userImgUrl, overrides = {} } = props; - - const [UserMenuProfileListItem, userMenuProfileListItemProps] = getOverrides( - overrides.UserMenuProfileListItem, - StyledUserMenuProfileListItem - ); - - const [UserMenuButton, userMenuButtonProps] = getOverrides(overrides.UserMenuButton, Button); - userMenuButtonProps.overrides = mergeOverrides( - { BaseButton: { component: StyledUserMenuButton } }, - userMenuButtonProps.overrides - ); - - const [UserMenu, userMenuProps] = getOverrides(overrides.UserMenu, StatefulMenu); - userMenuProps.overrides = mergeOverrides( - { - List: { - // eslint-disable-next-line react/display-name - component: React.forwardRef(({ children, ...restProps }, ref) => ( - - - {/* Replace with a renderer: renderUserProfileTile() */} - - - {children} - - )), - style: { width: MENU_ITEM_WIDTH }, - }, - // eslint-disable-next-line react/display-name - ListItem: React.forwardRef((listItemProps, ref) => { - return ( - - ); - }), - }, - userMenuProps.overrides - ); - - return ( - ( - { - props.onItemSelect(item); - close(); - }} - {...userMenuProps} - /> - )} - autoFocus={false} - dismissOnEsc={true} - dismissOnClickOutside={true} - onOpen={() => setIsOpen(true)} - onClose={() => setIsOpen(false)} - placement={PLACEMENT.bottomRight} - popperOptions={{ modifiers: { flip: { enabled: false } } }} - triggerType={TRIGGER_TYPE.click} - > - - - {isOpen ? ( - - ) : ( - - )} - - - ); -} diff --git a/src/app-nav-bar/user-profile-tile.js.flow b/src/app-nav-bar/user-profile-tile.js.flow deleted file mode 100644 index a7236b7a1e..0000000000 --- a/src/app-nav-bar/user-profile-tile.js.flow +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { Avatar } from '../avatar/index.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { LabelMedium, ParagraphSmall } from '../typography/index.js'; - -import { - StyledUserProfileTileContainer, - StyledUserProfilePictureContainer, - StyledUserProfileInfoContainer, -} from './styled-components.js'; -import type { OverridesT, UserMenuPropsT } from './types.js'; - -export default function UserProfileTile(props: {| ...UserMenuPropsT, overrides: OverridesT |}) { - const { overrides = {}, username, usernameSubtitle, userImgUrl } = props; - - const [UserProfileTileContainer, userProfileTileContainerProps] = getOverrides( - overrides.UserProfileTileContainer, - StyledUserProfileTileContainer - ); - - const [UserProfilePictureContainer, userProfilePictureContainerProps] = getOverrides( - overrides.UserProfilePictureContainer, - StyledUserProfilePictureContainer - ); - - const [UserProfileInfoContainer, userProfileInfoContainerProps] = getOverrides( - overrides.UserProfileInfoContainer, - StyledUserProfileInfoContainer - ); - - return ( - // Replace with a profile tile renderer: renderUserProfileTile() - - - - - - {username} - {usernameSubtitle ? ( - - {usernameSubtitle} - - ) : null} - - - ); -} diff --git a/src/app-nav-bar/utils.js.flow b/src/app-nav-bar/utils.js.flow deleted file mode 100644 index 75d96d9096..0000000000 --- a/src/app-nav-bar/utils.js.flow +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import type { NavItemT } from './types.js'; - -type GetUniqueIdentifierT = (NavItemT) => string | number; - -export function defaultMapItemToNode(item: NavItemT) { - if (__DEV__) { - if (!item.label) { - throw Error( - 'There needs to be an unique item.label. You can implement a custom mapping with the mapItemToNode prop.' - ); - } - } - return item.label; -} - -function defaultGetUniqueIdentifier(item: NavItemT) { - if (__DEV__) { - if (!item.label) { - throw Error( - 'There needs to be an unique item.label. You can implement a custom mapping with the getUniqueIdentifier argument to setItemActive.' - ); - } - } - return item.label; -} - -export function mapItemsActive(items: NavItemT[], predicate: (NavItemT) => boolean) { - return items.map((current) => { - if (predicate(current)) { - current.active = true; - } else { - current.active = false; - } - - if (current.children) { - current.children = mapItemsActive(current.children, predicate); - if (current.children.some((child) => child.active)) { - current.active = true; - } - } - - return current; - }); -} - -export function setItemActive( - items: NavItemT[], - item: NavItemT, - getUniqueIdentifier?: GetUniqueIdentifierT = defaultGetUniqueIdentifier -) { - return mapItemsActive( - items, - (current) => getUniqueIdentifier(current) === getUniqueIdentifier(item) - ); -} diff --git a/src/aspect-ratio-box/aspect-ratio-box-body.js.flow b/src/aspect-ratio-box/aspect-ratio-box-body.js.flow deleted file mode 100644 index 33d030e72e..0000000000 --- a/src/aspect-ratio-box/aspect-ratio-box-body.js.flow +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import Block from '../block/block.js'; -import type { BlockPropsT } from '../block/types.js'; - -export const AspectRatioBoxBody = ({ - position, - top, - bottom, - width, - ...restProps -}: $Exact): React.Node => ( - -); - -export default AspectRatioBoxBody; diff --git a/src/aspect-ratio-box/aspect-ratio-box.js.flow b/src/aspect-ratio-box/aspect-ratio-box.js.flow deleted file mode 100644 index 7bdeefda48..0000000000 --- a/src/aspect-ratio-box/aspect-ratio-box.js.flow +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Block } from '../block/index.js'; -import { mergeOverrides } from '../helpers/overrides.js'; -import type { AspectRatioBoxPropsT } from './types.js'; - -const aspectRatioBoxStyle = ({ $aspectRatio }) => ({ - position: 'relative', - '::before': { - content: '""', - width: '1px', - marginLeft: '-1px', - float: 'left', - height: 0, - paddingTop: `${100 / $aspectRatio}%`, - pointerEvents: 'none', - }, - '::after': { - content: '""', - display: 'table', - clear: 'both', - }, -}); - -const AspectRatioBox = ({ - forwardedRef, - aspectRatio = 1, - overrides = {}, - ...restProps -}: // flowlint-next-line unclear-type:off -AspectRatioBoxPropsT & { forwardedRef: any }): React.Node => { - const aspectRatioBoxOverrides = { - Block: { - style: aspectRatioBoxStyle, - }, - }; - const blockOverrides = mergeOverrides(aspectRatioBoxOverrides, overrides); - return ( - - ); -}; - -const AspectRatioBoxComponent = React.forwardRef( - (props: AspectRatioBoxPropsT, ref) => -); -AspectRatioBoxComponent.displayName = 'AspectRatioBox'; -export default AspectRatioBoxComponent; diff --git a/src/aspect-ratio-box/index.js.flow b/src/aspect-ratio-box/index.js.flow deleted file mode 100644 index 4917fab41c..0000000000 --- a/src/aspect-ratio-box/index.js.flow +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { default as AspectRatioBox } from './aspect-ratio-box.js'; -export { default as AspectRatioBoxBody } from './aspect-ratio-box-body.js'; -export type * from './types.js'; diff --git a/src/aspect-ratio-box/types.js.flow b/src/aspect-ratio-box/types.js.flow deleted file mode 100644 index bd6008e3c1..0000000000 --- a/src/aspect-ratio-box/types.js.flow +++ /dev/null @@ -1,14 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import type { BlockPropsT } from '../block/types.js'; - -export type AspectRatioBoxPropsT = { - /** Aspect ratio is width divided by height. */ - +aspectRatio?: number, -} & BlockPropsT; diff --git a/src/avatar/avatar.js.flow b/src/avatar/avatar.js.flow deleted file mode 100644 index b543d4c56b..0000000000 --- a/src/avatar/avatar.js.flow +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { getOverrides } from '../helpers/overrides.js'; - -import { - Avatar as StyledAvatar, - Initials as StyledInitials, - Root as StyledRoot, -} from './styled-components.js'; -import type { PropsT } from './types.js'; - -function getInitials(name) { - const words = name.split(' '); - const initials = words.map((word) => word[0]); - return initials.slice(0, 2).join('').toUpperCase(); -} - -export default function Avatar({ - initials, - name = '', - overrides = {}, - size = 'scale1000', - src, -}: PropsT) { - // $FlowFixMe - const imageRef = React.useRef(null); - const [imageLoaded, setImageLoaded] = React.useState(false); - - function handleLoad() { - setImageLoaded(true); - } - - function handleError() { - setImageLoaded(false); - } - - React.useEffect(() => { - setImageLoaded(false); - - if (imageRef.current) { - if (typeof src === 'string') { - imageRef.current.src = src; - imageRef.current.onload = handleLoad; - imageRef.current.onerror = handleError; - } - } - - return () => { - if (imageRef.current) { - imageRef.current.onload = null; - imageRef.current.onerror = null; - } - }; - }, [src]); - - const [Avatar, avatarProps] = getOverrides(overrides.Avatar, StyledAvatar); - const [Initials, initialsProps] = getOverrides(overrides.Initials, StyledInitials); - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - - return ( - - - - {!imageLoaded && {initials || getInitials(name)}} - - ); -} diff --git a/src/avatar/index.js.flow b/src/avatar/index.js.flow deleted file mode 100644 index ec51f5cdc7..0000000000 --- a/src/avatar/index.js.flow +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { default as Avatar } from './avatar.js'; - -// Styled elements -export { - Avatar as StyledAvatar, - Initials as StyledInitials, - Root as StyledRoot, -} from './styled-components.js'; - -// Flow -export type * from './types.js'; diff --git a/src/avatar/styled-components.js.flow b/src/avatar/styled-components.js.flow deleted file mode 100644 index 8d28d9ae26..0000000000 --- a/src/avatar/styled-components.js.flow +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import { styled } from '../styles/index.js'; -import type { AvatarStylePropsT, RootStylePropsT, InitialsStylePropsT } from './types.js'; - -function getSize(props) { - const { $size, $theme } = props; - - const defaultSize = $theme.sizing.scale1000; - const size = $size || defaultSize; - return $theme.sizing[size] || size; -} - -export const Avatar = styled('img', (props) => { - const themedSize = getSize(props); - - return { - borderTopLeftRadius: '50%', - borderTopRightRadius: '50%', - borderBottomRightRadius: '50%', - borderBottomLeftRadius: '50%', - boxSizing: 'border-box', - display: props.$imageLoaded ? 'block' : 'none', - height: themedSize, - width: themedSize, - objectFit: 'cover', - }; -}); - -export const Initials = styled('div', (props) => ({ - ...props.$theme.typography.font300, - color: props.$theme.colors.contentInversePrimary, - alignItems: 'center', - display: 'flex', - justifyContent: 'center', - height: '100%', -})); - -export const Root = styled('div', (props) => { - const { $didImageFailToLoad } = props; - const themedSize = getSize(props); - - return ({ - backgroundColor: $didImageFailToLoad ? props.$theme.colors.backgroundInversePrimary : null, - borderTopLeftRadius: '50%', - borderTopRightRadius: '50%', - borderBottomRightRadius: '50%', - borderBottomLeftRadius: '50%', - boxSizing: 'border-box', - display: 'inline-block', - - // image previously set the root height/width - // since image is not rendered, set the height/width - height: $didImageFailToLoad ? themedSize : null, - width: $didImageFailToLoad ? themedSize : null, - }: {}); -}); diff --git a/src/avatar/types.js.flow b/src/avatar/types.js.flow deleted file mode 100644 index 5d4cf2fb14..0000000000 --- a/src/avatar/types.js.flow +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import type { OverrideT } from '../helpers/overrides.js'; - -export type InitialsStylePropsT = {}; -export type AvatarStylePropsT = { - $didImageFailToLoad?: boolean, - $imageLoaded?: boolean, - $size?: string, -}; -export type RootStylePropsT = { - $didImageFailToLoad: boolean, - $size?: string, -}; -export type StylePropsT = RootStylePropsT; - -export type OverridesT = { - Avatar?: OverrideT, - Initials?: OverrideT, - Root?: OverrideT, -}; - -export type PropsT = {| - /** Bypasses initial generation from provided name with this value. */ - initials?: string, - /** Defines an alternative text description of the image. */ - name?: string, - overrides?: OverridesT, - /** Defines the width/height of the image. Accepts labels from theme.sizing, or passes value to height/width. */ - size?: string, - /** Image to display. */ - src?: string, -|}; diff --git a/src/badge/badge.js.flow b/src/badge/badge.js.flow deleted file mode 100644 index e536a20e1a..0000000000 --- a/src/badge/badge.js.flow +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { getOverrides } from '../helpers/overrides.js'; -import { StyledBadge, StyledRoot, StyledPositioner } from './styled-components.js'; -import type { BadgePropsT } from './types.js'; -import { PLACEMENT, ROLE, SHAPE, HIERARCHY } from './constants.js'; -import { getAnchorFromChildren } from './utils.js'; - -const Badge = ({ - children, - content, - color, - shape = SHAPE.rectangle, - placement = PLACEMENT.topRight, - hierarchy, - horizontalOffset, - verticalOffset, - hidden, - overrides = {}, -}: BadgePropsT) => { - const [Badge, badgeProps] = getOverrides(overrides.Badge, StyledBadge); - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [Positioner, positionerProps] = getOverrides(overrides.Positioner, StyledPositioner); - - const anchor = getAnchorFromChildren(children); - - const VALID_RECT_PLACEMENTS = [ - PLACEMENT.topLeft, - PLACEMENT.topRight, - PLACEMENT.bottomRight, - PLACEMENT.bottomLeft, - ]; - - if (__DEV__) { - if (shape === SHAPE.rectangle && !VALID_RECT_PLACEMENTS.includes(placement)) { - console.warn('Rectangle badges should only be placed in a corner or used inline'); - } - if (shape === SHAPE.rectangle && hierarchy === HIERARCHY.secondary && anchor) { - console.warn( - 'Secondary badges should not be positioned. Use the inline version of this badge instead.' - ); - } - if (shape === SHAPE.pill && hierarchy === HIERARCHY.secondary) { - console.warn('Pill badges should only be used with primary hierarchy'); - } - } - - // If there's no anchor, render the badge inline - if (!anchor) { - return ( - - {content} - - ); - } - - return ( - - {anchor} - - - {content} - - - - ); -}; -export default Badge; diff --git a/src/badge/constants.js.flow b/src/badge/constants.js.flow deleted file mode 100644 index 2e7abac2ee..0000000000 --- a/src/badge/constants.js.flow +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const HIERARCHY = Object.freeze({ - primary: 'primary', - secondary: 'secondary', -}); - -export const SHAPE = Object.freeze({ - pill: 'pill', - rectangle: 'rectangle', -}); - -export const COLOR = Object.freeze({ - accent: 'accent', - primary: 'primary', - positive: 'positive', - negative: 'negative', - warning: 'warning', -}); - -export const PLACEMENT = Object.freeze({ - topLeft: 'topLeft', - topRight: 'topRight', - bottomRight: 'bottomRight', - bottomLeft: 'bottomLeft', - topLeftEdge: 'topLeftEdge', - topEdge: 'topEdge', - topRightEdge: 'topRightEdge', - bottomRightEdge: 'bottomRightEdge', - bottomEdge: 'bottomEdge', - bottomLeftEdge: 'bottomLeftEdge', - leftTopEdge: 'leftTopEdge', - rightTopEdge: 'rightTopEdge', - rightBottomEdge: 'rightBottomEdge', - leftBottomEdge: 'leftBottomEdge', -}); - -export const ROLE = Object.freeze({ - badge: 'badge', - notificationCircle: 'notificationCircle', - hintDot: 'hintDot', -}); diff --git a/src/badge/hint-dot.js.flow b/src/badge/hint-dot.js.flow deleted file mode 100644 index 5e1b31ee16..0000000000 --- a/src/badge/hint-dot.js.flow +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { useStyletron } from '../styles/index.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { StyledHintDot, StyledRoot, StyledPositioner } from './styled-components.js'; -import type { HintDotPropsT } from './types.js'; -import { PLACEMENT, ROLE } from './constants.js'; -import { getAnchorFromChildren } from './utils.js'; - -const HintDot = ({ - children, - color, - horizontalOffset: horizontalOffsetProp, - verticalOffset: verticalOffsetProp, - hidden, - overrides = {}, -}: HintDotPropsT) => { - const [HintDot, hintDotProps] = getOverrides(overrides.Badge, StyledHintDot); - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [Positioner, positionerProps] = getOverrides(overrides.Positioner, StyledPositioner); - - const [, theme] = useStyletron(); - - const anchor = getAnchorFromChildren(children); - - // if the anchor is a string, we supply default offsets - let horizontalOffset = horizontalOffsetProp; - let verticalOffset = verticalOffsetProp; - if (typeof anchor === 'string') { - if (!horizontalOffset) { - horizontalOffset = '-14px'; - } - if (!verticalOffset) { - verticalOffset = '-4px'; - } - } - return ( - - {anchor} - - - - - ); -}; -export default HintDot; diff --git a/src/badge/index.js.flow b/src/badge/index.js.flow deleted file mode 100644 index 97fe09e117..0000000000 --- a/src/badge/index.js.flow +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -export { default as Badge } from './badge.js'; -export { default as NotificationCircle } from './notification-circle.js'; -export { default as HintDot } from './hint-dot.js'; - -export { HIERARCHY, SHAPE, COLOR, PLACEMENT } from './constants.js'; - -export * from './styled-components.js'; - -export type * from './types.js'; diff --git a/src/badge/notification-circle.js.flow b/src/badge/notification-circle.js.flow deleted file mode 100644 index f5b3a9deea..0000000000 --- a/src/badge/notification-circle.js.flow +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { getOverrides } from '../helpers/overrides.js'; -import { StyledNotificationCircle, StyledRoot, StyledPositioner } from './styled-components.js'; -import type { NotificationCirclePropsT } from './types.js'; -import { PLACEMENT, ROLE } from './constants.js'; -import { getAnchorFromChildren } from './utils.js'; - -const NotificationCircle = ({ - children, - content: contentProp, - color, - placement = PLACEMENT.topRight, - horizontalOffset, - verticalOffset, - hidden, - overrides = {}, -}: NotificationCirclePropsT) => { - const [NotificationCircle, NotificationCircleProps] = getOverrides( - overrides.Badge, - StyledNotificationCircle - ); - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [Positioner, positionerProps] = getOverrides(overrides.Positioner, StyledPositioner); - - const anchor = getAnchorFromChildren(children); - - if (__DEV__) { - if (typeof contentProp === 'string') { - console.error(`[baseui] NotificationCircle child must be number or icon, found string`); - } - if (placement && placement !== PLACEMENT.topLeft && placement !== PLACEMENT.topRight) { - console.error( - `[baseui] NotificationCircle must be placed topLeft or topRight, found ${placement}` - ); - } - } - - let content = contentProp; - if (typeof content === 'number' && content > 9) { - content = '9+'; - } - - // If there's no anchor, render the badge inline - if (!anchor) { - return ( - - {content} - - ); - } - - return ( - - {anchor} - - - {content} - - - - ); -}; -export default NotificationCircle; diff --git a/src/badge/styled-components.js.flow b/src/badge/styled-components.js.flow deleted file mode 100644 index a8b7f21691..0000000000 --- a/src/badge/styled-components.js.flow +++ /dev/null @@ -1,326 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import { styled } from '../styles/index.js'; -import type { PlacementT, ColorT, ShapeT, RoleT, HierarchyT } from './types.js'; -import { COLOR, SHAPE, ROLE, PLACEMENT, HIERARCHY } from './constants.js'; - -function getColorStyles({ $theme, $hierarchy, $color }): {| - color: string, - backgroundColor: string, -|} { - const COLOR_STYLES = { - [HIERARCHY.primary]: { - [COLOR.accent]: { - color: $theme.colors.contentOnColor, - backgroundColor: $theme.colors.backgroundAccent, - }, - [COLOR.primary]: { - color: $theme.colors.contentInversePrimary, - backgroundColor: $theme.colors.backgroundInversePrimary, - }, - [COLOR.positive]: { - color: $theme.colors.contentOnColor, - backgroundColor: $theme.colors.backgroundPositive, - }, - [COLOR.negative]: { - color: $theme.colors.contentOnColor, - backgroundColor: $theme.colors.backgroundNegative, - }, - [COLOR.warning]: { - color: $theme.colors.contentOnColorInverse, - backgroundColor: $theme.colors.backgroundWarning, - }, - }, - [HIERARCHY.secondary]: { - [COLOR.accent]: { - color: $theme.colors.contentAccent, - backgroundColor: $theme.colors.backgroundAccentLight, - }, - [COLOR.primary]: { - color: $theme.colors.contentPrimary, - backgroundColor: $theme.colors.backgroundSecondary, - }, - [COLOR.positive]: { - color: $theme.colors.contentPositive, - backgroundColor: $theme.colors.backgroundPositiveLight, - }, - [COLOR.negative]: { - color: $theme.colors.contentNegative, - backgroundColor: $theme.colors.backgroundNegativeLight, - }, - [COLOR.warning]: { - color: $theme.colors.contentWarning, - backgroundColor: $theme.colors.backgroundWarningLight, - }, - }, - }; - - return COLOR_STYLES[$hierarchy][$color]; -} - -const DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT = { - top: '-10px', - right: '-10px', -}; - -const DEFAULT_HINT_DOT_PLACEMENT = { - top: '-4px', - right: '-4px', -}; - -const POSITION_STYLES = Object.freeze({ - [ROLE.badge]: { - [PLACEMENT.topEdge]: { - top: '-8px', - left: '50%', - transform: 'translateX(-50%)', - }, - [PLACEMENT.bottomEdge]: { - bottom: '-8px', - left: '50%', - transform: 'translateX(-50%)', - }, - [PLACEMENT.topLeft]: { - top: '16px', - left: '16px', - }, - [PLACEMENT.topRight]: { - top: '16px', - right: '16px', - }, - [PLACEMENT.bottomRight]: { - bottom: '16px', - right: '16px', - }, - - [PLACEMENT.bottomLeft]: { - bottom: '16px', - left: '16px', - }, - [PLACEMENT.topLeftEdge]: { - top: '-8px', - left: '16px', - }, - [PLACEMENT.topRightEdge]: { - top: '-8px', - right: '16px', - }, - [PLACEMENT.bottomRightEdge]: { - bottom: '-8px', - right: '16px', - }, - [PLACEMENT.bottomLeftEdge]: { - bottom: '-8px', - left: '16px', - }, - [PLACEMENT.leftTopEdge]: { - top: '16px', - left: '-8px', - }, - [PLACEMENT.rightTopEdge]: { - top: '16px', - right: '-8px', - }, - [PLACEMENT.rightBottomEdge]: { - bottom: '16px', - right: '-8px', - }, - [PLACEMENT.leftBottomEdge]: { - bottom: '16px', - left: '-8px', - }, - }, - [ROLE.notificationCircle]: { - [PLACEMENT.topLeft]: { - top: '-10px', - left: '-10px', - }, - [PLACEMENT.topRight]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - // NotificationCircle can only be placed topLeft or topRight, other values fall back to topRight - [PLACEMENT.topEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.bottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.bottomRight]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.bottomLeft]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.topLeftEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.topRightEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.bottomRightEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.bottomLeftEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.leftTopEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.rightTopEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.rightBottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - [PLACEMENT.leftBottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT, - }, - [ROLE.hintDot]: { - [PLACEMENT.topLeft]: { - top: '-4px', - left: '-4px', - }, - [PLACEMENT.topRight]: DEFAULT_HINT_DOT_PLACEMENT, - // HintDot can only be placed topLeft or topRight, other values fall back to topRight - [PLACEMENT.topEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.bottomEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.bottomRight]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.bottomLeft]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.topLeftEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.topRightEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.bottomRightEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.bottomLeftEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.leftTopEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.rightTopEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.rightBottomEdge]: DEFAULT_HINT_DOT_PLACEMENT, - [PLACEMENT.leftBottomEdge]: DEFAULT_HINT_DOT_PLACEMENT, - }, -}); - -export const StyledRoot = styled<{}>('div', () => { - return { - position: 'relative', - display: 'inline-block', - lineHeight: 'initial', - }; -}); -StyledRoot.displayName = 'StyledRoot'; - -const TOP_PLACEMENTS = [ - PLACEMENT.topLeft, - PLACEMENT.topRight, - PLACEMENT.topLeftEdge, - PLACEMENT.topEdge, - PLACEMENT.topRightEdge, - PLACEMENT.leftTopEdge, - PLACEMENT.rightTopEdge, -]; -const BOTTOM_PLACEMENTS = [ - PLACEMENT.bottomLeft, - PLACEMENT.bottomRight, - PLACEMENT.bottomLeftEdge, - PLACEMENT.bottomEdge, - PLACEMENT.bottomRightEdge, - PLACEMENT.leftBottomEdge, - PLACEMENT.rightBottomEdge, -]; -const LEFT_PLACEMENTS = [ - PLACEMENT.topLeft, - PLACEMENT.topLeftEdge, - PLACEMENT.topEdge, - PLACEMENT.bottomLeft, - PLACEMENT.bottomLeftEdge, - PLACEMENT.bottomEdge, - PLACEMENT.leftTopEdge, - PLACEMENT.leftBottomEdge, -]; -const RIGHT_PLACEMENTS = [ - PLACEMENT.topRight, - PLACEMENT.topRightEdge, - PLACEMENT.bottomRight, - PLACEMENT.bottomRightEdge, - PLACEMENT.rightTopEdge, - PLACEMENT.rightBottomEdge, -]; - -export const StyledPositioner = styled<{ - $role: RoleT, - $placement: PlacementT, - $horizontalOffset?: ?string, - $verticalOffset?: ?string, -}>('div', ({ $theme, $role, $placement, $horizontalOffset, $verticalOffset }) => { - let positionStyle = POSITION_STYLES[$role][$placement]; - - if ($verticalOffset) { - if (TOP_PLACEMENTS.includes($placement)) { - positionStyle = { ...positionStyle, top: $verticalOffset }; - } - if (BOTTOM_PLACEMENTS.includes($placement)) { - positionStyle = { ...positionStyle, bottom: $verticalOffset }; - } - } - - if ($horizontalOffset) { - if (LEFT_PLACEMENTS.includes($placement)) { - positionStyle = { ...positionStyle, left: $horizontalOffset }; - } - if (RIGHT_PLACEMENTS.includes($placement)) { - positionStyle = { ...positionStyle, right: $horizontalOffset }; - } - } - - return { - ...positionStyle, - position: 'absolute', - lineHeight: 'initial', - }; -}); -StyledPositioner.displayName = 'StyledPositioner'; - -export const StyledBadge = styled<{ - $shape?: ShapeT, - $color?: ColorT, - $hierarchy?: HierarchyT, - $hidden?: boolean, -}>( - 'div', - ({ - $theme, - $shape = SHAPE.rectangle, - $color = COLOR.accent, - $hierarchy = HIERARCHY.primary, - $hidden, - }) => { - return { - visibility: $hidden ? 'hidden' : 'inherit', - boxSizing: 'border-box', - height: $theme.sizing.scale700, - borderRadius: - $shape === SHAPE.rectangle ? $theme.borders.radius200 : $theme.borders.radius500, - paddingRight: $shape === SHAPE.rectangle ? $theme.sizing.scale100 : $theme.sizing.scale300, - paddingLeft: $shape === SHAPE.rectangle ? $theme.sizing.scale100 : $theme.sizing.scale300, - display: 'inline-flex', - alignItems: 'center', - ...getColorStyles({ $theme, $hierarchy, $color }), - ...($hierarchy === HIERARCHY.primary - ? $theme.typography.LabelSmall - : $theme.typography.ParagraphSmall), - }; - } -); -StyledBadge.displayName = 'StyledBadge'; - -export const StyledNotificationCircle = styled<{ - $color?: ColorT, - $hidden?: boolean, -}>('div', ({ $theme, $color = COLOR.accent, $hidden }) => { - return { - visibility: $hidden ? 'hidden' : 'inherit', - height: $theme.sizing.scale700, - width: $theme.sizing.scale700, - borderRadius: '20px', - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - ...getColorStyles({ $theme, $hierarchy: HIERARCHY.primary, $color }), - ...$theme.typography.LabelXSmall, - }; -}); -StyledNotificationCircle.displayName = 'StyledNotificationCircle'; - -export const StyledHintDot = styled<{ $color: ColorT, $hidden?: boolean }>( - 'div', - ({ $theme, $color = COLOR.accent, $hidden }) => { - return { - visibility: $hidden ? 'hidden' : 'inherit', - backgroundColor: $theme.colors[$color], - boxSizing: 'content-box', - height: '8px', - width: '8px', - borderRadius: '50%', - border: `4px solid ${$theme.colors.backgroundPrimary}`, - ...getColorStyles({ $theme, $hierarchy: HIERARCHY.primary, $color }), - }; - } -); -StyledHintDot.displayName = 'StyledHintDot'; diff --git a/src/badge/types.js.flow b/src/badge/types.js.flow deleted file mode 100644 index 5c2b786f85..0000000000 --- a/src/badge/types.js.flow +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { HIERARCHY, SHAPE, COLOR, PLACEMENT, ROLE } from './constants.js'; -import type { OverrideT } from '../helpers/overrides.js'; - -export type HierarchyT = $Values; -export type ShapeT = $Values; -export type ColorT = $Values; -export type PlacementT = $Values; -export type RoleT = $Values; - -export type BadgeOverridesT = { - Root?: OverrideT, - Positioner?: OverrideT, - Badge?: OverrideT, -}; - -export type BadgePropsT = { - content: React.Node, - hierarchy?: HierarchyT, - shape?: ShapeT, - color?: ColorT, - placement?: PlacementT, - hidden?: boolean, - horizontalOffset?: string, - verticalOffset?: string, - overrides?: BadgeOverridesT, - children?: React.Node, -}; - -export type NotificationCirclePropsT = { - content: React.Node, - color?: ColorT, - placement?: PlacementT, - hidden?: boolean, - horizontalOffset?: string, - verticalOffset?: string, - overrides?: BadgeOverridesT, - children?: React.Node, -}; - -export type HintDotPropsT = { - color?: ColorT, - hidden?: boolean, - horizontalOffset?: string, - verticalOffset?: string, - overrides?: BadgeOverridesT, - children?: React.Node, -}; diff --git a/src/badge/utils.js.flow b/src/badge/utils.js.flow deleted file mode 100644 index 639a2aba53..0000000000 --- a/src/badge/utils.js.flow +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; - -export const getAnchorFromChildren = (children: ?React.Node) => { - const childArray = React.Children.toArray(children); - if (childArray.length !== 1) { - // eslint-disable-next-line no-console - console.error( - `[baseui] No more than 1 child may be passed to Badge, found ${childArray.length} children` - ); - } - return childArray[0]; -}; diff --git a/src/banner/banner.js.flow b/src/banner/banner.js.flow deleted file mode 100644 index 4a415a21f1..0000000000 --- a/src/banner/banner.js.flow +++ /dev/null @@ -1,254 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { Button, SIZE as BUTTON_SIZE, SHAPE as BUTTON_SHAPE } from '../button/index.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { useStyletron } from '../styles/index.js'; -import { ACTION_POSITION, ARTWORK_TYPE, HIERARCHY, KIND } from './constants.js'; -import { - StyledRoot, - StyledLeadingContent, - StyledMessageContent, - StyledTitle, - StyledMessage, - StyledBelowContent, - StyledTrailingContent, - StyledTrailingButtonContainer, - StyledTrailingIconButton, -} from './styled-components.js'; -import type { PropsT } from './types.js'; - -function low(theme, kind) { - switch (kind) { - case KIND.negative: - return { - actionBackgroundColor: theme.colors.bannerActionLowNegative, - backgroundColor: theme.colors.backgroundNegativeLight, - color: theme.colors.contentPrimary, - }; - - case KIND.positive: - return { - actionBackgroundColor: theme.colors.bannerActionLowPositive, - backgroundColor: theme.colors.backgroundPositiveLight, - color: theme.colors.contentPrimary, - }; - - case KIND.warning: - return { - actionBackgroundColor: theme.colors.bannerActionLowWarning, - backgroundColor: theme.colors.backgroundWarningLight, - color: theme.colors.contentPrimary, - }; - - case KIND.info: - default: - return { - actionBackgroundColor: theme.colors.bannerActionLowInfo, - backgroundColor: theme.colors.backgroundAccentLight, - color: theme.colors.contentPrimary, - }; - } -} - -function high(theme, kind) { - switch (kind) { - case KIND.negative: - return { - actionBackgroundColor: theme.colors.bannerActionHighNegative, - backgroundColor: theme.colors.backgroundNegative, - color: theme.colors.contentOnColor, - }; - - case KIND.positive: - return { - actionBackgroundColor: theme.colors.bannerActionHighPositive, - backgroundColor: theme.colors.backgroundPositive, - color: theme.colors.contentOnColor, - }; - - case KIND.warning: - return { - actionBackgroundColor: theme.colors.bannerActionHighWarning, - backgroundColor: theme.colors.backgroundWarning, - color: theme.colors.contentPrimary, - }; - - case KIND.info: - default: - return { - actionBackgroundColor: theme.colors.bannerActionHighInfo, - backgroundColor: theme.colors.backgroundAccent, - color: theme.colors.contentOnColor, - }; - } -} - -function Leading({ artwork }) { - const [, theme] = useStyletron(); - - if (!artwork) { - return null; - } - - const size = artwork.type === ARTWORK_TYPE.badge ? theme.sizing.scale1000 : theme.sizing.scale800; - return artwork.icon({ size }); -} - -function Below({ action, backgroundColor, color }) { - if (!action || action.position !== ACTION_POSITION.below) { - return null; - } - - if (__DEV__) { - if (action.icon) { - console.error('Banner ACTION_POSITION.below must not have an icon.'); - return null; - } - } - - if (action.label) { - return ( - - ); - } - - return null; -} - -function Trailing({ action, backgroundColor, color, overrides, nested }) { - const [, theme] = useStyletron(); - - if (!action || (action.position && action.position !== ACTION_POSITION.trailing)) { - return null; - } - - const [TrailingIconButton, trailingIconButtonProps] = getOverrides( - overrides.TrailingIconButton, - StyledTrailingIconButton - ); - - if (action.icon) { - return ( - - {action.icon({ size: theme.sizing.scale650 })} - - ); - } - - const [TrailingButtonContainer, trailingButtonContainerProps] = getOverrides( - overrides.TrailingButtonContainer, - StyledTrailingButtonContainer - ); - - if (action.label) { - return ( - - - - ); - } - - return null; -} - -export function Banner({ - action, - artwork, - children, - hierarchy = HIERARCHY.low, - kind = KIND.info, - overrides = {}, - nested = false, - title, -}: PropsT) { - const [, theme] = useStyletron(); - const styles = hierarchy === HIERARCHY.high ? high(theme, kind) : low(theme, kind); - const actionPosition = action && action.position ? action.position : ACTION_POSITION.trailing; - - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [LeadingContent, leadingContentProps] = getOverrides( - overrides.LeadingContent, - StyledLeadingContent - ); - const [MessageContent, messageContentProps] = getOverrides( - overrides.MessageContent, - StyledMessageContent - ); - const [TrailingContent, trailingContentProps] = getOverrides( - overrides.TrailingContent, - StyledTrailingContent - ); - const [BelowContent, belowContentProps] = getOverrides( - overrides.BelowContent, - StyledBelowContent - ); - const [Title, titleProps] = getOverrides(overrides.Title, StyledTitle); - const [Message, messageProps] = getOverrides(overrides.Message, StyledMessage); - const ariaLabel = rootProps.hasOwnProperty('aria-label') - ? rootProps['aria-label'] - : 'this is an announcement banner'; - - return ( - - - - - - - {Boolean(title) && {title}} - {Boolean(children) && {children}} - - - - - - - - - - - ); -} diff --git a/src/banner/constants.js.flow b/src/banner/constants.js.flow deleted file mode 100644 index 7ed35ba87c..0000000000 --- a/src/banner/constants.js.flow +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const ACTION_POSITION = Object.freeze({ - below: 'below', - trailing: 'trailing', -}); - -export const ARTWORK_TYPE = Object.freeze({ - icon: 'icon', - badge: 'badge', -}); - -export const HIERARCHY = Object.freeze({ - low: 'low', - high: 'high', -}); - -export const KIND = Object.freeze({ - info: 'info', - negative: 'negative', - positive: 'positive', - warning: 'warning', -}); diff --git a/src/banner/index.js.flow b/src/banner/index.js.flow deleted file mode 100644 index a9776bb265..0000000000 --- a/src/banner/index.js.flow +++ /dev/null @@ -1,12 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { Banner } from './banner.js'; -export * from './constants.js'; -export * from './styled-components.js'; -export type * from './types.js'; diff --git a/src/banner/styled-components.js.flow b/src/banner/styled-components.js.flow deleted file mode 100644 index 25a0a40ce6..0000000000 --- a/src/banner/styled-components.js.flow +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import { styled } from '../styles/index.js'; -import { ACTION_POSITION } from './constants.js'; -import type { ActionPositionT } from './types.js'; - -export const StyledRoot = styled<{ $backgroundColor: string, $color: string, $nested: boolean }>( - 'div', - ({ $theme, $backgroundColor, $color, $nested }) => { - const radius = $nested ? $theme.borders.radius300 : $theme.borders.radius400; - return { - backgroundColor: $backgroundColor, - borderStartStartRadius: radius, - borderStartEndRadius: radius, - borderEndStartRadius: radius, - borderEndEndRadius: radius, - color: $color, - display: 'grid', - gridColumnGap: $theme.sizing.scale600, - gridTemplateColumns: 'min-content auto min-content', - gridTemplateRows: 'auto min-content', - margin: $theme.sizing.scale600, - }; - } -); - -export const StyledLeadingContent = styled<{ $includesArtwork: boolean }>( - 'div', - ({ $theme, $includesArtwork }) => { - return { - alignItems: 'center', - display: 'flex', - paddingInlineStart: $includesArtwork ? $theme.sizing.scale600 : null, - }; - } -); - -export const StyledMessageContent = styled<{ $actionPosition: ActionPositionT }>( - 'div', - ({ $theme, $actionPosition }) => { - return { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - paddingBlockStart: $theme.sizing.scale600, - paddingBlockEnd: $actionPosition === ACTION_POSITION.trailing ? $theme.sizing.scale600 : null, - }; - } -); - -export const StyledTrailingContent = styled<{}>('div', ({ $theme }) => { - return { - display: 'flex', - gridRowEnd: 'span 2', - marginInlineStart: 'auto', - }; -}); - -export const StyledBelowContent = styled<{ $actionPosition: ActionPositionT }>( - 'div', - ({ $theme, $actionPosition }) => { - return { - gridColumnStart: 2, - paddingBlockStart: $actionPosition === ACTION_POSITION.below ? $theme.sizing.scale300 : null, - paddingBlockEnd: $actionPosition === ACTION_POSITION.below ? $theme.sizing.scale600 : null, - }; - } -); - -export const StyledTitle = styled<{}>('div', ({ $theme }) => { - return $theme.typography.LabelMedium; -}); - -export const StyledMessage = styled<{}>('div', ({ $theme }) => { - return $theme.typography.ParagraphMedium; -}); - -export const StyledTrailingButtonContainer = styled<{}>('div', ({ $theme }) => { - return { - display: 'flex', - alignItems: 'center', - paddingInlineEnd: $theme.sizing.scale600, - }; -}); - -export const StyledTrailingIconButton = styled<{ $nested: boolean }>( - 'button', - ({ $theme, $nested }) => { - const radius = $nested ? $theme.borders.radius300 : $theme.borders.radius400; - return { - alignItems: 'center', - background: 'none', - border: 'none', - borderStartEndRadius: radius, - borderEndEndRadius: radius, - color: 'inherit', - cursor: 'pointer', - display: 'flex', - paddingInlineStart: $theme.sizing.scale600, - paddingInlineEnd: $theme.sizing.scale600, - ':hover': { - boxShadow: 'inset 999px 999px 0px rgba(0, 0, 0, 0.04)', - }, - ':active': { - boxShadow: 'inset 999px 999px 0px rgba(0, 0, 0, 0.08)', - }, - }; - } -); diff --git a/src/banner/types.js.flow b/src/banner/types.js.flow deleted file mode 100644 index e01522bdc5..0000000000 --- a/src/banner/types.js.flow +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { ACTION_POSITION, ARTWORK_TYPE, HIERARCHY, KIND } from './constants.js'; - -export type ActionPositionT = $Values; -export type ArtworkTypeT = $Values; -export type HierarchyT = $Values; -export type KindT = $Values; - -export type ActionContentT = {| - // Text shown within action button or applied to aria label. - label: string, - // If provided renders this icon instead of the text label. - icon?: ({ size: string }) => React.Node, - // Called when action button is activated. - onClick: (SyntheticEvent) => mixed, - // Determines if action button is positioned trailing message or below. - position?: ActionPositionT, -|}; - -export type ArtworkContentT = {| - // Element displayed, usually an icon. - icon: ({ size: string }) => React.Node, - // Determines artwork size. Icon for graphics with a strong silhouette or badge for more nuance. - type?: ArtworkTypeT, -|}; - -export type OverridesT = {| - BelowContent?: OverrideT, - LeadingContent?: OverrideT, - Message?: OverrideT, - MessageContent?: OverrideT, - Root?: OverrideT, - Title?: OverrideT, - TrailingContent?: OverrideT, - TrailingButtonContainer?: OverrideT, - TrailingIconButton?: OverrideT, -|}; - -export type PropsT = {| - // Provide a method to "accept", "dismiss", or otherwise interact with the message shown. - action?: ActionContentT, - // Visually convey the message text. - artwork?: ArtworkContentT, - // Message to display. - children: React.Node, - // Determines message priority by rendering in pale or saturated colors. - hierarchy?: HierarchyT, - // Determines color scheme and conveys message intent. - kind?: KindT, - overrides?: OverridesT, - // Used to make the banner visually distinct from its container element. - nested?: boolean, - // Bold text displayed when message content should be separated to two lines. - title?: React.Node, -|}; diff --git a/src/block/block.js.flow b/src/block/block.js.flow deleted file mode 100644 index 21f0b7536b..0000000000 --- a/src/block/block.js.flow +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import * as React from 'react'; -import type { BlockPropsT } from './types.js'; -import { StyledBlock } from './styled-components.js'; -import { getOverrides } from '../helpers/overrides.js'; - -function Block({ - forwardedRef, - children, - as = 'div', - overrides = {}, - color, - backgroundAttachment, - backgroundClip, - backgroundColor, - backgroundImage, - backgroundOrigin, - backgroundPosition, - backgroundRepeat, - backgroundSize, - font, - alignContent, - alignItems, - alignSelf, - flexDirection, - display, - flex, - grid, - gridArea, - gridAutoColumns, - gridAutoFlow, - gridAutoRows, - gridColumn, - gridColumnEnd, - gridColumnGap, - gridColumnStart, - gridGap, - gridRow, - gridRowEnd, - gridRowGap, - gridRowStart, - gridTemplate, - gridTemplateAreas, - gridTemplateColumns, - gridTemplateRows, - justifyContent, - justifyItems, - justifySelf, - position, - width, - minWidth, - maxWidth, - height, - minHeight, - maxHeight, - overflow, - margin, - marginTop, - marginRight, - marginBottom, - marginLeft, - padding, - paddingTop, - paddingRight, - paddingBottom, - paddingLeft, - placeContent, - placeItems, - placeSelf, - flexWrap, - left, - top, - right, - bottom, - textOverflow, - whiteSpace, - ...restProps -}) { - const [BaseBlock, baseBlockProps] = getOverrides(overrides.Block, StyledBlock); - - return ( - - {children} - - ); -} - -const BlockComponent = React.forwardRef((props: BlockPropsT, ref) => ( - -)); -BlockComponent.displayName = 'Block'; -export default BlockComponent; diff --git a/src/block/index.js.flow b/src/block/index.js.flow deleted file mode 100644 index e68a780920..0000000000 --- a/src/block/index.js.flow +++ /dev/null @@ -1,11 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -export { default as Block } from './block.js'; -export type * from './types.js'; diff --git a/src/block/styled-components.js.flow b/src/block/styled-components.js.flow deleted file mode 100644 index 28082f59b3..0000000000 --- a/src/block/styled-components.js.flow +++ /dev/null @@ -1,361 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import { getMediaQueries } from '../helpers/responsive-helpers.js'; -import { styled } from '../styles/index.js'; -import type { BreakpointsT } from '../styles/types.js'; -import type { StyledBlockPropsT } from './types.js'; - -// styletron will throw when value is undefined. if so, replace with null -function constrainToNull(value) { - if (value === undefined) { - return null; - } - return value; -} - -type ApplyParams = { - property: string, - // flowlint-next-line unclear-type:off - value?: any | Array, - // flowlint-next-line unclear-type:off - transform?: Function, -}; - -function build(breakpoints: BreakpointsT) { - const styles = {}; - const mediaQueries = getMediaQueries(breakpoints); - - return { - apply: ({ property, transform = (x) => x, value }: ApplyParams) => { - if (value === null || value === undefined) { - return; - } - - if (Array.isArray(value)) { - value.forEach((v, index) => { - // Do not create a media query for the smallest breakpoint. - if (index === 0) { - styles[property] = constrainToNull(transform(v)); - return; - } - - const mediaQuery = mediaQueries[index - 1]; - if (!styles[mediaQuery]) { - styles[mediaQuery] = {}; - } - - styles[mediaQuery][property] = constrainToNull(transform(v)); - }); - } else { - styles[property] = constrainToNull(transform(value)); - } - }, - value: () => styles, - }; -} - -function getFontValue(obj, key) { - if (!obj) return; - return obj[key]; -} - -export const StyledBlock = styled('div', (props) => { - const { breakpoints, colors, typography, sizing } = props.$theme; - - const get = (obj, key) => obj[key]; - const getScale = (size) => sizing[size] || size; - - const styles = build(breakpoints); - styles.apply({ - property: 'color', - value: get(props, '$color'), - transform: (color) => colors[color] || color, - }); - styles.apply({ - property: 'backgroundAttachment', - value: get(props, '$backgroundAttachment'), - }); - styles.apply({ - property: 'backgroundClip', - value: get(props, '$backgroundClip'), - }); - styles.apply({ - property: 'backgroundColor', - value: get(props, '$backgroundColor'), - transform: (backgroundColor) => colors[backgroundColor] || backgroundColor, - }); - styles.apply({ - property: 'backgroundImage', - value: get(props, '$backgroundImage'), - }); - styles.apply({ - property: 'backgroundOrigin', - value: get(props, '$backgroundOrigin'), - }); - styles.apply({ - property: 'backgroundPosition', - value: get(props, '$backgroundPosition'), - }); - styles.apply({ - property: 'backgroundRepeat', - value: get(props, '$backgroundRepeat'), - }); - styles.apply({ - property: 'backgroundSize', - value: get(props, '$backgroundSize'), - }); - styles.apply({ - property: 'fontFamily', - value: get(props, '$font'), - transform: (font) => getFontValue(typography[font], 'fontFamily'), - }); - styles.apply({ - property: 'fontWeight', - value: get(props, '$font'), - transform: (font) => getFontValue(typography[font], 'fontWeight'), - }); - styles.apply({ - property: 'fontSize', - value: get(props, '$font'), - transform: (font) => getFontValue(typography[font], 'fontSize'), - }); - styles.apply({ - property: 'lineHeight', - value: get(props, '$font'), - transform: (font) => getFontValue(typography[font], 'lineHeight'), - }); - - styles.apply({ - property: 'alignContent', - value: get(props, '$alignContent'), - }); - styles.apply({ property: 'alignItems', value: get(props, '$alignItems') }); - styles.apply({ property: 'alignSelf', value: get(props, '$alignSelf') }); - styles.apply({ property: 'display', value: get(props, '$display') }); - styles.apply({ property: 'flex', value: get(props, '$flex') }); - styles.apply({ - property: 'flexDirection', - value: get(props, '$flexDirection'), - }); - styles.apply({ property: 'grid', value: get(props, '$grid') }); - styles.apply({ property: 'gridArea', value: get(props, '$gridArea') }); - styles.apply({ - property: 'gridAutoColumns', - value: get(props, '$gridAutoColumns'), - }); - styles.apply({ - property: 'gridAutoFlow', - value: get(props, '$gridAutoFlow'), - }); - styles.apply({ - property: 'gridAutoRows', - value: get(props, '$gridAutoRows'), - }); - styles.apply({ property: 'gridColumn', value: get(props, '$gridColumn') }); - styles.apply({ - property: 'gridColumnEnd', - value: get(props, '$gridColumnEnd'), - }); - styles.apply({ - property: 'gridColumnGap', - value: get(props, '$gridColumnGap'), - transform: getScale, - }); - styles.apply({ - property: 'gridColumnStart', - value: get(props, '$gridColumnStart'), - }); - styles.apply({ - property: 'gridGap', - value: get(props, '$gridGap'), - transform: getScale, - }); - styles.apply({ property: 'gridRow', value: get(props, '$gridRow') }); - styles.apply({ property: 'gridRowEnd', value: get(props, '$gridRowEnd') }); - styles.apply({ - property: 'gridRowGap', - value: get(props, '$gridRowGap'), - transform: getScale, - }); - styles.apply({ property: 'gridRowStart', value: get(props, '$gridRowStart') }); - styles.apply({ property: 'gridTemplate', value: get(props, '$gridTemplate') }); - styles.apply({ - property: 'gridTemplateAreas', - value: get(props, '$gridTemplateAreas'), - }); - styles.apply({ - property: 'gridTemplateColumns', - value: get(props, '$gridTemplateColumns'), - }); - styles.apply({ - property: 'gridTemplateRows', - value: get(props, '$gridTemplateRows'), - }); - styles.apply({ - property: 'justifyContent', - value: get(props, '$justifyContent'), - }); - styles.apply({ - property: 'justifyItems', - value: get(props, '$justifyItems'), - }); - styles.apply({ property: 'justifySelf', value: get(props, '$justifySelf') }); - styles.apply({ property: 'position', value: get(props, '$position') }); - styles.apply({ - property: 'width', - value: get(props, '$width'), - transform: getScale, - }); - styles.apply({ - property: 'minWidth', - value: get(props, '$minWidth'), - transform: getScale, - }); - styles.apply({ - property: 'maxWidth', - value: get(props, '$maxWidth'), - transform: getScale, - }); - styles.apply({ - property: 'height', - value: get(props, '$height'), - transform: getScale, - }); - styles.apply({ - property: 'minHeight', - value: get(props, '$minHeight'), - transform: getScale, - }); - styles.apply({ - property: 'maxHeight', - value: get(props, '$maxHeight'), - transform: getScale, - }); - styles.apply({ - property: 'overflowX', - value: get(props, '$overflow'), - transform: (overflow) => { - if (overflow === 'scrollX') { - return 'scroll'; - } - return null; - }, - }); - styles.apply({ - property: 'overflowY', - value: get(props, '$overflow'), - transform: (overflow) => { - if (overflow === 'scrollY') { - return 'scroll'; - } - return null; - }, - }); - styles.apply({ - property: 'overflow', - value: get(props, '$overflow'), - transform: (overflow) => { - if (overflow !== 'scrollX' && overflow !== 'scrollY') { - return overflow; - } - return null; - }, - }); - - styles.apply({ - property: 'margin', - value: get(props, '$margin'), - transform: getScale, - }); - styles.apply({ - property: 'marginTop', - value: get(props, '$marginTop'), - transform: getScale, - }); - styles.apply({ - property: 'marginRight', - value: get(props, '$marginRight'), - transform: getScale, - }); - styles.apply({ - property: 'marginBottom', - value: get(props, '$marginBottom'), - transform: getScale, - }); - styles.apply({ - property: 'marginLeft', - value: get(props, '$marginLeft'), - transform: getScale, - }); - - styles.apply({ - property: 'padding', - value: get(props, '$padding'), - transform: getScale, - }); - styles.apply({ - property: 'paddingTop', - value: get(props, '$paddingTop'), - transform: getScale, - }); - styles.apply({ - property: 'paddingRight', - value: get(props, '$paddingRight'), - transform: getScale, - }); - styles.apply({ - property: 'paddingBottom', - value: get(props, '$paddingBottom'), - transform: getScale, - }); - styles.apply({ - property: 'paddingLeft', - value: get(props, '$paddingLeft'), - transform: getScale, - }); - - styles.apply({ - property: 'placeContent', - value: get(props, '$placeContent'), - }); - styles.apply({ property: 'placeItems', value: get(props, '$placeItems') }); - styles.apply({ property: 'placeSelf', value: get(props, '$placeSelf') }); - styles.apply({ - property: 'flexWrap', - value: get(props, '$flexWrap'), - transform: () => 'wrap', - }); - - styles.apply({ - property: 'top', - value: get(props, '$top'), - transform: getScale, - }); - styles.apply({ - property: 'right', - value: get(props, '$right'), - transform: getScale, - }); - styles.apply({ - property: 'left', - value: get(props, '$left'), - transform: getScale, - }); - styles.apply({ - property: 'bottom', - value: get(props, '$bottom'), - transform: getScale, - }); - - styles.apply({ property: 'textOverflow', value: get(props, '$textOverflow') }); - styles.apply({ property: 'whiteSpace', value: get(props, '$whiteSpace') }); - - return styles.value(); -}); diff --git a/src/block/types.js.flow b/src/block/types.js.flow deleted file mode 100644 index 9a79a1ce80..0000000000 --- a/src/block/types.js.flow +++ /dev/null @@ -1,416 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import type { Node, ElementType } from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; - -export type OverridesT = { - Block?: OverrideT, -}; - -export type ResponsiveT = T | Array; - -type AlignContentT = - | 'center' - | 'start' - | 'end' - | 'flex-start' - | 'flex-end' - | 'normal' - | 'baseline' - | 'first baseline' - | 'last baseline' - | 'space-between' - | 'space-around' - | 'space-evenly' - | 'stretch' - | 'safe center' - | 'unsafe center' - | 'inherit' - | 'initial' - | 'unset'; - -type AlignItemsT = - | 'normal' - | 'stretch' - | 'center' - | 'start' - | 'end' - | 'flex-start' - | 'flex-end' - | 'self-start' - | 'self-end' - | 'baseline' - | 'first baseline' - | 'last baseline' - | 'safe center' - | 'unsafe center' - | 'inherit' - | 'initial' - | 'unset'; - -type AlignSelfT = - | 'auto' - | 'normal' - | 'center' - | 'start' - | 'end' - | 'self-start' - | 'self-end' - | 'flex-start' - | 'flex-end' - | 'baseline' - | 'first baseline' - | 'last baseline' - | 'stretch' - | 'safe center' - | 'unsafe center' - | 'inherit' - | 'initial' - | 'unset'; - -type FlexDirectionT = - | 'row' - | 'row-reverse' - | 'column' - | 'column-reverse' - | 'inherit' - | 'initial' - | 'unset'; - -type FlexT = number | string; - -type DisplayT = - | 'block' - | 'inline' - | 'run-in' - | 'flow' - | 'flow-root' - | 'table' - | 'flex' - | 'grid' - | 'ruby' - | 'block flow' - | 'inline table' - | 'flex run-in' - | 'list-item' - | 'list-item block' - | 'list-item inline' - | 'list-item flow' - | 'list-item flow-root' - | 'list-item block flow' - | 'list-item block flow-root' - | 'flow list-item block' - | 'table-row-group' - | 'table-header-group' - | 'table-footer-group' - | 'table-row' - | 'table-cell' - | 'table-column-group' - | 'table-column' - | 'table-caption' - | 'ruby-base' - | 'ruby-text' - | 'ruby-base-container' - | 'ruby-text-container' - | 'contents' - | 'none' - | 'inline-block' - | 'inline-table' - | 'inline-flex' - | 'inline-grid' - | 'inherit' - | 'initial' - | 'unset'; - -type GridAutoFlowT = - | 'row' - | 'column' - | 'dense' - | 'row dense' - | 'column dense' - | 'inherit' - | 'initial' - | 'unset'; - -type JustifyContentT = - | 'center' - | 'start' - | 'end' - | 'flex-start' - | 'flex-end' - | 'left' - | 'right' - | 'space-between' - | 'space-around' - | 'space-evenly' - | 'stretch' - | 'safe center' - | 'unsafe center' - | 'inherit' - | 'initial' - | 'unset'; - -type JustifyItemsT = - /* Basic keywords */ - | 'auto' - | 'normal' - | 'stretch' - | 'center' - | 'start' - | 'end' - | 'flex-start' - | 'flex-end' - | 'self-start' - | 'self-end' - | 'left' - | 'right' - | 'baseline' - | 'first baseline' - | 'last baseline' - | 'safe center' - | 'unsafe center' - | 'legacy right' - | 'legacy left' - | 'legacy center' - | 'inherit' - | 'initial' - | 'unset'; - -type JustifySelfT = - | 'auto' - | 'normal' - | 'stretch' - | 'center' - | 'start' - | 'end' - | 'flex-start' - | 'flex-end' - | 'self-start' - | 'self-end' - | 'left' - | 'right' - | 'baseline' - | 'first baseline' - | 'last baseline' - | 'safe center' - | 'unsafe center' - | 'inherit' - | 'initial' - | 'unset'; - -type PositionT = 'static' | 'absolute' | 'relative' | 'fixed' | 'sticky'; - -type OverflowT = - | 'visible' - | 'hidden' - | 'scroll' - | 'scrollX' - | 'scrollY' - | 'auto' - | 'inherit' - | 'initial' - | 'unset'; - -export type ScaleT = 0 | string; - -export type WhiteSpaceT = - | 'normal' - | 'nowrap' - | 'pre' - | 'pre-wrap' - | 'pre-line' - | 'break-spaces' - | 'inherit' - | 'initial' - | 'unset'; - -export type BlockPropsT = { - children?: Node, - /** Modifies the base element used to render the block. */ - as?: ElementType, - overrides?: OverridesT, - /** Accepts all themeable color properties (`primary200`, etc.). */ - color?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-attachment */ - backgroundAttachment?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-clip */ - backgroundClip?: ResponsiveT, - /** Accepts all themeable color properties (`primary200`, etc.). */ - backgroundColor?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-image */ - backgroundImage?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-origin */ - backgroundOrigin?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-position */ - backgroundPosition?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-repeat */ - backgroundRepeat?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/background-size */ - backgroundSize?: ResponsiveT, - /** Accepts all themeable font properties (`font200`, etc.). */ - font?: string | Array, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/align-content */ - alignContent?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/align-items */ - alignItems?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/align-self */ - alignSelf?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction */ - flexDirection?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/display */ - display?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/flex */ - flex?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid */ - grid?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-area */ - gridArea?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-columns */ - gridAutoColumns?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow */ - gridAutoFlow?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows */ - gridAutoRows?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column */ - gridColumn?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-end */ - gridColumnEnd?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-gap */ - gridColumnGap?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column-start */ - gridColumnStart?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-gap */ - gridGap?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row */ - gridRow?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-end */ - gridRowEnd?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-gap */ - gridRowGap?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start */ - gridRowStart?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template */ - gridTemplate?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas */ - gridTemplateAreas?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns */ - gridTemplateColumns?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-rows */ - gridTemplateRows?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content */ - justifyContent?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items */ - justifyItems?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self */ - justifySelf?: ResponsiveT, - position?: ResponsiveT, - width?: ResponsiveT, - minWidth?: ResponsiveT, - maxWidth?: ResponsiveT, - height?: ResponsiveT, - minHeight?: ResponsiveT, - maxHeight?: ResponsiveT, - overflow?: ResponsiveT, - margin?: ResponsiveT, - marginTop?: ResponsiveT, - marginRight?: ResponsiveT, - marginBottom?: ResponsiveT, - marginLeft?: ResponsiveT, - padding?: ResponsiveT, - paddingTop?: ResponsiveT, - paddingRight?: ResponsiveT, - paddingBottom?: ResponsiveT, - paddingLeft?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/place-content */ - placeContent?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/place-items */ - placeItems?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/place-self */ - placeSelf?: ResponsiveT, - flexWrap?: ResponsiveT, - left?: ResponsiveT, - top?: ResponsiveT, - right?: ResponsiveT, - bottom?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow */ - textOverflow?: ResponsiveT, - /** available values: https://developer.mozilla.org/en-US/docs/Web/CSS/white-space */ - whiteSpace?: ResponsiveT, - src?: string, -}; - -export type StyledBlockPropsT = { - $as?: ElementType, - $color?: ResponsiveT, - $backgroundAttachment?: ResponsiveT, - $backgroundClip?: ResponsiveT, - $backgroundColor?: ResponsiveT, - $backgroundImage?: ResponsiveT, - $backgroundOrigin?: ResponsiveT, - $backgroundPosition?: ResponsiveT, - $backgroundRepeat?: ResponsiveT, - $backgroundSize?: ResponsiveT, - $font?: ResponsiveT, - $alignContent?: ResponsiveT, - $alignItems?: ResponsiveT, - $alignSelf?: ResponsiveT, - $flexDirection?: ResponsiveT, - $display?: ResponsiveT, - $flex?: ResponsiveT, - $grid?: ResponsiveT, - $gridArea?: ResponsiveT, - $gridAutoColumns?: ResponsiveT, - $gridAutoFlow?: ResponsiveT, - $gridAutoRows?: ResponsiveT, - $gridColumn?: ResponsiveT, - $gridColumnEnd?: ResponsiveT, - $gridColumnGap?: ResponsiveT, - $gridColumnStart?: ResponsiveT, - $gridGap?: ResponsiveT, - $gridRow?: ResponsiveT, - $gridRowEnd?: ResponsiveT, - $gridRowGap?: ResponsiveT, - $gridRowStart?: ResponsiveT, - $gridTemplate?: ResponsiveT, - $gridTemplateAreas?: ResponsiveT, - $gridTemplateColumns?: ResponsiveT, - $gridTemplateRows?: ResponsiveT, - $justifyContent?: ResponsiveT, - $justifyItems?: ResponsiveT, - $justifySelf?: ResponsiveT, - $position?: ResponsiveT, - $width?: ResponsiveT, - $minWidth?: ResponsiveT, - $maxWidth?: ResponsiveT, - $height?: ResponsiveT, - $minHeight?: ResponsiveT, - $maxHeight?: ResponsiveT, - $overflow?: ResponsiveT, - $margin?: ResponsiveT, - $marginTop?: ResponsiveT, - $marginRight?: ResponsiveT, - $marginBottom?: ResponsiveT, - $marginLeft?: ResponsiveT, - $padding?: ResponsiveT, - $paddingTop?: ResponsiveT, - $paddingRight?: ResponsiveT, - $paddingBottom?: ResponsiveT, - $paddingLeft?: ResponsiveT, - $placeContent?: ResponsiveT, - $placeItems?: ResponsiveT, - $placeSelf?: ResponsiveT, - $flexWrap?: ResponsiveT, - $left?: ResponsiveT, - $top?: ResponsiveT, - $right?: ResponsiveT, - $bottom?: ResponsiveT, - $textOverflow?: ResponsiveT, - $whiteSpace?: ResponsiveT, -}; diff --git a/src/bottom-navigation/index.js.flow b/src/bottom-navigation/index.js.flow deleted file mode 100644 index 7efdf2883e..0000000000 --- a/src/bottom-navigation/index.js.flow +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { type StyletronComponent } from 'styletron-react'; - -export type NavItemOverridesT = { - Title?: OverrideT, - Selector?: OverrideT, - Panel?: OverrideT, -}; - -export type BottomNavigationOverridesT = { - Root?: OverrideT, - SelectorList?: OverrideT, - OverflowPanel?: OverrideT, - OverflowPanelList?: OverrideT, - OverflowTitle?: OverrideT, - OverflowSelector?: OverrideT, -}; - -export type IconT = React.AbstractComponent<{| size: number |}>; - -export type NavItemPropsT = { - children?: React.Node, - title: string, - icon?: IconT, - overrides?: NavItemOverridesT, -}; - -export type OnChangeT = (params: { activeKey: number }) => void; - -export type BottomNavigationPropsT = { - children?: React.Node, - activeKey?: number, - onChange?: OnChangeT, - overrides?: BottomNavigationOverridesT, -}; - -export type SelectorPropsT = { - title: string, - icon: IconT, - isActive: boolean, - onChange: OnChangeT, - overrides: NavItemOverridesT, -}; - -export type PanelPropsT = { - isActive: boolean, - overrides: NavItemOverridesT, - children: React.Node, -}; - -declare export var StyledRoot: StyletronComponent<'div', {}>; -declare export var StyledTitle: StyletronComponent<'div', { $isActive: boolean }>; -declare export var StyledSelectorList: StyletronComponent<'div', {}>; -declare export var StyledSelector: StyletronComponent<'button', {}>; -declare export var StyledPanel: StyletronComponent<'div', {}>; -declare export var StyledOverflowPanel: StyletronComponent<'div', {}>; -declare export var StyledOverflowPanelList: StyletronComponent<'div', {}>; - -declare export var NavItem: React.ComponentType; -declare export var BottomNavigation: React.ComponentType; diff --git a/src/breadcrumbs/breadcrumbs.js.flow b/src/breadcrumbs/breadcrumbs.js.flow deleted file mode 100644 index 998e1c5faa..0000000000 --- a/src/breadcrumbs/breadcrumbs.js.flow +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import React, { Children } from 'react'; - -import { LocaleContext } from '../locale/index.js'; -import { ThemeContext } from '../styles/theme-provider.js'; -import ChevronRight from '../icon/chevron-right.js'; -import ChevronLeft from '../icon/chevron-left.js'; -import type { BreadcrumbsPropsT } from './types.js'; -import { StyledList, StyledListItem, StyledRoot, StyledSeparator } from './styled-components.js'; -import { getOverrides, mergeOverrides } from '../helpers/overrides.js'; - -export function Breadcrumbs(props: BreadcrumbsPropsT) { - const { overrides = {}, showTrailingSeparator = false } = props; - const childrenArray = Children.toArray(props.children); - const childrenWithSeparators = []; - - const [Root, baseRootProps] = getOverrides(overrides.Root, StyledRoot); - const [Right, baseIconProps] = getOverrides(overrides.Icon, ChevronRight); - const [Left] = getOverrides(overrides.Icon, ChevronLeft); - const [List, baseListProps] = getOverrides(overrides.List, StyledList); - const [ListItem, baseListItemProps] = getOverrides(overrides.ListItem, StyledListItem); - const [Separator, baseSeparatorProps] = getOverrides(overrides.Separator, StyledSeparator); - - baseIconProps.overrides = mergeOverrides( - { Svg: { style: { verticalAlign: 'text-bottom' } } }, - baseIconProps && baseIconProps.overrides - ); - - childrenArray.forEach((child, index) => { - childrenWithSeparators.push( - - {child} - {(showTrailingSeparator || index !== childrenArray.length - 1) && ( - - - {(theme) => - theme.direction === 'rtl' ? ( - - ) : ( - - ) - } - - - )} - - ); - }); - - return ( - - {(locale) => ( - - {childrenWithSeparators} - - )} - - ); -} - -Breadcrumbs.defaultProps = { - overrides: {}, - showTrailingSeparator: false, -}; - -export default Breadcrumbs; diff --git a/src/breadcrumbs/index.js.flow b/src/breadcrumbs/index.js.flow deleted file mode 100644 index e7e397312c..0000000000 --- a/src/breadcrumbs/index.js.flow +++ /dev/null @@ -1,12 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -export { default as Breadcrumbs } from './breadcrumbs.js'; -export * from './styled-components.js'; -export type * from './types.js'; diff --git a/src/breadcrumbs/locale.js.flow b/src/breadcrumbs/locale.js.flow deleted file mode 100644 index 4282470b03..0000000000 --- a/src/breadcrumbs/locale.js.flow +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export type BreadcrumbLocaleT = {| - ariaLabel: string, -|}; - -const locale = { - ariaLabel: 'Breadcrumbs navigation', -}; - -export default locale; diff --git a/src/breadcrumbs/styled-components.js.flow b/src/breadcrumbs/styled-components.js.flow deleted file mode 100644 index 46671681f5..0000000000 --- a/src/breadcrumbs/styled-components.js.flow +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import { styled } from '../styles/index.js'; - -export const StyledRoot = styled<{}>('nav', ({ $theme }) => { - return { - color: $theme.colors.breadcrumbsText, - ...$theme.typography.font350, - }; -}); - -export const StyledList = styled<{}>('ol', ({ $theme }) => { - return { - listStyleType: 'none', - margin: 0, - padding: 0, - ...$theme.typography.font350, - }; -}); - -export const StyledListItem = styled<{}>('li', ({ $theme }) => { - return { - display: 'inline-block', - ...$theme.typography.font350, - }; -}); - -export const StyledSeparator = styled<{}>('div', ({ $theme }) => { - return { - display: 'inline-block', - color: $theme.colors.breadcrumbsSeparatorFill, - marginLeft: $theme.sizing.scale300, - marginRight: $theme.sizing.scale300, - verticalAlign: 'middle', - }; -}); diff --git a/src/breadcrumbs/types.js.flow b/src/breadcrumbs/types.js.flow deleted file mode 100644 index aa1e1e0abb..0000000000 --- a/src/breadcrumbs/types.js.flow +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -// @flow - -import type { Node } from 'react'; - -import type { OverrideT } from '../helpers/overrides.js'; - -export type OverridesT = { - Root?: OverrideT, - Separator?: OverrideT, - List?: OverrideT, - ListItem?: OverrideT, - Icon?: OverrideT, -}; - -export type BreadcrumbsPropsT = {| - children?: Node, - overrides?: OverridesT, - ariaLabel?: string, - 'aria-label'?: string, - /** Whether to show a trailing separator after the last breadcrumb. */ - showTrailingSeparator?: boolean, -|}; diff --git a/src/button-dock/index.js.flow b/src/button-dock/index.js.flow deleted file mode 100644 index c53ae3f776..0000000000 --- a/src/button-dock/index.js.flow +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { type StyletronComponent } from 'styletron-react'; - -export type ButtonDockOverridesT = { - Root?: OverrideT, - ActionContainer?: OverrideT, - ActionSubContainer?: OverrideT, -}; - -export type ButtonDockPropsT = { - primaryAction: React.Node, - secondaryActions?: [React.Node] | [React.Node, React.Node], - dismissiveAction?: React.Node, - topAccessory?: React.Node, - overrides?: ButtonDockOverridesT, -}; - -declare export var StyledRoot: StyletronComponent<'div', {}>; -declare export var StyledActionContainer: StyletronComponent<'div', {}>; -declare export var StyledActionSubContainer: StyletronComponent< - 'div', - { $reverseWhenWide: boolean } ->; - -declare export var ButtonDock: React.ComponentType; diff --git a/src/button-docked/index.js.flow b/src/button-docked/index.js.flow deleted file mode 100644 index 89c5a2deb8..0000000000 --- a/src/button-docked/index.js.flow +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { type StyletronComponent } from 'styletron-react'; - -export type ButtonDockedOverridesT = { - Root?: OverrideT, - ActionContainer?: OverrideT, - ActionSubContainer?: OverrideT, -}; - -export type ButtonDockedPropsT = { - primaryAction: React.Node, - secondaryActions?: [React.Node] | [React.Node, React.Node], - dismissiveAction?: React.Node, - topAccessory?: React.Node, - overrides?: ButtonDockedOverridesT, -}; - -declare export var StyledRoot: StyletronComponent<'div', {}>; -declare export var StyledActionContainer: StyletronComponent<'div', {}>; -declare export var StyledActionSubContainer: StyletronComponent< - 'div', - { $reverseWhenWide: boolean } ->; - -declare export var ButtonDocked: React.ComponentType; diff --git a/src/button-group/button-group.js.flow b/src/button-group/button-group.js.flow deleted file mode 100644 index 5a1f4ba6fa..0000000000 --- a/src/button-group/button-group.js.flow +++ /dev/null @@ -1,161 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { KIND, SIZE, SHAPE } from '../button/index.js'; -import { MODE } from './constants.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { LocaleContext } from '../locale/index.js'; - -import { StyledRoot } from './styled-components.js'; -import type { PropsT } from './types.js'; - -function isIndexSelected(selected, index) { - if (!Array.isArray(selected) && typeof selected !== 'number') { - return false; - } - - if (Array.isArray(selected)) { - return selected.includes(index); - } - - return selected === index; -} - -export default class ButtonGroup extends React.Component { - childRefs: // flowlint-next-line unclear-type:off - { [key: number]: React.ElementRef } = {}; - static defaultProps = { - disabled: false, - onClick: () => {}, - shape: SHAPE.default, - size: SIZE.default, - kind: KIND.secondary, - }; - render() { - const { - overrides = {}, - mode = MODE.checkbox, - children, - selected, - disabled, - onClick, - kind, - shape, - size, - } = this.props; - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const ariaLabel = this.props['aria-label'] || this.props.ariaLabel; - const isRadio = mode === MODE.radio; - - const numItems = React.Children.count(children); - - return ( - - {(locale) => ( - - {React.Children.map(children, (child, index) => { - if (!React.isValidElement(child)) { - return null; - } - - const isSelected = child.props.isSelected - ? child.props.isSelected - : isIndexSelected(selected, index); - - if (isRadio) { - this.childRefs[index] = React.createRef(); - } - return React.cloneElement(child, { - disabled: disabled || child.props.disabled, - isSelected, - ref: isRadio ? this.childRefs[index] : undefined, - tabIndex: - !isRadio || - isSelected || - (isRadio && (!selected || selected === -1) && index === 0) - ? 0 - : -1, - onKeyDown: (e) => { - if (!isRadio) return; - const value = Number(selected) ? Number(selected) : 0; - if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { - e.preventDefault && e.preventDefault(); - const prevIndex = value - 1 < 0 ? numItems - 1 : value - 1; - onClick && onClick(e, prevIndex); - this.childRefs[prevIndex].current && this.childRefs[prevIndex].current.focus(); - } - if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { - e.preventDefault && e.preventDefault(); - const nextIndex = value + 1 > numItems - 1 ? 0 : value + 1; - onClick && onClick(e, nextIndex); - this.childRefs[nextIndex].current && this.childRefs[nextIndex].current.focus(); - } - }, - kind, - onClick: (event) => { - if (disabled) { - return; - } - - if (child.props.onClick) { - child.props.onClick(event); - } - - if (onClick) { - onClick(event, index); - } - }, - shape, - size, - overrides: { - BaseButton: { - style: ({ $theme }) => { - // Even though baseui's buttons have square corners, some applications override to - // rounded. Maintaining corner radius in this circumstance is ideal to avoid further - // customization. - if (children.length === 1) { - return {}; - } - - if (shape !== SHAPE.default) { - return { - marginLeft: $theme.sizing.scale100, - marginRight: $theme.sizing.scale100, - }; - } - - return { - marginLeft: '0.5px', - marginRight: '0.5px', - }; - }, - props: { - 'aria-checked': isSelected, - role: isRadio ? 'radio' : 'checkbox', - }, - }, - - ...child.props.overrides, - }, - }); - })} - - )} - - ); - } -} diff --git a/src/button-group/constants.js.flow b/src/button-group/constants.js.flow deleted file mode 100644 index 25b2e557d7..0000000000 --- a/src/button-group/constants.js.flow +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const MODE = Object.freeze({ - radio: 'radio', - checkbox: 'checkbox', -}); - -export const STATE_CHANGE_TYPE = Object.freeze({ - change: 'change', -}); diff --git a/src/button-group/index.js.flow b/src/button-group/index.js.flow deleted file mode 100644 index 2599349c8e..0000000000 --- a/src/button-group/index.js.flow +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { default as ButtonGroup } from './button-group.js'; -export { default as StatefulButtonGroup } from './stateful-button-group.js'; -export { default as StatefulContainer } from './stateful-container.js'; - -// Constants -export { SIZE, SHAPE } from '../button/constants.js'; -export { MODE, STATE_CHANGE_TYPE } from './constants.js'; - -// Styled elements -export { StyledRoot } from './styled-components.js'; - -// Types -export type * from './types.js'; diff --git a/src/button-group/locale.js.flow b/src/button-group/locale.js.flow deleted file mode 100644 index 00e3f3a5f3..0000000000 --- a/src/button-group/locale.js.flow +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export type ButtonGroupLocaleT = {| - ariaLabel: string, -|}; - -const locale = { - ariaLabel: 'button group', -}; - -export default locale; diff --git a/src/button-group/stateful-button-group.js.flow b/src/button-group/stateful-button-group.js.flow deleted file mode 100644 index bbbb309e58..0000000000 --- a/src/button-group/stateful-button-group.js.flow +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import ButtonGroup from './button-group.js'; -import StatefulContainer from './stateful-container.js'; -import type { StatefulPropsT } from './types.js'; - -export default function StatefulButtonGroup(props: StatefulPropsT) { - const { children, initialState, ...restProps } = props; - return ( - - {({ ...containerProps }) => ( - //$FlowFixMe - {props.children} - )} - - ); -} diff --git a/src/button-group/stateful-container.js.flow b/src/button-group/stateful-container.js.flow deleted file mode 100644 index a43dc17f57..0000000000 --- a/src/button-group/stateful-container.js.flow +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { MODE, STATE_CHANGE_TYPE } from './constants.js'; - -import type { StatefulContainerPropsT, StateT } from './types.js'; - -// handles the case where selected = 0 -function isSelectedDefined(selected) { - return Array.isArray(selected) || typeof selected === 'number'; -} - -function defaultStateReducer( - type: $Values, - nextState: StateT, - currentState: StateT -) { - return nextState; -} - -export default class StatefulContainer extends React.Component { - static defaultProps = { - initialState: { selected: [] }, - stateReducer: defaultStateReducer, - }; - - constructor(props: StatefulContainerPropsT) { - super(props); - - const { initialState = {} } = props; - const { selected = [] } = initialState; - - this.state = { - selected: isSelectedDefined(selected) ? [].concat(selected) : [], - }; - } - - changeState = (nextState: StateT) => { - if (this.props.stateReducer) { - this.setState(this.props.stateReducer(STATE_CHANGE_TYPE.change, nextState, this.state)); - } else { - this.setState(nextState); - } - }; - - onClick = (event: SyntheticEvent, index: number) => { - if (this.props.mode === MODE.radio) { - if (this.state.selected.length === 0 || this.state.selected[0] !== index) { - this.changeState({ selected: [index] }); - } else { - this.changeState({ selected: [] }); - } - } - - if (this.props.mode === MODE.checkbox) { - if (!this.state.selected.includes(index)) { - this.changeState({ selected: [...this.state.selected, index] }); - } else { - this.changeState({ - selected: this.state.selected.filter((value) => value !== index), - }); - } - } - - if (this.props.onClick) { - this.props.onClick(event, index); - } - }; - - render() { - const { initialState, stateReducer, ...props } = this.props; - return this.props.children({ - ...props, - onClick: this.onClick, - selected: this.state.selected, - }); - } -} diff --git a/src/button-group/styled-components.js.flow b/src/button-group/styled-components.js.flow deleted file mode 100644 index 8ab93ac8ae..0000000000 --- a/src/button-group/styled-components.js.flow +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import { styled } from '../styles/index.js'; -import { SHAPE } from '../button/index.js'; - -export const StyledRoot = styled<{ $shape: string, $length: number }>( - 'div', - ({ $shape, $length, $theme }) => { - const margin = - $length === 1 - ? undefined - : $shape !== SHAPE.default - ? `-${$theme.sizing.scale100}` - : '-0.5px'; - return { - display: 'flex', - marginLeft: margin, - marginRight: margin, - }; - } -); diff --git a/src/button-group/types.js.flow b/src/button-group/types.js.flow deleted file mode 100644 index 5d090a02b4..0000000000 --- a/src/button-group/types.js.flow +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { SIZE, SHAPE, KIND } from '../button/index.js'; -import type { OverrideT } from '../helpers/overrides.js'; -import { MODE, STATE_CHANGE_TYPE } from './constants.js'; - -// button-group -export type PropsT = {| - /** Accessible label. */ - ariaLabel?: string, - 'aria-label'?: string, - /** Set of more than one `Button` components */ - children: Array, - /** Defines if the button group is disabled. */ - disabled?: boolean, - /** - * Use the `mode` prop to render toggleable Buttons: - * the value `radio` will cause Buttons to behave like radio buttons, - * the value `checkbox` will cause Buttons to behave like checkboxes. - */ - mode?: $Values, - /** - * Called with click events from children. If a child button has its - * own click handler, the local handler will be called first, then - * this handler will trigger. - */ - onClick?: ClickHandlerT, - overrides?: OverridesT, - /** - * Index or array of indices of the selected Button(s). - * Primarily for use with controlled components with a `mode` prop defined. - */ - selected?: number | Array, - /** Defines the shape of the buttons in the button group. */ - shape?: $Values, - /** Defines the size of the buttons in the button group. */ - size?: $Values, - /** Defines the `kind` of the buttons in the group */ - kind?: $Values, -|}; - -type OverridesT = { - Root?: OverrideT, -}; - -// stateful-group -// eslint-disable-next-line flowtype/generic-spacing -export type StatefulPropsT = $Diff< - {| - ...PropsT, - initialState?: { selected: number | Array }, - stateReducer?: StateReducerT, - |}, - { selected: mixed } // excluded from type definition ->; - -// stateful-container -export type StatefulContainerPropsT = {| - ...StatefulPropsT, - children: (props: { - ...$Diff, - onClick: ClickHandlerT, - selected: number | Array, - }) => React.Node, -|}; - -export type StateT = { - selected: Array, -}; - -export type StateReducerT = ( - stateType: $Values, - nextState: StateT, - currentState: StateT -) => StateT; - -// general -type ClickHandlerT = (event: SyntheticEvent, index: number) => mixed; diff --git a/src/button-timed/index.js.flow b/src/button-timed/index.js.flow deleted file mode 100644 index 5433f84b0b..0000000000 --- a/src/button-timed/index.js.flow +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import { type StyletronComponent } from 'styletron-react'; -import type { CustomColorsT } from '../button'; - -export type OverridesT = { - BaseButtonTimed?: OverrideT, - TimerContainer?: OverrideT, - Root?: OverrideT, - StartEnhancer?: OverrideT, - EndEnhancer?: OverrideT, - LoadingSpinnerContainer?: OverrideT, - LoadingSpinner?: OverrideT, -}; - -export type ButtonTimedPropsT = { - initialTime: number, - onClick: (a?: SyntheticEvent) => mixed, - overrides?: OverridesT, - paused?: boolean, - children?: React$Node, - colors?: CustomColorsT, - disabled?: boolean, - /** A helper rendered at the end of the button. */ - // flowlint-next-line unclear-type:off - endEnhancer?: React.Node | React.AbstractComponent, - /** Show loading button style and spinner. */ - isLoading?: boolean, - /** Indicates that the button is selected */ - isSelected?: boolean, - /** A helper rendered at the start of the button. */ - // flowlint-next-line unclear-type:off - startEnhancer?: React.Node | React.AbstractComponent, - type?: 'submit' | 'reset' | 'button', -}; - -export { - StyledStartEnhancer, - StyledEndEnhancer, - StyledLoadingSpinner, - StyledLoadingSpinnerContainer, -} from '../button'; -declare export var StyledBaseButtonTimed: StyletronComponent<'div', {}>; -declare export var StyledTimerContainer: StyletronComponent<'div', {}>; - -declare export var ButtonTimed: React.ComponentType; diff --git a/src/button/button-internals.js.flow b/src/button/button-internals.js.flow deleted file mode 100644 index 66ed0129f2..0000000000 --- a/src/button/button-internals.js.flow +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import * as ReactIs from 'react-is'; -import { - StartEnhancer as StyledStartEnhancer, - EndEnhancer as StyledEndEnhancer, -} from './styled-components.js'; -import { getSharedProps } from './utils.js'; -import { getOverrides } from '../helpers/overrides.js'; - -import type { ButtonPropsT } from './types.js'; - -function RenderEnhancer(props) { - const { Enhancer, ...restProps } = props; - if (typeof Enhancer === 'string') { - return Enhancer; - } - if (ReactIs.isValidElementType(Enhancer)) { - // $FlowFixMe - return ; - } - // $FlowFixMe - return Enhancer; -} - -export default function ButtonInternals(props: ButtonPropsT) { - const { children, overrides = {}, startEnhancer, endEnhancer } = props; - const [StartEnhancer, startEnhancerProps] = getOverrides( - overrides.StartEnhancer, - StyledStartEnhancer - ); - const [EndEnhancer, endEnhancerProps] = getOverrides(overrides.EndEnhancer, StyledEndEnhancer); - const sharedProps = getSharedProps(props); - return ( - - {startEnhancer !== null && startEnhancer !== undefined && ( - - - - )} - {children} - {endEnhancer !== null && endEnhancer !== undefined && ( - - - - )} - - ); -} diff --git a/src/button/button.js.flow b/src/button/button.js.flow deleted file mode 100644 index 05fd705bd1..0000000000 --- a/src/button/button.js.flow +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { - BaseButton as StyledBaseButton, - LoadingSpinner as StyledLoadingSpinner, - LoadingSpinnerContainer as StyledLoadingSpinnerContainer, -} from './styled-components.js'; -import { getSharedProps } from './utils.js'; -import ButtonInternals from './button-internals.js'; -import { defaultProps } from './default-props.js'; -import { getOverrides } from '../helpers/overrides.js'; -import { isFocusVisible, forkFocus, forkBlur } from '../utils/focusVisible.js'; - -import type { ButtonPropsT, SharedStylePropsT, ReactRefT } from './types.js'; - -class Button extends React.Component< - ButtonPropsT & { forwardedRef: ReactRefT }, - { isFocusVisible: boolean } -> { - static defaultProps = defaultProps; - state = { isFocusVisible: false }; - - internalOnClick = (...args) => { - const { isLoading, onClick } = this.props; - if (isLoading) { - args[0].preventDefault(); - return; - } - onClick && onClick(...args); - }; - - handleFocus = (event: SyntheticEvent<>) => { - if (isFocusVisible(event)) { - this.setState({ isFocusVisible: true }); - } - }; - - handleBlur = (event: SyntheticEvent<>) => { - if (this.state.isFocusVisible !== false) { - this.setState({ isFocusVisible: false }); - } - }; - - render() { - const { - overrides = {}, - size, - kind, - shape, - isLoading, - isSelected, - // Removing from restProps - startEnhancer, - endEnhancer, - children, - forwardedRef, - colors, - ...restProps - } = this.props; - // Get overrides - const [BaseButton, baseButtonProps] = getOverrides( - // adding both (1) BaseButton and (2) Root - // (1) because it's a Button under the hood - // (2) because we want consistency with the rest of the components - overrides.BaseButton || overrides.Root, - StyledBaseButton - ); - const [LoadingSpinner, loadingSpinnerProps] = getOverrides( - overrides.LoadingSpinner, - StyledLoadingSpinner - ); - const [LoadingSpinnerContainer, loadingSpinnerContainerProps] = getOverrides( - overrides.LoadingSpinnerContainer, - StyledLoadingSpinnerContainer - ); - const sharedProps: SharedStylePropsT = { - ...getSharedProps(this.props), - $isFocusVisible: this.state.isFocusVisible, - }; - return ( - - {isLoading ? ( - - {/* This is not meant to be overridable by users */} -
- -
- - - -
- ) : ( - - )} -
- ); - } -} - -const ForwardedButton = React.forwardRef( - //$FlowFixMe - (props: ButtonPropsT, ref) => - - - ); -} - -const StyledHighlightLabel = withStyle(StyledLabel, (props) => { - const style = { - whiteSpace: 'pre', - color: props.$isActive - ? props.$theme.colors.contentPrimary - : props.$theme.colors.contentSecondary, - }; - - if (!props.$isFirst) { - // $FlowFixMe - style.paddingLeft = null; - } - - return style; -}); - -function HighlightCheckboxLabel(props) { - const { children, ...restProps } = props; - - if (!props.query) { - return {children}; - } - - return splitByQuery(children, props.query).map((el, i) => { - if (matchesQuery(el, props.query)) { - return ( - - {el} - - ); - } - return ( - - {el} - - ); - }); -} - -type CategoricalFilterProps = { - data: string[], - close: () => void, - setFilter: (FilterParametersT) => void, - filterParams?: FilterParametersT, -}; - -export function CategoricalFilter(props: CategoricalFilterProps) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const [selection, setSelection] = React.useState>( - props.filterParams ? props.filterParams.selection : new Set() - ); - const [exclude, setExclude] = React.useState( - props.filterParams ? props.filterParams.exclude : false - ); - const [query, setQuery] = React.useState(''); - const categories = React.useMemo(() => { - return props.data.reduce((set, category) => set.add(category), new Set()); - }, [props.data]); - - const checkboxStyles = css({ marginBottom: theme.sizing.scale200 }); - - const showQuery = Boolean(categories.size >= 10); - const filteredCategories = Array.from(categories, (c) => c).filter((c) => matchesQuery(c, query)); - - return ( - setExclude(!exclude)} - onApply={() => { - props.setFilter({ - description: Array.from(selection).join(', '), - exclude, - selection, - }); - props.close(); - }} - > - {showQuery && ( - setQuery(event.target.value)} - clearable - /> - )} - - {!query && ( -
- { - categories.forEach((c) => selection.add(c)); - setSelection(new Set(selection)); - }} - onClearSelection={() => { - setSelection(new Set()); - }} - /> -
- )} - -
- {!filteredCategories.length && ( - {locale.datatable.categoricalFilterEmpty} - )} - - {Boolean(filteredCategories.length) && - filteredCategories.map((category, i) => ( -
- { - if (selection.has(category)) { - selection.delete(category); - } else { - selection.add(category); - } - setSelection(new Set(selection)); - }} - overrides={{ - Label: { component: HighlightCheckboxLabel, props: { query } }, - }} - > - {category} - -
- ))} -
-
- ); -} - -function CategoricalCell(props) { - const [css] = useStyletron(); - return ( -
- {props.textQuery ? ( - - ) : ( - props.value - )} -
- ); -} - -function CategoricalColumn(options: OptionsT): CategoricalColumnT { - return Column({ - kind: COLUMNS.CATEGORICAL, - buildFilter: function (params) { - return function (data) { - const included = params.selection.has(data); - return params.exclude ? !included : included; - }; - }, - cellBlockAlign: options.cellBlockAlign, - fillWidth: options.fillWidth, - filterable: options.filterable === undefined ? true : options.filterable, - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - renderCell: CategoricalCell, - renderFilter: CategoricalFilter, - sortable: options.sortable === undefined ? true : options.sortable, - sortFn: function (a, b) { - return a.localeCompare(b); - }, - textQueryFilter: function (textQuery, data) { - return data.toLowerCase().includes(textQuery.toLowerCase()); - }, - title: options.title, - }); -} - -export default CategoricalColumn; diff --git a/src/data-table/column-custom.js.flow b/src/data-table/column-custom.js.flow deleted file mode 100644 index 464601e1db..0000000000 --- a/src/data-table/column-custom.js.flow +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import Column from './column.js'; -import { COLUMNS } from './constants.js'; -import type { ColumnT, RenderCellT, RenderFilterT, SharedColumnOptionsT } from './types.js'; - -// I could not re-use the ColumnT type to build this.. tried to spread the ColumnT -// and define renderFilter, etc. to optional, but required status was maintained. -type OptionsT = {| - ...SharedColumnOptionsT, - renderCell: RenderCellT, - renderFilter?: RenderFilterT, - buildFilter?: (FilterParamsT) => (ValueT) => boolean, - textQueryFilter?: (string, ValueT) => boolean, - sortFn?: (ValueT, ValueT) => number, -|}; - -function CustomColumn( - options: OptionsT -): ColumnT { - //$FlowFixMe - return Column({ kind: COLUMNS.CUSTOM, ...options }); -} - -export default CustomColumn; diff --git a/src/data-table/column-datetime.js.flow b/src/data-table/column-datetime.js.flow deleted file mode 100644 index 225ebbf3b1..0000000000 --- a/src/data-table/column-datetime.js.flow +++ /dev/null @@ -1,641 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import format from 'date-fns/format/index.js'; -import getYear from 'date-fns/getYear/index.js'; -import getMonth from 'date-fns/getMonth/index.js'; -import getQuarter from 'date-fns/getQuarter/index.js'; -import getDay from 'date-fns/getDay/index.js'; -import isAfter from 'date-fns/isAfter/index.js'; -import isBefore from 'date-fns/isBefore/index.js'; -import isEqual from 'date-fns/isEqual/index.js'; -import set from 'date-fns/set/index.js'; - -import { Button, SIZE } from '../button/index.js'; -import { ButtonGroup, MODE } from '../button-group/index.js'; -import { Checkbox } from '../checkbox/index.js'; -import { - applyDateToTime, - applyTimeToDate, - getMonthInLocale, - getWeekdayInLocale, - getQuarterInLocale, - getStartOfWeek, - addDays, -} from '../datepicker/utils/index.js'; -import { Datepicker } from '../datepicker/index.js'; -import { TimePicker } from '../timepicker/index.js'; -import { useStyletron } from '../styles/index.js'; -import { Select, type ValueT } from '../select/index.js'; - -import Column from './column.js'; -import { COLUMNS, DATETIME_OPERATIONS } from './constants.js'; -import FilterShell from './filter-shell.js'; -import type { ColumnT, SharedColumnOptionsT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; - -type OptionsT = {| - ...SharedColumnOptionsT, - formatString?: string, - // flowlint-next-line unclear-type:off - locale?: any, -|}; - -type DatetimeOperationsT = - | typeof DATETIME_OPERATIONS.RANGE_DATETIME - | typeof DATETIME_OPERATIONS.RANGE_DATE - | typeof DATETIME_OPERATIONS.RANGE_TIME - | typeof DATETIME_OPERATIONS.WEEKDAY - | typeof DATETIME_OPERATIONS.MONTH - | typeof DATETIME_OPERATIONS.QUARTER - | typeof DATETIME_OPERATIONS.HALF - | typeof DATETIME_OPERATIONS.YEAR; - -type FilterParametersT = {| - operation: DatetimeOperationsT, - range: Date[], - selection: number[], - description: string, - exclude: boolean, -|}; - -type DatetimeColumnT = ColumnT; - -const DATE_FORMAT = 'MM-dd-yyyy'; -const MASK = '99-99-9999 - 99-99-9999'; -const TIME_FORMAT = 'HH:mm ss:SS'; -const FORMAT_STRING = `${DATE_FORMAT} ${TIME_FORMAT}`; - -function sortDates(a, b) { - return a - b; -} - -const RANGE_OPERATIONS = [ - { - localeLabelKey: 'datetimeFilterRangeDatetime', - id: DATETIME_OPERATIONS.RANGE_DATETIME, - }, - { - localeLabelKey: 'datetimeFilterRangeDate', - id: DATETIME_OPERATIONS.RANGE_DATE, - }, - { - localeLabelKey: 'datetimeFilterRangeTime', - id: DATETIME_OPERATIONS.RANGE_TIME, - }, -]; - -const CATEGORICAL_OPERATIONS = [ - { - localeLabelKey: 'datetimeFilterCategoricalWeekday', - id: DATETIME_OPERATIONS.WEEKDAY, - }, - { - localeLabelKey: 'datetimeFilterCategoricalMonth', - id: DATETIME_OPERATIONS.MONTH, - }, - { - localeLabelKey: 'datetimeFilterCategoricalQuarter', - id: DATETIME_OPERATIONS.QUARTER, - }, - { - localeLabelKey: 'datetimeFilterCategoricalHalf', - id: DATETIME_OPERATIONS.HALF, - }, - { - localeLabelKey: 'datetimeFilterCategoricalYear', - id: DATETIME_OPERATIONS.YEAR, - }, -]; - -const WEEKDAYS = [0, 1, 2, 3, 4, 5, 6]; - -const MONTHS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; - -const QUARTERS = [0, 1, 2, 3]; - -function Checks(props) { - const [css, theme] = useStyletron(); - return ( -
- {props.options.map((item) => { - const checked = props.value.includes(item.id); - return ( -
- { - if (checked) { - props.setValue((prev) => prev.filter((i) => i !== item.id)); - } else { - props.setValue((prev) => [...prev, item.id]); - } - }} - > - {item.label} - -
- ); - })} -
- ); -} - -function filterParamsToInitialState(input) { - const output = { - exclude: false, - comparatorIndex: 0, - rangeOperator: RANGE_OPERATIONS[0], - categoricalOperator: CATEGORICAL_OPERATIONS[0], - rangeDates: [], - years: [], - halves: [], - quarters: [], - months: [], - weekdays: [], - }; - - if (input) { - const op = input.operation; - if (input.range && input.range.length) { - if (op === DATETIME_OPERATIONS.RANGE_DATETIME) { - output.rangeDates = input.range; - output.rangeOperator = RANGE_OPERATIONS[0]; - } else if (op === DATETIME_OPERATIONS.RANGE_DATE) { - output.rangeDates = input.range; - output.rangeOperator = RANGE_OPERATIONS[1]; - } else if (op === DATETIME_OPERATIONS.RANGE_TIME) { - output.rangeDates = input.range; - output.rangeOperator = RANGE_OPERATIONS[2]; - } - } else if (input.selection && input.selection.length) { - output.comparatorIndex = 1; - if (op === DATETIME_OPERATIONS.YEAR) { - output.years = input.selection; - output.categoricalOperator = CATEGORICAL_OPERATIONS[4]; - } else if (op === DATETIME_OPERATIONS.HALF) { - output.halves = input.selection; - output.categoricalOperator = CATEGORICAL_OPERATIONS[3]; - } else if (op === DATETIME_OPERATIONS.QUARTER) { - output.quarters = input.selection; - output.categoricalOperator = CATEGORICAL_OPERATIONS[2]; - } else if (op === DATETIME_OPERATIONS.MONTH) { - output.months = input.selection; - output.categoricalOperator = CATEGORICAL_OPERATIONS[1]; - } else if (op === DATETIME_OPERATIONS.WEEKDAY) { - output.weekdays = input.selection; - output.categoricalOperator = CATEGORICAL_OPERATIONS[0]; - } - } - - if (input.exclude) { - output.exclude = input.exclude; - } - } - - return output; -} - -function DatetimeFilter(props) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const mountNode = React.useRef(); - const initialState = filterParamsToInitialState(props.filterParams); - - const datesSorted = React.useMemo(() => { - return props.data.sort(sortDates); - }, [props.data]); - const presentYears = React.useMemo(() => { - const dict = {}; - props.data.forEach((date) => { - dict[getYear(date)] = true; - }); - return Object.keys(dict).map((n) => parseInt(n)); - }, [props.data]); - const startOfWeek = React.useMemo(() => { - return getStartOfWeek(new Date(), props.locale); - }, [props.locale]); - const localizedWeekdays = React.useMemo(() => { - return [...WEEKDAYS.slice(getDay(startOfWeek), 7), ...WEEKDAYS.slice(0, getDay(startOfWeek))]; - }, [props.locale]); - - const [exclude, setExclude] = React.useState(initialState.exclude); - const [comparatorIndex, setComparatorIndex] = React.useState(initialState.comparatorIndex); - const [rangeOperator, setRangeOperator] = React.useState([initialState.rangeOperator]); - const [categoricalOperator, setCategoricalOperator] = React.useState([ - initialState.categoricalOperator, - ]); - // flowlint-next-line unclear-type:off - const [rangeDates, setRangeDates] = React.useState( - initialState.rangeDates.length - ? initialState.rangeDates - : [new Date(datesSorted[0]), new Date(datesSorted[datesSorted.length - 1])] - ); - - const [years, setYears] = React.useState(initialState.years); - const [halves, setHalves] = React.useState(initialState.halves); - const [quarters, setQuarters] = React.useState(initialState.quarters); - const [months, setMonths] = React.useState(initialState.months); - const [weekdays, setWeekdays] = React.useState(initialState.weekdays); - - const isRange = comparatorIndex === 0; - const isCategorical = comparatorIndex === 1; - - return ( - setExclude(!exclude)} - onApply={() => { - if (isRange) { - // flowlint-next-line unclear-type:off - const op: DatetimeOperationsT = (rangeOperator[0].id: any); - - let description = ''; - if (op === DATETIME_OPERATIONS.RANGE_DATETIME) { - const left = format(rangeDates[0], FORMAT_STRING); - const right = format(rangeDates[1], FORMAT_STRING); - description = `${left} - ${right}`; - } else if (op === DATETIME_OPERATIONS.RANGE_DATE) { - const left = format(rangeDates[0], DATE_FORMAT); - const right = format(rangeDates[1], DATE_FORMAT); - description = `${left} - ${right}`; - } else if (op === DATETIME_OPERATIONS.RANGE_TIME) { - const left = format(rangeDates[0], TIME_FORMAT); - const right = format(rangeDates[1], TIME_FORMAT); - description = `${left} - ${right}`; - } - - props.setFilter({ - operation: op, - range: rangeDates, - selection: [], - description: description, - exclude, - }); - } - - if (isCategorical) { - // flowlint-next-line unclear-type:off - const op: DatetimeOperationsT = (categoricalOperator[0].id: any); - - let selection: number[] = []; - let operatorLocaleLabelKey = ''; - let description = ''; - if (op === DATETIME_OPERATIONS.WEEKDAY) { - selection = weekdays; - operatorLocaleLabelKey = CATEGORICAL_OPERATIONS[0].localeLabelKey; - description = weekdays - .map((w) => { - const day = addDays(startOfWeek, localizedWeekdays.indexOf(w)); - - return getWeekdayInLocale(day, props.locale); - }) - .join(', '); - } else if (op === DATETIME_OPERATIONS.MONTH) { - selection = months; - operatorLocaleLabelKey = CATEGORICAL_OPERATIONS[1].localeLabelKey; - description = months.map((m) => getMonthInLocale(m, props.locale)).join(', '); - } else if (op === DATETIME_OPERATIONS.QUARTER) { - selection = quarters; - operatorLocaleLabelKey = CATEGORICAL_OPERATIONS[2].localeLabelKey; - description = quarters.map((q) => getQuarterInLocale(q, props.locale)).join(', '); - } else if (op === DATETIME_OPERATIONS.HALF) { - selection = halves; - operatorLocaleLabelKey = CATEGORICAL_OPERATIONS[3].localeLabelKey; - description = halves - .map((h) => - h === 0 - ? locale.datatable.datetimeFilterCategoricalFirstHalf - : locale.datatable.datetimeFilterCategoricalSecondHalf - ) - .join(', '); - } else if (op === DATETIME_OPERATIONS.YEAR) { - selection = years; - operatorLocaleLabelKey = CATEGORICAL_OPERATIONS[4].localeLabelKey; - description = years.join(', '); - } - - if (operatorLocaleLabelKey) { - description = `${locale.datatable[operatorLocaleLabelKey]} - ${description}`; - } - - props.setFilter({ - operation: op, - range: [], - selection, - description, - exclude, - }); - } - - props.close(); - }} - > -
- setComparatorIndex(index)} - overrides={{ - Root: { - style: ({ $theme }) => ({ marginBottom: $theme.sizing.scale300 }), - }, - }} - > - - - - - {isRange && ( -
- setCategoricalOperator(params.value)} - options={CATEGORICAL_OPERATIONS.map((op) => ({ - label: locale.datatable[op.localeLabelKey], - id: op.id, - }))} - // flowlint-next-line unclear-type:off - mountNode={(mountNode.current: any)} - size="compact" - clearable={false} - /> - -
- {categoricalOperator[0].id === DATETIME_OPERATIONS.WEEKDAY && ( - { - const day = addDays(startOfWeek, offset); - - return { - label: getWeekdayInLocale(day, props.locale), - id: w, - }; - })} - /> - )} - - {categoricalOperator[0].id === DATETIME_OPERATIONS.MONTH && ( - ({ - label: getMonthInLocale(m, props.locale), - id: m, - }))} - /> - )} - - {categoricalOperator[0].id === DATETIME_OPERATIONS.QUARTER && ( - ({ - label: getQuarterInLocale(q, props.locale), - id: q, - }))} - /> - )} - - {categoricalOperator[0].id === DATETIME_OPERATIONS.HALF && ( - - )} - - {categoricalOperator[0].id === DATETIME_OPERATIONS.YEAR && ( - ({ - label: year, - id: year, - }))} - /> - )} -
-
- )} -
-
- ); -} - -function DatetimeCell(props) { - const [css, theme] = useStyletron(); - return ( -
- {format(props.value, props.formatString)} -
- ); -} - -const defaultOptions = { - title: '', - sortable: true, - filterable: true, - formatString: FORMAT_STRING, -}; - -function DatetimeColumn(options: OptionsT): DatetimeColumnT { - const normalizedOptions = { - ...defaultOptions, - ...options, - }; - - return Column({ - kind: COLUMNS.DATETIME, - buildFilter: function (params) { - return function (data) { - let included = true; - if (params.operation === DATETIME_OPERATIONS.YEAR) { - included = params.selection.includes(getYear(data)); - } else if (params.operation === DATETIME_OPERATIONS.HALF) { - const month = getMonth(data); - const half = month < 6 ? 0 : 1; - included = params.selection.includes(half); - } else if (params.operation === DATETIME_OPERATIONS.QUARTER) { - // date-fns quarters are 1 indexed - const quarter = getQuarter(data) - 1; - included = params.selection.includes(quarter); - } else if (params.operation === DATETIME_OPERATIONS.MONTH) { - included = params.selection.includes(getMonth(data)); - } else if (params.operation === DATETIME_OPERATIONS.WEEKDAY) { - included = params.selection.includes(getDay(data)); - } - - if ( - params.operation === DATETIME_OPERATIONS.RANGE_DATE || - params.operation === DATETIME_OPERATIONS.RANGE_TIME || - params.operation === DATETIME_OPERATIONS.RANGE_DATETIME - ) { - let [left, right] = params.range; - - if (params.operation === DATETIME_OPERATIONS.RANGE_DATE) { - left = set(left, { hours: 0, minutes: 0, seconds: 0 }); - right = set(right, { hours: 0, minutes: 0, seconds: 0 }); - data = set(data, { hours: 0, minutes: 0, seconds: 0 }); - } - - if (params.operation === DATETIME_OPERATIONS.RANGE_TIME) { - left = set(left, { year: 2000, month: 1, date: 1 }); - right = set(right, { year: 2000, month: 1, date: 1 }); - data = set(data, { year: 2000, month: 1, date: 1 }); - } - - const after = isAfter(data, left) || isEqual(data, left); - const before = isBefore(data, right) || isEqual(data, right); - included = after && before; - } - - return params.exclude ? !included : included; - }; - }, - cellBlockAlign: options.cellBlockAlign, - fillWidth: options.fillWidth, - filterable: normalizedOptions.filterable, - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - renderCell: function RenderDatetimeCell(props) { - return ; - }, - renderFilter: function RenderDatetimeFilter(props) { - return ; - }, - sortable: normalizedOptions.sortable, - sortFn: sortDates, - - title: options.title, - }); -} - -export default DatetimeColumn; diff --git a/src/data-table/column-numerical.js.flow b/src/data-table/column-numerical.js.flow deleted file mode 100644 index e171c1a45f..0000000000 --- a/src/data-table/column-numerical.js.flow +++ /dev/null @@ -1,521 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Button, SIZE } from '../button/index.js'; -import { ButtonGroup, MODE } from '../button-group/index.js'; -import { Input, SIZE as INPUT_SIZE } from '../input/index.js'; -import { useStyletron } from '../styles/index.js'; - -import Column from './column.js'; -import { COLUMNS, NUMERICAL_FORMATS, MAX_BIN_COUNT, HISTOGRAM_SIZE } from './constants.js'; -import FilterShell, { type ExcludeKind } from './filter-shell.js'; -import type { ColumnT, SharedColumnOptionsT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; -import { bin, max as maxFunc, extent, scaleLinear, median, bisector } from 'd3'; -import { Slider } from '../slider/index.js'; - -type NumericalFormats = - | typeof NUMERICAL_FORMATS.DEFAULT - | typeof NUMERICAL_FORMATS.ACCOUNTING - | typeof NUMERICAL_FORMATS.PERCENTAGE; - -type OptionsT = {| - ...SharedColumnOptionsT, - format?: NumericalFormats | ((value: number) => string), - highlight?: (number) => boolean, - precision?: number, -|}; - -type FilterParametersT = {| - lowerValue: number, - upperValue: number, - description: string, - exclude: boolean, - excludeKind: ExcludeKind, -|}; - -type NumericalColumnT = ColumnT; - -function roundToFixed(value: number, precision: number) { - const k = Math.pow(10, precision); - return Math.round(value * k) / k; -} - -function format(value: number, options) { - if (typeof options.format === 'function') { - return options.format(value); - } - let formatted = value.toString(); - switch (options.format) { - case NUMERICAL_FORMATS.ACCOUNTING: { - const abs = Math.abs(value); - if (value < 0) { - formatted = `($${roundToFixed(abs, options.precision)})`; - break; - } - formatted = `$${roundToFixed(abs, options.precision)}`; - break; - } - case NUMERICAL_FORMATS.PERCENTAGE: { - formatted = `${roundToFixed(value, options.precision)}%`; - break; - } - case NUMERICAL_FORMATS.DEFAULT: - default: - formatted = roundToFixed(value, options.precision); - break; - } - return formatted; -} - -function validateInput(input) { - return Boolean(parseFloat(input)) || input === '' || input === '-'; -} - -const bisect = bisector((d) => d.x0); - -const Histogram = React.memo(function Histogram({ - data, - lower, - upper, - isRange, - exclude, - precision, -}) { - const [css, theme] = useStyletron(); - - const { bins, xScale, yScale } = React.useMemo(() => { - const bins = bin().thresholds(Math.min(data.length, MAX_BIN_COUNT))(data); - - const xScale = scaleLinear() - .domain([bins[0].x0, bins[bins.length - 1].x1]) - .range([0, HISTOGRAM_SIZE.width]) - .clamp(true); - - const yScale = scaleLinear() - .domain([0, maxFunc(bins, (d) => d.length)]) - .nice() - .range([HISTOGRAM_SIZE.height, 0]); - return { bins, xScale, yScale }; - }, [data]); - - // We need to find the index of bar which is nearest to the given single value - const singleIndexNearest = React.useMemo(() => { - if (isRange) { - return null; - } - return bisect.center(bins, lower); - }, [isRange, data, lower, upper]); - - return ( -
- - {bins.map((d, index) => { - const x = xScale(d.x0) + 1; - const y = yScale(d.length); - const width = Math.max(0, xScale(d.x1) - xScale(d.x0) - 1); - const height = yScale(0) - yScale(d.length); - - let included; - if (singleIndexNearest != null) { - included = index === singleIndexNearest; - } else { - const withinLower = d.x1 > lower; - const withinUpper = d.x0 <= upper; - included = withinLower && withinUpper; - } - - if (exclude) { - included = !included; - } - - return ( - - ); - })} - -
- ); -}); - -function NumericalFilter(props) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - - const precision = props.options.precision; - - // The state handling of this component could be refactored and cleaned up if we used useReducer. - const initialState = React.useMemo(() => { - return ( - props.filterParams || { - exclude: false, - excludeKind: 'range', - comparatorIndex: 0, - lowerValue: null, - upperValue: null, - } - ); - }, [props.filterParams]); - - const [exclude, setExclude] = React.useState(initialState.exclude); - - // the api of our ButtonGroup forces these numerical indexes... - // TODO look into allowing semantic names, similar to the radio component. Tricky part would be backwards compat - const [comparatorIndex, setComparatorIndex] = React.useState(() => { - switch (initialState.excludeKind) { - case 'value': - return 1; - case 'range': - default: - // fallthrough - return 0; - } - }); - - // We use the d3 function to get the extent as it's a little more robust to null, -Infinity, etc. - const [min, max] = React.useMemo(() => extent(props.data), [props.data]); - - const [lv, setLower] = React.useState(() => - roundToFixed(initialState.lowerValue || min, precision) - ); - const [uv, setUpper] = React.useState(() => - roundToFixed(initialState.upperValue || max, precision) - ); - - // We keep a separate value for the single select, to give a user the ability to toggle between - // the range and single values without losing their previous input. - const [sv, setSingle] = React.useState(() => - roundToFixed(initialState.lowerValue || median(props.data), precision) - ); - - // This is the only conditional which we want to use to determine - // if we are in range or single value mode. - // Don't derive it via something else, e.g. lowerValue === upperValue, etc. - const isRange = comparatorIndex === 0; - - const excludeKind = isRange ? 'range' : 'value'; - - // while the user is inputting values, we take their input at face value, - // if we don't do this, a user can't input partial numbers, e.g. "-", or "3." - const [focused, setFocus] = React.useState(false); - const [inputValueLower, inputValueUpper] = React.useMemo(() => { - if (focused) { - return [isRange ? lv : sv, uv]; - } - - // once the user is done inputting. - // we validate then format to the given precision - let l = isRange ? lv : sv; - l = validateInput(l) ? l : min; - let h = validateInput(uv) ? uv : max; - - return [roundToFixed(l, precision), roundToFixed(h, precision)]; - }, [isRange, focused, sv, lv, uv, precision]); - - // We have our slider values range from 1 to the bin size, so we have a scale which - // takes in the data driven range and maps it to values the scale can always handle - const sliderScale = React.useMemo( - () => - scaleLinear() - .domain([min, max]) - .rangeRound([1, MAX_BIN_COUNT]) - // We clamp the values within our min and max even if a user enters a huge number - .clamp(true), - [min, max] - ); - - let sliderValue = isRange - ? [sliderScale(inputValueLower), sliderScale(inputValueUpper)] - : [sliderScale(inputValueLower)]; - - // keep the slider happy by sorting the two values - if (isRange && sliderValue[0] > sliderValue[1]) { - sliderValue = [sliderValue[1], sliderValue[0]]; - } - - return ( - setExclude(!exclude)} - excludeKind={excludeKind} - onApply={() => { - if (isRange) { - const lowerValue = parseFloat(inputValueLower); - const upperValue = parseFloat(inputValueUpper); - props.setFilter({ - description: `≥ ${lowerValue} and ≤ ${upperValue}`, - exclude: exclude, - lowerValue, - upperValue, - excludeKind, - }); - } else { - const value = parseFloat(inputValueLower); - props.setFilter({ - description: `= ${value}`, - exclude: exclude, - lowerValue: inputValueLower, - upperValue: inputValueLower, - excludeKind, - }); - } - - props.close(); - }} - > - setComparatorIndex(index)} - overrides={{ - Root: { - style: ({ $theme }) => ({ marginBottom: $theme.sizing.scale300 }), - }, - }} - > - - - - - - -
- { - if (!value) { - return; - } - // we convert back from the slider scale to the actual data's scale - if (isRange) { - const [lowerValue, upperValue] = value; - setLower(sliderScale.invert(lowerValue)); - setUpper(sliderScale.invert(upperValue)); - } else { - const [singleValue] = value; - setSingle(sliderScale.invert(singleValue)); - } - }} - overrides={{ - InnerThumb: function InnerThumb({ $value, $thumbIndex }) { - return {$value[$thumbIndex]}; - }, - TickBar: ({ $min, $max }) => null, // we don't want the ticks - ThumbValue: () => null, - Root: { - style: () => ({ - // Aligns the center of the slider handles with the histogram bars - width: 'calc(100% + 14px)', - margin: '0 -7px', - }), - }, - InnerTrack: { - style: ({ $theme }) => { - if (!isRange) { - return { - // For range selection we use the color as is, but when selecting the single value, - // we don't want the track standing out, so mute its color - background: theme.colors.backgroundSecondary, - }; - } - }, - }, - Thumb: { - style: () => ({ - // Slider handles are small enough to visually be centered within each histogram bar - height: '18px', - width: '18px', - fontSize: '0px', - }), - }, - }} - /> -
-
- { - if (validateInput(event.target.value)) { - isRange - ? // $FlowFixMe - we know it is a number by now - setLower(event.target.value) - : // $FlowFixMe - we know it is a number by now - setSingle(event.target.value); - } - }} - onFocus={() => setFocus(true)} - onBlur={() => setFocus(false)} - /> - {isRange && ( - { - if (validateInput(event.target.value)) { - // $FlowFixMe - we know it is a number by now - setUpper(event.target.value); - } - }} - onFocus={() => setFocus(true)} - onBlur={() => setFocus(false)} - /> - )} -
-
- ); -} - -function NumericalCell(props) { - const [css, theme] = useStyletron(); - return ( -
- {format(props.value, { - format: props.format, - precision: props.precision, - })} -
- ); -} - -const defaultOptions = { - title: '', - sortable: true, - filterable: true, - format: NUMERICAL_FORMATS.DEFAULT, - highlight: ((n) => false: (number) => boolean), - precision: 0, -}; - -function NumericalColumn(options: OptionsT): NumericalColumnT { - const normalizedOptions = { - ...defaultOptions, - ...options, - }; - - if ( - normalizedOptions.format !== NUMERICAL_FORMATS.DEFAULT && - (options.precision === null || options.precision === undefined) - ) { - normalizedOptions.precision = 2; - } - - if ( - normalizedOptions.format === NUMERICAL_FORMATS.ACCOUNTING && - (options.highlight === null || options.highlight === undefined) - ) { - normalizedOptions.highlight = (n: number) => (n < 0: boolean); - } - - return Column({ - kind: COLUMNS.NUMERICAL, - buildFilter: function (params) { - return function (data) { - const value = roundToFixed(data, normalizedOptions.precision); - const included = value >= params.lowerValue && value <= params.upperValue; - return params.exclude ? !included : included; - }; - }, - cellBlockAlign: options.cellBlockAlign, - fillWidth: options.fillWidth, - filterable: normalizedOptions.filterable, - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - renderCell: function RenderNumericalCell(props) { - return ( - - ); - }, - renderFilter: function RenderNumericalFilter(props) { - return ; - }, - sortable: normalizedOptions.sortable, - sortFn: function (a, b) { - return a - b; - }, - title: normalizedOptions.title, - }); -} - -export default NumericalColumn; diff --git a/src/data-table/column-row-index.js.flow b/src/data-table/column-row-index.js.flow deleted file mode 100644 index 717d044a31..0000000000 --- a/src/data-table/column-row-index.js.flow +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { useStyletron } from '../styles/index.js'; - -import Column from './column.js'; -import { COLUMNS } from './constants.js'; -import type { ColumnT } from './types.js'; - -type ValueT = null; -type FilterParametersT = {}; - -type RowIndexColumnT = ColumnT; - -function RowIndexFilter() { - return
not implemented for row index column
; -} - -function RowIndexCell(props) { - const [css, theme] = useStyletron(); - return ( -
- {props.y + 1} -
- ); -} - -function RowIndexColumn(): RowIndexColumnT { - return Column({ - kind: COLUMNS.ROW_INDEX, - buildFilter: () => () => true, - cellBlockAlign: 'start', // how to configure? - fillWidth: false, - filterable: false, - mapDataToValue: () => null, - renderCell: RowIndexCell, - renderFilter: RowIndexFilter, - sortable: false, - sortFn: () => 0, - title: '', - }); -} - -export default RowIndexColumn; diff --git a/src/data-table/column-string.js.flow b/src/data-table/column-string.js.flow deleted file mode 100644 index 6fba867f20..0000000000 --- a/src/data-table/column-string.js.flow +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { useStyletron } from '../styles/index.js'; - -import Column from './column.js'; -import { COLUMNS } from './constants.js'; -import { HighlightCellText } from './text-search.js'; -import type { ColumnT, SharedColumnOptionsT } from './types.js'; - -type OptionsT = {| - ...SharedColumnOptionsT, - lineClamp?: number, -|}; - -type FilterParametersT = {| - description: string, - exclude: boolean, -|}; - -type StringColumnT = ColumnT; - -function StringFilter(props) { - return
not implemented for string column
; -} - -function StringCell(props) { - const [css] = useStyletron(); - return ( -
- {props.textQuery ? ( - - ) : ( - props.value - )} -
- ); -} - -function StringColumn(options: OptionsT): StringColumnT { - return Column({ - kind: COLUMNS.STRING, - cellBlockAlign: options.cellBlockAlign, - buildFilter: function (params) { - return function (data) { - return true; - }; - }, - fillWidth: options.fillWidth, - filterable: false, - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - renderCell: function RenderStringCell(props) { - return ; - }, - renderFilter: StringFilter, - sortable: options.sortable === undefined ? true : options.sortable, - sortFn: function (a, b) { - return a.localeCompare(b); - }, - textQueryFilter: function (textQuery, data) { - return data.toLowerCase().includes(textQuery.toLowerCase()); - }, - title: options.title, - }); -} - -export default StringColumn; diff --git a/src/data-table/column.js.flow b/src/data-table/column.js.flow deleted file mode 100644 index 662be7cbab..0000000000 --- a/src/data-table/column.js.flow +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Checkbox } from '../checkbox/index.js'; -import { useStyletron } from '../styles/index.js'; - -import type { ColumnT } from './types.js'; - -function Column( - options: ColumnT -): ColumnT { - return { - kind: options.kind, - buildFilter: options.buildFilter || (() => () => true), - textQueryFilter: options.textQueryFilter, - fillWidth: options.fillWidth === undefined ? true : options.fillWidth, - filterable: - Boolean(options.filterable) && Boolean(options.renderFilter) && Boolean(options.buildFilter), - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - // eslint-disable-next-line react/display-name - renderCell: React.forwardRef((props, ref) => { - const [css, theme] = useStyletron(); - const ProvidedCell = options.renderCell; - - let cellBlockAlign = 'flex-start'; - if (options.cellBlockAlign === 'center') { - cellBlockAlign = 'center'; - } else if (options.cellBlockAlign === 'end') { - cellBlockAlign = 'flex-end'; - } - - return ( -
-
- {Boolean(props.onSelect) && ( - - - - )} - -
-
- ); - }), - renderFilter: options.renderFilter || (() => null), - sortable: Boolean(options.sortable) && Boolean(options.sortFn), - sortFn: options.sortFn || (() => 0), - title: options.title, - }; -} - -export default Column; diff --git a/src/data-table/constants.js.flow b/src/data-table/constants.js.flow deleted file mode 100644 index 4ce3dd1ed3..0000000000 --- a/src/data-table/constants.js.flow +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export const COLUMNS = Object.freeze({ - ANCHOR: 'ANCHOR', - BOOLEAN: 'BOOLEAN', - CATEGORICAL: 'CATEGORICAL', - CUSTOM: 'CUSTOM', - DATETIME: 'DATETIME', - NUMERICAL: 'NUMERICAL', - ROW_INDEX: 'ROW_INDEX', - STRING: 'STRING', -}); - -export const NUMERICAL_FORMATS = Object.freeze({ - DEFAULT: 'DEFAULT', - ACCOUNTING: 'ACCOUNTING', - PERCENTAGE: 'PERCENTAGE', -}); - -export const DATETIME_OPERATIONS = Object.freeze({ - RANGE_DATETIME: 'RANGE_DATETIME', - RANGE_DATE: 'RANGE_DATE', - RANGE_TIME: 'RANGE_TIME', - WEEKDAY: 'WEEKDAY', - MONTH: 'MONTH', - QUARTER: 'QUARTER', - HALF: 'HALF', - YEAR: 'YEAR', -}); - -export const SORT_DIRECTIONS = Object.freeze({ - ASC: 'ASC', - DESC: 'DESC', -}); - -// If modifying this, take a look at the histogram and adjust. see HISTOGRAM_SIZE -export const FILTER_SHELL_WIDTH = '320px'; - -// Depends on FILTER_SHELL_WIDTH -export const HISTOGRAM_SIZE = { width: 308, height: 120 }; - -// Arguably visually appealing within the given width. -// Smaller and we don't have enough detail per bar. -// Larger and the bars are too granular and don't align well with the slider steps -export const MAX_BIN_COUNT = 50; diff --git a/src/data-table/data-table.js.flow b/src/data-table/data-table.js.flow deleted file mode 100644 index ff86a4cd1e..0000000000 --- a/src/data-table/data-table.js.flow +++ /dev/null @@ -1,1039 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import { VariableSizeGrid } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; - -import { - Button, - SHAPE as BUTTON_SHAPES, - SIZE as BUTTON_SIZES, - KIND as BUTTON_KINDS, -} from '../button/index.js'; -import { useStyletron } from '../styles/index.js'; -import { Tooltip, PLACEMENT } from '../tooltip/index.js'; - -import { SORT_DIRECTIONS } from './constants.js'; -import HeaderCell from './header-cell.js'; -import MeasureColumnWidths from './measure-column-widths.js'; -import type { ColumnT, DataTablePropsT, RowT, SortDirectionsT, RowActionT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; - -// consider pulling this out to a prop if useful. -const HEADER_ROW_HEIGHT = 48; - -type HeaderContextT = {| - columns: ColumnT<>[], - columnHighlightIndex: number, - emptyMessage: string | React.ComponentType<{||}>, - filters: $PropertyType, - loading: boolean, - loadingMessage: string | React.ComponentType<{||}>, - isScrollingX: boolean, - isSelectable: boolean, - isSelectedAll: boolean, - isSelectedIndeterminate: boolean, - measuredWidths: number[], - onMouseEnter: (number) => void, - onMouseLeave: () => void, - onResize: (columnIndex: number, delta: number) => void, - onSelectMany: () => void, - onSelectNone: () => void, - onSort: (number) => void, - resizableColumnWidths: boolean, - rowActions: RowActionT[] | ((RowT) => RowActionT[]), - rowHeight: number, - rowHighlightIndex: number, - rows: RowT[], - scrollLeft: number, - sortIndex: number, - sortDirection: SortDirectionsT, - tableHeight: number, - widths: number[], -|}; - -type CellPlacementPropsT = { - columnIndex: number, - rowIndex: number, - style: { - position: string, - height: number, - width: number, - top: number, - left: number, - }, - data: { - columns: ColumnT<>[], - columnHighlightIndex: number, - isSelectable: boolean, - isRowSelected: (string | number) => boolean, - onRowMouseEnter: (number, RowT) => void, - onSelectOne: (RowT) => void, - rowHighlightIndex: number, - rows: RowT[], - textQuery: string, - }, -}; - -const sum = (ns) => ns.reduce((s, n) => s + n, 0); - -function CellPlacement({ columnIndex, rowIndex, data, style }) { - const [css, theme] = useStyletron(); - - // ignores the table header row - if (rowIndex === 0) { - return null; - } - - let backgroundColor = theme.colors.backgroundPrimary; - if ( - (Boolean(rowIndex % 2) && columnIndex === data.columnHighlightIndex) || - rowIndex === data.rowHighlightIndex - ) { - backgroundColor = theme.colors.backgroundTertiary; - } else if (rowIndex % 2 || columnIndex === data.columnHighlightIndex) { - backgroundColor = theme.colors.backgroundSecondary; - } - - const Cell = data.columns[columnIndex].renderCell; - const value = data.columns[columnIndex].mapDataToValue(data.rows[rowIndex - 1].data); - - return ( -
data.onRowMouseEnter(rowIndex, data.rows[rowIndex - 1])} - > - data.onSelectOne(data.rows[rowIndex - 1]) - : undefined - } - isSelected={data.isRowSelected(data.rows[rowIndex - 1].id)} - textQuery={data.textQuery} - x={columnIndex} - y={rowIndex - 1} - /> -
- ); -} - -function compareCellPlacement(prevProps, nextProps) { - // header cells are not rendered through this component - if (prevProps.rowIndex === 0) { - return true; - } - - if ( - prevProps.data.columns !== nextProps.data.columns || - prevProps.data.rows !== nextProps.data.rows || - prevProps.style !== nextProps.style - ) { - return false; - } - - if ( - prevProps.data.isSelectable === nextProps.data.isSelectable && - prevProps.data.columnHighlightIndex === nextProps.data.columnHighlightIndex && - prevProps.data.rowHighlightIndex === nextProps.data.rowHighlightIndex && - prevProps.data.textQuery === nextProps.data.textQuery && - prevProps.data.isRowSelected === nextProps.data.isRowSelected - ) { - return true; - } - - // at this point we know that the rowHighlightIndex or the columnHighlightIndex has changed. - // row does not need to re-render if not transitioning _from_ or _to_ highlighted - // also ensures that all cells are invalidated on column-header hover - if ( - prevProps.rowIndex !== prevProps.data.rowHighlightIndex && - prevProps.rowIndex !== nextProps.data.rowHighlightIndex && - prevProps.data.columnHighlightIndex === nextProps.data.columnHighlightIndex && - prevProps.data.isRowSelected === nextProps.data.isRowSelected - ) { - return true; - } - - // similar to the row highlight optimization, do not update the cell if not in the previously - // highlighted column or next highlighted. - if ( - prevProps.columnIndex !== prevProps.data.columnHighlightIndex && - prevProps.columnIndex !== nextProps.data.columnHighlightIndex && - prevProps.data.rowHighlightIndex === nextProps.data.rowHighlightIndex && - prevProps.data.isRowSelected === nextProps.data.isRowSelected - ) { - return true; - } - - return false; -} -const CellPlacementMemo = React.memo( - CellPlacement, - compareCellPlacement -); -CellPlacementMemo.displayName = 'CellPlacement'; - -const HeaderContext = React.createContext({ - columns: [], - columnHighlightIndex: -1, - emptyMessage: '', - filters: new Map(), - loading: false, - loadingMessage: '', - isScrollingX: false, - isSelectable: false, - isSelectedAll: false, - isSelectedIndeterminate: false, - measuredWidths: [], - onMouseEnter: () => {}, - onMouseLeave: () => {}, - onResize: () => {}, - onSelectMany: () => {}, - onSelectNone: () => {}, - onSort: () => {}, - resizableColumnWidths: false, - rowActions: [], - rowHeight: 0, - rowHighlightIndex: -1, - rows: [], - scrollLeft: 0, - sortIndex: -1, - sortDirection: null, - tableHeight: 0, - widths: [], -}); -HeaderContext.displayName = 'HeaderContext'; - -type HeaderProps = {| - columnTitle: string, - hoverIndex: number, - index: number, - isSortable: boolean, - isSelectable: boolean, - isSelectedAll: boolean, - isSelectedIndeterminate: boolean, - onMouseEnter: (number) => void, - onMouseLeave: () => void, - onResize: (columnIndex: number, delta: number) => void, - onResizeIndexChange: (columnIndex: number) => void, - onSelectMany: () => void, - onSelectNone: () => void, - onSort: () => void, - resizableColumnWidths: boolean, - resizeIndex: number, - resizeMaxWidth: number, - resizeMinWidth: number, - sortIndex: number, - sortDirection: SortDirectionsT, - tableHeight: number, -|}; -function Header(props: HeaderProps) { - const [css, theme] = useStyletron(); - const [startResizePos, setStartResizePos] = React.useState(0); - const [endResizePos, setEndResizePos] = React.useState(0); - // flowlint-next-line unclear-type:off - const headerCellRef = React.useRef(null); - - const RULER_OFFSET = 2; - const isResizingThisColumn = props.resizeIndex === props.index; - const isResizing = props.resizeIndex >= 0; - - function getPositionX(el) { - if (__BROWSER__) { - const rect = el.getBoundingClientRect(); - return rect.left + window.scrollX; - } - return 0; - } - - React.useLayoutEffect(() => { - function handleMouseMove(event: MouseEvent) { - if (isResizingThisColumn) { - event.preventDefault(); - - if (headerCellRef.current) { - const left = getPositionX(headerCellRef.current); - const width = event.clientX - left - 5; - const max = Math.ceil(props.resizeMaxWidth); - const min = Math.ceil(props.resizeMinWidth); - - if (min === max) { - return; - } - - if (width >= min && width <= max) { - setEndResizePos(event.clientX - RULER_OFFSET); - } - if (width < min) { - setEndResizePos(left + min - RULER_OFFSET); - } - if (width > max) { - setEndResizePos(max - width - RULER_OFFSET); - } - } - } - } - - function handleMouseUp(event: MouseEvent) { - props.onResize(props.index, endResizePos - startResizePos); - props.onResizeIndexChange(-1); - setStartResizePos(0); - setEndResizePos(0); - } - - if (__BROWSER__) { - if (isResizingThisColumn) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - } - return () => { - if (__BROWSER__) { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - } - }; - }, [ - isResizingThisColumn, - setEndResizePos, - setStartResizePos, - setEndResizePos, - props.onResize, - props.onResizeIndexChange, - props.index, - endResizePos, - startResizePos, - headerCellRef.current, - ]); - - return ( - - { - if (!isResizing) { - props.onMouseEnter(props.index); - } - }} - onMouseLeave={() => { - if (!isResizing) { - props.onMouseLeave(); - } - }} - onSelectAll={props.onSelectMany} - onSelectNone={props.onSelectNone} - onSort={props.onSort} - sortDirection={props.sortIndex === props.index ? props.sortDirection : null} - title={props.columnTitle} - /> - {props.resizableColumnWidths && ( -
-
{ - props.onResizeIndexChange(props.index); - const x = getPositionX(event.target); - setStartResizePos(x); - setEndResizePos(x); - }} - className={css({ - backgroundColor: isResizingThisColumn ? theme.colors.contentPrimary : null, - cursor: 'ew-resize', - position: 'absolute', - height: '100%', - width: '3px', - ':hover': { - backgroundColor: theme.colors.contentPrimary, - }, - })} - style={{ - right: `${(RULER_OFFSET + endResizePos - startResizePos) * -1}px`, - }} - > - {isResizingThisColumn && ( -
- )} -
-
- )} - - ); -} - -function Headers() { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const ctx = React.useContext(HeaderContext); - const [resizeIndex, setResizeIndex] = React.useState(-1); - - return ( -
- {ctx.columns.map((column, columnIndex) => { - const activeFilter = ctx.filters ? ctx.filters.get(column.title) : null; - - return ( - - { - return ( -
-

- {locale.datatable.filterAppliedTo} {column.title} -

- {activeFilter && ( -

- {activeFilter.description} -

- )} -
- ); - }} - > -
-
ctx.onSort(columnIndex)} - resizableColumnWidths={ctx.resizableColumnWidths} - resizeIndex={resizeIndex} - resizeMinWidth={ctx.measuredWidths[columnIndex]} - resizeMaxWidth={column.maxWidth || Infinity} - sortIndex={ctx.sortIndex} - sortDirection={ctx.sortDirection} - tableHeight={ctx.tableHeight} - /> -
-
-
- ); - })} -
- ); -} - -function LoadingOrEmptyMessage(props) { - const [css, theme] = useStyletron(); - return ( -

- {typeof props.children === 'function' ? props.children() : String(props.children)} -

- ); -} - -// replaces the content of the virtualized window with contents. in this case, -// we are prepending a table header row before the table rows (children to the fn). -const InnerTableElement = React.forwardRef< - {| children: React.Node, style: { [string]: mixed } |}, - HTMLDivElement ->((props, ref) => { - const [, theme] = useStyletron(); - const ctx = React.useContext(HeaderContext); - - // no need to render the cells until the columns have been measured - if (!ctx.widths.filter(Boolean).length) { - return null; - } - - const RENDERING = 0; - const LOADING = 1; - const EMPTY = 2; - let viewState = RENDERING; - if (ctx.loading) { - viewState = LOADING; - } else if (ctx.rows.length === 0) { - viewState = EMPTY; - } - - const highlightedRow = ctx.rows[ctx.rowHighlightIndex - 1]; - - return ( -
- - - {viewState === LOADING && {ctx.loadingMessage}} - - {viewState === EMPTY && {ctx.emptyMessage}} - - {viewState === RENDERING && props.children} - - {ctx.rowActions && - Boolean(ctx.rowActions.length) && - ctx.rowHighlightIndex > 0 && - Boolean(highlightedRow) && - !ctx.isScrollingX && ( -
- {(typeof ctx.rowActions === 'function' - ? ctx.rowActions(highlightedRow) - : ctx.rowActions - ).map((rowAction) => { - if (rowAction.renderButton) { - const RowActionButton = rowAction.renderButton; - return ; - } - - const RowActionIcon = rowAction.renderIcon; - return ( - - ); - })} -
- )} -
- ); -}); -InnerTableElement.displayName = 'InnerTableElement'; - -function MeasureScrollbarWidth(props) { - const [css] = useStyletron(); - const outerRef = React.useRef(); - const innerRef = React.useRef(); - React.useEffect(() => { - if (outerRef.current && innerRef.current) { - const width = outerRef.current.offsetWidth - innerRef.current.offsetWidth; - props.onWidthChange(width); - } - }, [outerRef.current, innerRef.current]); - return ( -
-
-
- ); -} - -export function DataTable({ - batchActions, - columns, - filters, - emptyMessage, - loading, - loadingMessage, - onIncludedRowsChange, - onRowHighlightChange, - onSelectMany, - onSelectNone, - onSelectOne, - onSort, - resizableColumnWidths = false, - rows: allRows, - rowActions = [], - rowHeight = 36, - rowHighlightIndex: rowHighlightIndexControlled, - selectedRowIds, - sortIndex, - sortDirection, - textQuery = '', - controlRef, -}: DataTablePropsT) { - const [, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - - const rowHeightAtIndex = React.useCallback( - (index) => { - if (index === 0) { - return HEADER_ROW_HEIGHT; - } - return rowHeight; - }, - [rowHeight] - ); - - // We use state for our ref, to allow hooks to update when the ref changes. - // flowlint-next-line unclear-type:off - const [gridRef, setGridRef] = React.useState>(null); - const [measuredWidths, setMeasuredWidths] = React.useState(columns.map(() => 0)); - const [resizeDeltas, setResizeDeltas] = React.useState(columns.map(() => 0)); - React.useEffect(() => { - setMeasuredWidths((prev) => { - return columns.map((v, index) => prev[index] || 0); - }); - setResizeDeltas((prev) => { - return columns.map((v, index) => prev[index] || 0); - }); - }, [columns]); - - const resetAfterColumnIndex = React.useCallback( - (columnIndex) => { - if (gridRef) { - // $FlowFixMe trigger react-window to layout the elements again - gridRef.resetAfterColumnIndex(columnIndex, true); - } - }, - [gridRef] - ); - const handleWidthsChange = React.useCallback( - (nextWidths) => { - setMeasuredWidths(nextWidths); - resetAfterColumnIndex(0); - }, - [setMeasuredWidths, resetAfterColumnIndex] - ); - const handleColumnResize = React.useCallback( - (columnIndex, delta) => { - setResizeDeltas((prev) => { - prev[columnIndex] = Math.max(prev[columnIndex] + delta, 0); - return [...prev]; - }); - resetAfterColumnIndex(columnIndex); - }, - [setResizeDeltas, resetAfterColumnIndex] - ); - - const [scrollLeft, setScrollLeft] = React.useState(0); - const [isScrollingX, setIsScrollingX] = React.useState(false); - const [recentlyScrolledX, setRecentlyScrolledX] = React.useState(false); - React.useLayoutEffect(() => { - if (recentlyScrolledX !== isScrollingX) { - setIsScrollingX(recentlyScrolledX); - } - - if (recentlyScrolledX) { - const timeout = setTimeout(() => { - setRecentlyScrolledX(false); - }, 200); - return () => clearTimeout(timeout); - } - }, [recentlyScrolledX]); - const handleScroll = React.useCallback( - (params) => { - setScrollLeft(params.scrollLeft); - if (params.scrollLeft !== scrollLeft) { - setRecentlyScrolledX(true); - } - }, - [scrollLeft, setScrollLeft, setRecentlyScrolledX] - ); - - const sortedIndices = React.useMemo(() => { - let toSort = allRows.map((r, i) => [r, i]); - const index = sortIndex; - - if (index !== null && index !== undefined && index !== -1 && columns[index]) { - const sortFn = columns[index].sortFn; - const getValue = (row) => columns[index].mapDataToValue(row.data); - if (sortDirection === SORT_DIRECTIONS.ASC) { - toSort.sort((a, b) => sortFn(getValue(a[0]), getValue(b[0]))); - } else if (sortDirection === SORT_DIRECTIONS.DESC) { - toSort.sort((a, b) => sortFn(getValue(b[0]), getValue(a[0]))); - } - } - - return toSort.map((el) => el[1]); - }, [sortIndex, sortDirection, columns, allRows]); - - const filteredIndices = React.useMemo(() => { - const set = new Set(allRows.map((_, idx) => idx)); - Array.from(filters || new Set(), (f) => f).forEach(([title, filter]) => { - const columnIndex = columns.findIndex((c) => c.title === title); - const column = columns[columnIndex]; - if (!column) { - return; - } - - const filterFn = column.buildFilter(filter); - Array.from(set).forEach((idx) => { - if (!filterFn(column.mapDataToValue(allRows[idx].data))) { - set.delete(idx); - } - }); - }); - - if (textQuery) { - const stringishColumnIndices = []; - for (let i = 0; i < columns.length; i++) { - if (columns[i].textQueryFilter) { - stringishColumnIndices.push(i); - } - } - Array.from(set).forEach((idx) => { - const matches = stringishColumnIndices.some((cdx) => { - const column = columns[cdx]; - const textQueryFilter = column.textQueryFilter; - if (textQueryFilter) { - return textQueryFilter(textQuery, column.mapDataToValue(allRows[idx].data)); - } - return false; - }); - - if (!matches) { - set.delete(idx); - } - }); - } - - return set; - }, [filters, textQuery, columns, allRows]); - - const rows = React.useMemo(() => { - const result = sortedIndices - .filter((idx) => filteredIndices.has(idx)) - .map((idx) => allRows[idx]); - - if (onIncludedRowsChange) { - onIncludedRowsChange(result); - } - return result; - }, [sortedIndices, filteredIndices, onIncludedRowsChange, allRows]); - - const [browserScrollbarWidth, setBrowserScrollbarWidth] = React.useState(0); - const normalizedWidths = React.useMemo(() => { - const resizedWidths = measuredWidths.map((w, i) => Math.floor(w) + Math.floor(resizeDeltas[i])); - if (gridRef) { - const gridProps = gridRef.props; - - let isContentTallerThanContainer = false; - let visibleRowHeight = 0; - for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { - visibleRowHeight += rowHeightAtIndex(rowIndex); - if (visibleRowHeight >= gridProps.height) { - isContentTallerThanContainer = true; - break; - } - } - - const scrollbarWidth = isContentTallerThanContainer ? browserScrollbarWidth : 0; - - const remainder = gridProps.width - sum(resizedWidths) - scrollbarWidth; - const padding = Math.floor( - remainder / columns.filter((c) => (c ? c.fillWidth : true)).length - ); - if (padding > 0) { - const result = []; - // -1 so that we loop over all but the last item - for (let i = 0; i < resizedWidths.length - 1; i++) { - if (columns[i] && columns[i].fillWidth) { - result.push(resizedWidths[i] + padding); - } else { - result.push(resizedWidths[i]); - } - } - result.push(gridProps.width - sum(result) - scrollbarWidth); - resetAfterColumnIndex(0); - return result; - } - } - return resizedWidths; - }, [gridRef, measuredWidths, resizeDeltas, browserScrollbarWidth, rows.length, columns]); - - const isSelectable = batchActions ? !!batchActions.length : false; - const isSelectedAll = React.useMemo(() => { - if (!selectedRowIds) { - return false; - } - return !!rows.length && selectedRowIds.size >= rows.length; - }, [selectedRowIds, rows.length]); - const isSelectedIndeterminate = React.useMemo(() => { - if (!selectedRowIds) { - return false; - } - return !!selectedRowIds.size && selectedRowIds.size < rows.length; - }, [selectedRowIds, rows.length]); - const isRowSelected = React.useCallback( - (id) => { - if (selectedRowIds) { - return selectedRowIds.has(id); - } - return false; - }, - [selectedRowIds] - ); - const handleSelectMany = React.useCallback(() => { - if (onSelectMany) { - onSelectMany(rows); - } - }, [rows, onSelectMany]); - const handleSelectNone = React.useCallback(() => { - if (onSelectNone) { - onSelectNone(); - } - }, [onSelectNone]); - const handleSelectOne = React.useCallback( - (row) => { - if (onSelectOne) { - onSelectOne(row); - } - }, - [onSelectOne] - ); - - const handleSort = React.useCallback( - (columnIndex) => { - if (onSort) { - onSort(columnIndex); - } - }, - [onSort] - ); - - React.useImperativeHandle(controlRef, () => ({ getRows: () => rows, clearSelection: handleSelectNone }), [handleSelectNone, rows]); - - const [columnHighlightIndex, setColumnHighlightIndex] = React.useState(-1); - const [rowHighlightIndex, setRowHighlightIndex] = React.useState(-1); - - function handleRowHighlightIndexChange(nextIndex) { - setRowHighlightIndex(nextIndex); - if (gridRef) { - if (nextIndex >= 0) { - // $FlowFixMe - unable to get react-window types - gridRef.scrollToItem({ rowIndex: nextIndex }); - } - if (onRowHighlightChange) { - onRowHighlightChange(nextIndex, rows[nextIndex - 1]); - } - } - } - - const handleRowMouseEnter = React.useCallback( - (nextIndex) => { - setColumnHighlightIndex(-1); - if (nextIndex !== rowHighlightIndex) { - handleRowHighlightIndexChange(nextIndex); - } - }, - [rowHighlightIndex] - ); - function handleColumnHeaderMouseEnter(columnIndex) { - setColumnHighlightIndex(columnIndex); - handleRowHighlightIndexChange(-1); - } - function handleColumnHeaderMouseLeave() { - setColumnHighlightIndex(-1); - } - - React.useEffect(() => { - if (typeof rowHighlightIndexControlled === 'number') { - handleRowHighlightIndexChange(rowHighlightIndexControlled); - } - }, [rowHighlightIndexControlled]); - - const itemData = React.useMemo(() => { - return { - columnHighlightIndex, - rowHighlightIndex, - isRowSelected, - isSelectable, - onRowMouseEnter: handleRowMouseEnter, - onSelectOne: handleSelectOne, - columns: columns, - rows, - textQuery, - }; - }, [ - handleRowMouseEnter, - columnHighlightIndex, - isRowSelected, - isSelectable, - rowHighlightIndex, - rows, - columns, - handleSelectOne, - textQuery, - ]); - - return ( - - - setBrowserScrollbarWidth(w)} /> - - {({ height, width }) => ( - - normalizedWidths[columnIndex]} - height={height - 2} - // plus one to account for additional header row - rowCount={rows.length + 1} - rowHeight={rowHeightAtIndex} - width={width - 2} - itemData={itemData} - onScroll={handleScroll} - style={{ - ...theme.borders.border200, - borderColor: theme.colors.borderOpaque, - }} - direction={theme.direction === 'rtl' ? 'rtl' : 'ltr'} - > - {CellPlacementMemo} - - - )} - - - ); -} diff --git a/src/data-table/filter-menu.js.flow b/src/data-table/filter-menu.js.flow deleted file mode 100644 index 9ad6e5b95d..0000000000 --- a/src/data-table/filter-menu.js.flow +++ /dev/null @@ -1,323 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import React from 'react'; -import { Button, SHAPE, SIZE } from '../button/index.js'; -import { Filter as FilterIcon } from '../icon/index.js'; -import { Input, SIZE as INPUT_SIZE } from '../input/index.js'; -import { Popover, PLACEMENT } from '../popover/index.js'; -import { useStyletron } from '../styles/index.js'; -import { useUIDSeed } from 'react-uid'; - -import { COLUMNS } from './constants.js'; -import { matchesQuery } from './text-search.js'; -import type { ColumnT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; - -import { isFocusVisible } from '../utils/focusVisible.js'; - -function ColumnIcon(props: { column: ColumnT<> }) { - if (props.column.kind === COLUMNS.BOOLEAN) { - return '01'; - } - - if (props.column.kind === COLUMNS.CATEGORICAL) { - return 'abc'; - } - - if (props.column.kind === COLUMNS.DATETIME) { - return 'dt'; - } - - if (props.column.kind === COLUMNS.NUMERICAL) { - return '#'; - } - - return ; -} - -type OptionsPropsT = { - columns: ColumnT<>[], - highlightIndex: number, - onClick: (ColumnT<>) => void, - onKeyDown: (KeyboardEvent) => void, - onMouseEnter: (number) => void, - onQueryChange: (string) => void, - query: string, - searchable: boolean, -}; - -function Options(props: OptionsPropsT) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const inputRef = React.useRef(null); - React.useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, [inputRef.current]); - - const [focusVisible, setFocusVisible] = React.useState(false); - const seed = useUIDSeed(); - const buiRef = React.useRef(props.columns.map((col) => seed(col))); - - const handleFocus = (event: SyntheticEvent<>) => { - if (isFocusVisible(event)) { - setFocusVisible(true); - } - }; - - const handleBlur = (event: SyntheticEvent<>) => { - if (focusVisible !== false) { - setFocusVisible(false); - } - }; - - return ( -
-

- {locale.datatable.optionsLabel} -

- - {props.searchable && ( -
- props.onQueryChange(event.target.value)} - placeholder={locale.datatable.optionsSearch} - size={INPUT_SIZE.compact} - clearable - /> -
- )} - - {!props.columns.length && ( -
- {locale.datatable.optionsEmpty} -
- )} - -
    - {props.columns.map((column, index) => { - const isHighlighted = index === props.highlightIndex; - - return ( - // handled on the wrapper element - // eslint-disable-next-line jsx-a11y/click-events-have-key-events -
  • props.onMouseEnter(index)} - onClick={() => props.onClick(column)} - key={column.title} - className={css({ - ...theme.typography.font100, - alignItems: 'center', - backgroundColor: isHighlighted ? theme.colors.menuFillHover : null, - cursor: 'pointer', - display: 'flex', - paddingTop: theme.sizing.scale100, - paddingRight: theme.sizing.scale600, - paddingBottom: theme.sizing.scale100, - paddingLeft: theme.sizing.scale600, - })} - > -
    - -
    - {column.title} -
  • - ); - })} -
-
- ); -} - -type PropsT = { - columns: ColumnT<>[], - // flowlint-next-line unclear-type:off - filters: Map, - // flowlint-next-line unclear-type:off - rows: any[], - onSetFilter: (columnTitle: string, filterParams: { description: string }) => void, -}; - -function FilterMenu(props: PropsT) { - const [, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const [isOpen, setIsOpen] = React.useState(false); - const [highlightIndex, setHighlightIndex] = React.useState(-1); - const [query, setQuery] = React.useState(''); - - const [activeColumn, setActiveColumn] = React.useState(null); - const handleOptionClick = React.useCallback(setActiveColumn, []); - const handleClose = React.useCallback(() => { - setIsOpen(false); - setActiveColumn(null); - setHighlightIndex(-1); - setQuery(''); - }, []); - - const filterableColumns = React.useMemo(() => { - return props.columns.filter((column) => { - return column.filterable && !props.filters.has(column.title); - }); - }, [props.columns, props.filters]); - - const columns = React.useMemo(() => { - return filterableColumns.filter((column) => matchesQuery(column.title, query)); - }, [filterableColumns, query]); - - const Filter = React.useMemo(() => { - if (!activeColumn) return null; - return activeColumn.renderFilter; - }, [activeColumn]); - - const activeColumnData = React.useMemo(() => { - const columnIndex = props.columns.findIndex((c) => c === activeColumn); - if (columnIndex < 0) return []; - return props.rows.map((row) => props.columns[columnIndex].mapDataToValue(row.data)); - }, [props.columns, props.rows, activeColumn]); - - function handleKeyDown(event) { - if (event.keyCode === 13) { - event.preventDefault(); - setActiveColumn(columns[highlightIndex]); - } - if (event.keyCode === 38) { - event.preventDefault(); - setHighlightIndex(Math.max(0, highlightIndex - 1)); - } - if (event.keyCode === 40) { - event.preventDefault(); - if (!isOpen) { - setIsOpen(true); - } else { - setHighlightIndex(Math.min(columns.length - 1, highlightIndex + 1)); - } - } - } - - return ( - { - if (Filter && activeColumn) { - return ( - props.onSetFilter(activeColumn.title, filterParams)} - /> - ); - } - return ( - = 10} - /> - ); - }} - onClick={() => { - if (isOpen) { - handleClose(); - } else { - setIsOpen(true); - } - }} - onClickOutside={handleClose} - onEsc={handleClose} - isOpen={isOpen} - ignoreBoundary - > - - - ); -} - -export default FilterMenu; diff --git a/src/data-table/filter-shell.js.flow b/src/data-table/filter-shell.js.flow deleted file mode 100644 index 5e347a19f4..0000000000 --- a/src/data-table/filter-shell.js.flow +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Button, SIZE as BUTTON_SIZE } from '../button/index.js'; -import { Checkbox, STYLE_TYPE } from '../checkbox/index.js'; -import { useStyletron } from '../styles/index.js'; -import { LocaleContext } from '../locale/index.js'; -import { FILTER_SHELL_WIDTH } from './constants.js'; - -export type ExcludeKind = 'value' | 'range'; - -type PropsT = { - children: React.Node, - exclude: boolean, - excludeKind?: ExcludeKind, - onExcludeChange: () => void, - onApply: () => void, -}; - -function FilterShell(props: PropsT) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - let excludeText; - switch (props.excludeKind) { - case 'value': - excludeText = locale.datatable.filterExcludeValue; - break; - case 'range': - excludeText = locale.datatable.filterExcludeRange; - break; - default: - excludeText = locale.datatable.filterExclude; - } - return ( -
{ - event.preventDefault(); - props.onApply(); - }} - > - {props.children} -
-
- - {excludeText} - -
- - -
-
- ); -} - -export default FilterShell; diff --git a/src/data-table/header-cell.js.flow b/src/data-table/header-cell.js.flow deleted file mode 100644 index 5ea0cd63d5..0000000000 --- a/src/data-table/header-cell.js.flow +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { Checkbox } from '../checkbox/index.js'; -import { useStyletron } from '../styles/index.js'; -import ChevronDown from '../icon/chevron-down.js'; -import ChevronUp from '../icon/chevron-up.js'; - -import { SORT_DIRECTIONS } from './constants.js'; -import type { SortDirectionsT } from './types.js'; -import { isFocusVisible } from '../utils/focusVisible.js'; - -type HeaderCellPropsT = {| - index: number, - isHovered: boolean, - isMeasured?: boolean, - isSelectable: boolean, - isSelectedAll: boolean, - isSelectedIndeterminate: boolean, - onMouseEnter: (number) => void, - onMouseLeave: (number) => void, - onSelectAll: () => void, - onSelectNone: () => void, - onSort: (number) => void, - sortable: boolean, - sortDirection: SortDirectionsT, - title: string, -|}; - -const HeaderCell = React.forwardRef((props, ref) => { - const [css, theme] = useStyletron(); - const [focusVisible, setFocusVisible] = React.useState(false); - const checkboxRef = React.useRef(null); - - const handleFocus = (event: SyntheticEvent<>) => { - if (isFocusVisible(event)) { - setFocusVisible(true); - } - }; - - const handleBlur = (event: SyntheticEvent<>) => { - if (focusVisible !== false) { - setFocusVisible(false); - } - }; - - const backgroundColor = props.isHovered - ? theme.colors.backgroundSecondary - : theme.colors.backgroundPrimary; - - return ( -
{ - if (event.key === 'Enter') { - props.onSort(props.index); - } - }} - onClick={(event) => { - // Avoid column sort if select-all checkbox click. - if (checkboxRef.current && checkboxRef.current.contains(event.target)) { - return; - } - if (props.sortable) { - props.onSort(props.index); - } - }} - onFocus={handleFocus} - onBlur={handleBlur} - > - {props.isSelectable && ( - - { - if (props.isSelectedAll || props.isSelectedIndeterminate) { - props.onSelectNone(); - } else { - props.onSelectAll(); - } - }} - checked={props.isSelectedAll || props.isSelectedIndeterminate} - isIndeterminate={props.isSelectedIndeterminate} - /> - - )} - {props.title} -
- {(props.isHovered || props.sortDirection) && props.sortable && ( -
- {props.sortDirection === SORT_DIRECTIONS.DESC && ( - - )} - {(props.sortDirection === SORT_DIRECTIONS.ASC || !props.sortDirection) && ( - - )} -
- )} -
-
- ); -}); -HeaderCell.displayName = 'HeaderCell'; - -export default HeaderCell; diff --git a/src/data-table/index.js.flow b/src/data-table/index.js.flow deleted file mode 100644 index 6472780252..0000000000 --- a/src/data-table/index.js.flow +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export { DataTable } from './data-table.js'; -export { StatefulContainer } from './stateful-container.js'; -export { StatefulDataTable } from './stateful-data-table.js'; -export { DataTable as Unstable_DataTable } from './data-table.js'; -export { StatefulContainer as Unstable_StatefulContainer } from './stateful-container.js'; -export { StatefulDataTable as Unstable_StatefulDataTable } from './stateful-data-table.js'; -export { default as AnchorColumn } from './column-anchor.js'; -export { default as BooleanColumn } from './column-boolean.js'; -export { default as CategoricalColumn } from './column-categorical.js'; -export { default as CustomColumn } from './column-custom.js'; -export { default as DatetimeColumn } from './column-datetime.js'; -export { default as NumericalColumn } from './column-numerical.js'; -export { default as RowIndexColumn } from './column-row-index.js'; -export { default as StringColumn } from './column-string.js'; - -export { COLUMNS, DATETIME_OPERATIONS, NUMERICAL_FORMATS, SORT_DIRECTIONS } from './constants.js'; - -export type * from './types.js'; diff --git a/src/data-table/locale.js.flow b/src/data-table/locale.js.flow deleted file mode 100644 index dbb31b414c..0000000000 --- a/src/data-table/locale.js.flow +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export type DataTableLocaleT = {| - emptyState: string, - loadingState: string, - searchAriaLabel: string, - filterAdd: string, - filterExclude: string, - filterExcludeRange: string, - filterExcludeValue: string, - filterApply: string, - filterAppliedTo: string, - optionsLabel: string, - optionsSearch: string, - optionsEmpty: string, - categoricalFilterSearchLabel: string, - categoricalFilterSelectAll: string, - categoricalFilterSelectClear: string, - categoricalFilterEmpty: string, - datetimeFilterRange: string, - datetimeFilterRangeDatetime: string, - datetimeFilterRangeDate: string, - datetimeFilterRangeTime: string, - datetimeFilterCategorical: string, - datetimeFilterCategoricalWeekday: string, - datetimeFilterCategoricalMonth: string, - datetimeFilterCategoricalQuarter: string, - datetimeFilterCategoricalHalf: string, - datetimeFilterCategoricalFirstHalf: string, - datetimeFilterCategoricalSecondHalf: string, - datetimeFilterCategoricalYear: string, - numericalFilterRange: string, - numericalFilterSingleValue: string, - booleanFilterTrue: string, - booleanFilterFalse: string, - booleanColumnTrueShort: string, - booleanColumnFalseShort: string, - selectRow: string, - selectAllRows: string, - sortColumn: string, -|}; - -const locale = { - emptyState: - 'No rows match the filter criteria defined. Please remove one or more filters to view more data.', - loadingState: 'Loading rows.', - searchAriaLabel: 'Search by text', - filterAdd: 'Add Filter', - filterExclude: 'Exclude', - filterApply: 'Apply', - filterExcludeRange: 'Exclude range', - filterExcludeValue: 'Exclude value', - filterAppliedTo: 'filter applied to', - optionsLabel: 'Select column to filter by', - optionsSearch: 'Search for a column to filter by...', - optionsEmpty: 'No columns available.', - categoricalFilterSearchLabel: 'Search categories', - categoricalFilterSelectAll: 'Select All', - categoricalFilterSelectClear: 'Clear', - categoricalFilterEmpty: 'No categories found', - datetimeFilterRange: 'Range', - datetimeFilterRangeDatetime: 'Date, Time', - datetimeFilterRangeDate: 'Date', - datetimeFilterRangeTime: 'Time', - datetimeFilterCategorical: 'Categorical', - datetimeFilterCategoricalWeekday: 'Weekday', - datetimeFilterCategoricalMonth: 'Month', - datetimeFilterCategoricalQuarter: 'Quarter', - datetimeFilterCategoricalHalf: 'Half', - datetimeFilterCategoricalFirstHalf: 'H1', - datetimeFilterCategoricalSecondHalf: 'H2', - datetimeFilterCategoricalYear: 'Year', - numericalFilterRange: 'Range', - numericalFilterSingleValue: 'Single Value', - booleanFilterTrue: 'true', - booleanFilterFalse: 'false', - booleanColumnTrueShort: 'T', - booleanColumnFalseShort: 'F', - selectRow: 'Select row', - selectAllRows: 'Select all rows', - sortColumn: 'Sort column', -}; - -export default locale; diff --git a/src/data-table/measure-column-widths.js.flow b/src/data-table/measure-column-widths.js.flow deleted file mode 100644 index 9dfe059b69..0000000000 --- a/src/data-table/measure-column-widths.js.flow +++ /dev/null @@ -1,183 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { useStyletron } from '../styles/index.js'; - -import HeaderCell from './header-cell.js'; -import type { ColumnT, RowT } from './types.js'; -import { useRef } from 'react'; - -// Measures the column header + sampled data -function MeasureColumn({ sampleIndexes, column, columnIndex, rows, isSelectable, onLayout }) { - const [css] = useStyletron(); - - const ref = useRef(); - - React.useEffect(() => { - if (__BROWSER__) { - if (ref.current) { - onLayout(columnIndex, ref.current.getBoundingClientRect()); - } - } - }, []); - - return ( -
- {}} - onMouseLeave={() => {}} - onSelectAll={() => {}} - onSelectNone={() => {}} - onSort={(i) => {}} - sortable={column.sortable} - sortDirection={null} - title={column.title} - isSelectable={isSelectable} - /> - {sampleIndexes.map((rowIndex, i) => { - const Cell = column.renderCell; - return ( - - ); - })} -
- ); -} -type MeasureColumnWidthsPropsT = { - columns: ColumnT<>[], - // if selectable, measure the first column with checkbox included - isSelectable: boolean, - onWidthsChange: (number[]) => void, - rows: RowT[], - widths: number[], -}; - -const MAX_SAMPLE_SIZE = 50; - -function generateSampleIndices(inputMin, inputMax, maxSamples) { - const indices = []; - const queue = [[inputMin, inputMax]]; - - while (queue.length > 0) { - const [min, max] = queue.shift(); - if (indices.length < maxSamples) { - const pivot = Math.floor((min + max) / 2); - indices.push(pivot); - const left = pivot - 1; - const right = pivot + 1; - if (left >= min) { - queue.push([min, left]); - } - if (right <= max) { - queue.push([right, max]); - } - } - } - - return indices; -} - -export default function MeasureColumnWidths({ - columns, - rows, - widths, - isSelectable, - onWidthsChange, -}: MeasureColumnWidthsPropsT) { - const [css] = useStyletron(); - - const widthMap = React.useMemo(() => { - return new Map(); - }, []); - - const sampleSize = rows.length < MAX_SAMPLE_SIZE ? rows.length : MAX_SAMPLE_SIZE; - const finishedMeasurementCount = (sampleSize + 1) * columns.length; - - const sampleIndexes = React.useMemo(() => { - return generateSampleIndices(0, rows.length - 1, sampleSize); - }, [columns, rows, widths, sampleSize]); - - const handleDimensionsChange = React.useCallback( - (columnIndex, dimensions) => { - const nextWidth = Math.min( - Math.max( - columns[columnIndex].minWidth || 0, - widthMap.get(columnIndex) || 0, - dimensions.width + 1 - ), - columns[columnIndex].maxWidth || Infinity - ); - - if (nextWidth !== widthMap.get(columnIndex)) { - widthMap.set(columnIndex, nextWidth); - } - if ( - // Refresh at 100% of done - widthMap.size === columns.length || - // ...50% - widthMap.size === Math.floor(columns.length / 2) || - // ...25% - widthMap.size === Math.floor(columns.length / 4) - ) { - onWidthsChange(Array.from(widthMap.values())); - } - }, - [columns, finishedMeasurementCount, onWidthsChange] - ); - - const hiddenStyle = css({ - position: 'absolute', - overflow: 'hidden', - height: 0, - }); - - // Remove the measurement nodes after we are done updating our column width - if (widthMap.size === columns.length) { - return null; - } - - return ( - - ); -} diff --git a/src/data-table/stateful-container.js.flow b/src/data-table/stateful-container.js.flow deleted file mode 100644 index 191a13d685..0000000000 --- a/src/data-table/stateful-container.js.flow +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { SORT_DIRECTIONS } from './constants.js'; -import type { ColumnT, StatefulContainerPropsT } from './types.js'; - -function useDuplicateColumnTitleWarning(columns: ColumnT<>[]) { - React.useEffect(() => { - if (__DEV__) { - const titles = columns.reduce((set, column) => set.add(column.title), new Set()); - if (titles.size < columns.length) { - console.warn( - 'BaseWeb DataTable: Column titles must be unique else will result in non-deterministic filtering.' - ); - } - } - }, [columns]); -} - -function useSortParameters(initialSortIndex = -1, initialSortDirection = null) { - const [sortIndex, setSortIndex] = React.useState(initialSortIndex); - const [sortDirection, setSortDirection] = React.useState(initialSortDirection); - - function handleSort(columnIndex) { - if (columnIndex === sortIndex) { - if (sortDirection === SORT_DIRECTIONS.DESC) { - setSortIndex(-1); - setSortDirection(SORT_DIRECTIONS.ASC); - } else { - setSortDirection(SORT_DIRECTIONS.DESC); - } - } else { - setSortIndex(columnIndex); - setSortDirection(SORT_DIRECTIONS.ASC); - } - } - - return [sortIndex, sortDirection, handleSort]; -} - -export function StatefulContainer(props: StatefulContainerPropsT) { - useDuplicateColumnTitleWarning(props.columns); - const [sortIndex, sortDirection, handleSort] = useSortParameters( - props.initialSortIndex, - props.initialSortDirection - ); - const [filters, setFilters] = React.useState(props.initialFilters || new Map()); - const [textQuery, setTextQuery] = React.useState(''); - - function handleFilterAdd(title, filterParams) { - filters.set(title, filterParams); - if (props.onFilterAdd) { - props.onFilterAdd(title, filterParams); - } - setFilters(new Map(filters)); - } - function handleFilterRemove(title) { - filters.delete(title); - if (props.onFilterRemove) { - props.onFilterRemove(title); - } - setFilters(new Map(filters)); - } - - const [selectedRowIds, setSelectedRowIds] = React.useState( - props.initialSelectedRowIds || new Set() - ); - function handleSelectChange(next) { - setSelectedRowIds(next); - - const selectionCallback = props.onSelectionChange; - if (selectionCallback) { - selectionCallback(props.rows.filter((r) => next.has(r.id))); - } - } - function handleSelectMany(incomingRows) { - // only adds rows that are visible in the table - handleSelectChange(new Set([...selectedRowIds, ...incomingRows.map((r) => r.id)])); - } - function handleSelectNone() { - handleSelectChange(new Set()); - } - function handleSelectOne(row) { - if (selectedRowIds.has(row.id)) { - selectedRowIds.delete(row.id); - } else { - selectedRowIds.add(row.id); - } - handleSelectChange(new Set(selectedRowIds)); - } - - const handleIncludedRowsChange = React.useCallback( - (rows) => { - if (props.onIncludedRowsChange) { - props.onIncludedRowsChange(rows); - } - }, - [props.onIncludedRowsChange] - ); - - const handleRowHighlightChange = React.useCallback( - (rowIndex, row) => { - if (props.onRowHighlightChange) { - props.onRowHighlightChange(rowIndex, row); - } - }, - [props.rowHighlightIndex] - ); - - return props.children({ - filters, - onFilterAdd: handleFilterAdd, - onFilterRemove: handleFilterRemove, - onIncludedRowsChange: handleIncludedRowsChange, - onRowHighlightChange: handleRowHighlightChange, - onSelectMany: handleSelectMany, - onSelectNone: handleSelectNone, - onSelectOne: handleSelectOne, - onSort: handleSort, - onTextQueryChange: setTextQuery, - resizableColumnWidths: Boolean(props.resizableColumnWidths), - rowHighlightIndex: props.rowHighlightIndex, - selectedRowIds, - sortIndex, - sortDirection, - textQuery, - }); -} diff --git a/src/data-table/stateful-data-table.js.flow b/src/data-table/stateful-data-table.js.flow deleted file mode 100644 index 87fb685bc5..0000000000 --- a/src/data-table/stateful-data-table.js.flow +++ /dev/null @@ -1,316 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; -import ResizeObserver from 'resize-observer-polyfill'; - -import { - Button, - SHAPE as BUTTON_SHAPES, - SIZE as BUTTON_SIZES, - KIND as BUTTON_KINDS, -} from '../button/index.js'; -import Search from '../icon/search.js'; -import { Input, SIZE as INPUT_SIZES } from '../input/index.js'; -import { Popover } from '../popover/index.js'; -import { useStyletron } from '../styles/index.js'; -import { Tag } from '../tag/index.js'; -import FilterMenu from './filter-menu.js'; -import { DataTable } from './data-table.js'; -import { StatefulContainer } from './stateful-container.js'; -import type { StatefulDataTablePropsT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; - -function useResizeObserver( - ref: { current: HTMLElement | null }, - callback: (ResizeObserverEntry[], ResizeObserver) => mixed -) { - React.useLayoutEffect(() => { - if (__BROWSER__) { - if (ref.current) { - //$FlowFixMe - const observer = new ResizeObserver(callback); - observer.observe(ref.current); - return () => observer.disconnect(); - } - } - }, [ref]); -} - -function QueryInput(props) { - const [css, theme] = useStyletron(); - const locale = React.useContext(LocaleContext); - const [value, setValue] = React.useState(''); - - React.useEffect(() => { - const timeout = setTimeout(() => props.onChange(value), 250); - return () => clearTimeout(timeout); - }, [value]); - - return ( -
- - -
- ); - }, - }} - size={INPUT_SIZES.compact} - onChange={(event) => setValue(event.target.value)} - value={value} - clearable - /> -
- ); -} - -function FilterTag(props) { - const [, theme] = useStyletron(); - const [isOpen, setIsOpen] = React.useState(false); - const columnIndex = props.columns.findIndex((c) => c.title === props.title); - const column = props.columns[columnIndex]; - if (!column) { - return null; - } - - const data = props.rows.map((r) => column.mapDataToValue(r.data)); - const Filter = column.renderFilter; - - return ( - setIsOpen(false)} - content={() => ( - setIsOpen(false)} - data={data} - filterParams={props.filter} - setFilter={(filterParams) => props.onFilterAdd(props.title, filterParams)} - /> - )} - > -
- setIsOpen(!isOpen)} - onActionClick={() => props.onFilterRemove(props.title)} - overrides={{ - Root: { - style: { - borderTopLeftRadius: '36px', - borderTopRightRadius: '36px', - borderBottomLeftRadius: '36px', - borderBottomRightRadius: '36px', - height: '36px', - marginTop: null, - marginBottom: theme.sizing.scale500, - }, - }, - Action: { - style: { - borderTopRightRadius: '36px', - borderBottomRightRadius: '36px', - height: '22px', - }, - }, - Text: { - style: { - maxWidth: '160px', - }, - }, - }} - > - {props.title}: {props.filter.description} - -
-
- ); -} - -export function StatefulDataTable(props: StatefulDataTablePropsT) { - const [css, theme] = useStyletron(); - const headlineRef = React.useRef(null); - const [headlineHeight, setHeadlineHeight] = React.useState(64); - useResizeObserver(headlineRef, (entries) => { - setHeadlineHeight(entries[0].contentRect.height); - }); - - const filterable = props.filterable === undefined ? true : props.filterable; - const searchable = props.searchable === undefined ? true : props.searchable; - - return ( - - {({ - filters, - onFilterAdd, - onFilterRemove, - onIncludedRowsChange, - onRowHighlightChange, - onSelectMany, - onSelectNone, - onSelectOne, - onSort, - onTextQueryChange, - resizableColumnWidths, - rowHighlightIndex, - selectedRowIds, - sortIndex, - sortDirection, - textQuery, - }) => ( - -
-
- {!selectedRowIds.size && ( -
- {searchable && } - - {filterable && ( - - - - {Array.from(filters).map(([title, filter]) => ( - - ))} - - )} -
- )} - - {Boolean(selectedRowIds.size) && props.batchActions && ( -
- {props.batchActions.map((action) => { - function onClick(event) { - action.onClick({ - clearSelection: onSelectNone, - event, - selection: props.rows.filter((r) => selectedRowIds.has(r.id)), - }); - } - - if (action.renderIcon) { - const Icon = action.renderIcon; - return ( - - ); - } - - return ( - - ); - })} -
- )} -
-
- -
- -
-
- )} -
- ); -} diff --git a/src/data-table/text-search.js.flow b/src/data-table/text-search.js.flow deleted file mode 100644 index 9e5bfbe4da..0000000000 --- a/src/data-table/text-search.js.flow +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import React from 'react'; - -import { useStyletron } from '../styles/index.js'; - -export function matchesQuery(text: string, query: string): boolean { - return text.toLowerCase().includes(query.toLowerCase()); -} - -export function splitByQuery(text: string, query: string): string[] { - const start = text.toLowerCase().indexOf(query.toLowerCase()); - - // query not found - if (start === -1) { - return [text]; - } - - if (start === 0) { - return [text.slice(0, query.length), text.slice(query.length)]; - } - - const substrings = []; - let substring = ''; - for (let i = 0; i < text.length; i++) { - substring = substring + text[i]; - if ( - // prefix - i === start - 1 || - // query - i === start + query.length - 1 || - // suffix - i === text.length - 1 - ) { - substrings.push(substring); - substring = ''; - } - } - return substrings; -} - -export function HighlightCellText(props: { text: string, query: string }) { - const [css, theme] = useStyletron(); - - if (!props.query) { - return props.text; - } - - return ( - - {splitByQuery(props.text, props.query).map((el, i) => { - if (matchesQuery(el, props.query)) { - return ( - - {el} - - ); - } - - return el; - })} - - ); -} diff --git a/src/data-table/types.js.flow b/src/data-table/types.js.flow deleted file mode 100644 index 985e0bfb97..0000000000 --- a/src/data-table/types.js.flow +++ /dev/null @@ -1,164 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import { COLUMNS, SORT_DIRECTIONS } from './constants.js'; - -export type SortDirectionsT = typeof SORT_DIRECTIONS.ASC | typeof SORT_DIRECTIONS.DESC | null; - -export type ColumnsT = - | typeof COLUMNS.ANCHOR - | typeof COLUMNS.BOOLEAN - | typeof COLUMNS.CATEGORICAL - | typeof COLUMNS.CUSTOM - | typeof COLUMNS.DATETIME - | typeof COLUMNS.NUMERICAL - | typeof COLUMNS.STRING; - -// These options are available on all column kinds. Most have additional -// unique options depending on the data visualization requirements. -export type SharedColumnOptionsT = {| - cellBlockAlign?: 'start' | 'center' | 'end', - fillWidth?: boolean, - filterable?: boolean, - // flowlint-next-line unclear-type:off - mapDataToValue: (data: any) => ValueT, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, -|}; - -export type RenderCellT = React.AbstractComponent<{ - value: ValueT, - isMeasured?: boolean, - isSelected?: boolean, - onSelect?: () => void, - textQuery?: string, - x: number, - y: number, -}>; - -export type RenderFilterT = React.AbstractComponent<{| - close: () => void, - data: ValueT[], - filterParams?: FilterParamsT, - setFilter: (FilterParamsT) => void, -|}>; - -// flowlint-next-line unclear-type:off -export type ColumnT = {| - ...SharedColumnOptionsT, - kind: ColumnsT, - sortable: boolean, - renderCell: RenderCellT, - renderFilter: RenderFilterT, - buildFilter: (FilterParamsT) => (ValueT) => boolean, - textQueryFilter?: (string, ValueT) => boolean, - sortFn: (ValueT, ValueT) => number, -|}; - -export type RowT = { - id: number | string, - // flowlint-next-line unclear-type:off - data: any, -}; - -export type BatchActionT = {| - label: string, - onClick: ({ - clearSelection: () => mixed, - event: SyntheticEvent, - selection: RowT[], - }) => mixed, - renderIcon?: React.AbstractComponent<{| size: number |}>, -|}; - -export type RowActionT = {| - label: string, - onClick: ({ event: SyntheticEvent, row: RowT }) => mixed, - renderIcon: React.AbstractComponent<{| size: number |}>, - renderButton?: React.AbstractComponent<{||}>, -|}; - -type ImperativeMethodsT = {| - getRows: () => RowT[], - clearSelection: () => mixed, -|}; -export type ControlRefT = { - current: ImperativeMethodsT | null, -}; - -export type StatefulDataTablePropsT = {| - batchActions?: BatchActionT[], - columns: ColumnT<>[], - emptyMessage?: string | React.AbstractComponent<{||}>, - filterable?: boolean, - initialFilters?: Map, - initialSelectedRowIds?: Set, - initialSortIndex?: number, - initialSortDirection?: SortDirectionsT, - loading?: boolean, - loadingMessage?: string | React.AbstractComponent<{||}>, - onFilterAdd?: (string, { description: string }) => mixed, - onFilterRemove?: (string) => mixed, - onIncludedRowsChange?: (rows: RowT[]) => void, - onRowHighlightChange?: (rowIndex: number, row: RowT) => void, - onSelectionChange?: (RowT[]) => mixed, - onSort?: (columnIndex: number, sortDirection: SortDirectionsT) => void, - resizableColumnWidths?: boolean, - rows: RowT[], - rowActions?: RowActionT[] | ((RowT) => RowActionT[]), - rowHeight?: number, - rowHighlightIndex?: number, - searchable?: boolean, - controlRef?: ControlRefT, -|}; - -export type DataTablePropsT = {| - ...StatefulDataTablePropsT, - emptyMessage?: string | React.AbstractComponent<{||}>, - filters?: Map, - loading?: boolean, - loadingMessage?: string | React.AbstractComponent<{||}>, - onIncludedRowsChange?: (rows: RowT[]) => void, - onRowHighlightChange?: (rowIndex: number, row: RowT) => void, - onSelectMany?: (rows: RowT[]) => void, - onSelectNone?: () => void, - onSelectOne?: (row: RowT) => void, - onSort?: (columnIndex: number) => void, - resizableColumnWidths?: boolean, - rowHighlightIndex?: number, - selectedRowIds?: Set, - sortIndex?: number, - sortDirection?: SortDirectionsT, - textQuery?: string, -|}; - -export type StatefulContainerPropsT = {| - ...StatefulDataTablePropsT, - children: ({| - filters: Map, - onFilterAdd: (title: string, filterParams: { description: string }) => void, - onFilterRemove: (title: string) => void, - onIncludedRowsChange: (rows: RowT[]) => void, - onRowHighlightChange: (rowIndex: number, row: RowT) => void, - onSelectMany: (rows: RowT[]) => void, - onSelectNone: () => void, - onSelectOne: (row: RowT) => void, - onSort: (columnIndex: number) => void, - onTextQueryChange: (query: string) => void, - resizableColumnWidths: boolean, - rowHighlightIndex?: number, - selectedRowIds: Set, - sortIndex: number, - sortDirection: SortDirectionsT, - textQuery: string, - |}) => React.Node, -|}; diff --git a/src/datepicker/calendar-header.js.flow b/src/datepicker/calendar-header.js.flow deleted file mode 100644 index 6eed0c61a5..0000000000 --- a/src/datepicker/calendar-header.js.flow +++ /dev/null @@ -1,602 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import ChevronRight from '../icon/chevron-right.js'; -import ChevronLeft from '../icon/chevron-left.js'; -import ChevronDown from '../icon/chevron-down.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import DateHelpers from './utils/date-helpers.js'; -import { getFilteredMonthItems } from './utils/calendar-header-helpers.js'; -import { StatefulMenu } from '../menu/index.js'; -import { Popover } from '../popover/index.js'; -import { LocaleContext } from '../locale/index.js'; -import { ThemeContext } from '../styles/theme-provider.js'; -import { - StyledCalendarHeader, - StyledMonthHeader, - StyledMonthYearSelectButton, - StyledMonthYearSelectIconContainer, - StyledNextButton, - StyledPrevButton, - StyledWeekdayHeader, -} from './styled-components.js'; -import { DENSITY, ORIENTATION, WEEKDAYS } from './constants.js'; -import { getOverrides, mergeOverrides } from '../helpers/overrides.js'; -import type { HeaderPropsT } from './types.js'; -import type { LocaleT } from '../locale/types.js'; -import type { ThemeT } from '../styles/types.js'; -import { forkBlur, forkFocus, isFocusVisible } from '../utils/focusVisible.js'; - -const navBtnStyle = ({ $theme }) => ({ - cursor: 'pointer', -}); - -const MIN_YEAR = 2000; -const MAX_YEAR = 2030; -const MIN_MONTH = 0; -const MAX_MONTH = 11; - -const DIRECTION = { - NEXT: 'next', - PREVIOUS: 'previous', -}; - -function idToYearMonth(id) { - return id.split('-').map(Number); -} - -export default class CalendarHeader extends React.Component< - HeaderPropsT, - { - isMonthDropdownOpen: boolean, - isYearDropdownOpen: boolean, - isFocusVisible: boolean, - } -> { - static defaultProps = { - adapter: dateFnsAdapter, - locale: null, - maxDate: null, - minDate: null, - onYearChange: () => {}, - overrides: {}, - }; - - dateHelpers: DateHelpers; - monthItems: Array<{ id: string, label: string, disabled?: boolean }>; - yearItems: Array<{ id: string, label: string, disabled?: boolean }>; - - constructor(props: HeaderPropsT) { - super(props); - //$FlowFixMe - this.dateHelpers = new DateHelpers(props.adapter); - this.monthItems = []; - this.yearItems = []; - } - - state = { - isMonthDropdownOpen: false, - isYearDropdownOpen: false, - isFocusVisible: false, - }; - - componentDidMount() { - this.getYearItems(); - this.getMonthItems(); - } - - componentDidUpdate(prevProps: HeaderPropsT) { - const selectedMonthDidChange = - this.dateHelpers.getMonth(this.props.date) !== this.dateHelpers.getMonth(prevProps.date); - - const selectedYearDidChange = - this.dateHelpers.getYear(this.props.date) !== this.dateHelpers.getYear(prevProps.date); - - if (selectedMonthDidChange) { - // re-calculate yearItems - this.getYearItems(); - } - - if (selectedYearDidChange) { - // re-calculate monthItems - this.getMonthItems(); - } - } - - getDateProp: () => T = () => { - return this.props.date || this.dateHelpers.date(); - }; - - getYearItems = () => { - const date = this.getDateProp(); - const maxDate = this.props.maxDate; - const minDate = this.props.minDate; - const maxYear = maxDate ? this.dateHelpers.getYear(maxDate) : MAX_YEAR; - const minYear = minDate ? this.dateHelpers.getYear(minDate) : MIN_YEAR; - const selectedMonth = this.dateHelpers.getMonth(date); - - // TODO: this logic can be optimized to only run when minDate / maxDate change - this.yearItems = Array.from({ length: maxYear - minYear + 1 }, (_, i) => minYear + i).map( - (year) => ({ id: year.toString(), label: year.toString() }) - ); - const monthOfMaxDate = maxDate ? this.dateHelpers.getMonth(maxDate) : MAX_MONTH; - const monthOfMinDate = minDate ? this.dateHelpers.getMonth(minDate) : MIN_MONTH; - // Generates array like [0,1,.... monthOfMaxDate] - const maxYearMonths = Array.from({ length: monthOfMaxDate + 1 }, (x, i) => i); - // Generates array like [monthOfMinDate, ...., 10, 11] - const minYearMonths = Array.from({ length: 12 - monthOfMinDate }, (x, i) => i + monthOfMinDate); - - if (selectedMonth > maxYearMonths[maxYearMonths.length - 1]) { - const lastIdx = this.yearItems.length - 1; - this.yearItems[lastIdx] = { ...this.yearItems[lastIdx], disabled: true }; - } - - if (selectedMonth < minYearMonths[0]) { - this.yearItems[0] = { ...this.yearItems[0], disabled: true }; - } - }; - - getMonthItems = () => { - const date = this.getDateProp(); - const year = this.dateHelpers.getYear(date); - const maxDate = this.props.maxDate; - const minDate = this.props.minDate; - const maxYear = maxDate ? this.dateHelpers.getYear(maxDate) : MAX_YEAR; - const minYear = minDate ? this.dateHelpers.getYear(minDate) : MIN_YEAR; - - const monthOfMaxDate = maxDate ? this.dateHelpers.getMonth(maxDate) : MAX_MONTH; - // Generates array like [0,1,.... monthOfMaxDate] - const maxYearMonths = Array.from({ length: monthOfMaxDate + 1 }, (x, i) => i); - - const monthOfMinDate = minDate ? this.dateHelpers.getMonth(minDate) : MIN_MONTH; - - // Generates array like [monthOfMinDate, ...., 10, 11] - const minYearMonths = Array.from({ length: 12 - monthOfMinDate }, (x, i) => i + monthOfMinDate); - - const maxMinYearMonthsIntersection = maxYearMonths.filter((year) => - minYearMonths.includes(year) - ); - - const filterMonthsList = - year === maxYear && year === minYear - ? maxMinYearMonthsIntersection - : year === maxYear - ? maxYearMonths - : year === minYear - ? minYearMonths - : null; - - const formatMonthLabel = (month) => this.dateHelpers.getMonthInLocale(month, this.props.locale); - - this.monthItems = getFilteredMonthItems({ - filterMonthsList, - formatMonthLabel, - }); - }; - - increaseMonth = () => { - if (this.props.onMonthChange) { - // $FlowFixMe - this.props.onMonthChange({ - date: this.dateHelpers.addMonths( - this.getDateProp(), - // in a multi-month context, `order` is the number months ahead of - // the root Calendar month that this CalendarHeader displays. We account - // for this by incrementing the month by 1, less the value of `order`. - 1 - this.props.order - ), - }); - } - }; - - decreaseMonth = () => { - if (this.props.onMonthChange) { - // $FlowFixMe - this.props.onMonthChange({ - date: this.dateHelpers.subMonths(this.getDateProp(), 1), - }); - } - }; - - isMultiMonthHorizontal = () => { - const { monthsShown, orientation } = this.props; - - if (!monthsShown) { - return false; - } - - return orientation === ORIENTATION.horizontal && monthsShown > 1; - }; - - isHiddenPaginationButton = (direction: $Values) => { - const { monthsShown, order } = this.props; - - if (!!monthsShown && this.isMultiMonthHorizontal()) { - if (direction === DIRECTION.NEXT) { - const isLastMonth = order === monthsShown - 1; - return !isLastMonth; - } else { - const isFirstMonth = order === 0; - return !isFirstMonth; - } - } - - return false; - }; - - handleFocus = (event: SyntheticEvent<>) => { - if (isFocusVisible(event)) { - this.setState({ isFocusVisible: true }); - } - }; - - handleBlur = (event: SyntheticEvent<>) => { - if (this.state.isFocusVisible !== false) { - this.setState({ isFocusVisible: false }); - } - }; - - renderPreviousMonthButton = ({ locale, theme }: { locale: LocaleT, theme: ThemeT }) => { - const date = this.getDateProp(); - const { overrides = {}, density } = this.props; - const allPrevDaysDisabled = this.dateHelpers.monthDisabledBefore(date, this.props); - - let isDisabled = false; - if (allPrevDaysDisabled) { - isDisabled = true; - } - const nextMonth = this.dateHelpers.subMonths(date, 1); - const minYear = this.props.minDate ? this.dateHelpers.getYear(this.props.minDate) : MIN_YEAR; - if (this.dateHelpers.getYear(nextMonth) < minYear) { - isDisabled = true; - } - - const isHidden = this.isHiddenPaginationButton(DIRECTION.PREVIOUS); - if (isHidden) { - isDisabled = true; - } - - const [PrevButton, prevButtonProps] = getOverrides(overrides.PrevButton, StyledPrevButton); - const [PrevButtonIcon, prevButtonIconProps] = getOverrides( - overrides.PrevButtonIcon, - theme.direction === 'rtl' ? ChevronRight : ChevronLeft - ); - let clickHandler = this.decreaseMonth; - if (allPrevDaysDisabled) { - clickHandler = null; - } - return ( - - {isHidden ? null : ( - - )} - - ); - }; - - renderNextMonthButton = ({ locale, theme }: { locale: LocaleT, theme: ThemeT }) => { - const date = this.getDateProp(); - const { overrides = {}, density } = this.props; - const allNextDaysDisabled = this.dateHelpers.monthDisabledAfter(date, this.props); - - let isDisabled = false; - if (allNextDaysDisabled) { - isDisabled = true; - } - const nextMonth = this.dateHelpers.addMonths(date, 1); - const maxYear = this.props.maxDate ? this.dateHelpers.getYear(this.props.maxDate) : MAX_YEAR; - if (this.dateHelpers.getYear(nextMonth) > maxYear) { - isDisabled = true; - } - - const isHidden = this.isHiddenPaginationButton(DIRECTION.NEXT); - if (isHidden) { - isDisabled = true; - } - - const [NextButton, nextButtonProps] = getOverrides(overrides.NextButton, StyledNextButton); - const [NextButtonIcon, nextButtonIconProps] = getOverrides( - overrides.NextButtonIcon, - theme.direction === 'rtl' ? ChevronLeft : ChevronRight - ); - - let clickHandler = this.increaseMonth; - // The other option is to always provide a click handler and let customers - // override its functionality based on the `$allPrevDaysDisabled` prop - // in a custom NextButton component override - // Their options would be to render `null` or not apply the components handler - // on click or do nothing - if (allNextDaysDisabled) { - clickHandler = null; - } - - return ( - - {isHidden ? null : ( - - )} - - ); - }; - - canArrowsOpenDropdown = (event: KeyboardEvent) => { - if (!this.state.isMonthDropdownOpen && !this.state.isYearDropdownOpen) { - if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { - return true; - } - } - return false; - }; - - renderMonthYearDropdown = () => { - const date = this.getDateProp(); - const month = this.dateHelpers.getMonth(date); - const year = this.dateHelpers.getYear(date); - - const { locale, overrides = {}, density } = this.props; - const [MonthYearSelectButton, monthYearSelectButtonProps] = getOverrides( - overrides.MonthYearSelectButton, - StyledMonthYearSelectButton - ); - const [MonthYearSelectIconContainer, monthYearSelectIconContainerProps] = getOverrides( - overrides.MonthYearSelectIconContainer, - StyledMonthYearSelectIconContainer - ); - const [OverriddenPopover, popoverProps] = getOverrides( - overrides.MonthYearSelectPopover, - Popover - ); - const [OverriddenStatefulMenu, menuProps] = getOverrides( - overrides.MonthYearSelectStatefulMenu, - StatefulMenu - ); - menuProps.overrides = mergeOverrides( - { List: { style: { height: 'auto', maxHeight: '257px' } } }, - menuProps && menuProps.overrides - ); - - const initialMonthIndex = this.monthItems.findIndex( - (month) => month.id === this.dateHelpers.getMonth(date).toString() - ); - const initialYearIndex = this.yearItems.findIndex( - (year) => year.id === this.dateHelpers.getYear(date).toString() - ); - - const monthTitle = `${this.dateHelpers.getMonthInLocale( - this.dateHelpers.getMonth(date), - locale - )}`; - const yearTitle = `${this.dateHelpers.getYear(date)}`; - - return this.isMultiMonthHorizontal() ? ( -
{`${monthTitle} ${yearTitle}`}
- ) : ( - <> - {/* Month Selection */} - { - this.setState((prev) => ({ - isMonthDropdownOpen: !prev.isMonthDropdownOpen, - })); - }} - onClickOutside={() => this.setState({ isMonthDropdownOpen: false })} - onEsc={() => this.setState({ isMonthDropdownOpen: false })} - content={() => ( - { - event.preventDefault(); - const month = idToYearMonth(item.id); - const updatedDate = this.dateHelpers.set(date, { - year, - month, - }); - this.props.onMonthChange && this.props.onMonthChange({ date: updatedDate }); - this.setState({ isMonthDropdownOpen: false }); - }} - {...menuProps} - /> - )} - {...popoverProps} - > - { - if (this.canArrowsOpenDropdown(event)) { - this.setState({ isMonthDropdownOpen: true }); - } - }} - onKeyDown={(event) => { - if (this.canArrowsOpenDropdown(event)) { - // disables page scroll - event.preventDefault(); - } - - if (event.key === 'Tab') { - this.setState({ isMonthDropdownOpen: false }); - } - }} - aria-label={`Month, ${monthTitle}`} - {...monthYearSelectButtonProps} - > - {monthTitle} - - - - - - - {/* Year Selection */} - { - this.setState((prev) => ({ - isYearDropdownOpen: !prev.isYearDropdownOpen, - })); - }} - onClickOutside={() => this.setState({ isYearDropdownOpen: false })} - onEsc={() => this.setState({ isYearDropdownOpen: false })} - content={() => ( - { - event.preventDefault(); - const year = idToYearMonth(item.id); - const updatedDate = this.dateHelpers.set(date, { - year, - month, - }); - this.props.onYearChange && this.props.onYearChange({ date: updatedDate }); - this.setState({ isYearDropdownOpen: false }); - }} - {...menuProps} - /> - )} - {...popoverProps} - > - { - if (this.canArrowsOpenDropdown(event)) { - this.setState({ isYearDropdownOpen: true }); - } - }} - onKeyDown={(event) => { - if (this.canArrowsOpenDropdown(event)) { - // disables page scroll - event.preventDefault(); - } - - if (event.key === 'Tab') { - this.setState({ isYearDropdownOpen: false }); - } - }} - aria-label={`Year, ${yearTitle}`} - {...monthYearSelectButtonProps} - > - {yearTitle} - - - - - - - ); - }; - - render() { - const { overrides = {}, density } = this.props; - const [CalendarHeader, calendarHeaderProps] = getOverrides( - overrides.CalendarHeader, - StyledCalendarHeader - ); - const [MonthHeader, monthHeaderProps] = getOverrides(overrides.MonthHeader, StyledMonthHeader); - const [WeekdayHeader, weekdayHeaderProps] = getOverrides( - overrides.WeekdayHeader, - StyledWeekdayHeader - ); - - const startOfWeek = this.dateHelpers.getStartOfWeek(this.getDateProp(), this.props.locale); - return ( - - {(theme) => ( - - {(locale) => ( - <> - - {this.renderPreviousMonthButton({ - locale, - theme, - })} - {this.renderMonthYearDropdown()} - {this.renderNextMonthButton({ locale, theme })} - - - {WEEKDAYS.map((offset) => { - const day = this.dateHelpers.addDays(startOfWeek, offset); - return ( - - - {this.dateHelpers.getWeekdayMinInLocale(day, this.props.locale)} - - - ); - })} - - - )} - - )} - - ); - } -} diff --git a/src/datepicker/calendar.js.flow b/src/datepicker/calendar.js.flow deleted file mode 100644 index dbd58422ea..0000000000 --- a/src/datepicker/calendar.js.flow +++ /dev/null @@ -1,723 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { FormControl } from '../form-control/index.js'; -import { LocaleContext } from '../locale/index.js'; -import { Select } from '../select/index.js'; -import CalendarHeader from './calendar-header.js'; -import Month from './month.js'; -import TimePicker from '../timepicker/timepicker.js'; -import type { DateIOAdapter } from './utils/types.js'; -import { - StyledCalendarContainer, - StyledMonthContainer, - StyledRoot, - StyledSelectorContainer, -} from './styled-components.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import DateHelpers from './utils/date-helpers.js'; -import { getOverrides, mergeOverrides } from '../helpers/overrides.js'; -import type { CalendarPropsT, CalendarInternalState } from './types.js'; -import { DENSITY, ORIENTATION } from './constants.js'; -import { Button, KIND, SIZE } from '../button/index.js'; -import { ButtonDock } from '../button-dock/index.js'; - -export default class Calendar extends React.Component< - CalendarPropsT, - CalendarInternalState -> { - static defaultProps: { adapter: DateIOAdapter } = { - autoFocusCalendar: false, - dateLabel: null, - density: DENSITY.default, - excludeDates: null, - filterDate: null, - highlightedDate: null, - includeDates: null, - range: false, - locale: null, - maxDate: null, - minDate: null, - onDayClick: () => {}, - onDayFocus: () => {}, - onDayMouseOver: () => {}, - onDayMouseLeave: () => {}, - onMonthChange: () => {}, - onYearChange: () => {}, - onChange: () => {}, - orientation: ORIENTATION.horizontal, - overrides: {}, - peekNextMonth: false, - adapter: dateFnsAdapter, - value: null, - trapTabbing: false, - }; - - dateHelpers: DateHelpers; - - calendar: React.ElementRef; - - constructor(props: CalendarPropsT) { - super(props); - - const { highlightedDate, value, adapter } = this.props; - //$FlowFixMe - this.dateHelpers = new DateHelpers(adapter); - const dateInView = this.getDateInView(); - let time = []; - if (Array.isArray(value)) { - time = [...value]; - } else if (value) { - time = [value]; - } - this.state = { - highlightedDate: - this.getSingleDate(value) || - (highlightedDate && this.dateHelpers.isSameMonth(dateInView, highlightedDate) - ? highlightedDate - : this.dateHelpers.date()), - focused: false, - date: dateInView, - quickSelectId: null, - rootElement: null, - time, - }; - } - - componentDidMount() { - if (this.props.autoFocusCalendar) { - this.focusCalendar(); - } - } - - componentDidUpdate(prevProps: CalendarPropsT) { - if ( - this.props.highlightedDate && - !this.dateHelpers.isSameDay(this.props.highlightedDate, prevProps.highlightedDate) - ) { - this.setState({ - date: this.props.highlightedDate, - }); - } - if ( - this.props.autoFocusCalendar && - this.props.autoFocusCalendar !== prevProps.autoFocusCalendar - ) { - this.focusCalendar(); - } - - if (prevProps.value !== this.props.value) { - const nextDate = this.getDateInView(); - if (!this.isInView(nextDate)) { - this.setState({ - date: nextDate, - }); - } - } - } - - isInView(date: T): boolean { - // we calculate the month delta between the date arg and the date in the state. - const currentDate = this.state.date; - - // First we get the year delta - const yearDelta = this.dateHelpers.getYear(date) - this.dateHelpers.getYear(currentDate); - - // then we convert it to months. Then we simply add the date-without-year month delta back in. - const monthDelta = - yearDelta * 12 + this.dateHelpers.getMonth(date) - this.dateHelpers.getMonth(currentDate); - - // we just check that the delta is between the range given by "this month" (i.e. 0) and "the last month" (i.e. monthsShown) - return monthDelta >= 0 && monthDelta < (this.props.monthsShown || 1); - } - - getSingleDate(value: ?T | $ReadOnlyArray): ?T { - // need to check this.props.range but flow would complain - // at the return value in the else clause - if (Array.isArray(value)) { - return value[0] || null; - } - return value; - } - - getDateInView: () => T = () => { - const { highlightedDate, value } = this.props; - const minDate = this.dateHelpers.getEffectiveMinDate(this.props); - const maxDate = this.dateHelpers.getEffectiveMaxDate(this.props); - const current = this.dateHelpers.date(); - const initialDate = this.getSingleDate(value) || highlightedDate; - if (initialDate) { - return initialDate; - } else { - if (minDate && this.dateHelpers.isBefore(current, minDate)) { - return minDate; - } else if (maxDate && this.dateHelpers.isAfter(current, maxDate)) { - return maxDate; - } - } - return current; - }; - - handleMonthChange: (T) => void = (date) => { - this.setHighlightedDate(this.dateHelpers.getStartOfMonth(date)); - if (this.props.onMonthChange) { - this.props.onMonthChange({ date }); - } - }; - - handleYearChange: (T) => void = (date) => { - this.setHighlightedDate(date); - if (this.props.onYearChange) { - this.props.onYearChange({ date }); - } - }; - - changeMonth: ({ date: T }) => mixed = ({ date }) => { - this.setState({ date: date }, () => this.handleMonthChange(this.state.date)); - }; - - changeYear: ({ date: T }) => mixed = ({ date }) => { - this.setState({ date: date }, () => this.handleYearChange(this.state.date)); - }; - - renderCalendarHeader: (T, number) => React.Node = (date = this.state.date, order) => { - return ( - - ); - }; - - onKeyDown = (event: KeyboardEvent) => { - switch (event.key) { - case 'ArrowUp': - case 'ArrowDown': - case 'ArrowLeft': - case 'ArrowRight': - case 'Home': - case 'End': - case 'PageUp': - case 'PageDown': - this.handleArrowKey(event.key); - event.preventDefault(); - event.stopPropagation(); - break; - } - }; - - handleArrowKey = (key: string) => { - const { highlightedDate: oldDate } = this.state; - let highlightedDate = oldDate; - const currentDate = this.dateHelpers.date(); - switch (key) { - case 'ArrowLeft': - // adding `new Date()` as the last option to satisfy Flow - highlightedDate = this.dateHelpers.subDays( - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - case 'ArrowRight': - highlightedDate = this.dateHelpers.addDays( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - case 'ArrowUp': - highlightedDate = this.dateHelpers.subWeeks( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - case 'ArrowDown': - highlightedDate = this.dateHelpers.addWeeks( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - case 'Home': - highlightedDate = this.dateHelpers.getStartOfWeek( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate - ); - break; - case 'End': - highlightedDate = this.dateHelpers.getEndOfWeek( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate - ); - break; - case 'PageUp': - highlightedDate = this.dateHelpers.subMonths( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - case 'PageDown': - highlightedDate = this.dateHelpers.addMonths( - // adding `new Date()` as the last option to satisfy Flow - highlightedDate ? highlightedDate : currentDate, - 1 - ); - break; - } - this.setState({ highlightedDate, date: highlightedDate }); - }; - - focusCalendar = () => { - if (!this.state.focused) { - this.setState({ focused: true }); - } - }; - - blurCalendar = () => { - if (__BROWSER__) { - const activeElm = document.activeElement; - if (this.calendar && !this.calendar.contains(activeElm)) { - this.setState({ focused: false }); - } - } - }; - - handleTabbing = (event: KeyboardEvent) => { - if (__BROWSER__) { - if (event.keyCode === 9) { - const activeElm = document.activeElement; - // need to look for any tabindex >= 0 and ideally for not disabled - // focusable by default elements like input, button, etc. - const focusable = this.state.rootElement - ? this.state.rootElement.querySelectorAll('[tabindex="0"]') - : null; - const length = focusable ? focusable.length : 0; - if (event.shiftKey) { - if (focusable && activeElm === focusable[0]) { - event.preventDefault(); - focusable[length - 1].focus(); - } - } else { - if (focusable && activeElm === focusable[length - 1]) { - event.preventDefault(); - focusable[0].focus(); - } - } - } - } - }; - - onDayFocus: ({ event: Event, date: T }) => mixed = (data) => { - const { date } = data; - this.setState({ highlightedDate: date }); - this.focusCalendar(); - this.props.onDayFocus && this.props.onDayFocus(data); - }; - - onDayMouseOver: ({ event: Event, date: T }) => mixed = (data) => { - const { date } = data; - this.setState({ highlightedDate: date }); - this.props.onDayMouseOver && this.props.onDayMouseOver(data); - }; - - onDayMouseLeave: ({ event: Event, date: T }) => mixed = (data) => { - const { date } = data; - const { value } = this.props; - const selected = this.getSingleDate(value); - this.setState({ highlightedDate: selected || date }); - this.props.onDayMouseLeave && this.props.onDayMouseLeave(data); - }; - - /** Responsible for merging time values into date values. Note: the 'Day' component - * determines how the days themselves change when a new day is selected. */ - handleDateChange: ({ +date: ?T | Array }) => void = (data) => { - const { onChange = (params) => {} } = this.props; - let updatedDate = data.date; - // Apply the currently selected time values (saved in state) to the updated date - if (Array.isArray(data.date)) { - // We'll need to update the date in time values of internal state - const newTimeState = [...this.state.time]; - const start = data.date[0] - ? this.dateHelpers.applyDateToTime(newTimeState[0], data.date[0]) - : null; - const end = data.date[1] - ? this.dateHelpers.applyDateToTime(newTimeState[1], data.date[1]) - : null; - newTimeState[0] = start; - if (end) { - updatedDate = [start, end]; - newTimeState[1] = end; - } else { - updatedDate = [start]; - } - // Update the date in time values of internal state - this.setState({ time: newTimeState }); - } else if (!Array.isArray(this.props.value) && data.date) { - const newTimeState = this.dateHelpers.applyDateToTime(this.state.time[0], data.date); - updatedDate = newTimeState; - - // Update the date in time values of internal state - this.setState({ time: [newTimeState] }); - } - - onChange({ date: updatedDate }); - }; - - handleTimeChange = (time: T, index: number) => { - const { onChange = (params) => {} } = this.props; - // Save/update the time value in internal state - const newTimeState = [...this.state.time]; - newTimeState[index] = this.dateHelpers.applyTimeToDate(newTimeState[index], time); - this.setState({ time: newTimeState }); - // Time change calls calendar's onChange handler - // with the date value set to the date with updated time - if (Array.isArray(this.props.value)) { - const dates = this.props.value.map((date, i) => { - if (date && index === i) { - return this.dateHelpers.applyTimeToDate(date, time); - } - return date; - }); - onChange({ date: [dates[0], dates[1]] }); - } else { - const date = this.dateHelpers.applyTimeToDate(this.props.value, time); - onChange({ date }); - } - }; - - setHighlightedDate(date: T) { - const { value } = this.props; - const selected = this.getSingleDate(value); - let nextState; - if ( - selected && - this.dateHelpers.isSameMonth(selected, date) && - this.dateHelpers.isSameYear(selected, date) - ) { - nextState = { highlightedDate: selected }; - } else { - nextState = { - highlightedDate: date, - }; - } - this.setState(nextState); - } - - renderMonths = (translations: { ariaRoleDescCalMonth: string }) => { - const { overrides = {}, orientation } = this.props; - const monthList = []; - const [CalendarContainer, calendarContainerProps] = getOverrides( - overrides.CalendarContainer, - StyledCalendarContainer - ); - const [MonthContainer, monthContainerProps] = getOverrides( - overrides.MonthContainer, - StyledMonthContainer - ); - - for (let i = 0; i < (this.props.monthsShown || 1); ++i) { - const monthSubComponents = []; - const monthDate = this.dateHelpers.addMonths(this.state.date, i); - const monthKey = `month-${i}`; - monthSubComponents.push(this.renderCalendarHeader(monthDate, i)); - monthSubComponents.push( - { - this.calendar = calendar; - }} - role="grid" - aria-roledescription={translations.ariaRoleDescCalMonth} - aria-multiselectable={this.props.range || null} - onKeyDown={this.onKeyDown} - {...calendarContainerProps} - $density={this.props.density} - > - - - ); - monthList.push(
{monthSubComponents}
); - } - return ( - - {monthList} - - ); - }; - - // flowlint-next-line unclear-type:off - renderTimeSelect: (?T, Function, string) => React.Node = (value, onChange, label) => { - const { overrides = {} } = this.props; - const [TimeSelectContainer, timeSelectContainerProps] = getOverrides( - overrides.TimeSelectContainer, - StyledSelectorContainer - ); - const [TimeSelectFormControl, timeSelectFormControlProps] = getOverrides( - overrides.TimeSelectFormControl, - FormControl - ); - const [TimeSelect, timeSelectProps] = getOverrides(overrides.TimeSelect, TimePicker); - - return ( - - - - - - ); - }; - - renderQuickSelect = () => { - const { overrides = {} } = this.props; - const [QuickSelectContainer, quickSelectContainerProps] = getOverrides( - overrides.QuickSelectContainer, - StyledSelectorContainer - ); - const [QuickSelectFormControl, quickSelectFormControlProps] = getOverrides( - overrides.QuickSelectFormControl, - FormControl - ); - const [QuickSelect, { overrides: quickSelectOverrides, ...restQuickSelectProps }] = - getOverrides( - // - overrides.QuickSelect, - Select - ); - - if (!this.props.quickSelect) { - return null; - } - - const NOW = this.dateHelpers.set(this.dateHelpers.date(), { - hours: 12, - minutes: 0, - seconds: 0, - }); - - return ( - - {(locale) => ( - - - { - if (!params.option) { - this.setState({ quickSelectId: null }); - this.props.onChange && this.props.onChange({ date: [] }); - } else { - this.setState({ - quickSelectId: params.option.id, - }); - if (this.props.onChange) { - if (this.props.range) { - this.props.onChange({ - date: [params.option.beginDate, params.option.endDate || NOW], - }); - } else { - this.props.onChange({ - date: params.option.beginDate, - }); - } - } - } - if (this.props.onQuickSelectChange) { - this.props.onQuickSelectChange(params.option); - } - }} - options={ - this.props.quickSelectOptions || [ - { - id: locale.datepicker.pastWeek, - beginDate: this.dateHelpers.subWeeks(NOW, 1), - }, - { - id: locale.datepicker.pastMonth, - beginDate: this.dateHelpers.subMonths(NOW, 1), - }, - { - id: locale.datepicker.pastThreeMonths, - beginDate: this.dateHelpers.subMonths(NOW, 3), - }, - { - id: locale.datepicker.pastSixMonths, - beginDate: this.dateHelpers.subMonths(NOW, 6), - }, - { - id: locale.datepicker.pastYear, - beginDate: this.dateHelpers.subYears(NOW, 1), - }, - { - id: locale.datepicker.pastTwoYears, - beginDate: this.dateHelpers.subYears(NOW, 2), - }, - ] - } - placeholder={locale.datepicker.quickSelectPlaceholder} - value={this.state.quickSelectId && [{ id: this.state.quickSelectId }]} - overrides={mergeOverrides( - { - Dropdown: { - style: { - textAlign: 'start', - }, - }, - }, - quickSelectOverrides - )} - {...restQuickSelectProps} - /> - - - )} - - ); - }; - - renderActionBar = () => { - const { overrides = {}, primaryButton, secondaryButton } = this.props; - const [ButtonDockComponent, buttonDockProps] = getOverrides(overrides.ButtonDock, ButtonDock); - const [PrimaryButtonComponent, primaryButtonProps] = getOverrides( - overrides.PrimaryButton, - Button - ); - const [SecondaryButtonComponent, secondaryButtonProps] = getOverrides( - overrides.SecondaryButton, - Button - ); - - const primaryButtonComponent = - primaryButton != null ? ( - primaryButton.onClick()} - {...primaryButtonProps} - > - {primaryButton.label} - - ) : null; - const secondaryButtonComponent = - secondaryButton != null ? ( - secondaryButton.onClick()} - kind={KIND.tertiary} - {...secondaryButtonProps} - > - {secondaryButton.label} - - ) : null; - - if (primaryButtonComponent || secondaryButtonComponent) { - return ( - - ); - } - - return null; - }; - - render() { - const { overrides = {} } = this.props; - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [startDate, endDate] = [].concat(this.props.value); - - return ( - - {(locale) => ( - { - if (root && root instanceof HTMLElement && !this.state.rootElement) { - this.setState({ - rootElement: (root: HTMLElement), - }); - } - }} - aria-label={locale.datepicker.ariaLabelCalendar} - onKeyDown={this.props.trapTabbing ? this.handleTabbing : null} - {...rootProps} - > - {this.renderMonths({ - ariaRoleDescCalMonth: locale.datepicker.ariaRoleDescriptionCalendarMonth, - })} - {this.props.timeSelectStart && - this.renderTimeSelect( - startDate, - (time) => this.handleTimeChange(time, 0), - locale.datepicker.timeSelectStartLabel - )} - {this.props.timeSelectEnd && - this.props.range && - this.renderTimeSelect( - endDate, - (time) => this.handleTimeChange(time, 1), - locale.datepicker.timeSelectEndLabel - )} - {this.renderQuickSelect()} - {this.renderActionBar()} - - )} - - ); - } -} diff --git a/src/datepicker/constants.js.flow b/src/datepicker/constants.js.flow deleted file mode 100644 index b1de758ceb..0000000000 --- a/src/datepicker/constants.js.flow +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -export const DISPLAY_FORMAT = 'L'; -export const ISO_FORMAT = 'YYYY-MM-DD'; -export const ISO_MONTH_FORMAT = 'YYYY-MM'; - -export const ORIENTATION = Object.freeze({ - horizontal: 'horizontal', - vertical: 'vertical', -}); - -export const STATE_CHANGE_TYPE = Object.freeze({ - change: 'change', - moveUp: 'moveUp', - moveDown: 'moveDown', - moveLeft: 'moveLeft', - moveRight: 'moveRight', - mouseOver: 'mouseOver', - mouseLeave: 'mouseLeave', -}); - -export const WEEKDAYS = [0, 1, 2, 3, 4, 5, 6]; - -export const DEFAULT_MONTHS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; - -export const DENSITY = { - high: 'high', - default: 'default', -}; - -export const INPUT_ROLE = { - startDate: 'startDate', - endDate: 'endDate', -}; - -export const RANGED_CALENDAR_BEHAVIOR = { - default: 'default', - locked: 'locked', -}; diff --git a/src/datepicker/datepicker.js.flow b/src/datepicker/datepicker.js.flow deleted file mode 100644 index bc19bbc0fa..0000000000 --- a/src/datepicker/datepicker.js.flow +++ /dev/null @@ -1,611 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; - -import { MaskedInput } from '../input/index.js'; -import { Popover, PLACEMENT } from '../popover/index.js'; -import Calendar from './calendar.js'; -import { getOverrides } from '../helpers/overrides.js'; -import getInterpolatedString from '../helpers/i18n-interpolation.js'; -import { LocaleContext } from '../locale/index.js'; -import { - StyledInputWrapper, - StyledInputLabel, - StyledStartDate, - StyledEndDate, -} from './styled-components.js'; -import type { DatepickerPropsT, InputRoleT } from './types.js'; -import DateHelpers from './utils/date-helpers.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import type { LocaleT } from '../locale/types.js'; -import { INPUT_ROLE, RANGED_CALENDAR_BEHAVIOR } from './constants.js'; - -export const DEFAULT_DATE_FORMAT = 'yyyy/MM/dd'; - -const INPUT_DELIMITER = '–'; - -const combineSeparatedInputs = (newInputValue, prevCombinedInputValue = '', inputRole) => { - let inputValue = newInputValue; - const [prevStartDate = '', prevEndDate = ''] = prevCombinedInputValue.split( - ` ${INPUT_DELIMITER} ` - ); - if (inputRole === INPUT_ROLE.startDate && prevEndDate) { - inputValue = `${inputValue} ${INPUT_DELIMITER} ${prevEndDate}`; - } - if (inputRole === INPUT_ROLE.endDate) { - inputValue = `${prevStartDate} ${INPUT_DELIMITER} ${inputValue}`; - } - return inputValue; -}; - -type StateT = {| - calendarFocused: boolean, - isOpen: boolean, - selectedInput: ?InputRoleT, - isPseudoFocused: boolean, - lastActiveElm: ?HTMLElement, - inputValue?: string, -|}; - -export default class Datepicker extends React.Component, StateT> { - static defaultProps = { - 'aria-describedby': 'datepicker--screenreader--message--input', - value: null, - formatString: DEFAULT_DATE_FORMAT, - adapter: dateFnsAdapter, - }; - - calendar: ?HTMLElement; - - dateHelpers: DateHelpers; - - constructor(props: DatepickerPropsT) { - super(props); - //$FlowFixMe[incompatible-call] - this.dateHelpers = new DateHelpers(props.adapter); - this.state = { - calendarFocused: false, - isOpen: false, - selectedInput: null, - isPseudoFocused: false, - lastActiveElm: null, - inputValue: this.formatDisplayValue(props.value) || '', - }; - } - - handleChange: (?T | $ReadOnlyArray) => void = (date) => { - const onChange = this.props.onChange; - const onRangeChange = this.props.onRangeChange; - - if (Array.isArray(date)) { - if (onChange && date.every(Boolean)) { - // flowlint-next-line unclear-type:off - onChange({ date: ((date: any): Array) }); - } - - if (onRangeChange) { - onRangeChange({ date: [...date] }); - } - } else { - if (onChange) { - onChange({ date }); - } - - if (onRangeChange) { - onRangeChange({ date }); - } - } - }; - - onCalendarSelect: ({ +date: ?T | Array }) => void = (data) => { - let isOpen = false; - let isPseudoFocused = false; - let calendarFocused = false; - let nextDate = data.date; - - if (Array.isArray(nextDate) && this.props.range) { - if (!nextDate[0] || !nextDate[1]) { - isOpen = true; - isPseudoFocused = true; - calendarFocused = null; - } else if (nextDate[0] && nextDate[1]) { - const [start, end] = nextDate; - if (this.dateHelpers.isAfter(start, end)) { - if (this.hasLockedBehavior()) { - nextDate = this.props.value; - isOpen = true; - } else { - nextDate = [start, start]; - } - } else if ( - this.dateHelpers.dateRangeIncludesDates( - // $FlowFixMe Cannot call `this.dateHelpers.dateRangeIncludesDates` with `nextDate` bound to the first parameter because read-only array type [1] is incompatible with array type [2] - nextDate, - this.props.excludeDates - ) - ) { - nextDate = this.props.value; - isOpen = true; - } - - if (this.state.lastActiveElm) { - this.state.lastActiveElm.focus(); - } - } - } else if (this.state.lastActiveElm) { - this.state.lastActiveElm.focus(); - } - - // Time selectors previously caused the calendar popover to close. - // The check below refrains from closing the popover if only times changed. - const onlyTimeChanged = (prev: ?T, next: ?T) => { - if (!prev || !next) return false; - const p = this.dateHelpers.format(prev, 'keyboardDate'); - const n = this.dateHelpers.format(next, 'keyboardDate'); - if (p === n) { - return ( - this.dateHelpers.getHours(prev) !== this.dateHelpers.getHours(next) || - this.dateHelpers.getMinutes(prev) !== this.dateHelpers.getMinutes(next) - ); - } - return false; - }; - - const prevValue = this.props.value; - if (Array.isArray(nextDate) && Array.isArray(prevValue)) { - if (nextDate.some((d, i) => onlyTimeChanged(prevValue[i], d))) { - isOpen = true; - } - } else if (!Array.isArray(nextDate) && !Array.isArray(prevValue)) { - if (onlyTimeChanged(prevValue, nextDate)) { - isOpen = true; - } - } - - this.setState({ - isOpen, - isPseudoFocused, - ...(calendarFocused === null ? {} : { calendarFocused }), - inputValue: this.formatDisplayValue(nextDate), - }); - - this.handleChange(nextDate); - }; - - getNullDatePlaceholder(formatString: string) { - return (this.getMask() || formatString).split(INPUT_DELIMITER)[0].replace(/[0-9]|[a-z]/g, ' '); - } - - formatDate(date: ?T | $ReadOnlyArray, formatString: string) { - const format = (date: T) => { - if (formatString === DEFAULT_DATE_FORMAT) { - return this.dateHelpers.format(date, 'slashDate', this.props.locale); - } - return this.dateHelpers.formatDate(date, formatString, this.props.locale); - }; - - if (!date) { - return ''; - } else if (Array.isArray(date) && !date[0] && !date[1]) { - return ''; - } else if (Array.isArray(date) && !date[0] && date[1]) { - const endDate = format(date[1]); - const startDate = this.getNullDatePlaceholder(formatString); - return [startDate, endDate].join(` ${INPUT_DELIMITER} `); - } else if (Array.isArray(date)) { - return date.map((day) => (day ? format(day) : '')).join(` ${INPUT_DELIMITER} `); - } else { - return format(date); - } - } - - formatDisplayValue: (?T | $ReadOnlyArray) => string = (date) => { - const { displayValueAtRangeIndex, formatDisplayValue, range } = this.props; - const formatString = this.normalizeDashes(this.props.formatString); - - if (typeof displayValueAtRangeIndex === 'number') { - if (__DEV__) { - if (!range) { - console.error('displayValueAtRangeIndex only applies if range'); - } - if (range && displayValueAtRangeIndex > 1) { - console.error('displayValueAtRangeIndex value must be 0 or 1'); - } - } - if (date && Array.isArray(date)) { - const value = date[displayValueAtRangeIndex]; - if (formatDisplayValue) { - return formatDisplayValue(value, formatString); - } - return this.formatDate(value, formatString); - } - } - - if (formatDisplayValue) { - return formatDisplayValue(date, formatString); - } - - return this.formatDate(date, formatString); - }; - - open = (inputRole?: InputRoleT) => { - this.setState( - { - isOpen: true, - isPseudoFocused: true, - calendarFocused: false, - selectedInput: inputRole, - }, - this.props.onOpen - ); - }; - - close = () => { - const isPseudoFocused = false; - this.setState( - { - isOpen: false, - selectedInput: null, - isPseudoFocused, - calendarFocused: false, - }, - this.props.onClose - ); - }; - - handleEsc = () => { - if (this.state.lastActiveElm) { - this.state.lastActiveElm.focus(); - } - this.close(); - }; - - handleInputBlur = () => { - if (!this.state.isPseudoFocused) { - this.close(); - } - }; - - getMask = () => { - const { formatString, mask, range, separateRangeInputs } = this.props; - - if (mask === null || (mask === undefined && formatString !== DEFAULT_DATE_FORMAT)) { - return null; - } - - if (mask) { - return this.normalizeDashes(mask); - } - - if (range && !separateRangeInputs) { - return `9999/99/99 ${INPUT_DELIMITER} 9999/99/99`; - } - - return '9999/99/99'; - }; - - handleInputChange = (event: SyntheticInputEvent, inputRole?: InputRoleT) => { - const inputValue = - this.props.range && this.props.separateRangeInputs - ? combineSeparatedInputs(event.currentTarget.value, this.state.inputValue, inputRole) - : event.currentTarget.value; - - const mask = this.getMask(); - const formatString = this.normalizeDashes(this.props.formatString); - - if ( - (typeof mask === 'string' && inputValue === mask.replace(/9/g, ' ')) || - inputValue.length === 0 - ) { - if (this.props.range) { - this.handleChange([]); - } else { - this.handleChange(null); - } - } - - this.setState({ inputValue }); - - const parseDateString = (dateString) => { - if (formatString === DEFAULT_DATE_FORMAT) { - return this.dateHelpers.parse(dateString, 'slashDate', this.props.locale); - } - return this.dateHelpers.parseString(dateString, formatString, this.props.locale); - }; - - if (this.props.range && typeof this.props.displayValueAtRangeIndex !== 'number') { - const [left, right] = this.normalizeDashes(inputValue).split(` ${INPUT_DELIMITER} `); - - let startDate = this.dateHelpers.date(left); - let endDate = this.dateHelpers.date(right); - - if (formatString) { - startDate = parseDateString(left); - endDate = parseDateString(right); - } - - const datesValid = this.dateHelpers.isValid(startDate) && this.dateHelpers.isValid(endDate); - - // added equal case so that times within the same day can be expressed - const rangeValid = - this.dateHelpers.isAfter(endDate, startDate) || - this.dateHelpers.isEqual(startDate, endDate); - - if (datesValid && rangeValid) { - this.handleChange([startDate, endDate]); - } - } else { - const dateString = this.normalizeDashes(inputValue); - let date = this.dateHelpers.date(dateString); - const formatString = this.props.formatString; - - // Prevent early parsing of value. - // Eg 25.12.2 will be transformed to 25.12.0002 formatted from date to string - if (dateString.replace(/(\s)*/g, '').length < formatString.replace(/(\s)*/g, '').length) { - date = null; - } else { - date = parseDateString(dateString); - } - - const { displayValueAtRangeIndex, range, value } = this.props; - if (date && this.dateHelpers.isValid(date)) { - if (range && Array.isArray(value) && typeof displayValueAtRangeIndex === 'number') { - let [left, right] = value; - if (displayValueAtRangeIndex === 0) { - left = date; - if (!right) { - this.handleChange([left]); - } else { - if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) { - this.handleChange([left, right]); - } else { - // Is resetting back to previous value appropriate? Invalid range is not - // communicated to the user, but if it was not reset the text value would - // show one value and date value another. This seems a bit better but clearly - // has a downside. - this.handleChange([...value]); - } - } - } else if (displayValueAtRangeIndex === 1) { - right = date; - if (!left) { - // If start value is not defined, set start/end to the same day. - this.handleChange([right, right]); - } else { - if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) { - this.handleChange([left, right]); - } else { - // See comment above about resetting dates on invalid range - this.handleChange([...value]); - } - } - } - } else { - this.handleChange(date); - } - } - } - }; - - handleKeyDown = (event: KeyboardEvent) => { - if (!this.state.isOpen && event.keyCode === 40) { - this.open(); - } else if (this.state.isOpen && event.key === 'ArrowDown') { - // next line prevents the page jump on the initial arrowDown - event.preventDefault(); - this.focusCalendar(); - } else if (this.state.isOpen && event.keyCode === 9) { - this.close(); - } - }; - - focusCalendar = () => { - if (__BROWSER__) { - const lastActiveElm = document.activeElement; - this.setState({ - calendarFocused: true, - lastActiveElm, - }); - } - }; - - normalizeDashes = (inputValue: string) => { - // replacing both hyphens and em-dashes with en-dashes - return inputValue.replace(/-/g, INPUT_DELIMITER).replace(/—/g, INPUT_DELIMITER); - }; - - hasLockedBehavior = () => { - return ( - this.props.rangedCalendarBehavior === RANGED_CALENDAR_BEHAVIOR.locked && - this.props.range && - this.props.separateRangeInputs - ); - }; - - componentDidUpdate(prevProps: DatepickerPropsT) { - if (prevProps.value !== this.props.value) { - this.setState({ - inputValue: this.formatDisplayValue(this.props.value), - }); - } - } - - renderInputComponent(locale: LocaleT, inputRole?: InputRoleT) { - const { overrides = {} } = this.props; - - const [InputComponent, inputProps] = getOverrides(overrides.Input, MaskedInput); - - const placeholder = - this.props.placeholder || this.props.placeholder === '' - ? this.props.placeholder - : this.props.range && !this.props.separateRangeInputs - ? `YYYY/MM/DD ${INPUT_DELIMITER} YYYY/MM/DD` - : 'YYYY/MM/DD'; - - const [startDate = '', endDate = ''] = (this.state.inputValue || '').split( - ` ${INPUT_DELIMITER} ` - ); - - const value = - inputRole === INPUT_ROLE.startDate - ? startDate - : inputRole === INPUT_ROLE.endDate - ? endDate - : this.state.inputValue; - - return ( - this.open(inputRole)} - onBlur={this.handleInputBlur} - onKeyDown={this.handleKeyDown} - onChange={(event) => this.handleInputChange(event, inputRole)} - placeholder={placeholder} - mask={this.getMask()} - required={this.props.required} - clearable={this.props.clearable} - {...inputProps} - /> - ); - } - - render() { - const { overrides = {}, startDateLabel = 'Start Date', endDateLabel = 'End Date' } = this.props; - const [PopoverComponent, popoverProps] = getOverrides(overrides.Popover, Popover); - const [InputWrapper, inputWrapperProps] = getOverrides( - overrides.InputWrapper, - StyledInputWrapper - ); - const [StartDate, startDateProps] = getOverrides(overrides.StartDate, StyledStartDate); - const [EndDate, endDateProps] = getOverrides(overrides.EndDate, StyledEndDate); - const [InputLabel, inputLabelProps] = getOverrides(overrides.InputLabel, StyledInputLabel); - - return ( - - {(locale) => ( - - - } - {...popoverProps} - > - {} - - {this.props.range && this.props.separateRangeInputs ? ( - <> - - {startDateLabel} - {this.renderInputComponent(locale, INPUT_ROLE.startDate)} - - - {endDateLabel} - {this.renderInputComponent(locale, INPUT_ROLE.endDate)} - - - ) : ( - <>{this.renderInputComponent(locale)} - )} - - -

- {locale.datepicker.screenReaderMessageInput} -

-

- { - // No date selected - !this.props.value || - (Array.isArray(this.props.value) && !this.props.value[0] && !this.props.value[1]) - ? '' - : // Date selected in a non-range picker - !Array.isArray(this.props.value) - ? getInterpolatedString(locale.datepicker.selectedDate, { - date: this.state.inputValue || '', - }) - : // Start and end dates are selected in a range picker - this.props.value[0] && this.props.value[1] - ? getInterpolatedString(locale.datepicker.selectedDateRange, { - startDate: this.formatDisplayValue(this.props.value[0]), - endDate: this.formatDisplayValue( - // $FlowFixMe - this.props.value[1] - ), - }) - : // A single date selected in a range picker - `${getInterpolatedString(locale.datepicker.selectedDate, { - date: this.formatDisplayValue(this.props.value[0]), - })} ${locale.datepicker.selectSecondDatePrompt}` - } -

-
- )} -
- ); - } -} diff --git a/src/datepicker/day.js.flow b/src/datepicker/day.js.flow deleted file mode 100644 index 444733e3f6..0000000000 --- a/src/datepicker/day.js.flow +++ /dev/null @@ -1,454 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { StyledDay, StyledDayLabel } from './styled-components.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import DateHelpers from './utils/date-helpers.js'; -import { getOverrides } from '../helpers/overrides.js'; -import type { DayPropsT, DayStateT } from './types.js'; -import { LocaleContext } from '../locale/index.js'; -import type { LocaleT } from '../locale/types.js'; -import { isFocusVisible } from '../utils/focusVisible.js'; -import { INPUT_ROLE } from './constants.js'; - -export default class Day extends React.Component, DayStateT> { - static defaultProps = { - disabled: false, - highlighted: false, - range: false, - adapter: dateFnsAdapter, - onClick: () => {}, - onSelect: () => {}, - onFocus: () => {}, - onBlur: () => {}, - onMouseOver: () => {}, - onMouseLeave: () => {}, - overrides: {}, - peekNextMonth: true, - value: null, - }; - - dayElm: React.ElementRef; - - state = { - isHovered: false, - isFocusVisible: false, - }; - - dateHelpers: DateHelpers; - - constructor(props: DayPropsT) { - super(props); - this.dateHelpers = new DateHelpers(props.adapter); - } - - componentDidMount() { - if (this.dayElm && this.props.focusedCalendar) { - if (this.props.highlighted || (!this.props.highlightedDate && this.isSelected())) { - this.dayElm.focus(); - } - } - } - - componentDidUpdate(prevProps: DayPropsT) { - if (this.dayElm && this.props.focusedCalendar) { - if (this.props.highlighted || (!this.props.highlightedDate && this.isSelected())) { - this.dayElm.focus(); - } - } - } - - getDateProp: () => T = () => { - return this.props.date === undefined ? this.dateHelpers.date() : this.props.date; - }; - - getMonthProp: () => number = () => { - return this.props.month === undefined || this.props.month === null - ? this.dateHelpers.getMonth(this.getDateProp()) - : this.props.month; - }; - - /** - * Determines how the day value(s) should be updated when a new day is selected. - * Note: time values are incorporated into new day/date values downstream in `Calendar`. - * Note: Situations where Start Dates are after End Dates are handled downstream in `Datepicker`. - * */ - onSelect: (T) => void = (selectedDate) => { - const { range, value } = this.props; - - let nextDate; - if (Array.isArray(value) && range && this.props.hasLockedBehavior) { - const currentDate = this.props.value; - let nextStartDate = null; - let nextEndDate = null; - - if (this.props.selectedInput === INPUT_ROLE.startDate) { - nextStartDate = selectedDate; - nextEndDate = Array.isArray(currentDate) && currentDate[1] ? currentDate[1] : null; - } else if (this.props.selectedInput === INPUT_ROLE.endDate) { - nextStartDate = Array.isArray(currentDate) && currentDate[0] ? currentDate[0] : null; - nextEndDate = selectedDate; - } - - nextDate = [nextStartDate]; - if (nextEndDate) { - nextDate.push(nextEndDate); - } - } else if (Array.isArray(value) && range && !this.props.hasLockedBehavior) { - const [start, end] = value; - - // Starting a new range - if ((!start && !end) || (start && end)) { - nextDate = [selectedDate, null]; - - // EndDate needs a StartDate, SelectedDate comes before EndDate - } else if (!start && end && this.dateHelpers.isAfter(end, selectedDate)) { - nextDate = [selectedDate, end]; - - // EndDate needs a StartDate, but SelectedDate comes after EndDate - } else if (!start && end && this.dateHelpers.isAfter(selectedDate, end)) { - nextDate = [end, selectedDate]; - - // StartDate needs an EndDate, SelectedDate comes after StartDate - } else if (start && !end && this.dateHelpers.isAfter(selectedDate, start)) { - nextDate = [start, selectedDate]; - } else { - nextDate = [selectedDate, start]; - } - } else { - nextDate = selectedDate; - } - - this.props.onSelect({ date: nextDate }); - }; - - onKeyDown = (event: KeyboardEvent) => { - const date = this.getDateProp(); - const { highlighted, disabled } = this.props; - if (event.key === 'Enter' && highlighted && !disabled) { - event.preventDefault(); - this.onSelect(date); - } - }; - - onClick = (event: Event) => { - const date = this.getDateProp(); - const { disabled } = this.props; - if (!disabled) { - this.props.onClick({ event, date }); - this.onSelect(date); - } - }; - - onFocus = (event: Event) => { - if (isFocusVisible(event)) { - this.setState({ isFocusVisible: true }); - } - this.props.onFocus({ event, date: this.getDateProp() }); - }; - - onBlur = (event: Event) => { - if (this.state.isFocusVisible !== false) { - this.setState({ isFocusVisible: false }); - } - this.props.onBlur({ event, date: this.getDateProp() }); - }; - - onMouseOver = (event: Event) => { - this.setState({ isHovered: true }); - this.props.onMouseOver({ event, date: this.getDateProp() }); - }; - - onMouseLeave = (event: Event) => { - this.setState({ isHovered: false }); - this.props.onMouseLeave({ event, date: this.getDateProp() }); - }; - - isOutsideMonth = () => { - const month = this.getMonthProp(); - return month !== undefined && month !== this.dateHelpers.getMonth(this.getDateProp()); - }; - - getOrderedDates: () => T[] = () => { - const { highlightedDate, value } = this.props; - if (!value || !Array.isArray(value) || !value[0] || (!value[1] && !highlightedDate)) { - return []; - } - const firstValue = value[0]; - const secondValue = value.length > 1 && value[1] ? value[1] : highlightedDate; - if (!firstValue || !secondValue) { - return []; - } - const firstDate = this.clampToDayStart(firstValue); - const secondDate = this.clampToDayStart(secondValue); - return this.dateHelpers.isAfter(firstDate, secondDate) - ? [secondDate, firstDate] - : [firstDate, secondDate]; - }; - - isOutsideOfMonthButWithinRange = () => { - const date = this.clampToDayStart(this.getDateProp()); - const dates = this.getOrderedDates(); - if (dates.length < 2 || this.dateHelpers.isSameDay(dates[0], dates[1])) { - return false; - } - const day = this.dateHelpers.getDate(date); - /** - * Empty days (no number label) at the beginning/end of the month should be included - * within the range if the last day of a month and the first day of the next month are - * within the range. - */ - if (day > 15) { - const firstDayOfNextMonth = this.clampToDayStart( - this.dateHelpers.addDays(this.dateHelpers.getEndOfMonth(date), 1) - ); - return ( - this.dateHelpers.isOnOrBeforeDay(dates[0], this.dateHelpers.getEndOfMonth(date)) && - this.dateHelpers.isOnOrAfterDay(dates[1], firstDayOfNextMonth) - ); - } else { - const lastDayOfPreviousMonth = this.clampToDayStart( - this.dateHelpers.subDays(this.dateHelpers.getStartOfMonth(date), 1) - ); - return ( - this.dateHelpers.isOnOrAfterDay(dates[1], this.dateHelpers.getStartOfMonth(date)) && - this.dateHelpers.isOnOrBeforeDay(dates[0], lastDayOfPreviousMonth) - ); - } - }; - - isSelected() { - const date = this.getDateProp(); - const { value } = this.props; - if (Array.isArray(value)) { - return ( - this.dateHelpers.isSameDay(date, value[0]) || this.dateHelpers.isSameDay(date, value[1]) - ); - } else { - return this.dateHelpers.isSameDay(date, value); - } - } - - clampToDayStart: (T) => T = (dt) => { - const { setSeconds, setMinutes, setHours } = this.dateHelpers; - return setSeconds(setMinutes(setHours(dt, 0), 0), 0); - }; - - // calculated for range case only - isPseudoSelected() { - const date = this.getDateProp(); - const { value } = this.props; - - if (Array.isArray(value)) { - const [start, end] = value; - - if (!start && !end) { - return false; - } - - if (start && end) { - return this.dateHelpers.isDayInRange( - this.clampToDayStart(date), - this.clampToDayStart(start), - this.clampToDayStart(end) - ); - } - } - } - - // calculated for range case only - isPseudoHighlighted() { - const date = this.getDateProp(); - const { value, highlightedDate } = this.props; - - if (Array.isArray(value)) { - const [start, end] = value; - - if (!start && !end) { - return false; - } - - if (highlightedDate && start && !end) { - if (this.dateHelpers.isAfter(highlightedDate, start)) { - return this.dateHelpers.isDayInRange( - this.clampToDayStart(date), - this.clampToDayStart(start), - this.clampToDayStart(highlightedDate) - ); - } else { - return this.dateHelpers.isDayInRange( - this.clampToDayStart(date), - this.clampToDayStart(highlightedDate), - this.clampToDayStart(start) - ); - } - } - - if (highlightedDate && !start && end) { - if (this.dateHelpers.isAfter(highlightedDate, end)) { - return this.dateHelpers.isDayInRange( - this.clampToDayStart(date), - this.clampToDayStart(end), - this.clampToDayStart(highlightedDate) - ); - } else { - return this.dateHelpers.isDayInRange( - this.clampToDayStart(date), - this.clampToDayStart(highlightedDate), - this.clampToDayStart(end) - ); - } - } - } - } - - getSharedProps() { - const date = this.getDateProp(); - const { value, highlightedDate, range, highlighted, peekNextMonth } = this.props; - const $isHighlighted = highlighted; - const $selected = this.isSelected(); - const $hasRangeHighlighted = !!( - Array.isArray(value) && - range && - highlightedDate && - ((value[0] && !value[1] && !this.dateHelpers.isSameDay(value[0], highlightedDate)) || - (!value[0] && value[1] && !this.dateHelpers.isSameDay(value[1], highlightedDate))) - ); - const $outsideMonth = !peekNextMonth && this.isOutsideMonth(); - const $outsideMonthWithinRange = !!( - Array.isArray(value) && - range && - $outsideMonth && - !peekNextMonth && - this.isOutsideOfMonthButWithinRange() - ); - return { - $date: date, - $density: this.props.density, - $disabled: this.props.disabled, - $endDate: - (Array.isArray(value) && - !!(value[0] && value[1]) && - range && - $selected && - this.dateHelpers.isSameDay(date, value[1])) || - false, - $hasDateLabel: !!this.props.dateLabel, - $hasRangeHighlighted, - $hasRangeOnRight: - Array.isArray(value) && - $hasRangeHighlighted && - highlightedDate && - ((value[0] && this.dateHelpers.isAfter(highlightedDate, value[0])) || - (value[1] && this.dateHelpers.isAfter(highlightedDate, value[1]))), - $hasRangeSelected: Array.isArray(value) ? !!(value[0] && value[1]) : false, - $highlightedDate: highlightedDate, - $isHighlighted, - $isHovered: this.state.isHovered, - $isFocusVisible: this.state.isFocusVisible, - $startOfMonth: this.dateHelpers.isStartOfMonth(date), - $endOfMonth: this.dateHelpers.isEndOfMonth(date), - $month: this.getMonthProp(), - $outsideMonth, - $outsideMonthWithinRange, - $peekNextMonth: peekNextMonth, - $pseudoHighlighted: - range && !$isHighlighted && !$selected ? this.isPseudoHighlighted() : false, - $pseudoSelected: range && !$selected ? this.isPseudoSelected() : false, - $range: range, - $selected, - $startDate: - Array.isArray(value) && value[0] && value[1] && range && $selected - ? this.dateHelpers.isSameDay(date, value[0]) - : false, - $hasLockedBehavior: this.props.hasLockedBehavior, - $selectedInput: this.props.selectedInput, - $value: this.props.value, - }; - } - - getAriaLabel( - sharedProps: { - $disabled: boolean, - $range: boolean, - $selected: boolean, - $startDate: boolean, - $endDate: boolean, - }, - localeContext: LocaleT - ) { - const date = this.getDateProp(); - return `${ - sharedProps.$selected - ? sharedProps.$range - ? sharedProps.$endDate - ? localeContext.datepicker.selectedEndDateLabel - : localeContext.datepicker.selectedStartDateLabel - : localeContext.datepicker.selectedLabel - : sharedProps.$disabled - ? localeContext.datepicker.dateNotAvailableLabel - : localeContext.datepicker.chooseLabel - } ${this.dateHelpers.format(date, 'fullOrdinalWeek', this.props.locale)}. ${ - !sharedProps.$disabled ? localeContext.datepicker.dateAvailableLabel : '' - }`; - } - - render() { - const date = this.getDateProp(); - const { peekNextMonth, overrides = {} } = this.props; - const sharedProps = this.getSharedProps(); - const [Day, dayProps] = getOverrides(overrides.Day, StyledDay); - const [DayLabel, dayLabelProps] = getOverrides(overrides.DayLabel, StyledDayLabel); - const dateLabel = this.props.dateLabel && this.props.dateLabel(date); - return !peekNextMonth && sharedProps.$outsideMonth ? ( - - ) : ( - // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events - - {(locale: LocaleT) => ( - { - this.dayElm = dayElm; - }} - role="gridcell" - aria-roledescription="button" - tabIndex={ - this.props.highlighted || (!this.props.highlightedDate && this.isSelected()) ? 0 : -1 - } - {...sharedProps} - {...dayProps} - // Adding event handlers after customers overrides in order to - // make sure the components functions as expected - // We can extract the handlers from props overrides - // and call it along with internal handlers by creating an inline handler - onFocus={this.onFocus} - onBlur={this.onBlur} - onClick={this.onClick} - onKeyDown={this.onKeyDown} - onMouseOver={this.onMouseOver} - onMouseLeave={this.onMouseLeave} - > -
{this.dateHelpers.getDate(date)}
- {dateLabel ? ( - - {dateLabel} - - ) : null} -
- )} -
- ); - } -} diff --git a/src/datepicker/index.js.flow b/src/datepicker/index.js.flow deleted file mode 100644 index bf5f9da7b3..0000000000 --- a/src/datepicker/index.js.flow +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -export { default as StatefulContainer } from './stateful-container.js'; -export { default as Calendar } from './calendar.js'; -export { default as StatefulCalendar } from './stateful-calendar.js'; -//$FlowFixMe -export { default as Datepicker, default as DatePicker } from './datepicker.js'; -export { - default as StatefulDatepicker, - default as StatefulDatePicker, -} from './stateful-datepicker.js'; -export { default as TimePicker } from '../timepicker/timepicker.js'; -export { default as TimezonePicker } from '../timezonepicker/timezone-picker.js'; -// Util functions -export { formatDate } from './utils/index.js'; -// Constants -export { DENSITY, ORIENTATION, STATE_CHANGE_TYPE } from './constants.js'; -// Styled elements -export * from './styled-components.js'; -// Flow -export type * from './types.js'; diff --git a/src/datepicker/locale.js.flow b/src/datepicker/locale.js.flow deleted file mode 100644 index 3d9b4c2cc2..0000000000 --- a/src/datepicker/locale.js.flow +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -export type DatepickerLocaleT = {| - ariaLabel: string, - ariaLabelRange: string, - ariaLabelCalendar: string, - ariaRoleDescriptionCalendarMonth: string, - nextMonth: string, - previousMonth: string, - pastWeek: string, - pastMonth: string, - pastThreeMonths: string, - pastSixMonths: string, - pastYear: string, - pastTwoYears: string, - screenReaderMessageInput: string, - selectedDate: string, - selectedDateRange: string, - selectSecondDatePrompt: string, - quickSelectLabel: string, - quickSelectAriaLabel: string, - quickSelectPlaceholder: string, - timeSelectEndLabel: string, - timeSelectStartLabel: string, - timePickerAriaLabel12Hour: string, - timePickerAriaLabel24Hour: string, - timezonePickerAriaLabel: string, - selectedStartDateLabel: string, - selectedEndDateLabel: string, - dateNotAvailableLabel: string, - dateAvailableLabel: string, - selectedLabel: string, - chooseLabel: string, -|}; - -const locale = { - ariaLabel: 'Select a date.', - ariaLabelRange: 'Select a date range.', - ariaLabelCalendar: 'Calendar.', - ariaRoleDescriptionCalendarMonth: 'Calendar month', - previousMonth: 'Previous month.', - nextMonth: 'Next month.', - pastWeek: 'Past Week', - pastMonth: 'Past Month', - pastThreeMonths: 'Past 3 Months', - pastSixMonths: 'Past 6 Months', - pastYear: 'Past Year', - pastTwoYears: 'Past 2 Years', - screenReaderMessageInput: - 'Press the down arrow key to interact with the calendar and select a date. Press the escape button to close the calendar.', - selectedDate: 'Selected date is ${date}.', - selectedDateRange: 'Selected date range is from ${startDate} to ${endDate}.', - selectSecondDatePrompt: 'Select the second date.', - quickSelectLabel: 'Choose a date range', - quickSelectAriaLabel: 'Choose a date range', - quickSelectPlaceholder: 'None', - timeSelectEndLabel: 'End time', - timeSelectStartLabel: 'Start time', - timePickerAriaLabel12Hour: 'Select a time, 12-hour format.', - timePickerAriaLabel24Hour: 'Select a time, 24-hour format.', - timezonePickerAriaLabel: 'Select a timezone.', - selectedStartDateLabel: 'Selected start date.', - selectedEndDateLabel: 'Selected end date.', - dateNotAvailableLabel: 'Not available.', - dateAvailableLabel: "It's available.", - selectedLabel: 'Selected.', - chooseLabel: 'Choose', -}; - -export default locale; diff --git a/src/datepicker/month.js.flow b/src/datepicker/month.js.flow deleted file mode 100644 index cc100d4ed4..0000000000 --- a/src/datepicker/month.js.flow +++ /dev/null @@ -1,120 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import Week from './week.js'; -import { StyledMonth } from './styled-components.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import DateHelpers from './utils/date-helpers.js'; -import { getOverrides } from '../helpers/overrides.js'; -import type { MonthPropsT } from './types.js'; -import { DENSITY } from './constants.js'; - -const defaultProps = { - dateLabel: null, - density: DENSITY.high, - excludeDates: null, - filterDate: null, - highlightDates: null, - includeDates: null, - locale: null, - maxDate: null, - minDate: null, - month: null, - adapter: dateFnsAdapter, - onDayClick: () => {}, - onDayFocus: () => {}, - onDayBlur: () => {}, - onDayMouseOver: () => {}, - onDayMouseLeave: () => {}, - overrides: {}, - peekNextMonth: false, - value: null, -}; - -const CALENDAR_MAX_ROWS = 6; - -export default class CalendarMonth extends React.Component> { - static defaultProps = defaultProps; - - dateHelpers: DateHelpers; - - constructor(props: MonthPropsT) { - super(props); - this.dateHelpers = new DateHelpers(props.adapter); - } - - getDateProp: () => T = () => { - return this.props.date || this.dateHelpers.date(); - }; - - isWeekInMonth: (T) => boolean = (startOfWeek) => { - const date = this.getDateProp(); - const endOfWeek = this.dateHelpers.addDays(startOfWeek, 6); - return ( - this.dateHelpers.isSameMonth(startOfWeek, date) || - this.dateHelpers.isSameMonth(endOfWeek, date) - ); - }; - - renderWeeks = () => { - const weeks = []; - let currentWeekStart = this.dateHelpers.getStartOfWeek( - this.dateHelpers.getStartOfMonth(this.getDateProp()), - this.props.locale - ); - let i = 0; - let isWithinMonth = true; - - while ( - isWithinMonth || - (this.props.fixedHeight && this.props.peekNextMonth && i < CALENDAR_MAX_ROWS) - ) { - weeks.push( - - ); - i++; - currentWeekStart = this.dateHelpers.addWeeks(currentWeekStart, 1); - // It will break on the next week if the week is out of the month - isWithinMonth = this.isWeekInMonth(currentWeekStart); - } - return weeks; - }; - - render() { - const { overrides = {} } = this.props; - const [Month, monthProps] = getOverrides(overrides.Month, StyledMonth); - return {this.renderWeeks()}; - } -} diff --git a/src/datepicker/stateful-calendar.js.flow b/src/datepicker/stateful-calendar.js.flow deleted file mode 100644 index 999bd328a6..0000000000 --- a/src/datepicker/stateful-calendar.js.flow +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import StatefulContainer from './stateful-container.js'; -import Calendar from './calendar.js'; -import type { CalendarPropsT, StatefulDatepickerPropsT } from './types.js'; - -type PropsT = StatefulDatepickerPropsT>; - -class StatefulComponent extends React.Component> { - static defaultProps: PropsT = { - initialState: {}, - stateReducer: (type, nextState) => nextState, - onSelect: () => {}, - }; - - render() { - return ( - - {(extendedProps) => ( - - )} - - ); - } -} - -export default StatefulComponent; diff --git a/src/datepicker/stateful-container.js.flow b/src/datepicker/stateful-container.js.flow deleted file mode 100644 index 34d19b511d..0000000000 --- a/src/datepicker/stateful-container.js.flow +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { STATE_CHANGE_TYPE } from './constants.js'; -import type { - CalendarPropsT, - ContainerStateT, - DatepickerPropsT, - StatefulContainerPropsT, - StateChangeTypeT, - StateReducerT, -} from './types.js'; - -type InputProps = CalendarPropsT | DatepickerPropsT; - -type PropsT = StatefulContainerPropsT, T>; - -class StatefulContainer extends React.Component, ContainerStateT> { - static defaultProps: { stateReducer: StateReducerT } = { - initialState: {}, - stateReducer: (type, nextState) => nextState, - onChange: () => {}, - }; - - constructor(props: PropsT) { - super(props); - const value = props.range ? [] : (null: ?T); - this.state = { value, ...props.initialState }; - } - - onChange: ({ +date: ?T | Array }) => mixed = (data) => { - const { date } = data; - this.internalSetState(STATE_CHANGE_TYPE.change, { value: date }); - - const onChange = this.props.onChange; - if (onChange) { - if (Array.isArray(date)) { - if (date.every(Boolean)) { - // flowlint-next-line unclear-type:off - onChange({ date: ((date: any): Array) }); - } - } else { - onChange({ date }); - } - } - - const onRangeChange = this.props.onRangeChange; - if (onRangeChange) { - onRangeChange({ date }); - } - }; - - internalSetState(type: StateChangeTypeT, changes: ContainerStateT) { - const { stateReducer } = this.props; - this.setState((prevState) => stateReducer(type, changes, prevState)); - } - - render() { - const { children, initialState, stateReducer, ...restProps } = this.props; - return this.props.children({ - ...restProps, - value: this.state.value, - onChange: this.onChange, - }); - } -} - -export default StatefulContainer; diff --git a/src/datepicker/stateful-datepicker.js.flow b/src/datepicker/stateful-datepicker.js.flow deleted file mode 100644 index d6f21014ee..0000000000 --- a/src/datepicker/stateful-datepicker.js.flow +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import StatefulContainer from './stateful-container.js'; -import Datepicker from './datepicker.js'; -import type { StatefulDatepickerPropsT, DatepickerPropsT } from './types.js'; - -type PropsT = StatefulDatepickerPropsT, T>; - -class StatefulComponent extends React.Component> { - static defaultProps: PropsT = { - initialState: {}, - stateReducer: (type, nextState) => nextState, - onChange: () => {}, - }; - render() { - return ( - - {(extendedProps) => ( - - )} - - ); - } -} - -export default StatefulComponent; diff --git a/src/datepicker/styled-components.js.flow b/src/datepicker/styled-components.js.flow deleted file mode 100644 index 91103e12cc..0000000000 --- a/src/datepicker/styled-components.js.flow +++ /dev/null @@ -1,591 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import { styled } from '../styles/index.js'; -import getDayStateCode from './utils/day-state.js'; -import type { SharedStylePropsT, CalendarPropsT } from './types.js'; -import { ORIENTATION, DENSITY, INPUT_ROLE } from './constants.js'; - -/** - * Main component container element - */ -export const StyledInputWrapper = styled<{ - ...SharedStylePropsT, - $separateRangeInputs: boolean, -}>('div', (props) => { - const { $separateRangeInputs } = props; - - return { - width: '100%', - ...($separateRangeInputs ? { display: 'flex', justifyContent: 'center' } : {}), - }; -}); - -export const StyledInputLabel = styled<{}>('div', ({ $theme }) => ({ - ...$theme.typography.LabelMedium, - marginBottom: $theme.sizing.scale300, -})); - -export const StyledStartDate = styled<{}>('div', ({ $theme }) => ({ - width: '100%', - marginRight: $theme.sizing.scale300, -})); - -export const StyledEndDate = styled<{}>('div', ({ $theme }) => ({ - width: '100%', -})); - -/** - * Main component container element - */ -export const StyledRoot = styled('div', (props) => { - const { - $theme: { typography, colors, borders }, - } = props; - return { - ...typography.font200, - color: colors.calendarForeground, - backgroundColor: colors.calendarBackground, - textAlign: 'center', - borderTopLeftRadius: borders.surfaceBorderRadius, - borderTopRightRadius: borders.surfaceBorderRadius, - borderBottomRightRadius: borders.surfaceBorderRadius, - borderBottomLeftRadius: borders.surfaceBorderRadius, - display: 'inline-block', - }; -}); - -export const StyledMonthContainer = styled<{ - $orientation: $PropertyType, 'orientation'>, -}>('div', (props) => { - const { $orientation } = props; - return { - display: 'flex', - flexDirection: $orientation === ORIENTATION.vertical ? 'column' : 'row', - }; -}); - -export const StyledCalendarContainer = styled('div', (props) => { - const { - $theme: { sizing }, - $density, - } = props; - return { - paddingTop: sizing.scale300, - paddingBottom: $density === DENSITY.high ? sizing.scale400 : sizing.scale300, - paddingLeft: sizing.scale500, - paddingRight: sizing.scale500, - }; -}); - -export const StyledSelectorContainer = styled('div', ({ $theme }) => { - const textAlign = $theme.direction === 'rtl' ? 'right' : 'left'; - return { - marginBottom: $theme.sizing.scale600, - paddingLeft: $theme.sizing.scale600, - paddingRight: $theme.sizing.scale600, - textAlign, - }; -}); - -export const StyledCalendarHeader = styled('div', (props) => { - const { - $theme: { typography, borders, colors, sizing }, - $density, - } = props; - return { - ...($density === DENSITY.high ? typography.LabelMedium : typography.LabelLarge), - color: colors.calendarHeaderForeground, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - paddingTop: sizing.scale600, - paddingBottom: sizing.scale300, - paddingLeft: sizing.scale600, - paddingRight: sizing.scale600, - backgroundColor: colors.calendarHeaderBackground, - borderTopLeftRadius: borders.surfaceBorderRadius, - borderTopRightRadius: borders.surfaceBorderRadius, - borderBottomRightRadius: 0, - borderBottomLeftRadius: 0, - // account for the left/right arrow heights - minHeight: - $density === DENSITY.high ? `calc(${sizing.scale800} + ${sizing.scale0})` : sizing.scale950, - }; -}); - -export const StyledMonthHeader = styled('div', (props) => { - return { - color: props.$theme.colors.calendarHeaderForeground, - backgroundColor: props.$theme.colors.calendarHeaderBackground, - whiteSpace: 'nowrap', - }; -}); - -export const StyledMonthYearSelectButton = styled('button', (props) => { - const { - $theme: { typography, colors }, - $isFocusVisible, - $density, - } = props; - return { - ...($density === DENSITY.high ? typography.LabelMedium : typography.LabelLarge), - alignItems: 'center', - backgroundColor: 'transparent', - borderLeftWidth: 0, - borderRightWidth: 0, - borderTopWidth: 0, - borderBottomWidth: 0, - color: colors.calendarHeaderForeground, - cursor: 'pointer', - display: 'flex', - outline: 'none', - ':focus': { - boxShadow: $isFocusVisible ? `0 0 0 3px ${colors.borderAccent}` : 'none', - }, - }; -}); - -export const StyledMonthYearSelectIconContainer = styled<{}>('span', (props) => { - const marginDirection: string = props.$theme.direction === 'rtl' ? 'marginRight' : 'marginLeft'; - return { - alignItems: 'center', - display: 'flex', - [marginDirection]: props.$theme.sizing.scale500, - }; -}); - -function getArrowBtnStyle({ $theme, $disabled, $isFocusVisible }) { - return { - boxSizing: 'border-box', - display: 'flex', - color: $disabled - ? $theme.colors.calendarHeaderForegroundDisabled - : $theme.colors.calendarHeaderForeground, - cursor: $disabled ? 'default' : 'pointer', - backgroundColor: 'transparent', - borderLeftWidth: 0, - borderRightWidth: 0, - borderTopWidth: 0, - borderBottomWidth: 0, - paddingTop: '0', - paddingBottom: '0', - paddingLeft: '0', - paddingRight: '0', - marginBottom: 0, - marginTop: 0, - outline: 'none', - ':focus': $disabled - ? {} - : { - boxShadow: $isFocusVisible ? `0 0 0 3px ${$theme.colors.borderAccent}` : 'none', - }, - }; -} - -export const StyledPrevButton = styled('button', getArrowBtnStyle); - -export const StyledNextButton = styled('button', getArrowBtnStyle); - -export const StyledMonth = styled('div', (props: SharedStylePropsT) => { - return { - display: 'inline-block', - }; -}); - -export const StyledWeek = styled('div', (props) => { - const { - $theme: { sizing }, - } = props; - return { - whiteSpace: 'nowrap', - display: 'flex', - marginBottom: sizing.scale0, - }; -}); - -function generateDayStyles(defaultCode: string, defaultStyle) { - const codeForSM = defaultCode.substr(0, 12) + '1' + defaultCode.substr(12 + 1); - const codeForEM = defaultCode.substr(0, 13) + '1' + defaultCode.substr(13 + 1); - return { - [defaultCode]: defaultStyle, - [codeForSM]: defaultStyle, - [codeForEM]: defaultStyle, - }; -} - -// flowlint-next-line unclear-type:off -function getDayStyles(code, { colors }): any { - const undefinedDayStyle = { - ':before': { content: null }, - ':after': { content: null }, - }; - let defaultDayStyle = undefinedDayStyle; - const disabledDateStyle = { - color: colors.calendarForegroundDisabled, - ':before': { content: null }, - ':after': { content: null }, - }; - const outsideMonthDateStyle = { - color: colors.calendarForegroundDisabled, - ':before': { - borderTopStyle: 'none', - borderBottomStyle: 'none', - borderLeftStyle: 'none', - borderRightStyle: 'none', - backgroundColor: 'transparent', - }, - ':after': { - borderTopLeftRadius: '0%', - borderTopRightRadius: '0%', - borderBottomLeftRadius: '0%', - borderBottomRightRadius: '0%', - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderRightColor: 'transparent', - borderLeftColor: 'transparent', - }, - }; - const highlightedStyle = { - ':before': { content: null }, - }; - const CODE_DISABLED_INDEX = 1; - if (code && code[CODE_DISABLED_INDEX] === '1') { - defaultDayStyle = disabledDateStyle; - } - // See the ./utils/day-state.js file for the description of all available states - // rdhsrSsDeDpSrHpHrRrLsMeMoM - // '000000000000000' - const dayStateStyle = Object.assign( - {}, - // highlighted date - generateDayStyles('001000000000000', { - color: colors.calendarDayForegroundPseudoSelected, - }), - // selected date - generateDayStyles('000100000000000', { - color: colors.calendarDayForegroundSelected, - }), - // selected highlighted date - generateDayStyles('001100000000000', { - color: colors.calendarDayForegroundSelectedHighlighted, - }), - // disabled date - { - '010000000000000': { - color: colors.calendarForegroundDisabled, - ':after': { content: null }, - }, - }, - // disabled highlighted date - { - '011000000000000': { - color: colors.calendarForegroundDisabled, - ':after': { content: null }, - }, - }, - // date outside of the currently displayed month (when peekNextMonth is true) - generateDayStyles('000000000000001', outsideMonthDateStyle), - // Range Datepicker - // range: highlighted date outside of a selected range - generateDayStyles('101000000000000', highlightedStyle), - generateDayStyles('101010000000000', highlightedStyle), - // range: selected date - generateDayStyles('100100000000000', { - color: colors.calendarDayForegroundSelected, - }), - // range: selected highlighted date - // when single date selected in a range - generateDayStyles('101100000000000', { - color: colors.calendarDayForegroundSelectedHighlighted, - ':before': { content: null }, - }), - // range: selected start and end dates are the same - generateDayStyles('100111100000000', { - color: colors.calendarDayForegroundSelected, - ':before': { content: null }, - }), - generateDayStyles('101111100000000', { - color: colors.calendarDayForegroundSelectedHighlighted, - ':before': { content: null }, - }), - // range: selected start date - generateDayStyles('100111000000000', { - color: colors.calendarDayForegroundSelected, - }), - // range: selected end date - generateDayStyles('100110100000000', { - color: colors.calendarDayForegroundSelected, - ':before': { left: null, right: '50%' }, - }), - // range: first selected date while a range is highlighted but no second date selected yet - // highlighted range on the right from the selected - generateDayStyles('100100001010000', { - color: colors.calendarDayForegroundSelected, - }), - // highlighted range on the left from the selected - generateDayStyles('100100001001000', { - color: colors.calendarDayForegroundSelected, - ':before': { left: null, right: '50%' }, - }), - // range: second date in a range that is highlighted but not selected - generateDayStyles('101000001010000', { - ':before': { left: null, right: '50%' }, - }), - { '101000001001000': {} }, - { '101000001001100': {} }, - { '101000001001010': {} }, - // range: pseudo-selected date - generateDayStyles('100010010000000', { - color: colors.calendarDayForegroundPseudoSelected, - ':before': { left: '0', width: '100%' }, - ':after': { content: null }, - }), - // range: pseudo-highlighted date (in a range where only one date is - // selected and second date is highlighted) - { - '101000001100000': { - color: colors.calendarDayForegroundPseudoSelected, - ':before': { - left: '0', - width: '100%', - }, - ':after': { - content: null, - }, - }, - }, - generateDayStyles('100000001100000', { - color: colors.calendarDayForegroundPseudoSelected, - ':before': { - left: '0', - width: '100%', - }, - ':after': { - content: null, - }, - }), - // highlighted start date in a range - generateDayStyles('101111000000000', { - color: colors.calendarDayForegroundSelectedHighlighted, - }), - // highlighted end date in a range - generateDayStyles('101110100000000', { - color: colors.calendarDayForegroundSelectedHighlighted, - ':before': { left: null, right: '50%' }, - }), - // range: pseudo-selected date - generateDayStyles('101010010000000', { - color: colors.calendarDayForegroundPseudoSelectedHighlighted, - ':before': { left: '0', width: '100%' }, - }), - // Range is true Date outside current month (when peekNextMonth is true) - generateDayStyles('100000000000001', outsideMonthDateStyle), - // peekNextMonth is true, date is outside month, start date is selected and range is highlighted is on right - generateDayStyles('100000001010001', outsideMonthDateStyle), - // peekNextMonth is true, date is outside month, start date is selected and range is highlighted is on left - generateDayStyles('100000001001001', outsideMonthDateStyle), - // peekNextMonth is true, date is outside month, range is selected - generateDayStyles('100010000000001', outsideMonthDateStyle) - ); - return dayStateStyle[code] || defaultDayStyle; -} - -export const StyledDay = styled('div', (props) => { - const { - $disabled, - $isFocusVisible, - $isHighlighted, - $peekNextMonth, - $pseudoSelected, - $range, - $selected, - $outsideMonth, - $outsideMonthWithinRange, - $hasDateLabel, - $density, - $hasLockedBehavior, - $selectedInput, - $value, - $theme: { colors, typography, sizing }, - } = props; - const code = getDayStateCode(props); - - let height; - if ($hasDateLabel) { - if ($density === DENSITY.high) { - height = '60px'; - } else { - height = '70px'; - } - } else { - if ($density === DENSITY.high) { - height = '40px'; - } else { - height = '48px'; - } - } - - const [startDate, endDate] = Array.isArray($value) ? $value : [$value, null]; - const oppositeInputIsPopulated = - $selectedInput === INPUT_ROLE.startDate - ? endDate !== null && typeof endDate !== 'undefined' - : startDate !== null && typeof startDate !== 'undefined'; - const shouldHighlightRange = $range && !($hasLockedBehavior && !oppositeInputIsPopulated); - - return ({ - ...($density === DENSITY.high ? typography.ParagraphSmall : typography.ParagraphMedium), - boxSizing: 'border-box', - position: 'relative', - cursor: $disabled || (!$peekNextMonth && $outsideMonth) ? 'default' : 'pointer', - color: colors.calendarForeground, - display: 'inline-block', - width: $density === DENSITY.high ? '42px' : '50px', - height: height, - // setting lineHeight equal to the contents height to vertically center the text - lineHeight: $density === DENSITY.high ? sizing.scale700 : sizing.scale900, - textAlign: 'center', - paddingTop: sizing.scale300, - paddingBottom: sizing.scale300, - paddingLeft: sizing.scale300, - paddingRight: sizing.scale300, - marginTop: 0, - marginBottom: 0, - marginLeft: 0, - marginRight: 0, - outline: 'none', - backgroundColor: 'transparent', - // `transform` creates a stacking context so - // a z-index used on its' children doesn't - // interfere with anything outside the component - transform: 'scale(1)', - ...getDayStyles(code, props.$theme), - // :after pseudo element defines the selected - // or highlighted day's circle styles - ':after': { - zIndex: -1, - content: '""', - boxSizing: 'border-box', - display: 'inline-block', - boxShadow: - $isFocusVisible && (!$outsideMonth || $peekNextMonth) - ? `0 0 0 3px ${colors.borderAccent}` - : 'none', - backgroundColor: $selected - ? colors.calendarDayBackgroundSelectedHighlighted - : $pseudoSelected && $isHighlighted - ? colors.calendarDayBackgroundPseudoSelectedHighlighted - : colors.calendarBackground, - height: $hasDateLabel ? '100%' : $density === DENSITY.high ? '42px' : '50px', - width: '100%', - position: 'absolute', - top: $hasDateLabel ? 0 : '-1px', - left: 0, - paddingTop: sizing.scale200, - paddingBottom: sizing.scale200, - borderLeftWidth: '2px', - borderRightWidth: '2px', - borderTopWidth: '2px', - borderBottomWidth: '2px', - borderLeftStyle: 'solid', - borderRightStyle: 'solid', - borderTopStyle: 'solid', - borderBottomStyle: 'solid', - borderTopColor: colors.borderSelected, - borderBottomColor: colors.borderSelected, - borderRightColor: colors.borderSelected, - borderLeftColor: colors.borderSelected, - borderTopLeftRadius: $hasDateLabel ? sizing.scale800 : '100%', - borderTopRightRadius: $hasDateLabel ? sizing.scale800 : '100%', - borderBottomLeftRadius: $hasDateLabel ? sizing.scale800 : '100%', - borderBottomRightRadius: $hasDateLabel ? sizing.scale800 : '100%', - ...(getDayStyles(code, props.$theme)[':after'] || {}), - ...($outsideMonthWithinRange ? { content: null } : {}), - }, - ...(shouldHighlightRange - ? { - // :before pseudo element defines a grey background style that extends - // the selected/highlighted day's circle and spans through a range - ':before': { - zIndex: -1, - content: '""', - boxSizing: 'border-box', - display: 'inline-block', - backgroundColor: colors.backgroundTertiary, - position: 'absolute', - height: '100%', - width: '50%', - top: 0, - left: '50%', - borderTopWidth: '2px', - borderBottomWidth: '2px', - borderLeftWidth: '0', - borderRightWidth: '0', - borderTopStyle: 'solid', - borderBottomStyle: 'solid', - borderLeftStyle: 'solid', - borderRightStyle: 'solid', - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - borderLeftColor: 'transparent', - borderRightColor: 'transparent', - ...(getDayStyles(code, props.$theme)[':before'] || {}), - ...($outsideMonthWithinRange - ? { - backgroundColor: colors.backgroundTertiary, - left: '0', - width: '100%', - content: '""', - } - : {}), - }, - } - : // a hack to make flow happy, otherwise it complains about complexity - // flowlint-next-line unclear-type:off - ({}: any)), - }: {}); -}); - -export const StyledDayLabel = styled('div', (props) => { - const { - $theme: { typography, colors }, - $selected, - } = props; - return { - ...typography.ParagraphXSmall, - color: $selected ? colors.contentInverseTertiary : colors.contentTertiary, - }; -}); - -export const StyledWeekdayHeader = styled('div', (props) => { - const { - $theme: { typography, colors, sizing }, - $density, - } = props; - return ({ - ...typography.LabelMedium, - color: colors.contentTertiary, - boxSizing: 'border-box', - position: 'relative', - cursor: 'default', - display: 'inline-block', - width: $density === DENSITY.high ? '42px' : '50px', - height: $density === DENSITY.high ? '40px' : '48px', - textAlign: 'center', - // setting lineHeight equal to the contents height to vertically center the text - lineHeight: sizing.scale900, - paddingTop: sizing.scale300, - paddingBottom: sizing.scale300, - paddingLeft: sizing.scale200, - paddingRight: sizing.scale200, - marginTop: 0, - marginBottom: 0, - marginLeft: 0, - marginRight: 0, - backgroundColor: 'transparent', - }: {}); -}); diff --git a/src/datepicker/types.js.flow b/src/datepicker/types.js.flow deleted file mode 100644 index 409d21906c..0000000000 --- a/src/datepicker/types.js.flow +++ /dev/null @@ -1,389 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -/* eslint-disable flowtype/generic-spacing */ -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import type { SizeT } from '../input/types.js'; -import { - INPUT_ROLE, - ORIENTATION, - RANGED_CALENDAR_BEHAVIOR, - STATE_CHANGE_TYPE, - DENSITY, -} from './constants.js'; -import type { DateIOAdapter } from './utils/types.js'; -import type { - TimePickerPropsT as TimePickerPropsTBase, - TimePickerStateT as TimePickerStateTBase, -} from '../timepicker/types.js'; - -import type { OptionT } from '../select/index.js'; - -// flowlint-next-line unclear-type:off -type LocaleT = any; // see https://github.com/date-fns/date-fns/blob/master/src/locale/index.js.flow - -export type DensityT = $Keys; - -export type DatepickerOverridesT = { - Root?: OverrideT, - /** Override for reused Select component. QuickSelect is **not a styled element** but a react component that can be replaced */ - QuickSelect?: OverrideT, - QuickSelectContainer?: OverrideT, - /** Override for reused Select component. QuickSelectFormControl is **not a styled element** but a react component that can be replaced */ - QuickSelectFormControl?: OverrideT, - /** Override for reused TimePicker component. TimeSelect is **not a styled element** but a react component that can be replaced */ - TimeSelect?: OverrideT, - TimeSelectContainer?: OverrideT, - /** Override for reused Select component. TimeSelectFormControl is **not a styled element** but a react component that can be replaced */ - TimeSelectFormControl?: OverrideT, - CalendarContainer?: OverrideT, - CalendarHeader?: OverrideT, - PrevButton?: OverrideT, - PrevButtonIcon?: OverrideT, - NextButton?: OverrideT, - NextButtonIcon?: OverrideT, - MonthContainer?: OverrideT, - MonthHeader?: OverrideT, - MonthYearSelectButton?: OverrideT, - MonthYearSelectIconContainer?: OverrideT, - MonthYearSelectPopover?: OverrideT, - MonthYearSelectStatefulMenu?: OverrideT, - WeekdayHeader?: OverrideT, - Month?: OverrideT, - Week?: OverrideT, - Day?: OverrideT, - DayLabel?: OverrideT, - /** Override for reused Input component. Input is **not a styled element** but a react component that can be replaced */ - Input?: OverrideT, - InputWrapper?: OverrideT, - /** Override for reused Popover component. Popover is **not a styled element** but a react component that can be replaced */ - Popover?: OverrideT, - StartDate?: OverrideT, - EndDate?: OverrideT, - InputLabel?: OverrideT, - ButtonDock?: OverrideT, - PrimaryButton?: OverrideT, - SecondaryButton?: OverrideT, -}; - -export type DayPropsT = { - disabled: boolean, - date: T, - dateLabel: ?(day: T) => React.Node, - density: DensityT, - filterDate: ?(day: T) => boolean, - highlightedDate: ?T, - includeDates: ?Array, - highlighted: boolean, - range: boolean, - hasLockedBehavior: boolean, - selectedInput: InputRoleT, - focusedCalendar: boolean, - locale: ?LocaleT, - maxDate: ?T, - adapter: DateIOAdapter, - minDate: ?T, - month: ?number, - onBlur: ({ event: Event, date: T }) => mixed, - onFocus: ({ event: Event, date: T }) => mixed, - onSelect: ({ date: T | Array }) => mixed, - onClick: ({ event: Event, date: T }) => mixed, - onMouseOver: ({ event: Event, date: T }) => mixed, - onMouseLeave: ({ event: Event, date: T }) => mixed, - overrides?: DatepickerOverridesT, - peekNextMonth: boolean, - value: ?T | $ReadOnlyArray, -}; - -export type DayStateT = { - isHovered: boolean, - isFocusVisible: boolean, -}; - -export type WeekPropsT = { - date: T, - dateLabel: ?(date: T) => React.Node, - density: DensityT, - excludeDates: ?Array, - filterDate: ?(day: T) => boolean, - // highlighted while keyboard navigating or hovered - highlightedDate: ?T, - includeDates: ?Array, - focusedCalendar: boolean, - range?: boolean, - locale: ?LocaleT, - maxDate: ?T, - minDate: ?T, - adapter: DateIOAdapter, - month: ?number, - onDayBlur: ({ date: T, event: Event }) => mixed, - onDayClick: ({ date: T, event: Event }) => mixed, - onDayFocus: ({ date: T, event: Event }) => mixed, - onDayMouseOver: ({ date: T, event: Event }) => mixed, - onDayMouseLeave: ({ date: T, event: Event }) => mixed, - onChange?: ({ +date: ?T | Array }) => mixed, - overrides?: DatepickerOverridesT, - peekNextMonth: boolean, - value: ?T | $ReadOnlyArray, - hasLockedBehavior: boolean, - selectedInput?: InputRoleT, -}; - -export type MonthPropsT = { - ...WeekPropsT, - fixedHeight?: boolean, -}; - -export type CalendarInternalState = { - highlightedDate: T, - focused: boolean, - date: T, - quickSelectId: ?string, - rootElement: ?HTMLElement, - time: Array, -}; - -export type CalendarPropsT = { - /** Defines if the calendar is set to be focused on an initial render. */ - autoFocusCalendar?: boolean, - /** Determines the density of the calendar */ - density?: DensityT, - /** A list of dates to disable. */ - excludeDates?: ?Array, - /** Display select for quickly choosing date ranges. `range` must be true as well. */ - quickSelect?: boolean, - /** Array of custom options displayed in the quick select. Overrides default options if provided. */ - quickSelectOptions?: Array>, - /** A filter function that is called to check the disabled state of a day. If `false` is returned the day is considered to be disabled. */ - filterDate?: ?(day: T) => boolean, - /** A function that is called with the current date to render the label text under that day on the calendar. */ - dateLabel?: ?(day: T) => React.Node, - /** Indicates a highlighted date on hover and keyboard navigation */ - highlightedDate?: ?T, - /** A list of selectable dates. */ - includeDates?: ?Array, - /** Defines if a range of dates can be selected. */ - range?: boolean, - /** Determines whether startDate and endDate should be updated independently of eachother */ - hasLockedBehavior?: boolean, - /** A locale object. See `date-fns` for more details https://github.com/date-fns/date-fns/tree/master/src/locale. */ - locale?: ?LocaleT, - /** A max date that is selectable. */ - maxDate?: ?T, - /** A min date that is selectable. */ - minDate?: ?T, - adapter?: DateIOAdapter, - /** A number of months rendered in the calendar. */ - monthsShown?: number, - /** Day's `click` event handler. */ - onDayClick?: ({ date: T, event: Event }) => mixed, - /** Day's `focus` event handler. */ - onDayFocus?: ({ date: T, event: Event }) => mixed, - /** Day's `mouseover` event handler. */ - onDayMouseOver?: ({ date: T, event: Event }) => mixed, - /** Day's `mouseleave` event handler. */ - onDayMouseLeave?: ({ date: T, event: Event }) => mixed, - /** Event handler that is called when the current rendered month is changed. */ - onMonthChange?: ({ date: T }) => mixed, - /** Event handler that is called when the current rendered month's year is changed. */ - onYearChange?: ({ date: T }) => mixed, - /** Event handler that is called when a new date is selected. */ - onChange?: ({ +date: ?T | Array }) => mixed, - /** Event handler that is called when a selection is made using the quick select menu. */ - onQuickSelectChange?: (option?: QuickSelectOption) => mixed, - /** Sets the orientation of the calendar when multiple months are displayed */ - orientation?: $Values, - overrides?: DatepickerOverridesT, - /** Defines if dates outside of the range of the current month are displayed. */ - peekNextMonth?: boolean, - /** Determines if `TimePicker` component will be enabled for start time */ - timeSelectStart?: boolean, - /** Determines if `TimePicker` component will be enabled for end time */ - timeSelectEnd?: boolean, - /** Defines if tabbing inside the calendar is circled within it. */ - trapTabbing?: boolean, - /** Currently selected date. */ - value?: ?T | $ReadOnlyArray, - fixedHeight?: boolean, - /** Determines whether user clicked startDate or endDate input to trigger calendar open */ - selectedInput?: InputRoleT, - /** Primary button on the action dock */ - primaryButton?: { - label: React.Node, - onClick: () => mixed, - }, - /** Secondary button on the action dock */ - secondaryButton?: { - label: React.Node, - onClick: () => mixed, - }, -}; - -export type HeaderPropsT = CalendarPropsT & { - date: T, - order: number, -}; - -export type QuickSelectOption = { - id: string, - beginDate: T, - endDate?: T, -}; - -export type DatepickerPropsT = { - ...CalendarPropsT, - 'aria-label'?: string, - 'aria-labelledby'?: string, - 'aria-describedby'?: ?string, - disabled?: boolean, - size?: SizeT, - /** Renders UI in 'error' state. */ - error?: boolean, - positive?: boolean, - placeholder?: string, - required?: boolean, - clearable?: boolean, - displayValueAtRangeIndex?: number, - formatDisplayValue?: (date: ?T | $ReadOnlyArray, formatString: string) => string, - formatString: string, - /** Where to mount the popover */ - mountNode?: HTMLElement, - /** When single picker, fn is always called. When range picker, fn is called when start and end date are selected. */ - onChange?: ({ date: ?T | Array }) => mixed, - /** Called when calendar is closed */ - onClose?: () => mixed, - /** Called when calendar is opened */ - onOpen?: () => mixed, - /** When single picker, fn is always called. When range picker, fn is called when either start or end date changes. */ - onRangeChange?: ({ +date: ?T | Array }) => mixed, - mask?: string | null, - /** Determines whether startDate and endDate should be updated independently of eachother */ - rangedCalendarBehavior?: RangedCalendarBehaviorT, - /** Determines if startDate and endDate should be separated into two input fields. Ignored if `range` is not true. */ - separateRangeInputs?: boolean, - startDateLabel?: string, - endDateLabel?: string, - value?: ?T | $ReadOnlyArray, -}; - -export type SharedStylePropsT = { - // flowlint-next-line unclear-type:off - $date: any, - $disabled: ?boolean, - $density: DensityT, - $endDate: ?boolean, - $endOfMonth: ?boolean, - $isHighlighted: ?boolean, - $isHovered: ?boolean, - $isFocusVisible: ?boolean, - $month: ?number, - $outsideMonth: ?boolean, - $outsideMonthWithinRange: ?boolean, - $peekNextMonth: ?boolean, - $pseudoHighlighted: ?boolean, - $pseudoSelected: ?boolean, - $selected: ?boolean, - $startDate: ?boolean, - $startOfMonth: ?boolean, - $range: ?boolean, - $hasRangeHighlighted: ?boolean, - $hasRangeOnRight: ?boolean, - $hasRangeSelected: ?boolean, - $hasLockedBehavior: boolean, - $selectedInput: InputRoleT, - $value: Date | Array, - $order: ?number, - $hasDateLabel: ?boolean, -}; - -export type StateChangeTypeT = ?$Values; - -export type ContainerStateT = { - /** Selected `Date`. If `range` is set, `value` is an array of 2 values. */ - value?: ?T | $ReadOnlyArray, -}; - -export type NavigationContainerStateT = { - // indicates a highlighted date on hover and keyboard navigation - highlightedDate?: ?T, - // used to disable keyboard navigation when a month or year select - // dropdown is opened - isActive?: boolean, - // last remembered highlighted date to restore - // when keyboard navigating after a mouse moved off the cal and reset - // highlightedDate value - lastHighlightedDate?: T, -}; - -export type StateReducerT = ( - stateType: StateChangeTypeT, - nextState: ContainerStateT, - currentState: ContainerStateT -) => ContainerStateT; - -export type StatefulContainerPropsT = { - children: (PropsT) => React.Node, - /** Initial state of an uncontrolled datepicker component. */ - initialState: ContainerStateT, - /** A state change handler. */ - stateReducer: StateReducerT, - /** When single picker, fn is called when date/time is selected. When range picker, fn is called when both start and end are selected. */ - onChange?: ({ date: ?T | Array }) => mixed, - /** When single picker, fn is called when date/time is selected. When range picker, fn is called when either start or end date changes. */ - onRangeChange?: ({ +date: ?T | Array }) => mixed, - adapter?: DateIOAdapter, - /** Should the date value be stored as an array or single value. */ - range?: boolean, -}; - -export type StatefulDatepickerPropsT = $Diff< - StatefulContainerPropsT, - { - children: (PropsT) => React.Node, - } ->; - -export type TimePickerPropsT = TimePickerPropsTBase; -export type TimePickerStateT = TimePickerStateTBase; - -export type TimezonePickerStateT = { - /** List of timezones from the IANA database. */ - timezones: OptionT[], - /** Value provided to the select component. */ - value: ?string, -}; -export type TimezonePickerPropsT = { - /** - * If not provided, defaults to new Date(). Important to note that the timezone picker only - * displays options related to the provided date. Take Pacific Time for example. On March 9th, - * Pacific Time equates to the more specific Pacific Standard Time. On March 10th, it operates on - * Pacific Daylight Time. The timezone picker will never display PST and PDT together. If you need - * exact specificity, provide a date. Otherwise it will default to the relevant timezone at render. - */ - date?: Date, - /** - * Customize the option's label. Useful for translations and optionally mapping from - * 'America/Los_Angeles' to 'Pacific Time'. - */ - mapLabels?: (OptionT) => React.Node, - /** Callback for when the timezone selection changes. */ - onChange?: (value: ?{ id: string, label: string, offset: number }) => mixed, - overrides?: { Select?: OverrideT }, - /** - * Optional value that can be provided to fully control the component. If not provided, - * TimezonePicker will manage state internally. - */ - value?: ?string, - disabled?: boolean, - error?: boolean, - positive?: boolean, -}; - -export type InputRoleT = ?$Values; - -export type RangedCalendarBehaviorT = ?$Values; diff --git a/src/datepicker/utils/calendar-header-helpers.js.flow b/src/datepicker/utils/calendar-header-helpers.js.flow deleted file mode 100644 index 343b3666ca..0000000000 --- a/src/datepicker/utils/calendar-header-helpers.js.flow +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import { DEFAULT_MONTHS } from '../constants.js'; - -export type OptionT = { - id: string, - label: string, - disabled?: boolean, -}; - -type GetMonthItemsArgsT = { - filterMonthsList: number[] | null, - formatMonthLabel: (number) => string, -}; - -const getDefaultMonthItems = (formatMonthLabel: (number) => string) => - DEFAULT_MONTHS.map((month) => ({ - id: month.toString(), - label: formatMonthLabel(month), - })); - -export const filterMonthItems = (monthItems: OptionT[], filterList: number[]) => - monthItems.map((month) => { - if (!filterList.includes(Number(month.id))) { - return { - ...month, - disabled: true, - }; - } - return month; - }); - -export const getFilteredMonthItems = ({ - filterMonthsList, - formatMonthLabel, -}: GetMonthItemsArgsT) => { - let monthItems = getDefaultMonthItems(formatMonthLabel); - - if (filterMonthsList) { - monthItems = filterMonthItems(monthItems, filterMonthsList); - } - - return monthItems; -}; diff --git a/src/datepicker/utils/date-fns-adapter.js.flow b/src/datepicker/utils/date-fns-adapter.js.flow deleted file mode 100644 index 1e29453a7f..0000000000 --- a/src/datepicker/utils/date-fns-adapter.js.flow +++ /dev/null @@ -1,13 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import DateFnsUtils from '@date-io/date-fns'; -import type { DateIOAdapter } from './types.js'; - -const adapter: DateIOAdapter = new DateFnsUtils({}); - -export default adapter; diff --git a/src/datepicker/utils/date-helpers.js.flow b/src/datepicker/utils/date-helpers.js.flow deleted file mode 100644 index 616a7fa81e..0000000000 --- a/src/datepicker/utils/date-helpers.js.flow +++ /dev/null @@ -1,430 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import type { DateIOAdapter, DateInput, AdapterOptions } from './types.js'; - -const MINUTE = 60; -const HOUR = MINUTE * 60; - -class DateHelpers { - adapter: DateIOAdapter; - constructor(adapter: DateIOAdapter) { - this.adapter = this.cloneAdapter(adapter); - } - cloneAdapter: (DateIOAdapter, ?(AdapterOptions) => AdapterOptions) => DateIOAdapter = ( - adapter, - updateOptionsBase - ) => { - const adapterMap = { - // all utils classes set the arguments passed into their constructor as public members in some way - // it just varies by class, most just set formats and locale, but this handles the exceptions - MomentUtils: { - formats: { - monthNumber: 'M', - dayOfMonthNumber: 'D', - fullOrdinalWeek: 'dddd, MMMM Do YYYY', - slashDate: 'YYYY/MM/DD', - weekday: 'dddd', - // moment does not have a similar 'single character' weekday format like the other libraries - // the format below will only supply two character abbreviations. - weekdaymin: 'dd', - quarter: '[Q]Q', - }, - }, - DateFnsUtils: { - formats: { - monthNumber: 'M', - dayOfMonthNumber: 'd', - weekday: 'EEEE', - weekdaymin: 'EEEEEE', - slashDate: 'yyyy/MM/dd', - fullOrdinalWeek: 'EEEE, MMMM do yyyy', - quarter: 'QQQ', - }, - }, - LuxonUtils: { - formats: { - monthNumber: 'M', - dayOfMonthNumber: 'd', - weekday: 'EEEE', - weekdaymin: 'EEEEE', - slashDate: 'yyyy/MM/dd', - fullOrdinalWeek: 'EEEE, MMMM dd yyyy', - quarter: 'Qq', - }, - }, - }; - const defaultGetOptions = (instance) => ({ - formats: instance.formats, - locale: instance.locale, - }); - const updateOptions = updateOptionsBase || defaultGetOptions; - const UtilsClass = adapter.constructor; - const className = adapter.constructor.name; - // This ensures that if the adapter class isn't - // supported it just falls back the default formats - - // NOTE: in e2e tests playwright seems to add - // a JSHandle wrapping class to all objects - // so className always resolves to JSHandle:e - // and if falls back to the default - // if we want to test other adapter implementation - // in e2e tests down the road, we're going to have - // to figure that out - const { getOptions = defaultGetOptions, formats } = - adapterMap[className] || adapterMap.DateFnsUtils; - const options = getOptions(adapter); - return new UtilsClass( - Object.assign( - {}, - updateOptions( - Object.assign({}, options, { - formats: Object.assign({}, options.formats, formats), - }) - ) - ) - ); - }; - // flowlint-next-line unclear-type:off - format: (T, string, any) => string = (date, format, locale) => { - const adapter = locale ? this.getAdapterWithNewLocale(locale) : this.adapter; - - return adapter.format(date, format); - }; - getAdapterWithNewLocale: (mixed) => DateIOAdapter = (locale) => { - return this.cloneAdapter(this.adapter, (options) => ({ ...options, locale })); - }; - date: (DateInput | void) => T = (date) => this.adapter.date(date); - dateToSeconds: (T) => number = (date) => { - const seconds = this.adapter.getSeconds(date); - const minutes = this.adapter.getMinutes(date) * MINUTE; - const hours = this.adapter.getHours(date) * HOUR; - return seconds + minutes + hours; - }; - secondsToHourMinute: (number) => [number, number] = (seconds) => { - const d = this.adapter.toJsDate(this.adapter.date(seconds * 1000)); - return [d.getUTCHours(), d.getUTCMinutes()]; - }; - differenceInCalendarMonths: (T, T) => number = (fromDate, toDate) => { - var yearDiff = this.adapter.getYear(fromDate) - this.adapter.getYear(toDate); - var monthDiff = this.adapter.getMonth(fromDate) - this.adapter.getMonth(toDate); - return yearDiff * 12 + monthDiff; - }; - // flowlint-next-line unclear-type:off - getStartOfWeek: (T, any) => T = (date, locale) => { - const adapter = locale ? this.getAdapterWithNewLocale(locale) : this.adapter; - // rewrapping this date here ensures that the locale will be taken into account in all adapters - return adapter.startOfWeek(adapter.date(date)); - }; - // flowlint-next-line unclear-type:off - formatDate: (T, string, any) => string = (date, formatString, locale) => { - const adapter = locale ? this.getAdapterWithNewLocale(locale) : this.adapter; - return adapter.formatByString(date, formatString); - }; - // flowlint-next-line unclear-type:off - getWeekdayMinInLocale: (T, any) => string = (date, locale) => { - return this.getAdapterWithNewLocale(locale).format(date, 'weekdaymin'); - }; - // flowlint-next-line unclear-type:off - getMonthInLocale: (number, any) => string = (monthNumber, locale) => { - const localeAdapter = this.getAdapterWithNewLocale(locale); - return localeAdapter.format(localeAdapter.setMonth(localeAdapter.date(), monthNumber), 'month'); - }; - // flowlint-next-line unclear-type:off - getWeekdayInLocale: (T, any) => string = (date, locale) => { - return this.getAdapterWithNewLocale(locale).format(date, 'weekday'); - }; - // flowlint-next-line unclear-type:off - getQuarterInLocale: (number, any) => string = (quarterNumber, locale) => { - const localeAdapter = this.getAdapterWithNewLocale(locale); - return localeAdapter.format( - localeAdapter.setMonth(localeAdapter.date(), quarterNumber * 3), - 'quarter' - ); - }; - getEndOfWeek: (T) => T = (date) => { - return this.adapter.endOfWeek(date); - }; - getDay: (T) => number = (date) => { - return Number(this.adapter.formatByString(date, 'e')) - 1; - }; - addWeeks: (T, number) => T = (date, weekNumber) => { - return this.adapter.addDays(date, weekNumber * 7); - }; - subWeeks: (T, number) => T = (date, weekNumber) => { - return this.addWeeks(date, weekNumber * -1); - }; - addYears: (T, number) => T = (date, yearNumber) => { - return this.adapter.addMonths(date, yearNumber * 12); - }; - subYears: (T, number) => T = (date, yearNumber) => { - return this.addYears(date, yearNumber * -1); - }; - isSameYear: (?T, ?T) => boolean = (fromDate, toDate) => { - if (fromDate && toDate) { - return this.adapter.isSameYear(fromDate, toDate); - } - return false; - }; - isStartOfMonth: (T) => boolean = (date) => { - return this.adapter.isSameDay(date, this.adapter.startOfMonth(date)); - }; - isEndOfMonth: (T) => boolean = (date) => { - return this.adapter.isSameDay(date, this.adapter.endOfMonth(date)); - }; - isDayInRange: (T, T, T) => boolean = (date, startDate, endDate) => { - return this.adapter.isWithinRange(date, [startDate, endDate]); - }; - isSameDay: (?T, ?T) => boolean = (fromDate, toDate) => { - if (fromDate && toDate) { - return this.adapter.isSameDay(fromDate, toDate); - } - return false; - }; - isSameMonth: (?T, ?T) => boolean = (fromDate, toDate) => { - if (fromDate && toDate) { - return this.adapter.isSameMonth(fromDate, toDate); - } - return false; - }; - dateRangeIncludesDates: (Array, ?Array) => boolean = (dateRange, dates) => { - const [startDate, endDate] = dateRange; - if (startDate && endDate && Array.isArray(dates) && dates.length) { - for (let i = 0; i < dates.length; i++) { - const day = dates[i]; - if (this.isDayInRange(day, startDate, endDate)) { - return true; - } - } - } - return false; - }; - subDays: (T, number) => T = (date, days) => { - return this.adapter.addDays(date, days * -1); - }; - subMonths: (T, number) => T = (date, months) => { - return this.adapter.addMonths(date, months * -1); - }; - min: (Array) => T = (dates) => { - return dates.reduce((minDate, date) => { - return this.adapter.isBefore(date, minDate) ? date : minDate; - }); - }; - max: (Array) => T = (dates) => { - return dates.reduce((maxDate, date) => { - return this.adapter.isAfter(date, maxDate) ? date : maxDate; - }); - }; - getEffectiveMinDate: ({ minDate: ?T, includeDates: ?Array }) => T = ({ - minDate, - includeDates, - }) => { - if (includeDates && minDate) { - let minDates = includeDates.filter((includeDate) => - this.isOnOrAfterDay(includeDate, minDate) - ); - return this.min(minDates); - } else if (includeDates && includeDates.length) { - return this.min(includeDates); - } else if (!(includeDates && includeDates.length) && minDate) { - return minDate; - } - // this condition can't ever be reached - // but flow isn't smart enough to see that all of the conditions are covered - return this.adapter.date(); - }; - getEffectiveMaxDate: ({ maxDate: ?T, includeDates: ?Array }) => T = ({ - maxDate, - includeDates, - }) => { - if (includeDates && maxDate) { - let maxDates = includeDates.filter((includeDate) => - this.isOnOrBeforeDay(includeDate, maxDate) - ); - return this.max(maxDates); - } else if (includeDates) { - return this.max(includeDates); - } else if (!includeDates && maxDate) { - return maxDate; - } - // this condition can't ever be reached - // but flow isn't smart enough to see that all of the conditions are covered - return this.adapter.date(); - }; - monthDisabledBefore: (T, { minDate: ?T, includeDates: ?Array }) => boolean = ( - day, - { minDate, includeDates } = {} - ) => { - const previousMonth = this.subMonths(day, 1); - return ( - (!!minDate && this.differenceInCalendarMonths(minDate, previousMonth) > 0) || - (!!includeDates && - includeDates.every( - (includeDate) => this.differenceInCalendarMonths(includeDate, previousMonth) > 0 - )) || - false - ); - }; - monthDisabledAfter: (T, { maxDate: ?T, includeDates: ?Array }) => boolean = ( - day, - { maxDate, includeDates } = {} - ) => { - const nextMonth = this.adapter.addMonths(day, 1); - return ( - (!!maxDate && this.differenceInCalendarMonths(nextMonth, maxDate) > 0) || - (!!includeDates && - includeDates.every( - (includeDate) => this.differenceInCalendarMonths(nextMonth, includeDate) > 0 - )) || - false - ); - }; - setDate: (T, number) => T = (date, dayNumber) => { - const startOfMonthNoTime = this.adapter.startOfMonth(date); - const startOfMonthHoursAndMinutes = this.adapter.mergeDateAndTime(startOfMonthNoTime, date); - const startOfMonth = this.adapter.setSeconds( - startOfMonthHoursAndMinutes, - this.adapter.getSeconds(date) - ); - return this.adapter.addDays(startOfMonth, dayNumber - 1); - }; - getDate: (T) => number = (date) => Number(this.adapter.format(date, 'dayOfMonthNumber')); - applyDateToTime: (?T, T) => T = (time, date) => { - if (!time) return date; - const yearNumber = this.adapter.getYear(date); - const monthNumber = this.adapter.getMonth(date); - const dayNumber = this.getDate(date); - const yearDate = this.adapter.setYear(time, yearNumber); - const monthDate = this.adapter.setMonth(yearDate, monthNumber); - return this.setDate(monthDate, dayNumber); - }; - applyTimeToDate: (?T, T) => T = (date, time) => { - if (!date) return time; - return this.adapter.setSeconds(this.adapter.mergeDateAndTime(date, time), 0); - }; - isDayDisabled: ( - T, - { - minDate: ?T, - maxDate: ?T, - excludeDates: ?Array, - includeDates: ?Array, - filterDate: ?(day: T) => boolean, - } - ) => boolean = (day, { minDate, maxDate, excludeDates, includeDates, filterDate } = {}) => { - return ( - this.isOutOfBounds(day, { minDate, maxDate }) || - (excludeDates && - excludeDates.some((excludeDate) => this.adapter.isSameDay(day, excludeDate))) || - (includeDates && - !includeDates.some((includeDate) => this.adapter.isSameDay(day, includeDate))) || - (filterDate && !filterDate(day)) || - false - ); - }; - //Tue Apr 12 2011 00:00:00 GMT-0500, Tue Apr 12 2011 11:21:31 GMT-0500 - isOnOrAfterDay: (T, T) => boolean = (fromDate, toDate) => { - if (this.adapter.isSameDay(fromDate, toDate)) { - return true; - } - return this.adapter.isAfter(fromDate, toDate); - }; - isOnOrBeforeDay: (T, T) => boolean = (fromDate, toDate) => { - if (this.adapter.isSameDay(fromDate, toDate)) { - return true; - } - return this.adapter.isBefore(fromDate, toDate); - }; - isOutOfBounds: (T, { minDate: ?T, maxDate: ?T }) => boolean = ( - day, - { minDate, maxDate } = {} - ) => { - return ( - (!!minDate && !this.isOnOrAfterDay(day, minDate)) || - (!!maxDate && !this.isOnOrBeforeDay(day, maxDate)) - ); - }; - // flowlint-next-line unclear-type:off - parseString: (string, string, ?any) => T = (string, formatString, locale) => { - const adapter = locale ? this.getAdapterWithNewLocale(locale) : this.adapter; - - return adapter.parse(string, formatString); - }; - // flowlint-next-line unclear-type:off - parse: (string, string, ?any) => T = (string, format, locale) => { - const adapter = locale ? this.getAdapterWithNewLocale(locale) : this.adapter; - - return adapter.parse(string, adapter.formats[format]); - }; - setMilliseconds: (T, number) => T = (date, milliseconds) => { - return this.adapter.date( - this.adapter.getSeconds(this.adapter.startOfDay(date)) * 1000 + milliseconds - ); - }; - set: ( - T, - values: { - year?: number, - date?: number, - month?: number, - hours?: number, - minutes?: number, - seconds?: number, - } - ) => T = (date, values) => { - let updatedDate = date; - if (values.year != null) { - updatedDate = this.setYear(updatedDate, values.year); - } - - if (values.month != null) { - updatedDate = this.setMonth(updatedDate, values.month); - } - - if (values.date != null) { - updatedDate = this.setDate(updatedDate, Number(values.date)); - } - - if (values.hours != null) { - updatedDate = this.setHours(updatedDate, Number(values.hours)); - } - - if (values.minutes != null) { - updatedDate = this.setMinutes(updatedDate, Number(values.minutes)); - } - - if (values.seconds != null) { - updatedDate = this.setSeconds(updatedDate, Number(values.seconds)); - } - - return updatedDate; - }; - getQuarter: (T) => number = (date) => { - return Math.floor(this.getMonth(date) / 3) + 1; - }; - setSeconds: (T, number) => T = (date, seconds) => this.adapter.setSeconds(date, seconds); - setMinutes: (T, number) => T = (date, minutes) => this.adapter.setMinutes(date, minutes); - setHours: (T, number) => T = (date, hours) => this.adapter.setHours(date, hours); - setMonth: (T, number) => T = (date, monthNumber) => this.adapter.setMonth(date, monthNumber); - setYear: (T, number) => T = (date, yearNumber) => this.adapter.setYear(date, yearNumber); - getMinutes: (T) => number = (date) => this.adapter.getMinutes(date); - getHours: (T) => number = (date) => this.adapter.getHours(date); - getMonth: (T) => number = (date) => this.adapter.getMonth(date); - getYear: (T) => number = (date) => this.adapter.getYear(date); - getStartOfMonth: (T) => T = (date) => this.adapter.startOfMonth(date); - getEndOfMonth: (T) => T = (date) => this.adapter.endOfMonth(date); - addDays: (T, number) => T = (date, days) => this.adapter.addDays(date, days); - addMonths: (T, number) => T = (date, months) => this.adapter.addMonths(date, months); - isBefore: (T, T) => boolean = (fromDate, toDate) => this.adapter.isBefore(fromDate, toDate); - isAfter: (T, T) => boolean = (fromDate, toDate) => this.adapter.isAfter(fromDate, toDate); - isEqual: (T, T) => boolean = (fromDate, toDate) => this.adapter.isEqual(fromDate, toDate); - isValid: (mixed) => boolean = (possibleDate) => { - return this.adapter.isValid(possibleDate); - }; -} - -export default DateHelpers; diff --git a/src/datepicker/utils/day-state.js.flow b/src/datepicker/utils/day-state.js.flow deleted file mode 100644 index 8040abea0f..0000000000 --- a/src/datepicker/utils/day-state.js.flow +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import type { SharedStylePropsT } from '../types.js'; - -/** - r == range - date range can be selected - d == disabled - disabled date - h == highlighted - currently highlighted date, the highlight is triggered on hover or focus - mO == hovered (mouse-over) - currently hovered date - s == selected - selected date, in a range both start and end date are marked as `selected` - rS == range-selected - when start and end dates of a range are set - sD == start-date - selected start date of the range - eD == end-date - selected end date of the range - pS == pseudo-selected - any date between two selected dates in a range - rH == range-highlighed - when only a single date of a range selected and the second date is highlighted but not yet selected - pH == pseudo-highlighted - any date between a selected date in a range and the currently highlighted date (case when only one date selected in a range case) - rR == range-on-right - the range-highlighed case with the highlighed date is after the selected one - rL == range-on-left - the range-highlighed case with the highlighed date is before the selected one - sM == start-of-month - the first day of the month - eM == end-of-month - the last day of the month - oM == outside-month - date outside of currently displayed month (when peekNextMonth is set to true) - */ - -// r d h s rS sD eD pS rH pH rR rL sM eM oM -// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - -export default function getDayStateCode(props: SharedStylePropsT) { - const { - $range = false, - $disabled = false, - $isHighlighted = false, - $isHovered = false, - $selected = false, - $hasRangeSelected = false, - $startDate = false, - $endDate = false, - $pseudoSelected = false, - $hasRangeHighlighted = false, - $pseudoHighlighted = false, - $hasRangeOnRight = false, - $startOfMonth = false, - $endOfMonth = false, - $outsideMonth = false, - } = props; - return `${+$range}${+$disabled}${+( - $isHighlighted || $isHovered - )}${+$selected}${+$hasRangeSelected}${+$startDate}${+$endDate}${+$pseudoSelected}${+$hasRangeHighlighted}${+$pseudoHighlighted}${+( - $hasRangeHighlighted && - !$pseudoHighlighted && - $hasRangeOnRight - )}${+( - $hasRangeHighlighted && - !$pseudoHighlighted && - !$hasRangeOnRight - )}${+$startOfMonth}${+$endOfMonth}${+$outsideMonth}`; -} diff --git a/src/datepicker/utils/index.js.flow b/src/datepicker/utils/index.js.flow deleted file mode 100644 index 66d6875a48..0000000000 --- a/src/datepicker/utils/index.js.flow +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -/* eslint-disable import/extensions */ - -import defaultAdapter from './date-fns-adapter'; -import DateHelpers from './date-helpers'; - -const defaultDateHelpers = new DateHelpers(defaultAdapter); - -type DirtyDate = Date | number; -const { date: wrapDate } = defaultAdapter; - -export const formatDate = defaultDateHelpers.formatDate; -export const getStartOfWeek = defaultDateHelpers.getStartOfWeek; -export const getEndOfWeek = defaultDateHelpers.getEndOfWeek; -export const getStartOfMonth = defaultDateHelpers.getStartOfMonth; -export const getEndOfMonth = defaultDateHelpers.getEndOfMonth; -export const isSameYear = defaultDateHelpers.isSameYear; -export const isSameMonth = defaultDateHelpers.isSameMonth; -export const isSameDay = defaultDateHelpers.isSameDay; -export const isDayInRange = defaultDateHelpers.isDayInRange; -export const isStartOfMonth = defaultDateHelpers.isStartOfMonth; -export const isEndOfMonth = defaultDateHelpers.isEndOfMonth; -export const getWeekdayMinInLocale = defaultDateHelpers.getWeekdayMinInLocale; -export const getWeekdayInLocale = defaultDateHelpers.getWeekdayInLocale; -export const getMonthInLocale = defaultDateHelpers.getMonthInLocale; -export const getQuarterInLocale = defaultDateHelpers.getQuarterInLocale; -export const isDayDisabled = defaultDateHelpers.isDayDisabled; -export const isOutOfBounds = defaultDateHelpers.isOutOfBounds; -export const monthDisabledBefore = defaultDateHelpers.monthDisabledBefore; -export const monthDisabledAfter = defaultDateHelpers.monthDisabledAfter; -export const getEffectiveMinDate = defaultDateHelpers.getEffectiveMinDate; -export const getEffectiveMaxDate = defaultDateHelpers.getEffectiveMaxDate; -export const applyTimeToDate = defaultDateHelpers.applyTimeToDate; -export const applyDateToTime = defaultDateHelpers.applyDateToTime; - -const createDirtySetter = (setter: (Date, number) => Date) => { - return (dirtyDate: DirtyDate, number: number) => setter(wrapDate(dirtyDate), number); -}; - -const createDirtyGetter = (getter: (Date) => number) => { - return (dirtyDate: DirtyDate) => getter(wrapDate(dirtyDate)); -}; - -const createDirtyCompare = (compare: (Date, Date) => boolean) => { - return (fromDirty: DirtyDate, toDirty: DirtyDate) => { - return compare(wrapDate(fromDirty), wrapDate(toDirty)); - }; -}; - -// ** Re-exported from date-fns ** - -// these need to be able to accept either number or date -// to maintain parity with the old exports - -// ** Date Setters ** -export const setSeconds = createDirtySetter(defaultDateHelpers.setSeconds); -export const setMinutes = createDirtySetter(defaultDateHelpers.setMinutes); -export const setHours = createDirtySetter(defaultDateHelpers.setHours); -export const setMonth = createDirtySetter(defaultDateHelpers.setMonth); -export const setYear = createDirtySetter(defaultDateHelpers.setYear); - -// ** Date Getters ** -export const getMinutes = createDirtyGetter(defaultDateHelpers.getMinutes); -export const getHours = createDirtyGetter(defaultDateHelpers.getHours); -export const getDate = createDirtyGetter(defaultDateHelpers.getDate); -export const getMonth = createDirtyGetter(defaultDateHelpers.getMonth); -export const getYear = createDirtyGetter(defaultDateHelpers.getYear); - -// ** Date Math -export const addDays = createDirtySetter(defaultDateHelpers.addDays); -export const addWeeks = createDirtySetter(defaultDateHelpers.addWeeks); -export const addMonths = createDirtySetter(defaultDateHelpers.addMonths); -export const addYears = createDirtySetter(defaultDateHelpers.addYears); -export const subDays = createDirtySetter(defaultDateHelpers.subDays); -export const subWeeks = createDirtySetter(defaultDateHelpers.subWeeks); -export const subMonths = createDirtySetter(defaultDateHelpers.subMonths); -export const subYears = createDirtySetter(defaultDateHelpers.subYears); - -// ** Date Comparison - -export const isBefore = createDirtyCompare(defaultDateHelpers.isBefore); -export const isAfter = createDirtyCompare(defaultDateHelpers.isAfter); - -// flowlint-next-line unclear-type:off -export const format = (date: Date, format: string, locale: ?any) => - defaultDateHelpers.format(date, format, locale); diff --git a/src/datepicker/utils/types.js.flow b/src/datepicker/utils/types.js.flow deleted file mode 100644 index 8603747f4d..0000000000 --- a/src/datepicker/utils/types.js.flow +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -type DateValues = T | string | number; -export type DateInput = DateValues; - -type Comparison = (value: T, comparing: T) => boolean; -type DateInDateOut = (value: T) => T; - -type DateFunc = (DateInput | void) => T; - -export type AdapterOptions = { - locale?: mixed, - formats?: { [key: string]: string }, - instance?: mixed, -}; - -export type DateIOAdapter = { - locale?: mixed, - formats: { [key: string]: string }, - date: DateFunc, - toJsDate(value: DateInput): Date, - parse(value: string, format: string): T, - getCurrentLocaleCode(): string, - is12HourCycleInCurrentLocale(): boolean, - - isNull(value?: T): boolean, - isValid(value: mixed): boolean, - getDiff: (from: T, to: T) => number, - isEqual: Comparison, - isSameDay: Comparison, - isSameMonth: Comparison, - isSameYear: Comparison, - isSameHour: Comparison, - isAfter: Comparison, - isAfterDay: Comparison, - isAfterYear: Comparison, - - isBeforeDay: Comparison, - isBeforeYear: Comparison, - isBefore: Comparison, - startOfMonth: DateInDateOut, - endOfMonth: DateInDateOut, - startOfWeek: DateInDateOut, - endOfWeek(value: T): T, - addDays(value: T, count: number): T, - addMonths(value: T, count: number): T, - isWithinRange(value: T, range: T[]): boolean, - - startOfDay: DateInDateOut, - endOfDay: DateInDateOut, - format(value: T, formatKey: string): string, - formatByString(value: T, formatString: string): string, - formatNumber(numberToFormat: string): string, - getHours(value: T): number, - setHours(value: T, count: number): T, - - getMinutes(value: T): number, - setMinutes(value: T, count: number): T, - - getSeconds(value: T): number, - setSeconds(value: T, count: number): T, - - getMonth(value: T): number, - setMonth(value: T, count: number): T, - getNextMonth: DateInDateOut, - getPreviousMonth: DateInDateOut, - getMonthArray(value: T): T[], - - getYear(value: T): number, - setYear(value: T, count: number): T, - - mergeDateAndTime(date: T, time: T): T, - - getWeekdays(): string[], - getWeekArray(date: T): T[][], - getYearRange(start: T, end: T): T[], - - /** Allow to customize displaying "am/pm" strings */ - getMeridiemText(ampm: 'am' | 'pm'): string, -}; diff --git a/src/datepicker/week.js.flow b/src/datepicker/week.js.flow deleted file mode 100644 index 01ac4704e9..0000000000 --- a/src/datepicker/week.js.flow +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import Day from './day.js'; -import { StyledWeek } from './styled-components.js'; -import { WEEKDAYS } from './constants.js'; -import dateFnsAdapter from './utils/date-fns-adapter.js'; -import DateHelpers from './utils/date-helpers.js'; - -import { getOverrides } from '../helpers/overrides.js'; -import type { WeekPropsT } from './types.js'; - -export default class Week extends React.Component> { - static defaultProps = { - adapter: dateFnsAdapter, - highlightedDate: null, - onDayClick: () => {}, - onDayFocus: () => {}, - onDayBlur: () => {}, - onDayMouseOver: () => {}, - onDayMouseLeave: () => {}, - onChange: () => {}, - overrides: {}, - peekNextMonth: false, - }; - - dateHelpers: DateHelpers; - - constructor(props: WeekPropsT) { - super(props); - this.dateHelpers = new DateHelpers(props.adapter); - } - - renderDays = () => { - const startOfWeek = this.dateHelpers.getStartOfWeek( - this.props.date || this.dateHelpers.date(), - this.props.locale - ); - const days = []; - // $FlowFixMe - return days.concat( - WEEKDAYS.map((offset: number) => { - const day = this.dateHelpers.addDays(startOfWeek, offset); - return ( - // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events - - ); - }) - ); - }; - - render() { - const { overrides = {} } = this.props; - const [Week, weekProps] = getOverrides(overrides.Week, StyledWeek); - return ( - - {this.renderDays()} - - ); - } -} diff --git a/src/dialog/index.js.flow b/src/dialog/index.js.flow deleted file mode 100644 index dc72bff820..0000000000 --- a/src/dialog/index.js.flow +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import type { OverrideT } from '../helpers/overrides.js'; -import type { ButtonDockPropsT } from '../button-dock'; -import { type StyletronComponent } from 'styletron-react'; - -declare export var SIZE: { - xSmall: 'xSmall', - small: 'small', - medium: 'medium', - large: 'large', -}; - -declare export var PLACEMENT: { - center: 'center', - topLeft: 'topLeft', - topCenter: 'topCenter', - topRight: 'topRight', - bottomLeft: 'bottomLeft', - bottomCenter: 'bottomCenter', - bottomRight: 'bottomRight', -}; - -export type DialogOverridesT = { - Root?: OverrideT, - ScrollContainer?: OverrideT, - Heading?: OverrideT, - Body?: OverrideT, - ButtonDock?: OverrideT, - DismissButton?: OverrideT, -}; - -export type SizeT = $Values; -export type PlacementT = $Values; -//flowlint-next-line unclear-type:off -export type ArtworkT = React.Element | React.ComponentType<{}>; - -export type DialogPropsT = { - artwork?: ArtworkT, - buttonDock?: ButtonDockPropsT, - children?: React.Node, - handleDismiss?: () => void, - showDismissButton?: boolean, - hasOverlay?: boolean, - heading: string, - isOpen: boolean, - numHeadingLines?: number, - overrides?: DialogOverridesT, - placement?: PlacementT, - size?: SizeT, -}; - -declare export var StyledRoot: StyletronComponent< - 'dialog', - { - $size: SizeT, - $placement: PlacementT, - $isOpen: boolean, - } ->; -declare export var StyledScrollContainer: StyletronComponent<'div', {}>; -declare export var StyledHeading: StyletronComponent<'div', { $numHeadingLines: number }>; -declare export var StyledBody: StyletronComponent<'div', {}>; - -declare export var Dialog: React.ComponentType; diff --git a/src/divider/constants.js.flow b/src/divider/constants.js.flow deleted file mode 100644 index 3ab3f3d07c..0000000000 --- a/src/divider/constants.js.flow +++ /dev/null @@ -1,12 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -export const SIZE = { - cell: 'cell', - section: 'section', - module: 'module', -}; diff --git a/src/divider/index.js.flow b/src/divider/index.js.flow deleted file mode 100644 index bd46e468f4..0000000000 --- a/src/divider/index.js.flow +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow -import * as React from 'react'; -import { withWrapper } from '../styles/index.js'; - -import { StyledDivider as StyledDividerElement } from './styled-components.js'; - -export * from './constants.js'; - -export const StyledDivider = withWrapper( - StyledDividerElement, - (StyledComponent) => - function StyledDivider(props) { - return