diff --git a/src/elements/content-sidebar/ContentSidebar.scss b/src/elements/content-sidebar/ContentSidebar.scss index b16a8b098f..18290d0127 100644 --- a/src/elements/content-sidebar/ContentSidebar.scss +++ b/src/elements/content-sidebar/ContentSidebar.scss @@ -8,6 +8,7 @@ $sidebarDefaultErrorIncreasedWidth: $sidebarContentIncreasedWidth - 16px; .be { &.bcs { + position: relative; display: flex; width: auto; min-width: $sidebarTabsWidth; @@ -17,6 +18,16 @@ $sidebarDefaultErrorIncreasedWidth: $sidebarContentIncreasedWidth - 16px; &.bcs-is-wider { max-width: $sidebarIncreasedWidth; } + + // When resizable, the default min-width is the un-resized sidebar width. + // Inline style on the aside element overrides the default max-width to allow grow. + &.bcs-is-resizable.bcs-is-open { + min-width: $sidebarWidth; + + &.bcs-is-wider { + min-width: $sidebarIncreasedWidth; + } + } } .bcs-loading { @@ -61,11 +72,20 @@ $sidebarDefaultErrorIncreasedWidth: $sidebarContentIncreasedWidth - 16px; min-width: 0; max-width: none; max-height: 48px; - transition: max-height .5s ease-in-out 0s; + transition: max-height 0.5s ease-in-out 0s; &.bcs-is-wider { max-width: none; } + + // Neutralize resizable overrides on small screens — sidebar is a bottom sheet here + &.bcs-is-resizable.bcs-is-open { + min-width: 0; + + &.bcs-is-wider { + min-width: 0; + } + } } &.bcs-is-open { diff --git a/src/elements/content-sidebar/Sidebar.js b/src/elements/content-sidebar/Sidebar.js index 1bdf94f62e..cb196c8552 100644 --- a/src/elements/content-sidebar/Sidebar.js +++ b/src/elements/content-sidebar/Sidebar.js @@ -14,8 +14,11 @@ import { withRouter } from 'react-router-dom'; import type { Location, RouterHistory } from 'react-router-dom'; import LoadingIndicator from '../../components/loading-indicator/LoadingIndicator'; import LocalStore from '../../utils/LocalStore'; +import withMediaQuery from '../../components/media-query/withMediaQuery'; +import { VIEW_SIZE_TYPE } from '../../components/media-query/constants'; import SidebarNav from './SidebarNav'; import SidebarPanels from './SidebarPanels'; +import SidebarResizeHandle from './SidebarResizeHandle'; import SidebarUtils from './SidebarUtils'; // $FlowFixMe TypeScript file import ThemingStyles from '../common/theming'; @@ -74,12 +77,15 @@ type Props = { /** When true, enables data fetching. When false, defers data fetching. Used to prioritize preview loading. */ shouldFetchSidebarData?: boolean, signSidebarProps: SignSidebarProps, + size: $Values, theme?: Theme, versionsSidebarProps: VersionsSidebarProps, + viewWidth: number, }; type State = { isDirty: boolean, + width: ?number, }; export const SIDEBAR_FORCE_KEY: 'bcs.force' = 'bcs.force'; @@ -87,6 +93,13 @@ export const SIDEBAR_FORCE_VALUE_CLOSED: 'closed' = 'closed'; export const SIDEBAR_FORCE_VALUE_OPEN: 'open' = 'open'; export const SIDEBAR_SELECTED_PANEL_KEY: 'sidebar-selected-panel' = 'sidebar-selected-panel'; +// Default widths mirror the hardcoded SCSS values ($sidebarTabsWidth + $sidebarContent[Increased]Width). +// When the resizable feature flag is on, these become the minimum drag-to-resize widths. +const SIDEBAR_DEFAULT_WIDTH = 400; +const SIDEBAR_DEFAULT_WIDTH_WIDER = 440; +// Cap dragged width at this fraction of the current viewport width. +const SIDEBAR_MAX_WIDTH_RATIO = 0.5; + class Sidebar extends React.Component { static defaultProps = { annotatorState: {}, @@ -111,11 +124,24 @@ class Sidebar extends React.Component { this.state = { isDirty: this.getLocationState('open') || false, + width: null, }; this.setForcedByLocation(); } + /** + * Default sidebar width based on whether the "wider" (Box AI) variant is active. + * Mirrors the SCSS fallback so flipping the flag on doesn't change the rendered width at rest. + */ + getDefaultWidth(hasNativeBoxAISidebar: boolean, hasCustomBoxAISidebar: boolean): number { + return hasNativeBoxAISidebar || hasCustomBoxAISidebar ? SIDEBAR_DEFAULT_WIDTH_WIDER : SIDEBAR_DEFAULT_WIDTH; + } + + handleResize = (width: number): void => { + this.setState({ width }); + }; + componentDidMount() { const { file, api, metadataSidebarProps, docGenSidebarProps, onOpenChange = noop }: Props = this.props; // if docgen feature is enabled, load metadata to check whether file is a docgen template @@ -304,6 +330,7 @@ class Sidebar extends React.Component { customSidebarPanels = [], detailsSidebarProps, docGenSidebarProps, + features, file, fileId, getPreview, @@ -317,9 +344,12 @@ class Sidebar extends React.Component { onAnnotationSelect, onVersionChange, signSidebarProps, + size, theme, versionsSidebarProps, + viewWidth, }: Props = this.props; + const { width }: State = this.state; const isOpen = this.isOpen(); const hasCustomBoxAISidebar = customSidebarPanels.some(panel => panel.id === SIDEBAR_VIEW_BOXAI); @@ -331,14 +361,34 @@ class Sidebar extends React.Component { const hasMetadata = SidebarUtils.shouldRenderMetadataSidebar(this.props, metadataEditors); const hasSkills = SidebarUtils.shouldRenderSkillsSidebar(this.props, file); const onVersionHistoryClick = hasVersions ? this.handleVersionHistoryClick : this.props.onVersionHistoryClick; + + const isViewportWideEnoughToResize = size === VIEW_SIZE_TYPE.large || size === VIEW_SIZE_TYPE.xlarge; + const isResizable = + isFeatureEnabled(features, 'contentSidebar.resizable.enabled') && isViewportWideEnoughToResize; + const minWidth = this.getDefaultWidth(hasNativeBoxAISidebar, hasCustomBoxAISidebar); + const maxWidth = Math.max(minWidth, Math.round(viewWidth * SIDEBAR_MAX_WIDTH_RATIO)); + const currentWidth = width != null ? Math.min(Math.max(width, minWidth), maxWidth) : minWidth; + // Only force inline width once the user has actually dragged — otherwise leave the SCSS defaults in place. + const shouldApplyInlineWidth = isResizable && isOpen && width != null; + const inlineStyle = shouldApplyInlineWidth ? { width: currentWidth, maxWidth: currentWidth } : undefined; + const styleClassName = classNames('be bcs', className, { 'bcs-is-open': isOpen, + 'bcs-is-resizable': isResizable, 'bcs-is-wider': hasNativeBoxAISidebar || hasCustomBoxAISidebar, }); const defaultPanel = this.getDefaultPanel(); return ( -