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;