diff --git a/src/internal/widgets/__tests__/widgets.test.tsx b/src/internal/widgets/__tests__/widgets.test.tsx index a179e3da7f..d3c58e0917 100644 --- a/src/internal/widgets/__tests__/widgets.test.tsx +++ b/src/internal/widgets/__tests__/widgets.test.tsx @@ -4,29 +4,13 @@ import React from 'react'; import { render } from '@testing-library/react'; import { useVisualRefresh } from '../../../../lib/components/internal/hooks/use-visual-mode'; -import { createWidgetizedComponent, createWidgetizedForwardRef } from '../../../../lib/components/internal/widgets'; +import { createWidgetizedComponent } from '../../../../lib/components/internal/widgets'; import { describeWithAppLayoutFeatureFlagEnabled } from './utils'; const LoaderSkeleton = () =>
Loading...
; const RealComponent = () =>
Real content
; const WidgetizedComponent = createWidgetizedComponent(RealComponent)(LoaderSkeleton); -const LoaderWithRef = React.forwardRef((props, ref) => ( -
- Loading... -
-)); -const RealComponentWithRef = React.forwardRef((props, ref) => ( -
- Real content -
-)); -const WidgetizedComponentWithRef = createWidgetizedForwardRef< - { children?: React.ReactNode }, - HTMLDivElement, - typeof RealComponentWithRef ->(RealComponentWithRef)(LoaderWithRef); - function findLoader(container: HTMLElement) { return container.querySelector('[data-testid="loader"]'); } @@ -77,23 +61,3 @@ describe('Refresh design', () => { }); }); }); - -describe('Ref handling', () => { - test('should forward ref to content', () => { - const ref = React.createRef(); - const { container } = render(); - expect(findContent(container)).toBeTruthy(); - expect(findLoader(container)).toBeFalsy(); - expect(ref.current).toHaveTextContent('Real content'); - }); - - describeWithAppLayoutFeatureFlagEnabled(() => { - test('should forward ref to loader', () => { - const ref = React.createRef(); - const { container } = render(); - expect(findContent(container)).toBeFalsy(); - expect(findLoader(container)).toBeTruthy(); - expect(ref.current).toHaveTextContent('Loading...'); - }); - }); -}); diff --git a/src/internal/widgets/index.tsx b/src/internal/widgets/index.tsx index 7464fe4aa7..d506b91120 100644 --- a/src/internal/widgets/index.tsx +++ b/src/internal/widgets/index.tsx @@ -21,20 +21,3 @@ export function createWidgetizedComponent>, ->(Implementation: Component) { - return (Loader?: Component): Component => { - return React.forwardRef((props, ref) => { - const isRefresh = useVisualRefresh(); - if (isRefresh && getGlobalFlag('appLayoutWidget') && Loader) { - return ; - } - - return ; - }) as Component; - }; -} diff --git a/src/split-panel/__tests__/widgetized-panel.test.tsx b/src/split-panel/__tests__/widgetized-panel.test.tsx index da1387dfc5..9c49dfad95 100644 --- a/src/split-panel/__tests__/widgetized-panel.test.tsx +++ b/src/split-panel/__tests__/widgetized-panel.test.tsx @@ -5,15 +5,17 @@ import { render } from '@testing-library/react'; import { SplitPanelContextProvider } from '../../../lib/components/internal/context/split-panel-context'; import { useVisualRefresh } from '../../../lib/components/internal/hooks/use-visual-mode'; -import { createWidgetizedSplitPanel } from '../../../lib/components/split-panel/implementation'; -import { SplitPanelProps } from '../../../lib/components/split-panel/interfaces'; +import { + createWidgetizedSplitPanel, + SplitPanelImplementationProps, +} from '../../../lib/components/split-panel/implementation'; import createWrapper from '../../../lib/components/test-utils/dom'; import { describeWithAppLayoutFeatureFlagEnabled } from '../../internal/widgets/__tests__/utils'; import { defaultSplitPanelContextProps } from './helpers'; -const LoaderSkeleton = React.forwardRef(() => { +const LoaderSkeleton = () => { return
Loading...
; -}); +}; function findLoader(container: HTMLElement) { return container.querySelector('[data-testid="loader"]'); @@ -32,8 +34,10 @@ function renderComponent(jsx: React.ReactElement) { const WidgetizedPanel = createWidgetizedSplitPanel(LoaderSkeleton); -const defaultProps: SplitPanelProps = { +const defaultProps: SplitPanelImplementationProps = { header: '', + hidePreferencesButton: false, + closeBehavior: 'collapse', children: <>, }; diff --git a/src/split-panel/implementation.tsx b/src/split-panel/implementation.tsx index b41b60f570..7378cd368b 100644 --- a/src/split-panel/implementation.tsx +++ b/src/split-panel/implementation.tsx @@ -11,11 +11,13 @@ import { InternalButton } from '../button/internal'; import { getBaseProps } from '../internal/base-component'; import PanelResizeHandle from '../internal/components/panel-resize-handle'; import { useSplitPanelContext } from '../internal/context/split-panel-context'; +import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; import { useMergeRefs } from '../internal/hooks/use-merge-refs'; import { useUniqueId } from '../internal/hooks/use-unique-id'; import { useVisualRefresh } from '../internal/hooks/use-visual-mode'; import globalVars from '../internal/styles/global-vars'; -import { createWidgetizedForwardRef } from '../internal/widgets'; +import { SomeRequired } from '../internal/types'; +import { createWidgetizedComponent } from '../internal/widgets'; import { SplitPanelContentBottom } from './bottom'; import { SplitPanelProps } from './interfaces'; import PreferencesModal from './preferences-modal'; @@ -24,133 +26,134 @@ import { SplitPanelContentSide } from './side'; import styles from './styles.css.js'; import testUtilStyles from './test-classes/styles.css.js'; -export { SplitPanelProps }; +export type SplitPanelImplementationProps = SomeRequired & + InternalBaseComponentProps; -export const SplitPanelImplementation = React.forwardRef( - ( - { header, children, hidePreferencesButton = false, closeBehavior = 'collapse', i18nStrings = {}, ...restProps }, - __internalRootRef - ) => { - const isRefresh = useVisualRefresh(); - const isToolbar = useAppLayoutToolbarEnabled(); +export function SplitPanelImplementation({ + __internalRootRef, + header, + children, + hidePreferencesButton, + closeBehavior, + i18nStrings = {}, + ...restProps +}: SplitPanelImplementationProps) { + const isRefresh = useVisualRefresh(); + const isToolbar = useAppLayoutToolbarEnabled(); - const { - position, - topOffset, - bottomOffset, - rightOffset, - contentWidthStyles, - isOpen, - isForcedPosition, - onPreferencesChange, - onResize, - onToggle, - size, - relativeSize, - setSplitPanelToggle, - refs, - } = useSplitPanelContext(); - const baseProps = getBaseProps(restProps); - const [isPreferencesOpen, setPreferencesOpen] = useState(false); + const { + position, + topOffset, + bottomOffset, + rightOffset, + contentWidthStyles, + isOpen, + isForcedPosition, + onPreferencesChange, + onResize, + onToggle, + size, + relativeSize, + setSplitPanelToggle, + refs, + } = useSplitPanelContext(); + const baseProps = getBaseProps(restProps); + const [isPreferencesOpen, setPreferencesOpen] = useState(false); - const appLayoutMaxWidth = isRefresh && position === 'bottom' ? contentWidthStyles : undefined; + const appLayoutMaxWidth = isRefresh && position === 'bottom' ? contentWidthStyles : undefined; - const openButtonAriaLabel = i18nStrings.openButtonAriaLabel; - useEffect(() => { - setSplitPanelToggle({ displayed: closeBehavior === 'collapse', ariaLabel: openButtonAriaLabel }); + const openButtonAriaLabel = i18nStrings.openButtonAriaLabel; + useEffect(() => { + setSplitPanelToggle({ displayed: closeBehavior === 'collapse', ariaLabel: openButtonAriaLabel }); - return () => { - setSplitPanelToggle({ displayed: false, ariaLabel: undefined }); - }; - }, [setSplitPanelToggle, openButtonAriaLabel, closeBehavior]); - - const splitPanelRefObject = useRef(null); - - const sizeControlProps: SizeControlProps = { - position, - panelRef: splitPanelRefObject, - handleRef: refs.slider, - onResize, - hasTransitions: true, + return () => { + setSplitPanelToggle({ displayed: false, ariaLabel: undefined }); }; - const onSliderPointerDown = usePointerEvents(sizeControlProps); - const onKeyDown = useKeyboardEvents(sizeControlProps); + }, [setSplitPanelToggle, openButtonAriaLabel, closeBehavior]); - const contentStyle = { - [globalVars.stickyVerticalTopOffset]: topOffset, - [globalVars.stickyVerticalBottomOffset]: bottomOffset, - }; + const splitPanelRefObject = useRef(null); - const panelHeaderId = useUniqueId('split-panel-header'); + const sizeControlProps: SizeControlProps = { + position, + panelRef: splitPanelRefObject, + handleRef: refs.slider, + onResize, + hasTransitions: true, + }; + const onSliderPointerDown = usePointerEvents(sizeControlProps); + const onKeyDown = useKeyboardEvents(sizeControlProps); - const wrappedHeader = ( -
-

- {header} -

-
- {!hidePreferencesButton && isOpen && ( - <> - setPreferencesOpen(true)} - formAction="none" - ariaLabel={i18nStrings.preferencesTitle} - ref={refs.preferences} - /> - - - )} + const contentStyle = { + [globalVars.stickyVerticalTopOffset]: topOffset, + [globalVars.stickyVerticalBottomOffset]: bottomOffset, + }; - {isOpen ? ( - - ) : isToolbar || position === 'side' ? null : ( + const panelHeaderId = useUniqueId('split-panel-header'); + + const wrappedHeader = ( +
+

+ {header} +

+
+ {!hidePreferencesButton && isOpen && ( + <> setPreferencesOpen(true)} formAction="none" - ariaLabel={i18nStrings.openButtonAriaLabel} - ref={refs.toggle} - ariaExpanded={isOpen} + ariaLabel={i18nStrings.preferencesTitle} + ref={refs.preferences} /> - )} -
+ + + )} + + {isOpen ? ( + + ) : isToolbar || position === 'side' ? null : ( + + )}
- ); +
+ ); - const resizeHandle = ( - - ); + const resizeHandle = ( + + ); - /* + /* This effect forces the browser to recalculate the layout whenever the split panel might have moved. @@ -158,106 +161,101 @@ export const SplitPanelImplementation = React.forwardRef { - const root = splitPanelRefObject.current; - - if (root) { - const property = 'transform'; - const temporaryValue = 'translateZ(0)'; - - const valueBefore = root.style[property]; - root.style[property] = temporaryValue; + useLayoutEffect(() => { + const root = splitPanelRefObject.current; - // This line forces the browser to recalculate the layout - void root.offsetHeight; + if (root) { + const property = 'transform'; + const temporaryValue = 'translateZ(0)'; - root.style[property] = valueBefore; - } - }, [rightOffset, __internalRootRef]); + const valueBefore = root.style[property]; + root.style[property] = temporaryValue; - const mergedRef = useMergeRefs(splitPanelRefObject, __internalRootRef); + // This line forces the browser to recalculate the layout + void root.offsetHeight; - if (closeBehavior === 'hide' && !isOpen) { - return <>; + root.style[property] = valueBefore; } + }, [rightOffset, __internalRootRef]); - /** - * The AppLayout factor moved the circular buttons out of the - * SplitPanel and into the Tools component. This conditional - * is still needed for the early return to prevent execution - * of the following code. - */ - if (isRefresh && !isToolbar && !isOpen && position === 'side') { - return <>; - } + const mergedRef = useMergeRefs(splitPanelRefObject, __internalRootRef); - return ( - <> - {position === 'side' && ( - - {children} - - )} + if (closeBehavior === 'hide' && !isOpen) { + return <>; + } - {position === 'bottom' && ( - - {children} - - )} - {isPreferencesOpen && ( - { - onPreferencesChange({ ...preferences }); - setPreferencesOpen(false); - }} - onDismiss={() => { - setPreferencesOpen(false); - }} - /> - )} - - ); + /** + * The AppLayout factor moved the circular buttons out of the + * SplitPanel and into the Tools component. This conditional + * is still needed for the early return to prevent execution + * of the following code. + */ + if (isRefresh && !isToolbar && !isOpen && position === 'side') { + return <>; } -); -export const createWidgetizedSplitPanel = createWidgetizedForwardRef< - SplitPanelProps, - HTMLElement, - typeof SplitPanelImplementation ->(SplitPanelImplementation); + return ( + <> + {position === 'side' && ( + + {children} + + )} + + {position === 'bottom' && ( + + {children} + + )} + {isPreferencesOpen && ( + { + onPreferencesChange({ ...preferences }); + setPreferencesOpen(false); + }} + onDismiss={() => { + setPreferencesOpen(false); + }} + /> + )} + + ); +} + +export const createWidgetizedSplitPanel = createWidgetizedComponent(SplitPanelImplementation); diff --git a/src/split-panel/index.tsx b/src/split-panel/index.tsx index 699f87627b..64f8732524 100644 --- a/src/split-panel/index.tsx +++ b/src/split-panel/index.tsx @@ -23,7 +23,7 @@ export default function SplitPanel({ return (