diff --git a/src/elements/common/nav-button/BackButton.js b/src/elements/common/nav-button/BackButton.js.flow similarity index 97% rename from src/elements/common/nav-button/BackButton.js rename to src/elements/common/nav-button/BackButton.js.flow index a1431b87a1..bc64777613 100644 --- a/src/elements/common/nav-button/BackButton.js +++ b/src/elements/common/nav-button/BackButton.js.flow @@ -15,7 +15,7 @@ import './BackButton.scss'; type Props = { className?: string, - to?: Location, + to?: Location | string, }; const BackButton = ({ className, to, ...rest }: Props) => ( diff --git a/src/elements/common/nav-button/BackButton.tsx b/src/elements/common/nav-button/BackButton.tsx new file mode 100644 index 0000000000..1a226c26c8 --- /dev/null +++ b/src/elements/common/nav-button/BackButton.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import { FormattedMessage } from 'react-intl'; +import { Route } from 'react-router-dom'; +import type { Location } from 'history'; +import IconNavigateLeft from '../../../icons/general/IconNavigateLeft'; +import PlainButton from '../../../components/plain-button'; +import messages from '../messages'; +import { ButtonType } from '../../../components/button'; +import './BackButton.scss'; + +export interface BackButtonProps { + className?: string; + to?: Location; +} + +const BackButton = ({ className, to, ...rest }: BackButtonProps) => ( + + {({ history }) => ( + (to ? history.push(to) : history.goBack())} + type={ButtonType.BUTTON} + {...rest} + > + + + + + + )} + +); + +export default BackButton; diff --git a/src/elements/common/nav-button/NavButton.js b/src/elements/common/nav-button/NavButton.js.flow similarity index 94% rename from src/elements/common/nav-button/NavButton.js rename to src/elements/common/nav-button/NavButton.js.flow index 5f3b0f936d..682005d306 100644 --- a/src/elements/common/nav-button/NavButton.js +++ b/src/elements/common/nav-button/NavButton.js.flow @@ -11,6 +11,12 @@ import type { Match, Location } from 'react-router-dom'; import PlainButton from '../../../components/plain-button'; import { isLeftClick } from '../../../utils/dom'; +// Custom Location type that makes hash optional +type CustomLocation = { + ...Location, + hash?: string, +}; + type Props = { activeClassName?: string, children: React.Node, @@ -22,7 +28,7 @@ type Props = { onClick?: (event: SyntheticEvent<>) => void, replace?: boolean, strict?: boolean, - to: string | Location, + to: string | CustomLocation, }; const NavButton = React.forwardRef>((props: Props, ref: React.Ref) => { diff --git a/src/elements/common/nav-button/NavButton.tsx b/src/elements/common/nav-button/NavButton.tsx new file mode 100644 index 0000000000..3876660365 --- /dev/null +++ b/src/elements/common/nav-button/NavButton.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import { Route } from 'react-router-dom'; +import type { match } from 'react-router'; +import type { Location } from 'history'; +import PlainButton, { PlainButtonProps } from '../../../components/plain-button'; +import { isLeftClick } from '../../../utils/dom'; + +export interface NavButtonProps { + activeClassName?: string; + children: React.ReactNode; + className?: string; + component?: React.ComponentType }>; + exact?: boolean; + isActive?: (match: match, location: Location) => boolean; + isDisabled?: boolean; + onClick?: (event: React.SyntheticEvent) => void; + replace?: boolean; + strict?: boolean; + to: string | Location; +} + +const NavButton = React.forwardRef( + (props: NavButtonProps, ref: React.Ref) => { + const { + activeClassName = 'bdl-is-active', + children, + className = 'bdl-NavButton', + component: Component = PlainButton, + exact, + isActive, + isDisabled, + onClick, + replace, + strict, + to, + ...rest + } = props; + const path = typeof to === 'object' ? to.pathname : to; + + const disabledClassName = 'bdl-is-disabled'; + + return ( + + {({ history, location, match }) => { + const isActiveValue = !!(isActive ? isActive(match, location) : match); + + return ( + { + if (onClick) { + onClick(event); + } + + if (!event.defaultPrevented && isLeftClick(event)) { + const method = replace ? history.replace : history.push; + method(to); + } + }} + ref={ref} + {...rest} + > + {children} + + ); + }} + + ); + }, +); + +export default NavButton; diff --git a/src/elements/common/nav-button/index.js b/src/elements/common/nav-button/index.js.flow similarity index 91% rename from src/elements/common/nav-button/index.js rename to src/elements/common/nav-button/index.js.flow index e7cb60159d..b186704aee 100644 --- a/src/elements/common/nav-button/index.js +++ b/src/elements/common/nav-button/index.js.flow @@ -1,2 +1,3 @@ +// @flow export { default as BackButton } from './BackButton'; export { default } from './NavButton'; diff --git a/src/elements/common/nav-button/index.ts b/src/elements/common/nav-button/index.ts new file mode 100644 index 0000000000..ddd3595f9d --- /dev/null +++ b/src/elements/common/nav-button/index.ts @@ -0,0 +1,4 @@ +export { default as BackButton } from './BackButton'; +export { default } from './NavButton'; +export type { BackButtonProps } from './BackButton'; +export type { NavButtonProps } from './NavButton'; diff --git a/src/elements/content-sidebar/SidebarNavButton.js.flow b/src/elements/content-sidebar/SidebarNavButton.js.flow new file mode 100644 index 0000000000..67f150b87c --- /dev/null +++ b/src/elements/content-sidebar/SidebarNavButton.js.flow @@ -0,0 +1,86 @@ +/** + * @flow + * @file Preview sidebar nav button component + * @author Box + */ + +import * as React from 'react'; +import { Route } from 'react-router-dom'; +import noop from 'lodash/noop'; +import NavButton from '../common/nav-button'; +import Tooltip from '../../components/tooltip/Tooltip'; +import './SidebarNavButton.scss'; + +type Props = { + 'data-resin-target'?: string, + 'data-testid'?: string, + children: React.Node, + elementId?: string, + isDisabled?: boolean, + isOpen?: boolean, + onClick?: (sidebarView: string) => void, + sidebarView: string, + tooltip: React.Node, +}; + +const SidebarNavButton = React.forwardRef>((props: Props, ref: React.Ref) => { + const { + 'data-resin-target': dataResinTarget, + 'data-testid': dataTestId, + children, + elementId = '', + isDisabled, + isOpen, + onClick = noop, + sidebarView, + tooltip, + } = props; + const sidebarPath = `/${sidebarView}`; + + const handleNavButtonClick = () => { + onClick(sidebarView); + }; + + return ( + + {({ match }) => { + const isMatch = !!match; + const isActive = () => isMatch && !!isOpen; + const isActiveValue = isActive(); + const isExactMatch = isMatch && match.isExact; + const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`; + + return ( + + + {children} + + + ); + }} + + ); +}); + +export default SidebarNavButton; diff --git a/src/elements/content-sidebar/versions/StaticVersionSidebar.js.flow b/src/elements/content-sidebar/versions/StaticVersionSidebar.js.flow new file mode 100644 index 0000000000..c25454b9a6 --- /dev/null +++ b/src/elements/content-sidebar/versions/StaticVersionSidebar.js.flow @@ -0,0 +1,96 @@ +/** + * @flow + * @file Static Versions Sidebar component + * @author Box + */ + +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import BoxDrive140 from '../../../illustration/BoxDrive140'; + +import { BackButton } from '../../common/nav-button'; +import PrimaryButton from '../../../components/primary-button'; +import { LoadingIndicatorWrapper } from '../../../components/loading-indicator'; +import VersionsMenu from './VersionsMenu'; + +import messages from './messages'; + +import './StaticVersionsSidebar.scss'; + +type Props = { + isLoading: boolean, + onUpgradeClick: () => void, + parentName: string, +}; + +const StaticVersionsSidebar = ({ isLoading, onUpgradeClick, parentName }: Props): React.Node => { + const versionTimestamp = new Date(); + versionTimestamp.setDate(versionTimestamp.getDate() - 1); + + const versions = ['1', '2', '3'].map(versionNumber => { + return { + id: versionNumber, + version_number: versionNumber, + type: 'file_version', + permissions: { + can_preview: true, + }, + created_at: versionTimestamp.toUTCString(), + modified_by: null, + size: 1875887, + trashed_at: null, + uploader_display_name: 'John Doe', + }; + }); + + return ( +
+
+

+ <> + + + +

+
+ +
+ + + +
+ +
+
+ +

+ +

+

+ +

+ + + +
+
+
+ ); +}; + +export default StaticVersionsSidebar; diff --git a/src/elements/content-sidebar/versions/VersionsSidebar.js.flow b/src/elements/content-sidebar/versions/VersionsSidebar.js.flow new file mode 100644 index 0000000000..513f83d2e1 --- /dev/null +++ b/src/elements/content-sidebar/versions/VersionsSidebar.js.flow @@ -0,0 +1,83 @@ +/** + * @flow + * @file Versions Sidebar component + * @author Box + */ + +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; +import InlineError from '../../../components/inline-error'; +import messages from './messages'; +import SidebarContent from '../SidebarContent'; +import VersionsMenu from './VersionsMenu'; +import { BackButton } from '../../common/nav-button'; +import { DEFAULT_FETCH_END } from '../../../constants'; +import { LoadingIndicatorWrapper } from '../../../components/loading-indicator'; +import type { BoxItemVersion } from '../../../common/types/core'; +import './VersionsSidebar.scss'; + +const MAX_VERSIONS = DEFAULT_FETCH_END; + +type Props = { + error?: MessageDescriptor, + fileId: string, + isLoading: boolean, + parentName: string, + versionCount: number, + versionLimit: number, + versions: Array, +}; + +const VersionsSidebar = ({ error, isLoading, parentName, versions, ...rest }: Props) => { + const showLimit = versions.length >= MAX_VERSIONS; + const showVersions = !!versions.length; + const showEmpty = !isLoading && !showVersions; + const showError = !!error; + + return ( + + + + + } + > + + {showError && ( + }> + + + )} + + {showEmpty && ( +
+ +
+ )} + + {showVersions && ( +
+ +
+ )} + {showLimit && ( +
+ +
+ )} +
+
+ ); +}; + +export default VersionsSidebar;