From a4c422e35bda1ad91bea38f9c4ffd573c08a6cea Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 19 Jun 2024 15:41:56 +0200 Subject: [PATCH 01/44] PoC resizable layout --- .../StudioResizableLayoutContainer.module.css | 19 +++ .../StudioResizableLayoutContainer.tsx | 108 ++++++++++++++++++ .../StudioResizableLayoutRoot.module.css | 5 + .../StudioResizableLayoutRoot.tsx | 55 +++++++++ .../hooks/useResizable.ts | 73 ++++++++++++ .../hooks/useStudioResizableLayoutContext.ts | 4 + .../components/StudioResizableLayout/index.ts | 17 +++ .../studio-components/src/components/index.ts | 1 + 8 files changed, 282 insertions(+) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css new file mode 100644 index 00000000000..fb53147b0e3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css @@ -0,0 +1,19 @@ +.container { + display: flex; + overflow: hidden; + position: relative; + flex: 1; +} + +.resizeHandle { + background-color: #f0f0f0; + flex: 0 0 4px; + position: relative; +} + +.resizeHandle:after { + content: ''; + position: absolute; + left: -15px; + right: -15px; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx new file mode 100644 index 00000000000..16760cf4afe --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -0,0 +1,108 @@ +import React, { useCallback, useRef, useState } from 'react'; +import classes from './StudioResizableLayoutContainer.module.css'; +import { useResizable } from '../hooks/useResizable'; +import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; + +export type StudioResizableLayoutContainerProps = { + minimumSize?: number; + canBeCollapsed?: boolean; + flexGrow?: number; + children: React.ReactElement | React.ReactElement[]; +}; + +const StudioResizableLayoutContainer = ({ + children, + minimumSize = 0, + flexGrow = 1, +}: StudioResizableLayoutContainerProps) => { + const { resizeDelta } = useResizable(minimumSize); + const { orientation } = useStudioResizableLayoutContext(); + const containerRef = useRef(null); + const handleRef = useRef(null); + const lastMousePosition = useRef(0); + const [isResizing, setIsResizing] = useState(false); + + function handleKeyDown(event: React.KeyboardEvent): void { + const nextSibling = handleRef.current.nextElementSibling; + let deltaMove = 0; + switch (event.key) { + case 'ArrowLeft': + case 'ArrowUp': + event.preventDefault(); + deltaMove = -10; + if (event.shiftKey) { + deltaMove *= 5; + } + resizeDelta(containerRef.current, nextSibling, deltaMove); + break; + case 'ArrowRight': + case 'ArrowDown': + event.preventDefault(); + deltaMove = 10; + if (event.shiftKey) { + deltaMove *= 5; + } + resizeDelta(containerRef.current, nextSibling, deltaMove); + break; + } + } + + const mouseMove = useCallback( + (event: MouseEvent) => { + const mousePos = orientation === 'horizontal' ? event.pageX : event.pageY; + const mouseDelta = mousePos - lastMousePosition.current; + const nextSibling = handleRef.current.nextElementSibling; + resizeDelta(containerRef.current, nextSibling, mouseDelta); + lastMousePosition.current = mousePos; + }, + [resizeDelta, orientation], + ); + + const mouseUp = useCallback( + (_: MouseEvent) => { + setIsResizing(false); + window.removeEventListener('mousemove', mouseMove); + window.removeEventListener('mouseup', mouseUp); + }, + [mouseMove], + ); + + const mouseDown = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + setIsResizing(true); + lastMousePosition.current = orientation === 'horizontal' ? event.pageX : event.pageY; + window.addEventListener('mousemove', mouseMove); + window.addEventListener('mouseup', mouseUp); + + return () => { + window.removeEventListener('mousemove', mouseMove); + window.removeEventListener('mouseup', mouseUp); + }; + }, + [mouseMove, mouseUp, orientation], + ); + + return ( + <> +
+ {children} +
+
+ + ); +}; + +StudioResizableLayoutContainer.displayName = 'StudioResizableLayout.Container'; + +export { StudioResizableLayoutContainer }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css new file mode 100644 index 00000000000..4802e665a49 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css @@ -0,0 +1,5 @@ +.root { + display: flex; + flex-direction: row; + width: 100%; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx new file mode 100644 index 00000000000..481f69a859c --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx @@ -0,0 +1,55 @@ +import type { HTMLAttributes } from 'react'; +import React, { Children, useEffect, useRef } from 'react'; +import classes from './StudioResizableLayoutRoot.module.css'; + +export type StudioResizableLayoutRootProps = { + orientation: 'horizontal' | 'vertical'; + children: React.ReactElement[]; +}; + +export type SiblingElements = { + self: HTMLElement | null; + next: HTMLElement | null; +}; + +export const StudioResizableLayoutContext = React.createContext({ + orientation: 'horizontal', + // siblingElements: new Map(), + // containerFlexGrow: new Map(), +}); + +const StudioResizableLayoutRoot = ({ + children, + orientation, +}: StudioResizableLayoutRootProps): React.ReactElement => { + const rootRef = useRef(null); + + // const childrenRef = useRef([]); + // + // useEffect(() => { + // childrenRef.current = children.slice(0, children.length).map(() => React.createRef()); + // }, [children]); + + return ( + +
+ {children} + {/* {children.map((child, i: number) => { */} + {/* return React.cloneElement(child, { */} + {/* orentation: "vertical", */} + {/* key: i, */} + {/* ref: (el) => { childrenRef.current[i] = el } */} + {/* }); */} + {/* })} */} +
+
+ ); +}; + +StudioResizableLayoutRoot.displayName = 'StudioResizableLayout.Root'; + +export { StudioResizableLayoutRoot }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts new file mode 100644 index 00000000000..468cc4ffe77 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts @@ -0,0 +1,73 @@ +import { useStudioResizableLayoutContext } from './useStudioResizableLayoutContext'; + +export const useResizable = (minimumSize: number) => { + const { orientation } = useStudioResizableLayoutContext(); + + const calculateTotalFlexGrow = (previousElement: HTMLElement, nextElement: HTMLElement) => { + const previousFlexGrow = parseFloat(previousElement.style.flexGrow || '1'); + const nextFlexGrow = parseFloat(nextElement.style.flexGrow || '1'); + return previousFlexGrow + nextFlexGrow; + }; + + const calculateElementDimensions = ( + previousElement: HTMLElement, + nextElement: HTMLElement, + ): { sumFlexGrow: number; sumElementSize: number } => { + const sumFlexGrow = calculateTotalFlexGrow(previousElement, nextElement); + if (orientation === 'horizontal') { + const sumElementSize = previousElement.offsetWidth + nextElement.offsetWidth; + return { sumFlexGrow, sumElementSize }; + } else { + const sumElementSize = previousElement.offsetHeight + nextElement.offsetHeight; + return { sumFlexGrow, sumElementSize }; + } + }; + + const calculateFlexGrow = ( + previousElement: HTMLElement, + nextElement: HTMLElement, + delta: number, + minimumSize: number, + ): { previousElementFlexGrow: number; nextElementFlexGrow: number } => { + const { sumFlexGrow, sumElementSize } = calculateElementDimensions( + previousElement, + nextElement, + ); + let previousElementSize = + (orientation === 'vertical' ? previousElement.offsetHeight : previousElement.offsetWidth) + + delta; + let nextElementSize = + (orientation === 'vertical' ? nextElement.offsetHeight : nextElement.offsetWidth) - delta; + + if (previousElementSize < minimumSize) { + previousElementSize = minimumSize; + nextElementSize = sumElementSize - minimumSize; + } + + if (nextElementSize < 0) { + nextElementSize = 0; + previousElementSize = sumElementSize - minimumSize; + } + + const previousElementFlexGrow = sumFlexGrow * (previousElementSize / sumElementSize); + const nextElementFlexGrow = sumFlexGrow * (nextElementSize / sumElementSize); + return { previousElementFlexGrow, nextElementFlexGrow }; + }; + const resizeDelta = (previousElement: any, nextElement: any, delta: number) => { + if (!previousElement || !nextElement) { + console.log('No previous or next element found'); + return; + } + + const { previousElementFlexGrow, nextElementFlexGrow } = calculateFlexGrow( + previousElement, + nextElement, + delta, + minimumSize, + ); + previousElement.style.flexGrow = previousElementFlexGrow.toString(); + nextElement.style.flexGrow = nextElementFlexGrow.toString(); + }; + + return { resizeDelta }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts new file mode 100644 index 00000000000..b8d0d35bbe5 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts @@ -0,0 +1,4 @@ +import { useContext } from 'react'; +import { StudioResizableLayoutContext } from '../StudioResizableLayoutContainer/StudioResizableLayoutRoot'; + +export const useStudioResizableLayoutContext = () => useContext(StudioResizableLayoutContext); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts new file mode 100644 index 00000000000..149bb663aa7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts @@ -0,0 +1,17 @@ +import { + StudioResizableLayoutContainer, + type StudioResizableLayoutContainerProps, +} from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { StudioResizableLayoutRoot } from './StudioResizableLayoutContainer/StudioResizableLayoutRoot'; + +type StudioResizableLayoutComponent = { + Container: typeof StudioResizableLayoutContainer; + Root: typeof StudioResizableLayoutRoot; +}; + +export const StudioResizableLayout: StudioResizableLayoutComponent = { + Container: StudioResizableLayoutContainer, + Root: StudioResizableLayoutRoot, +}; + +export type { StudioResizableLayoutContainerProps }; diff --git a/frontend/libs/studio-components/src/components/index.ts b/frontend/libs/studio-components/src/components/index.ts index c663c262301..acdff85806a 100644 --- a/frontend/libs/studio-components/src/components/index.ts +++ b/frontend/libs/studio-components/src/components/index.ts @@ -20,6 +20,7 @@ export * from './StudioParagraph'; export * from './StudioPageSpinner'; export * from './StudioPopover'; export * from './StudioProperty'; +export * from './StudioResizableLayout'; export * from './StudioSectionHeader'; export * from './StudioSpinner'; export * from './StudioTableRemotePagination'; From 56ec604c92e7ba435a025c906c1817609d1ef0c6 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 19 Jun 2024 15:42:28 +0200 Subject: [PATCH 02/44] Alter `FormDesigner` view to test out resizable layout --- .../components/Elements/Elements.module.css | 1 - .../src/containers/FormDesigner.module.css | 1 - .../src/containers/FormDesigner.tsx | 28 +++++++++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css index c51c270534a..56e59dc3af4 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css +++ b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css @@ -1,6 +1,5 @@ .root { background: var(--fds-semantic-surface-neutral-subtle); - flex: var(--elements-width-fraction); } .pagesContent { diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css index d606a86f042..766e48e73a6 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css @@ -15,7 +15,6 @@ --properties-width-fraction: 2; --preview-width-fraction: 3; - flex-grow: 1; height: calc(100vh - var(--header-height)); overflow-y: hidden; } diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index 1992ff48f50..db6f6c0dadc 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -10,7 +10,7 @@ import { useFormLayoutsQuery } from '../hooks/queries/useFormLayoutsQuery'; import { useFormLayoutSettingsQuery } from '../hooks/queries/useFormLayoutSettingsQuery'; import { useRuleModelQuery } from '../hooks/queries/useRuleModelQuery'; import { ErrorPage } from '../components/ErrorPage'; -import { StudioPageSpinner } from '@studio/components'; +import { StudioPageSpinner, StudioResizableLayout } from '@studio/components'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import { useRuleConfigQuery } from '../hooks/queries/useRuleConfigQuery'; import { useInstanceIdQuery } from 'app-shared/hooks/queries'; @@ -32,6 +32,7 @@ import { FormLayoutActions } from '../features/formDesigner/formLayout/formLayou import { Preview } from '../components/Preview'; import { setSelectedLayoutInLocalStorage } from '../utils/localStorageUtils'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; +import { StudioResizableLayoutRoot } from 'libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot'; export interface FormDesignerProps { selectedLayout: string; @@ -147,10 +148,27 @@ export const FormDesigner = ({
- - - - + + + + + + + + + + + + + + + + + + + + +
From ac8454e913e8ef56892dc7b523806ad37ff40232 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 19 Jun 2024 15:47:15 +0200 Subject: [PATCH 03/44] Remove commented code --- .../StudioResizableLayoutRoot.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx index 481f69a859c..b13cac0f868 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx @@ -14,8 +14,6 @@ export type SiblingElements = { export const StudioResizableLayoutContext = React.createContext({ orientation: 'horizontal', - // siblingElements: new Map(), - // containerFlexGrow: new Map(), }); const StudioResizableLayoutRoot = ({ @@ -24,12 +22,6 @@ const StudioResizableLayoutRoot = ({ }: StudioResizableLayoutRootProps): React.ReactElement => { const rootRef = useRef(null); - // const childrenRef = useRef([]); - // - // useEffect(() => { - // childrenRef.current = children.slice(0, children.length).map(() => React.createRef()); - // }, [children]); - return (
{children} - {/* {children.map((child, i: number) => { */} - {/* return React.cloneElement(child, { */} - {/* orentation: "vertical", */} - {/* key: i, */} - {/* ref: (el) => { childrenRef.current[i] = el } */} - {/* }); */} - {/* })} */}
); From ce56af3a311778aefd36ee96a7740bba6e0149cd Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 11:32:07 +0200 Subject: [PATCH 04/44] Undo deletion of css rule --- .../ux-editor-v3/src/components/Elements/Elements.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css index 56e59dc3af4..c51c270534a 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css +++ b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.module.css @@ -1,5 +1,6 @@ .root { background: var(--fds-semantic-surface-neutral-subtle); + flex: var(--elements-width-fraction); } .pagesContent { From 9b43d1fa2415a756bbe001e8248484f30c4440b2 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 11:33:37 +0200 Subject: [PATCH 05/44] Move `LayoutSet` selector to own toolbar similar to `SchemaEditor` --- .../src/components/Elements/Elements.tsx | 3 --- .../Elements/LayoutSetsContainer.module.css | 2 -- .../src/components/Elements/LayoutSetsContainer.tsx | 2 +- .../ux-editor-v3/src/containers/FormDesigner.tsx | 2 ++ .../src/containers/FormDesignerToolbar.module.css | 6 ++++++ .../src/containers/FormDesignerToolbar.tsx | 13 +++++++++++++ 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css create mode 100644 frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx index 030a495a925..ff75c1d4de7 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx @@ -17,10 +17,8 @@ export const Elements = () => { const { org, app } = useStudioEnvironmentParams(); const selectedLayout: string = useSelector(selectedLayoutNameSelector); const { selectedLayoutSet } = useAppContext(); - const layoutSetsQuery = useLayoutSetsQuery(org, app); const { data: formLayoutSettings } = useFormLayoutSettingsQuery(org, app, selectedLayoutSet); const receiptName = formLayoutSettings?.receiptLayoutName; - const layoutSetNames = layoutSetsQuery?.data?.sets; const hideComponents = selectedLayout === 'default' || selectedLayout === undefined; @@ -28,7 +26,6 @@ export const Elements = () => { return (
- {layoutSetNames && } {t('left_menu.components')} diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css index ebe13d456a7..f2476301540 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css +++ b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css @@ -1,4 +1,2 @@ .dropDownContainer { - margin: var(--fds-spacing-5); - margin-bottom: 5px; } diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx index 08928286ec1..09cfce45e56 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx @@ -24,7 +24,7 @@ export function LayoutSetsContainer() { return (
onLayoutSetClick(event.target.value)} value={selectedLayoutSet} > diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index db6f6c0dadc..22d14d9c166 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -33,6 +33,7 @@ import { Preview } from '../components/Preview'; import { setSelectedLayoutInLocalStorage } from '../utils/localStorageUtils'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; import { StudioResizableLayoutRoot } from 'libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot'; +import { FormDesignerToolbar } from './FormDesignerToolbar'; export interface FormDesignerProps { selectedLayout: string; @@ -147,6 +148,7 @@ export const FormDesigner = ({ return (
+
diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css new file mode 100644 index 00000000000..e1c0d8b41fc --- /dev/null +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css @@ -0,0 +1,6 @@ +.toolbar { + align-items: center; + display: flex; + padding: 8px; + border-bottom: 1px solid #c9c9c9; +} diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx new file mode 100644 index 00000000000..9f0bf754b21 --- /dev/null +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { LayoutSetsContainer } from '../components/Elements/LayoutSetsContainer'; +import { useLayoutSetsQuery } from '../hooks/queries/useLayoutSetsQuery'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import classes from './FormDesignerToolbar.module.css'; + +export const FormDesignerToolbar = () => { + const { org, app } = useStudioEnvironmentParams(); + const layoutSetsQuery = useLayoutSetsQuery(org, app); + const layoutSetNames = layoutSetsQuery?.data?.sets; + + return
{layoutSetNames && }
; +}; From eb58be29b6f4dc6142ae29af5118dfac11b7e0cb Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 11:34:53 +0200 Subject: [PATCH 06/44] Refactor `ResizableLayout` implementation --- .../StudioResizableLayoutContainer.tsx | 142 +++++++----------- .../StudioResizableLayoutElement.tsx | 85 +++++++++++ .../StudioResizableLayoutRoot.tsx | 40 ----- .../classes/StudioResizableHandler.ts | 64 ++++++++ .../classes/StudioResizableLayoutElement.ts | 23 +++ .../hooks/useKeyboardControls.ts | 21 +++ .../hooks/useMouseMovement.ts | 50 ++++++ .../hooks/useResizable.ts | 90 +++-------- .../hooks/useResizableFunctions.ts | 73 +++++++++ .../hooks/useStudioResizableLayoutContext.ts | 10 +- .../components/StudioResizableLayout/index.ts | 18 ++- 11 files changed, 405 insertions(+), 211 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 16760cf4afe..3b8a27ebd4d 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -1,108 +1,68 @@ -import React, { useCallback, useRef, useState } from 'react'; -import classes from './StudioResizableLayoutContainer.module.css'; -import { useResizable } from '../hooks/useResizable'; -import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; +import React, { Children, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import classes from './StudioResizableLayoutRoot.module.css'; +import { type StudioResizableLayoutElementProps } from './StudioResizableLayoutElement'; +import { useResizableFunctions } from '../hooks/useResizableFunctions'; + +export type StudioResizableOrientation = 'horizontal' | 'vertical'; export type StudioResizableLayoutContainerProps = { - minimumSize?: number; - canBeCollapsed?: boolean; - flexGrow?: number; - children: React.ReactElement | React.ReactElement[]; + orientation: StudioResizableOrientation; + children: React.ReactElement[]; }; -const StudioResizableLayoutContainer = ({ - children, - minimumSize = 0, - flexGrow = 1, -}: StudioResizableLayoutContainerProps) => { - const { resizeDelta } = useResizable(minimumSize); - const { orientation } = useStudioResizableLayoutContext(); - const containerRef = useRef(null); - const handleRef = useRef(null); - const lastMousePosition = useRef(0); - const [isResizing, setIsResizing] = useState(false); - - function handleKeyDown(event: React.KeyboardEvent): void { - const nextSibling = handleRef.current.nextElementSibling; - let deltaMove = 0; - switch (event.key) { - case 'ArrowLeft': - case 'ArrowUp': - event.preventDefault(); - deltaMove = -10; - if (event.shiftKey) { - deltaMove *= 5; - } - resizeDelta(containerRef.current, nextSibling, deltaMove); - break; - case 'ArrowRight': - case 'ArrowDown': - event.preventDefault(); - deltaMove = 10; - if (event.shiftKey) { - deltaMove *= 5; - } - resizeDelta(containerRef.current, nextSibling, deltaMove); - break; - } - } +export type StudioResizableLayoutContextProps = { + orientation: StudioResizableOrientation; + containerSizes: number[]; + resizeDelta: (index: number, size: number) => void; + collapse: (index: number) => void; +}; - const mouseMove = useCallback( - (event: MouseEvent) => { - const mousePos = orientation === 'horizontal' ? event.pageX : event.pageY; - const mouseDelta = mousePos - lastMousePosition.current; - const nextSibling = handleRef.current.nextElementSibling; - resizeDelta(containerRef.current, nextSibling, mouseDelta); - lastMousePosition.current = mousePos; - }, - [resizeDelta, orientation], - ); +export const StudioResizableLayoutContext = + React.createContext>(undefined); - const mouseUp = useCallback( - (_: MouseEvent) => { - setIsResizing(false); - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - }, - [mouseMove], +const StudioResizableLayoutContainer = ({ + children, + orientation, +}: StudioResizableLayoutContainerProps): React.ReactElement => { + const elementRefs = useRef<(HTMLDivElement | null)[]>([]); + const [containerSizes, setContainerSizes] = useState([]); + const { resizeTo, resizeDelta } = useResizableFunctions( + orientation, + elementRefs, + children, + setContainerSizes, ); - const mouseDown = useCallback( - (event: React.MouseEvent) => { - event.preventDefault(); - setIsResizing(true); - lastMousePosition.current = orientation === 'horizontal' ? event.pageX : event.pageY; - window.addEventListener('mousemove', mouseMove); - window.addEventListener('mouseup', mouseUp); + const collapse = (index: number) => { + const collapsedSize = children[index].props.collapsedSize || 0; + resizeTo(index, collapsedSize); + }; - return () => { - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - }; - }, - [mouseMove, mouseUp, orientation], - ); + useEffect(() => { + elementRefs.current = elementRefs.current.slice(0, children.length); + }, [children]); return ( - <> -
- {children} -
+
- + className={classes.root} + style={{ flexDirection: orientation === 'horizontal' ? 'row' : 'column' }} + > + {Children.map(children, (child, index) => { + const hasNeighbour = index < children.length - 1; + return React.cloneElement(child, { + index, + hasNeighbour, + ref: (element: HTMLDivElement) => (elementRefs.current[index] = element), + }); + })} +
+ ); }; StudioResizableLayoutContainer.displayName = 'StudioResizableLayout.Container'; -export { StudioResizableLayoutContainer }; +export { StudioResizableLayoutContainer as StudioResizableLayoutContainer }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx new file mode 100644 index 00000000000..95a8eef804e --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx @@ -0,0 +1,85 @@ +import React, { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; +import classes from './StudioResizableLayoutContainer.module.css'; +import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; +import { useMouseMovement } from '../hooks/useMouseMovement'; +import { useKeyboardControls } from '../hooks/useKeyboardControls'; + +export type StudioResizableLayoutElementProps = { + minimumSize?: number; + canBeCollapsed?: boolean; + collapsedSize?: number; + collapsed?: boolean; + + resize?: (size: number) => void; + hasNeighbour?: boolean; + + index?: number; + children: React.ReactElement | React.ReactElement[]; + ref?: React.Ref; +}; + +const StudioResizableLayoutElement = forwardRef( + ( + { + minimumSize = 0, + index, + collapsed, + children, + hasNeighbour = false, + }: StudioResizableLayoutElementProps, + ref, + ) => { + const containerRef = useRef(null); + useImperativeHandle(ref, () => containerRef.current); + const { resizeDelta, collapse, orientation, containerSize } = + useStudioResizableLayoutContext(index); + const { onMouseDown, isResizing } = useMouseMovement(orientation, (delta) => { + resizeDelta(index, delta); + }); + const { onKeyDown } = useKeyboardControls((delta) => { + resizeDelta(index, delta); + }); + + useEffect(() => { + if (collapsed) { + collapse(index); + } else { + resizeDelta(index, 0); + } + // disable linter as we only want to run this effect if the collapsed prop changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [collapsed]); + + return ( + <> +
+ {collapsed} + {children} +
+ {hasNeighbour && ( +
+ )} + + ); + }, +); + +StudioResizableLayoutElement.displayName = 'StudioResizableLayout.Element'; + +export { StudioResizableLayoutElement as StudioResizableLayoutElement }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx deleted file mode 100644 index b13cac0f868..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { HTMLAttributes } from 'react'; -import React, { Children, useEffect, useRef } from 'react'; -import classes from './StudioResizableLayoutRoot.module.css'; - -export type StudioResizableLayoutRootProps = { - orientation: 'horizontal' | 'vertical'; - children: React.ReactElement[]; -}; - -export type SiblingElements = { - self: HTMLElement | null; - next: HTMLElement | null; -}; - -export const StudioResizableLayoutContext = React.createContext({ - orientation: 'horizontal', -}); - -const StudioResizableLayoutRoot = ({ - children, - orientation, -}: StudioResizableLayoutRootProps): React.ReactElement => { - const rootRef = useRef(null); - - return ( - -
- {children} -
-
- ); -}; - -StudioResizableLayoutRoot.displayName = 'StudioResizableLayout.Root'; - -export { StudioResizableLayoutRoot }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts new file mode 100644 index 00000000000..fe1b40d5d30 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts @@ -0,0 +1,64 @@ +import { StudioResizableLayoutElement } from './StudioResizableLayoutElement'; + +export class ResizeHandler { + private containerElement: StudioResizableLayoutElement; + private neighbourElement: StudioResizableLayoutElement; + + constructor( + private orientation: 'horizontal' | 'vertical', + containerElement: HTMLElement, + containerMinimumSize: number, + neghbourElement: HTMLElement, + neighbourMinimumSize: number, + ) { + if (containerElement === undefined || neghbourElement === undefined) { + throw new Error('Element is undefined'); + } + this.containerElement = new StudioResizableLayoutElement( + containerElement, + containerMinimumSize, + this.orientation, + ); + this.neighbourElement = new StudioResizableLayoutElement( + neghbourElement, + neighbourMinimumSize, + this.orientation, + ); // TODO: make this use the next element's minimum size somehow + } + + public ensureMinimumSize() { + if (!this.containerElement || !this.neighbourElement) { + return; + } + + // TODO: implement collapsed state for the container + // maybe on dragging a certain distance over the minimum size? + // could possibly be hard to implement with the current solution + if (this.containerElement.size < this.containerElement.minimumSize) { + this.containerElement.flexGrow = 0; + this.neighbourElement.flexGrow = 1; + } + } + + public resizeTo = (newSize: number): { containerFlexGrow: number; neighbourFlexGrow: number } => { + if (this.containerElement.minimumSize > newSize) { + newSize = this.containerElement.minimumSize; + } + const totalSize = this.containerElement.size + this.neighbourElement.size; + if (this.neighbourElement.minimumSize > totalSize - newSize) { + newSize = totalSize - this.neighbourElement.minimumSize; + } + const neighbourElementNewSize = totalSize - newSize; + + const totalFlexGrow = this.containerElement.flexGrow + this.neighbourElement.flexGrow; + const containerFlexGrow = totalFlexGrow * (newSize / totalSize); + const neighbourFlexGrow = totalFlexGrow * (neighbourElementNewSize / totalSize); + return { containerFlexGrow, neighbourFlexGrow }; + }; + + public resizeDelta = ( + delta: number, + ): { containerFlexGrow: number; neighbourFlexGrow: number } => { + return this.resizeTo(this.containerElement.size + delta); + }; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts new file mode 100644 index 00000000000..03694f0d6c7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts @@ -0,0 +1,23 @@ +export class StudioResizableLayoutElement { + constructor( + private element: HTMLElement, + public minimumSize: number, + public orientation: 'horizontal' | 'vertical', + ) { + if (element === undefined) { + throw new Error('Element is undefined'); + } + } + + public get size() { + return this.orientation === 'vertical' ? this.element.offsetHeight! : this.element.offsetWidth!; + } + + public get flexGrow() { + return parseFloat(this.element.style.flexGrow || '1'); + } + + public set flexGrow(value: number) { + this.element.style.setProperty('flex-grow', value.toString()); + } +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts new file mode 100644 index 00000000000..b7fa27aff0b --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts @@ -0,0 +1,21 @@ +export const useKeyboardControls = ( + onResize: (delta: number) => void, +): { onKeyDown: (event: React.KeyboardEvent) => void } => { + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { + if (event.shiftKey) { + onResize(-50); + } else { + onResize(-10); + } + } else if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { + if (event.shiftKey) { + onResize(50); + } else { + onResize(10); + } + } + }; + + return { onKeyDown }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts new file mode 100644 index 00000000000..ab9d92893a3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts @@ -0,0 +1,50 @@ +import { useRef, useCallback, useState } from 'react'; +import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; + +export const useMouseMovement = ( + orientation: StudioResizableOrientation, + onMousePosChange: (delta: number) => void, +): { + onMouseDown: (event: React.MouseEvent) => () => void; + isResizing: boolean; +} => { + const lastMousePosition = useRef(0); + const [isResizing, setIsResizing] = useState(false); + + const mouseMove = useCallback( + (event: MouseEvent) => { + const mousePos = orientation === 'horizontal' ? event.pageX : event.pageY; + const mouseDelta = mousePos - lastMousePosition.current; + onMousePosChange(mouseDelta); + lastMousePosition.current = mousePos; + }, + [orientation, onMousePosChange], + ); + + const mouseUp = useCallback( + (_: MouseEvent) => { + setIsResizing(false); + window.removeEventListener('mousemove', mouseMove); + window.removeEventListener('mouseup', mouseUp); + }, + [mouseMove], + ); + + const onMouseDown = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + setIsResizing(true); + lastMousePosition.current = orientation === 'horizontal' ? event.pageX : event.pageY; + window.addEventListener('mousemove', mouseMove); + window.addEventListener('mouseup', mouseUp); + + return () => { + window.removeEventListener('mousemove', mouseMove); + window.removeEventListener('mouseup', mouseUp); + }; + }, + [mouseMove, mouseUp, orientation], + ); + + return { onMouseDown, isResizing }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts index 468cc4ffe77..08a66262db7 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts @@ -1,73 +1,17 @@ -import { useStudioResizableLayoutContext } from './useStudioResizableLayoutContext'; - -export const useResizable = (minimumSize: number) => { - const { orientation } = useStudioResizableLayoutContext(); - - const calculateTotalFlexGrow = (previousElement: HTMLElement, nextElement: HTMLElement) => { - const previousFlexGrow = parseFloat(previousElement.style.flexGrow || '1'); - const nextFlexGrow = parseFloat(nextElement.style.flexGrow || '1'); - return previousFlexGrow + nextFlexGrow; - }; - - const calculateElementDimensions = ( - previousElement: HTMLElement, - nextElement: HTMLElement, - ): { sumFlexGrow: number; sumElementSize: number } => { - const sumFlexGrow = calculateTotalFlexGrow(previousElement, nextElement); - if (orientation === 'horizontal') { - const sumElementSize = previousElement.offsetWidth + nextElement.offsetWidth; - return { sumFlexGrow, sumElementSize }; - } else { - const sumElementSize = previousElement.offsetHeight + nextElement.offsetHeight; - return { sumFlexGrow, sumElementSize }; - } - }; - - const calculateFlexGrow = ( - previousElement: HTMLElement, - nextElement: HTMLElement, - delta: number, - minimumSize: number, - ): { previousElementFlexGrow: number; nextElementFlexGrow: number } => { - const { sumFlexGrow, sumElementSize } = calculateElementDimensions( - previousElement, - nextElement, - ); - let previousElementSize = - (orientation === 'vertical' ? previousElement.offsetHeight : previousElement.offsetWidth) + - delta; - let nextElementSize = - (orientation === 'vertical' ? nextElement.offsetHeight : nextElement.offsetWidth) - delta; - - if (previousElementSize < minimumSize) { - previousElementSize = minimumSize; - nextElementSize = sumElementSize - minimumSize; - } - - if (nextElementSize < 0) { - nextElementSize = 0; - previousElementSize = sumElementSize - minimumSize; - } - - const previousElementFlexGrow = sumFlexGrow * (previousElementSize / sumElementSize); - const nextElementFlexGrow = sumFlexGrow * (nextElementSize / sumElementSize); - return { previousElementFlexGrow, nextElementFlexGrow }; - }; - const resizeDelta = (previousElement: any, nextElement: any, delta: number) => { - if (!previousElement || !nextElement) { - console.log('No previous or next element found'); - return; - } - - const { previousElementFlexGrow, nextElementFlexGrow } = calculateFlexGrow( - previousElement, - nextElement, - delta, - minimumSize, - ); - previousElement.style.flexGrow = previousElementFlexGrow.toString(); - nextElement.style.flexGrow = nextElementFlexGrow.toString(); - }; - - return { resizeDelta }; -}; +// import { useEffect, useRef } from 'react'; +// import { useStudioResizableLayoutContext } from './useStudioResizableLayoutContext'; +// import { ResizeHandler } from '../classes/StudioResizableHandler'; +// +// export const useResizable = (minimumSize: number, ) => { +// const { orientation } = useStudioResizableLayoutContext(); +// +// const resizeDelta = (delta: number) => { +// resizeHandler.resizeDelta(delta); +// }; +// +// useEffect(() => { +// resizeHandler.ensureMinimumSize(); +// }, [minimumSize, resizeHandler]); +// +// return { resizeDelta }; +// }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts new file mode 100644 index 00000000000..145ad7c3cc3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts @@ -0,0 +1,73 @@ +import type { MutableRefObject, Dispatch, SetStateAction } from 'react'; +import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { ResizeHandler } from '../classes/StudioResizableHandler'; + +type useResizableFunctionsReturnType = { + resizeTo: (index: number, size: number) => void; + resizeDelta: (index: number, size: number) => void; +}; + +export const useResizableFunctions = ( + orientation: StudioResizableOrientation, + elementRefs: MutableRefObject, + children: any[], + setContainerSizes: Dispatch>, +): useResizableFunctionsReturnType => { + const getElementNeighbour = (index: number) => { + if (elementRefs.current.length < index + 2) { + return { index: index - 1, ref: elementRefs.current[index - 1], child: children[index - 1] }; + } else { + return { index: index + 1, ref: elementRefs.current[index + 1], child: children[index + 1] }; + } + }; + + const getElement = (index: number) => { + return { index, ref: elementRefs.current[index], child: children[index] }; + }; + + const resizeTo = (index: number, size: number) => { + const { ref: elementRef, child: element } = getElement(index); + const { + index: neighbourIndex, + ref: neighbourRef, + child: neighbour, + } = getElementNeighbour(index); + + const resizeHandler = new ResizeHandler(orientation, elementRef, 0, neighbourRef, 0); + const { containerFlexGrow, neighbourFlexGrow } = resizeHandler.resizeTo(size); + + setContainerSizes((prev) => { + const newSizes = [...prev]; + newSizes[index] = containerFlexGrow; + newSizes[neighbourIndex] = neighbourFlexGrow; + return newSizes; + }); + }; + + const resizeDelta = (index: number, size: number) => { + const { ref: elementRef, child: element } = getElement(index); + const { + index: neighbourIndex, + ref: neighbourRef, + child: neighbour, + } = getElementNeighbour(index); + + const resizeHandler = new ResizeHandler( + orientation, + elementRef, + element.props.minimumSize || 0, + neighbourRef, + neighbour.props.minimumSize || 0, + ); + const { containerFlexGrow, neighbourFlexGrow } = resizeHandler.resizeDelta(size); + + setContainerSizes((prev) => { + const newSizes = [...prev]; + newSizes[index] = containerFlexGrow; + newSizes[neighbourIndex] = neighbourFlexGrow; + return newSizes; + }); + }; + + return { resizeTo, resizeDelta }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts index b8d0d35bbe5..94ee8389d7b 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts @@ -1,4 +1,10 @@ import { useContext } from 'react'; -import { StudioResizableLayoutContext } from '../StudioResizableLayoutContainer/StudioResizableLayoutRoot'; +import { StudioResizableLayoutContext } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; -export const useStudioResizableLayoutContext = () => useContext(StudioResizableLayoutContext); +export const useStudioResizableLayoutContext = (index: number) => { + const { containerSizes, orientation, resizeDelta, collapse } = useContext( + StudioResizableLayoutContext, + ); + const containerSize = containerSizes[index]; + return { containerSize, orientation, resizeDelta, collapse }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts index 149bb663aa7..ff02e758bee 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts @@ -1,17 +1,25 @@ import { + StudioResizableLayoutElement, + type StudioResizableLayoutElementProps, +} from './StudioResizableLayoutContainer/StudioResizableLayoutElement'; +import { + StudioResizableLayoutContext, StudioResizableLayoutContainer, - type StudioResizableLayoutContainerProps, } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; -import { StudioResizableLayoutRoot } from './StudioResizableLayoutContainer/StudioResizableLayoutRoot'; type StudioResizableLayoutComponent = { + Element: typeof StudioResizableLayoutElement; Container: typeof StudioResizableLayoutContainer; - Root: typeof StudioResizableLayoutRoot; + Context: typeof StudioResizableLayoutContext; }; export const StudioResizableLayout: StudioResizableLayoutComponent = { + Element: StudioResizableLayoutElement, Container: StudioResizableLayoutContainer, - Root: StudioResizableLayoutRoot, + Context: StudioResizableLayoutContext, }; -export type { StudioResizableLayoutContainerProps }; +export type { + StudioResizableLayoutElementProps as StudioResizableLayoutContainerProps, + StudioResizableLayoutContext, +}; From 823884f4e997fa1c3ed6f6f3f87a1f3480ebbdc2 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 11:35:46 +0200 Subject: [PATCH 07/44] Make preview collapsing also collapse the resizable layout element --- .../src/components/Preview/Preview.tsx | 7 ++- .../src/containers/FormDesigner.tsx | 45 +++++++++++-------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx b/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx index 500d841ce27..49c3c6c3e05 100644 --- a/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx @@ -15,13 +15,18 @@ import { ViewToggler } from './ViewToggler/ViewToggler'; import { ArrowRightIcon } from '@studio/icons'; import { PreviewLimitationsInfo } from 'app-shared/components/PreviewLimitationsInfo/PreviewLimitationsInfo'; -export const Preview = () => { +export type PreviewProps = { + onCollapseToggle?: (collapsed: boolean) => void; +}; + +export const Preview = ({ onCollapseToggle }: PreviewProps) => { const { t } = useTranslation(); const [isPreviewHidden, setIsPreviewHidden] = useState(false); const layoutName = useSelector(selectedLayoutNameSelector); const noPageSelected = layoutName === 'default' || layoutName === undefined; const togglePreview = (): void => { + onCollapseToggle?.(!isPreviewHidden); setIsPreviewHidden((prev: boolean) => !prev); }; diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index 22d14d9c166..adb1b7e1d68 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Properties } from '../components/Properties'; import { DesignView } from './DesignView'; @@ -32,7 +32,7 @@ import { FormLayoutActions } from '../features/formDesigner/formLayout/formLayou import { Preview } from '../components/Preview'; import { setSelectedLayoutInLocalStorage } from '../utils/localStorageUtils'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; -import { StudioResizableLayoutRoot } from 'libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot'; +import previewGet from '@studio/testing/mockend/src/routes/preview-get'; import { FormDesignerToolbar } from './FormDesignerToolbar'; export interface FormDesignerProps { @@ -64,6 +64,7 @@ export const FormDesigner = ({ ); const [searchParams] = useSearchParams(); const { handleEdit } = useFormItemContext(); + const [previewCollapsed, setPreviewCollapsed] = useState(false); const layoutPagesOrder = formLayoutSettings?.pages.order; @@ -150,27 +151,33 @@ export const FormDesigner = ({
- - + + - - + + - - - - + + + + - - + + - - - - - - - + + + + + setPreviewCollapsed(collapsed)} + /> + +
From ee9552b8aacf85dde942023d78d60cf27ffe21cc Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 12:02:51 +0200 Subject: [PATCH 08/44] Refactor css and use negative inset to increase grab area of handles --- .../StudioResizableLayoutContainer.module.css | 20 ++--------- .../StudioResizableLayoutContainer.tsx | 2 +- .../StudioResizableLayoutElement.module.css | 34 +++++++++++++++++++ .../StudioResizableLayoutElement.tsx | 5 ++- .../StudioResizableLayoutRoot.module.css | 5 --- 5 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css index fb53147b0e3..4802e665a49 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.module.css @@ -1,19 +1,5 @@ -.container { +.root { display: flex; - overflow: hidden; - position: relative; - flex: 1; -} - -.resizeHandle { - background-color: #f0f0f0; - flex: 0 0 4px; - position: relative; -} - -.resizeHandle:after { - content: ''; - position: absolute; - left: -15px; - right: -15px; + flex-direction: row; + width: 100%; } diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 3b8a27ebd4d..88128078ac9 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -1,5 +1,5 @@ import React, { Children, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import classes from './StudioResizableLayoutRoot.module.css'; +import classes from './StudioResizableLayoutContainer.module.css'; import { type StudioResizableLayoutElementProps } from './StudioResizableLayoutElement'; import { useResizableFunctions } from '../hooks/useResizableFunctions'; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css new file mode 100644 index 00000000000..83be1a71fa6 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css @@ -0,0 +1,34 @@ +.container { + display: flex; + overflow: hidden; + position: relative; + flex: 1; +} + +.resizeHandleH { + background-color: #f0f0f0; + flex: 0 0 4px; + position: relative; + cursor: col-resize; +} + +.resizeHandleH:after { + z-index: 1; + content: ''; + position: absolute; + inset: 0px -4px; +} + +.resizeHandleV { + background-color: #f0f0f0; + flex: 0 0 4px; + position: relative; + cursor: row-resize; +} + +.resizeHandleV:after { + z-index: 1; + content: ''; + position: absolute; + inset: -4px 0px; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx index 95a8eef804e..461d473ca90 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx @@ -6,7 +6,7 @@ import React, { useRef, useState, } from 'react'; -import classes from './StudioResizableLayoutContainer.module.css'; +import classes from './StudioResizableLayoutElement.module.css'; import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; import { useMouseMovement } from '../hooks/useMouseMovement'; import { useKeyboardControls } from '../hooks/useKeyboardControls'; @@ -66,12 +66,11 @@ const StudioResizableLayoutElement = forwardRef( {hasNeighbour && (
)} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css deleted file mode 100644 index 4802e665a49..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.root { - display: flex; - flex-direction: row; - width: 100%; -} From 727a53a98311dae9b4201e2822a316c005decc75 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 25 Jun 2024 12:07:37 +0200 Subject: [PATCH 09/44] Update layout on minimumSize change --- .../StudioResizableLayoutElement.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx index 461d473ca90..86a86d8cefd 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx @@ -40,12 +40,14 @@ const StudioResizableLayoutElement = forwardRef( useImperativeHandle(ref, () => containerRef.current); const { resizeDelta, collapse, orientation, containerSize } = useStudioResizableLayoutContext(index); - const { onMouseDown, isResizing } = useMouseMovement(orientation, (delta) => { - resizeDelta(index, delta); - }); - const { onKeyDown } = useKeyboardControls((delta) => { - resizeDelta(index, delta); - }); + const { onMouseDown, isResizing } = useMouseMovement(orientation, (delta) => + resizeDelta(index, delta), + ); + const { onKeyDown } = useKeyboardControls((delta) => resizeDelta(index, delta)); + + useEffect(() => { + resizeDelta(index, 0); + }, [minimumSize]); useEffect(() => { if (collapsed) { From 8074bc2a169fdc9b2e9e21d88366a70ce6a472ce Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 26 Jun 2024 13:39:19 +0200 Subject: [PATCH 10/44] Add storybook entry for Resizable Layout --- .../StudioResizableLayout.mdx | 14 ++++++ .../StudioResizableLayout.stories.tsx | 50 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx new file mode 100644 index 00000000000..55c1baf6ee3 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx @@ -0,0 +1,14 @@ +import { Canvas, Meta } from '@storybook/blocks'; +import { Heading, Paragraph } from '@digdir/design-system-react'; +import * as StudioResizableLayoutStories from './StudioResizableLayout.stories'; + + + + + StudioResizableLayout + + + StudioResizableLayout is used to display a header for a config-section in the studio. + + + diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx new file mode 100644 index 00000000000..c6955ceb9fa --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx @@ -0,0 +1,50 @@ +import type { StoryFn, Meta } from '@storybook/react/*'; +import React from 'react'; +import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { StudioResizableLayoutElement } from './StudioResizableLayoutElement/StudioResizableLayoutElement'; + +type Story = StoryFn; + +const meta: Meta = { + title: 'Studio/StudioResizableLayoutContainer', + component: StudioResizableLayoutContainer, + argTypes: { + icon: { + control: false, + }, + }, +}; +export const Preview: Story = (args): React.ReactElement => ( +
+ + +
+ lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua +
+
+ + + +
+ lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua +
+
+ +
+ lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua +
+
+
+
+
+
+); + +Preview.args = { + layoutId: 'storylayout', + orientation: 'horizontal', +}; +export default meta; From d479f5f46a14c978941b4cc38fc4df76a9770dcf Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 27 Jun 2024 11:45:04 +0200 Subject: [PATCH 11/44] Refactor `StudioResizableLayout` --- .../StudioResizableLayoutContainer.test.tsx | 42 +++++++ .../StudioResizableLayoutContainer.tsx | 79 +++++++------ .../StudioResizableLayoutElement.module.css | 34 ------ .../StudioResizableLayoutElement.module.css | 11 ++ .../StudioResizableLayoutElement.tsx | 49 +++----- .../StudioResizableLayoutHandle.module.css | 31 +++++ .../StudioResizableLayoutHandle.tsx | 44 +++++++ .../classes/StudioResizableHandler.ts | 64 ---------- .../classes/StudioResizableLayoutElement.ts | 30 +++-- .../context/StudioResizableLayoutContext.ts | 13 +++ .../hooks/useResizable.ts | 17 --- .../hooks/useResizableFunctions.ts | 73 ------------ .../hooks/useStudioResizableFunctions.ts | 109 ++++++++++++++++++ .../hooks/useStudioResizableLayoutContext.ts | 6 +- ... useStudioResizableLayoutMouseMovement.ts} | 10 +- .../hooks/useTrackContainerSizes.ts | 18 +++ .../components/StudioResizableLayout/index.ts | 13 +-- 17 files changed, 368 insertions(+), 275 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.module.css rename frontend/libs/studio-components/src/components/StudioResizableLayout/{StudioResizableLayoutContainer => StudioResizableLayoutElement}/StudioResizableLayoutElement.tsx (60%) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts delete mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts rename frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/{useMouseMovement.ts => useStudioResizableLayoutMouseMovement.ts} (79%) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx new file mode 100644 index 00000000000..00909b57c57 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import type { StudioResizableLayoutContainerProps } from './StudioResizableLayoutContainer'; +import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer'; +import { render, screen } from '@testing-library/react'; +import { StudioResizableLayoutElement } from '../StudioResizableLayoutElement/StudioResizableLayoutElement'; + +describe('StudioResizableLayoutContainer', () => { + it('should render children', () => { + renderStudioResizableLayoutContainer(); + expect(screen.getByTestId('childone')).toBeInTheDocument(); + expect(screen.getByTestId('childtwo')).toBeInTheDocument(); + }); + + it('should render just one handle with two children', () => { + renderStudioResizableLayoutContainer(); + expect(screen.getAllByRole('separator').length).toBe(1); + }); +}); + +const renderStudioResizableLayoutContainer = ( + props: Partial = {}, +) => { + const defaultProps: StudioResizableLayoutContainerProps = { + layoutId: 'test', + orientation: 'horizontal', + children: [], + }; + return render( + + +
+ test1 +
+
+ +
+ test1 +
+
+
, + ); +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 88128078ac9..59c4a91a360 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -1,68 +1,79 @@ -import React, { Children, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import React, { Children, useEffect, useRef } from 'react'; import classes from './StudioResizableLayoutContainer.module.css'; -import { type StudioResizableLayoutElementProps } from './StudioResizableLayoutElement'; -import { useResizableFunctions } from '../hooks/useResizableFunctions'; +import { type StudioResizableLayoutElementProps } from '../StudioResizableLayoutElement/StudioResizableLayoutElement'; +import { useStudioResizableLayoutFunctions } from '../hooks/useStudioResizableFunctions'; +import { useTrackContainerSizes } from '../hooks/useTrackContainerSizes'; +import { StudioResizableLayoutContext } from '../context/StudioResizableLayoutContext'; export type StudioResizableOrientation = 'horizontal' | 'vertical'; export type StudioResizableLayoutContainerProps = { + layoutId: string; orientation: StudioResizableOrientation; children: React.ReactElement[]; + /*localStorageContext?: string;*/ + style?: React.CSSProperties; }; -export type StudioResizableLayoutContextProps = { - orientation: StudioResizableOrientation; - containerSizes: number[]; - resizeDelta: (index: number, size: number) => void; - collapse: (index: number) => void; -}; - -export const StudioResizableLayoutContext = - React.createContext>(undefined); - const StudioResizableLayoutContainer = ({ + layoutId, children, orientation, + // localStorageContext = "default", + style, }: StudioResizableLayoutContainerProps): React.ReactElement => { const elementRefs = useRef<(HTMLDivElement | null)[]>([]); - const [containerSizes, setContainerSizes] = useState([]); - const { resizeTo, resizeDelta } = useResizableFunctions( + useEffect(() => { + elementRefs.current = elementRefs.current.slice(0, getValidChildren(children).length); + }, [children]); + + const { containerSizes, setContainerSizes } = useTrackContainerSizes(layoutId); + const { resizeTo, resizeDelta, collapse } = useStudioResizableLayoutFunctions( orientation, elementRefs, - children, - setContainerSizes, + getValidChildren(children), + (index, size) => setContainerSizes((prev) => ({ ...prev, [index]: size })), ); - const collapse = (index: number) => { - const collapsedSize = children[index].props.collapsedSize || 0; - resizeTo(index, collapsedSize); + const renderChildren = () => { + return Children.map(getValidChildren(children), (child, index) => { + const hasNeighbour = index < getValidChildren(children).length - 1; + return React.cloneElement(child, { + index, + hasNeighbour, + ref: (element: HTMLDivElement) => (elementRefs.current[index] = element), + }); + }); }; - useEffect(() => { - elementRefs.current = elementRefs.current.slice(0, children.length); - }, [children]); - return (
- {Children.map(children, (child, index) => { - const hasNeighbour = index < children.length - 1; - return React.cloneElement(child, { - index, - hasNeighbour, - ref: (element: HTMLDivElement) => (elementRefs.current[index] = element), - }); - })} + {renderChildren()}
); }; +const getValidChildren = ( + children: React.ReactElement< + StudioResizableLayoutElementProps, + string | React.JSXElementConstructor + >[], +) => { + return Children.map(children, (child) => { + if (!child) { + return; + } + return child; + }).filter((child) => !!child); +}; + StudioResizableLayoutContainer.displayName = 'StudioResizableLayout.Container'; export { StudioResizableLayoutContainer as StudioResizableLayoutContainer }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css deleted file mode 100644 index 83be1a71fa6..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.module.css +++ /dev/null @@ -1,34 +0,0 @@ -.container { - display: flex; - overflow: hidden; - position: relative; - flex: 1; -} - -.resizeHandleH { - background-color: #f0f0f0; - flex: 0 0 4px; - position: relative; - cursor: col-resize; -} - -.resizeHandleH:after { - z-index: 1; - content: ''; - position: absolute; - inset: 0px -4px; -} - -.resizeHandleV { - background-color: #f0f0f0; - flex: 0 0 4px; - position: relative; - cursor: row-resize; -} - -.resizeHandleV:after { - z-index: 1; - content: ''; - position: absolute; - inset: -4px 0px; -} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.module.css new file mode 100644 index 00000000000..b397f224077 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.module.css @@ -0,0 +1,11 @@ +.container { + display: flex; + overflow: auto; + position: relative; + flex: 1; +} + +.resizingOverlay { + position: absolute; + z-index: 100; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx similarity index 60% rename from frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx rename to frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx index 86a86d8cefd..6bd9068319c 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx @@ -1,25 +1,19 @@ -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react'; +import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; import classes from './StudioResizableLayoutElement.module.css'; import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; -import { useMouseMovement } from '../hooks/useMouseMovement'; -import { useKeyboardControls } from '../hooks/useKeyboardControls'; +import { StudioResizableLayoutHandle } from '../StudioResizableLayoutHandle/StudioResizableLayoutHandle'; export type StudioResizableLayoutElementProps = { minimumSize?: number; - canBeCollapsed?: boolean; collapsedSize?: number; collapsed?: boolean; + style?: React.CSSProperties; + onResizing?: (resizing: boolean) => void; + + //** supplied from container **// resize?: (size: number) => void; hasNeighbour?: boolean; - index?: number; children: React.ReactElement | React.ReactElement[]; ref?: React.Ref; @@ -33,21 +27,16 @@ const StudioResizableLayoutElement = forwardRef( collapsed, children, hasNeighbour = false, + style, + onResizing, }: StudioResizableLayoutElementProps, ref, ) => { const containerRef = useRef(null); useImperativeHandle(ref, () => containerRef.current); + const { resizeDelta, collapse, orientation, containerSize } = useStudioResizableLayoutContext(index); - const { onMouseDown, isResizing } = useMouseMovement(orientation, (delta) => - resizeDelta(index, delta), - ); - const { onKeyDown } = useKeyboardControls((delta) => resizeDelta(index, delta)); - - useEffect(() => { - resizeDelta(index, 0); - }, [minimumSize]); useEffect(() => { if (collapsed) { @@ -61,20 +50,20 @@ const StudioResizableLayoutElement = forwardRef( return ( <> -
+
{collapsed} {children}
{hasNeighbour && ( -
+ )} ); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css new file mode 100644 index 00000000000..b344dc5b255 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css @@ -0,0 +1,31 @@ +.resizeHandle { + flex: 0 0 1px; + position: relative; + background-color: var(--fds-semantic-border-neutral-subtle); +} + +.resizeHandle:after { + z-index: 1; + content: ''; + position: absolute; +} + +.resizeHandle:hover:after { + background-color: rgba(0, 0, 0, 0.1); +} + +.resizeHandleH { + cursor: col-resize; +} + +.resizeHandleH:after { + inset: 0px -6px; +} + +.resizeHandleV { + cursor: row-resize; +} + +.resizeHandleV:after { + inset: -6px 0px; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx new file mode 100644 index 00000000000..62a9e875038 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react'; +import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; +import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import classes from './StudioResizableLayoutHandle.module.css'; +import { useKeyboardControls } from '../hooks/useKeyboardControls'; +import { useStudioResizableLayoutMouseMovement } from '../hooks/useStudioResizableLayoutMouseMovement'; + +export type StudioResizableLayoutHandleProps = { + orientation: StudioResizableOrientation; + index: number; + onResizing?: (resizing: boolean) => void; +}; + +export const StudioResizableLayoutHandle = ({ + orientation, + index, + onResizing, +}: StudioResizableLayoutHandleProps) => { + const { resizeDelta } = useStudioResizableLayoutContext(index); + const { onMouseDown, isResizing } = useStudioResizableLayoutMouseMovement( + orientation, + (delta, _) => { + resizeDelta(index, delta); + }, + ); + const { onKeyDown } = useKeyboardControls((delta) => resizeDelta(index, delta)); + + useEffect(() => { + onResizing?.(isResizing); + }, [isResizing, onResizing]); + + return ( +
+ ); +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts deleted file mode 100644 index fe1b40d5d30..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableHandler.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { StudioResizableLayoutElement } from './StudioResizableLayoutElement'; - -export class ResizeHandler { - private containerElement: StudioResizableLayoutElement; - private neighbourElement: StudioResizableLayoutElement; - - constructor( - private orientation: 'horizontal' | 'vertical', - containerElement: HTMLElement, - containerMinimumSize: number, - neghbourElement: HTMLElement, - neighbourMinimumSize: number, - ) { - if (containerElement === undefined || neghbourElement === undefined) { - throw new Error('Element is undefined'); - } - this.containerElement = new StudioResizableLayoutElement( - containerElement, - containerMinimumSize, - this.orientation, - ); - this.neighbourElement = new StudioResizableLayoutElement( - neghbourElement, - neighbourMinimumSize, - this.orientation, - ); // TODO: make this use the next element's minimum size somehow - } - - public ensureMinimumSize() { - if (!this.containerElement || !this.neighbourElement) { - return; - } - - // TODO: implement collapsed state for the container - // maybe on dragging a certain distance over the minimum size? - // could possibly be hard to implement with the current solution - if (this.containerElement.size < this.containerElement.minimumSize) { - this.containerElement.flexGrow = 0; - this.neighbourElement.flexGrow = 1; - } - } - - public resizeTo = (newSize: number): { containerFlexGrow: number; neighbourFlexGrow: number } => { - if (this.containerElement.minimumSize > newSize) { - newSize = this.containerElement.minimumSize; - } - const totalSize = this.containerElement.size + this.neighbourElement.size; - if (this.neighbourElement.minimumSize > totalSize - newSize) { - newSize = totalSize - this.neighbourElement.minimumSize; - } - const neighbourElementNewSize = totalSize - newSize; - - const totalFlexGrow = this.containerElement.flexGrow + this.neighbourElement.flexGrow; - const containerFlexGrow = totalFlexGrow * (newSize / totalSize); - const neighbourFlexGrow = totalFlexGrow * (neighbourElementNewSize / totalSize); - return { containerFlexGrow, neighbourFlexGrow }; - }; - - public resizeDelta = ( - delta: number, - ): { containerFlexGrow: number; neighbourFlexGrow: number } => { - return this.resizeTo(this.containerElement.size + delta); - }; -} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts index 03694f0d6c7..3219b45584d 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts @@ -1,23 +1,37 @@ -export class StudioResizableLayoutElement { +export class StudioResizableLayoutArea { constructor( - private element: HTMLElement, - public minimumSize: number, + public index: number, + public HTMLElementRef: HTMLElement, + public reactElement: React.ReactElement, public orientation: 'horizontal' | 'vertical', ) { - if (element === undefined) { + if (HTMLElementRef === undefined) { throw new Error('Element is undefined'); } + if (reactElement === undefined) { + throw new Error('React element is undefined'); + } } public get size() { - return this.orientation === 'vertical' ? this.element.offsetHeight! : this.element.offsetWidth!; + return this.orientation === 'vertical' + ? this.HTMLElementRef.offsetHeight! + : this.HTMLElementRef.offsetWidth!; } public get flexGrow() { - return parseFloat(this.element.style.flexGrow || '1'); + return parseFloat(this.HTMLElementRef.style.flexGrow || '1'); + } + + public get minimumSize() { + return this.reactElement.props.minimumSize || 0; + } + + public get collapsedSize() { + return this.reactElement.props.collapsedSize || 0; } - public set flexGrow(value: number) { - this.element.style.setProperty('flex-grow', value.toString()); + public get collapsed() { + return this.reactElement.props.collapsed; } } diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts new file mode 100644 index 00000000000..3d27e107352 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts @@ -0,0 +1,13 @@ +import React from 'react'; +import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; + +export type StudioResizableLayoutContextProps = { + orientation: StudioResizableOrientation; + containerSizes: number[]; + resizeDelta: (index: number, size: number) => void; + resizeTo: (index: number, size: number) => void; + collapse: (index: number) => void; +}; + +export const StudioResizableLayoutContext = + React.createContext>(undefined); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts deleted file mode 100644 index 08a66262db7..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizable.ts +++ /dev/null @@ -1,17 +0,0 @@ -// import { useEffect, useRef } from 'react'; -// import { useStudioResizableLayoutContext } from './useStudioResizableLayoutContext'; -// import { ResizeHandler } from '../classes/StudioResizableHandler'; -// -// export const useResizable = (minimumSize: number, ) => { -// const { orientation } = useStudioResizableLayoutContext(); -// -// const resizeDelta = (delta: number) => { -// resizeHandler.resizeDelta(delta); -// }; -// -// useEffect(() => { -// resizeHandler.ensureMinimumSize(); -// }, [minimumSize, resizeHandler]); -// -// return { resizeDelta }; -// }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts deleted file mode 100644 index 145ad7c3cc3..00000000000 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useResizableFunctions.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { MutableRefObject, Dispatch, SetStateAction } from 'react'; -import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; -import { ResizeHandler } from '../classes/StudioResizableHandler'; - -type useResizableFunctionsReturnType = { - resizeTo: (index: number, size: number) => void; - resizeDelta: (index: number, size: number) => void; -}; - -export const useResizableFunctions = ( - orientation: StudioResizableOrientation, - elementRefs: MutableRefObject, - children: any[], - setContainerSizes: Dispatch>, -): useResizableFunctionsReturnType => { - const getElementNeighbour = (index: number) => { - if (elementRefs.current.length < index + 2) { - return { index: index - 1, ref: elementRefs.current[index - 1], child: children[index - 1] }; - } else { - return { index: index + 1, ref: elementRefs.current[index + 1], child: children[index + 1] }; - } - }; - - const getElement = (index: number) => { - return { index, ref: elementRefs.current[index], child: children[index] }; - }; - - const resizeTo = (index: number, size: number) => { - const { ref: elementRef, child: element } = getElement(index); - const { - index: neighbourIndex, - ref: neighbourRef, - child: neighbour, - } = getElementNeighbour(index); - - const resizeHandler = new ResizeHandler(orientation, elementRef, 0, neighbourRef, 0); - const { containerFlexGrow, neighbourFlexGrow } = resizeHandler.resizeTo(size); - - setContainerSizes((prev) => { - const newSizes = [...prev]; - newSizes[index] = containerFlexGrow; - newSizes[neighbourIndex] = neighbourFlexGrow; - return newSizes; - }); - }; - - const resizeDelta = (index: number, size: number) => { - const { ref: elementRef, child: element } = getElement(index); - const { - index: neighbourIndex, - ref: neighbourRef, - child: neighbour, - } = getElementNeighbour(index); - - const resizeHandler = new ResizeHandler( - orientation, - elementRef, - element.props.minimumSize || 0, - neighbourRef, - neighbour.props.minimumSize || 0, - ); - const { containerFlexGrow, neighbourFlexGrow } = resizeHandler.resizeDelta(size); - - setContainerSizes((prev) => { - const newSizes = [...prev]; - newSizes[index] = containerFlexGrow; - newSizes[neighbourIndex] = neighbourFlexGrow; - return newSizes; - }); - }; - - return { resizeTo, resizeDelta }; -}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts new file mode 100644 index 00000000000..b82e6e442aa --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts @@ -0,0 +1,109 @@ +import type { MutableRefObject } from 'react'; +import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { StudioResizableLayoutArea } from '../classes/StudioResizableLayoutElement'; + +type useResizableFunctionsReturnType = { + resizeTo: (index: number, size: number) => void; + resizeDelta: (index: number, size: number) => void; + collapse: (index: number) => void; +}; + +export const useStudioResizableLayoutFunctions = ( + orientation: StudioResizableOrientation, + elementRefs: MutableRefObject, + children: any[], + setContainerSize: (index: number, size: number) => void, +): useResizableFunctionsReturnType => { + const getElementNeighbour = (index: number) => { + const neighbourIndex = elementRefs.current.length < index + 2 ? index - 1 : index + 1; + return new StudioResizableLayoutArea( + neighbourIndex, + elementRefs.current[neighbourIndex], + children[neighbourIndex], + orientation, + ); + }; + + const getElement = (index: number) => { + return new StudioResizableLayoutArea( + index, + elementRefs.current[index], + children[index], + orientation, + ); + }; + + const calculatePixelSizes = ( + element: StudioResizableLayoutArea, + neighbour: StudioResizableLayoutArea, + newSize: number, + ) => { + const totalSize = element.size + neighbour.size; + if (element.minimumSize > newSize) { + newSize = element.minimumSize; + } + if (neighbour.minimumSize > totalSize - newSize) { + newSize = totalSize - neighbour.minimumSize; + } + const neighbourNewSize = totalSize - newSize; + return { newSize, neighbourNewSize }; + }; + + const calculateFlexGrow = ( + element: StudioResizableLayoutArea, + neighbour: StudioResizableLayoutArea, + resizeTo: number, + ignoreMinimumSize: boolean = false, + ) => { + const totalPixelSize = element.size + neighbour.size; + const { newSize, neighbourNewSize } = ignoreMinimumSize + ? { newSize: resizeTo, neighbourNewSize: totalPixelSize - resizeTo } + : calculatePixelSizes(element, neighbour, resizeTo); + + const totalFlexGrow = element.flexGrow + neighbour.flexGrow; + const containerFlexGrow = totalFlexGrow * (newSize / totalPixelSize); + const neighbourFlexGrow = totalFlexGrow * (neighbourNewSize / totalPixelSize); + return { containerFlexGrow, neighbourFlexGrow }; + }; + + const forceSize = (index: number, size: number) => { + const element = getElement(index); + const neighbour = getElementNeighbour(index); + + const { containerFlexGrow, neighbourFlexGrow } = calculateFlexGrow( + element, + neighbour, + size, + true, + ); + + setContainerSize(index, containerFlexGrow); + setContainerSize(neighbour.index, neighbourFlexGrow); + }; + + const resizeTo = (index: number, size: number) => { + const element = getElement(index); + const neighbour = getElementNeighbour(index); + + if (element.collapsed || neighbour.collapsed) { + return; + } + + const { containerFlexGrow, neighbourFlexGrow } = calculateFlexGrow(element, neighbour, size); + + setContainerSize(index, containerFlexGrow); + setContainerSize(neighbour.index, neighbourFlexGrow); + }; + + const resizeDelta = (index: number, size: number) => { + const element = getElement(index); + resizeTo(index, element.size + size); + }; + + const collapse = (index: number) => { + const element = getElement(index); + forceSize(index, element.collapsedSize); + }; + + return { resizeTo, resizeDelta, collapse }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts index 94ee8389d7b..104be40e16c 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts @@ -1,10 +1,10 @@ import { useContext } from 'react'; -import { StudioResizableLayoutContext } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { StudioResizableLayoutContext } from '../context/StudioResizableLayoutContext'; export const useStudioResizableLayoutContext = (index: number) => { - const { containerSizes, orientation, resizeDelta, collapse } = useContext( + const { containerSizes, orientation, resizeDelta, resizeTo, collapse } = useContext( StudioResizableLayoutContext, ); const containerSize = containerSizes[index]; - return { containerSize, orientation, resizeDelta, collapse }; + return { containerSize, orientation, resizeDelta, resizeTo, collapse }; }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts similarity index 79% rename from frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts rename to frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts index ab9d92893a3..aa0379e6618 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useMouseMovement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts @@ -1,21 +1,23 @@ import { useRef, useCallback, useState } from 'react'; import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; -export const useMouseMovement = ( +export const useStudioResizableLayoutMouseMovement = ( orientation: StudioResizableOrientation, - onMousePosChange: (delta: number) => void, + onMousePosChange: (delta: number, position: number) => void, ): { onMouseDown: (event: React.MouseEvent) => () => void; isResizing: boolean; } => { const lastMousePosition = useRef(0); + const startMousePosition = useRef(0); const [isResizing, setIsResizing] = useState(false); const mouseMove = useCallback( (event: MouseEvent) => { const mousePos = orientation === 'horizontal' ? event.pageX : event.pageY; + const mouseTotalDelta = mousePos - startMousePosition.current; const mouseDelta = mousePos - lastMousePosition.current; - onMousePosChange(mouseDelta); + onMousePosChange(mouseDelta, mouseTotalDelta); lastMousePosition.current = mousePos; }, [orientation, onMousePosChange], @@ -32,9 +34,11 @@ export const useMouseMovement = ( const onMouseDown = useCallback( (event: React.MouseEvent) => { + if (event.button !== 0) return; event.preventDefault(); setIsResizing(true); lastMousePosition.current = orientation === 'horizontal' ? event.pageX : event.pageY; + startMousePosition.current = lastMousePosition.current; window.addEventListener('mousemove', mouseMove); window.addEventListener('mouseup', mouseUp); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts new file mode 100644 index 00000000000..131912de1b6 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts @@ -0,0 +1,18 @@ +// import { useLocalStorage } from "app-shared/hooks/useLocalStorage"; +import { useState } from 'react'; + +export const useTrackContainerSizes = (layoutId: string) => { + const [containerSizes, setContainerSizes] = useState([]); + + // const [value, setValue, removeValue] = useLocalStorage(`studio:resizable-layout:${layoutId}:${localStorageContextKey}`, containerSizes); + // useEffect(() => { + // setContainerSizes(value); + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, []); + // + // useEffect(() => { + // setValue(containerSizes); + // }, [containerSizes, setValue]); + // + return { containerSizes, setContainerSizes }; +}; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts index ff02e758bee..b5c809c1404 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts @@ -1,11 +1,9 @@ import { StudioResizableLayoutElement, type StudioResizableLayoutElementProps, -} from './StudioResizableLayoutContainer/StudioResizableLayoutElement'; -import { - StudioResizableLayoutContext, - StudioResizableLayoutContainer, -} from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +} from './StudioResizableLayoutElement/StudioResizableLayoutElement'; +import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { StudioResizableLayoutContext } from './context/StudioResizableLayoutContext'; type StudioResizableLayoutComponent = { Element: typeof StudioResizableLayoutElement; @@ -19,7 +17,4 @@ export const StudioResizableLayout: StudioResizableLayoutComponent = { Context: StudioResizableLayoutContext, }; -export type { - StudioResizableLayoutElementProps as StudioResizableLayoutContainerProps, - StudioResizableLayoutContext, -}; +export type { StudioResizableLayoutElementProps as StudioResizableLayoutContainerProps }; From 3241dc1aef842b90a4db455e703e614ff16b2ae7 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 27 Jun 2024 12:54:32 +0200 Subject: [PATCH 12/44] Add toolbar and add state to block iframe preview --- .../src/components/Elements/Elements.tsx | 2 - .../Elements/LayoutSetsContainer.module.css | 18 +++---- .../Elements/LayoutSetsContainer.tsx | 3 +- .../src/components/Preview/Preview.tsx | 28 ++++++++++- .../src/containers/FormDesigner.module.css | 3 +- .../ux-editor/src/containers/FormDesigner.tsx | 47 ++++++++++++++++--- .../containers/FormDesignerToolbar.module.css | 8 ++++ .../src/containers/FormDesignerToolbar.tsx | 13 +++++ 8 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css create mode 100644 frontend/packages/ux-editor/src/containers/FormDesignerToolbar.tsx diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx index 32163804e4b..6cf6f6e0559 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx @@ -42,7 +42,6 @@ export const Elements = (): React.ReactElement => { if (hasProcessTaskTypeError) { return (
-
@@ -63,7 +62,6 @@ export const Elements = (): React.ReactElement => { return (
- {t('left_menu.components')} diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css index f221821d800..278bfbed404 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css @@ -1,9 +1,9 @@ -.dropDownContainer { - margin: var(--fds-spacing-5); - margin-bottom: 5px; -} - -.layoutSetsDropDown { - width: 100%; - text-overflow: ellipsis; -} +/* .dropDownContainer { */ +/* margin: var(--fds-spacing-5); */ +/* margin-bottom: 5px; */ +/* } */ +/**/ +/* .layoutSetsDropDown { */ +/* width: 100%; */ +/* text-overflow: ellipsis; */ +/* } */ diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx index cbc397adb39..e92f746624d 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx @@ -39,10 +39,11 @@ export function LayoutSetsContainer() { return (
onLayoutSetClick(event.target.value)} value={selectedFormLayoutSetName} className={classes.layoutSetsDropDown} + style={{ minWidth: 400 }} > {layoutSetNames.map((set: string) => { return ( diff --git a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx index cf8571029f6..54f1426e5ba 100644 --- a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx +++ b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx @@ -13,7 +13,12 @@ import { ViewToggler } from './ViewToggler/ViewToggler'; import { ArrowRightIcon } from '@studio/icons'; import { PreviewLimitationsInfo } from 'app-shared/components/PreviewLimitationsInfo/PreviewLimitationsInfo'; -export const Preview = () => { +export type PreviewProps = { + onCollapseToggle?: (collapsed: boolean) => void; + hidePreview?: boolean; +}; + +export const Preview = ({ onCollapseToggle, hidePreview }: PreviewProps) => { const { t } = useTranslation(); const [isPreviewHidden, setIsPreviewHidden] = useState(false); const { selectedFormLayoutName } = useAppContext(); @@ -21,6 +26,7 @@ export const Preview = () => { selectedFormLayoutName === 'default' || selectedFormLayoutName === undefined; const togglePreview = (): void => { + onCollapseToggle?.(!isPreviewHidden); setIsPreviewHidden((prev: boolean) => !prev); }; @@ -42,7 +48,25 @@ export const Preview = () => { className={classes.closePreviewButton} onClick={togglePreview} /> - {noPageSelected ? : } + {noPageSelected ? ( + + ) : ( + <> + {hidePreview && ( +
+ )} + + + )}
); }; diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.module.css b/frontend/packages/ux-editor/src/containers/FormDesigner.module.css index 871738b7c49..1a73b049ba4 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.module.css +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.module.css @@ -29,9 +29,10 @@ } .container > * { + --header-with-toolbar-height: calc(var(--header-height) + var(--subtoolbar-height)); padding: 0; overflow-y: auto; - max-height: calc(100vh - var(--header-height)); + max-height: calc(100vh - var(--header-with-toolbar-height)); } .container > div:not(:last-child) { diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index e40b35505d5..4bb8f7b6b0c 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Properties } from '../components/Properties'; import { DesignView } from './DesignView'; import classes from './FormDesigner.module.css'; @@ -9,7 +9,7 @@ import { useFormLayoutsQuery } from '../hooks/queries/useFormLayoutsQuery'; import { useFormLayoutSettingsQuery } from '../hooks/queries/useFormLayoutSettingsQuery'; import { useRuleModelQuery } from '../hooks/queries/useRuleModelQuery'; import { ErrorPage } from '../components/ErrorPage'; -import { StudioPageSpinner } from '@studio/components'; +import { StudioPageSpinner, StudioResizableLayout } from '@studio/components'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import { useRuleConfigQuery } from '../hooks/queries/useRuleConfigQuery'; import { useInstanceIdQuery } from 'app-shared/hooks/queries'; @@ -28,6 +28,7 @@ import { useAddItemToLayoutMutation } from '../hooks/mutations/useAddItemToLayou import { useFormLayoutMutation } from '../hooks/mutations/useFormLayoutMutation'; import { Preview } from '../components/Preview'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; +import { FormDesignerToolbar } from './FormDesignerToolbar'; export const FormDesigner = (): JSX.Element => { const { org, app } = useStudioEnvironmentParams(); @@ -61,6 +62,8 @@ export const FormDesigner = (): JSX.Element => { selectedFormLayoutSetName, ); const { handleEdit } = useFormItemContext(); + const [previewCollapsed, setPreviewCollapsed] = useState(false); + const [hidePreview, setHidePreview] = useState(false); const t = useText(); @@ -142,11 +145,43 @@ export const FormDesigner = (): JSX.Element => { return (
+
- - - - + + + + + + + + setHidePreview(resizing)}> + + + + + + + + + + + setPreviewCollapsed(collapsed)} + hidePreview={hidePreview} + /> + +
diff --git a/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css new file mode 100644 index 00000000000..04cfa13ce87 --- /dev/null +++ b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css @@ -0,0 +1,8 @@ +.toolbar { + align-items: center; + display: flex; + padding: 8px; + border-bottom: 1px solid #c9c9c9; + box-sizing: border-box; + height: var(--subtoolbar-height); +} diff --git a/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.tsx b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.tsx new file mode 100644 index 00000000000..26a38a78a44 --- /dev/null +++ b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import classes from './FormDesignerToolbar.module.css'; +import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery'; +import { LayoutSetsContainer } from '../components/Elements/LayoutSetsContainer'; + +export const FormDesignerToolbar = () => { + const { org, app } = useStudioEnvironmentParams(); + const layoutSetsQuery = useLayoutSetsQuery(org, app); + const layoutSetNames = layoutSetsQuery?.data?.sets; + + return
{layoutSetNames && }
; +}; From 84f1ec98fd77195234ccc4e13de4e21e862de754 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 27 Jun 2024 12:56:17 +0200 Subject: [PATCH 13/44] Update ResizableLayout storybook entry --- .../StudioResizableLayout.mdx | 3 +- .../StudioResizableLayout.stories.tsx | 36 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx index 55c1baf6ee3..f5d5d13370a 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.mdx @@ -8,7 +8,8 @@ import * as StudioResizableLayoutStories from './StudioResizableLayout.stories'; StudioResizableLayout - StudioResizableLayout is used to display a header for a config-section in the studio. + StudioResizableLayout is used to create resizable layouts with mouse dragging and keyboard + support. Arrow keys can be used to resize the layout when the separator handle is focused. diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx index c6955ceb9fa..70053a7c270 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx @@ -1,22 +1,27 @@ import type { StoryFn, Meta } from '@storybook/react/*'; import React from 'react'; +import type { StudioResizableLayoutContainerProps } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { StudioResizableLayoutElement } from './StudioResizableLayoutElement/StudioResizableLayoutElement'; -type Story = StoryFn; +type PreviewProps = { + topContainerOrientation: StudioResizableLayoutContainerProps['orientation']; + subContainerOrientation: StudioResizableLayoutContainerProps['orientation']; +}; +type Story = StoryFn; const meta: Meta = { title: 'Studio/StudioResizableLayoutContainer', component: StudioResizableLayoutContainer, - argTypes: { - icon: { - control: false, - }, - }, }; + export const Preview: Story = (args): React.ReactElement => (
- +
lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut @@ -24,7 +29,10 @@ export const Preview: Story = (args): React.ReactElement => (
- +
lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor @@ -39,12 +47,20 @@ export const Preview: Story = (args): React.ReactElement => ( + +
+ lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua +
+
); +// TODO: use group story + Preview.args = { - layoutId: 'storylayout', - orientation: 'horizontal', + topContainerOrientation: 'vertical', + subContainerOrientation: 'horizontal', }; export default meta; From 90a6060e773a0ec8cfb489739f94a91c4d6aaa89 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 09:14:26 +0200 Subject: [PATCH 14/44] Move localstorage hook to studio-components `hook` folder As these are completely isolated and app independent they are better suited in this folder --- .../overview/components/Navigation.test.tsx | 2 +- .../features/textEditor/TextEditor.tsx | 2 +- .../mutations/useAddLayoutSetMutation.ts | 2 +- .../layout/AppBar/appBarConfig.test.ts | 2 +- .../app-preview/src/views/LandingPage.tsx | 2 +- .../guards/useContextRedirectionGuard.ts | 2 +- .../StudioResizableLayoutContainer.tsx | 9 ++++-- .../hooks/useTrackContainerSizes.ts | 29 ++++++++++--------- .../src/hooks/useLocalStorage.test.ts | 4 +-- .../src/hooks/useLocalStorage.ts | 4 +-- .../src/hooks}/webStorage.test.ts | 0 .../src/hooks}/webStorage.ts | 0 .../RedirectToCreatePageButton.tsx | 2 +- .../PreviewLimitationsInfo.tsx | 2 +- .../src/hooks/useReactiveLocalStorage.ts | 4 +-- .../src/utils/featureToggleUtils.test.ts | 2 +- .../shared/src/utils/featureToggleUtils.ts | 2 +- .../packages/ux-editor-v3/src/App.test.tsx | 2 +- .../src/components/TextResource.test.tsx | 2 +- .../containers/DesignView/DesignView.test.tsx | 2 +- .../src/containers/FormDesigner.tsx | 1 - frontend/packages/ux-editor/src/App.test.tsx | 2 +- .../TextResource/TextResource.test.tsx | 2 +- .../TextResourceValueEditor.test.tsx | 2 +- .../containers/DesignView/DesignView.test.tsx | 2 +- .../ux-editor/src/containers/FormDesigner.tsx | 17 +++-------- .../useSelectedFormLayoutSetName.test.tsx | 2 +- .../src/hooks/useSelectedFormLayoutSetName.ts | 2 +- 28 files changed, 51 insertions(+), 55 deletions(-) rename frontend/{packages/shared => libs/studio-components}/src/hooks/useLocalStorage.test.ts (91%) rename frontend/{packages/shared => libs/studio-components}/src/hooks/useLocalStorage.ts (89%) rename frontend/{packages/shared/src/utils => libs/studio-components/src/hooks}/webStorage.test.ts (100%) rename frontend/{packages/shared/src/utils => libs/studio-components/src/hooks}/webStorage.ts (100%) diff --git a/frontend/app-development/features/overview/components/Navigation.test.tsx b/frontend/app-development/features/overview/components/Navigation.test.tsx index 6ce244874d0..c840f4e51ef 100644 --- a/frontend/app-development/features/overview/components/Navigation.test.tsx +++ b/frontend/app-development/features/overview/components/Navigation.test.tsx @@ -7,7 +7,7 @@ import { renderWithProviders } from 'app-development/test/testUtils'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { TopBarMenu } from 'app-shared/enums/TopBarMenu'; import { RepositoryType } from 'app-shared/types/global'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import type { TopBarMenuItem } from 'app-shared/types/TopBarMenuItem'; describe('Navigation', () => { diff --git a/frontend/app-development/features/textEditor/TextEditor.tsx b/frontend/app-development/features/textEditor/TextEditor.tsx index d3fa1779271..3cdcca19695 100644 --- a/frontend/app-development/features/textEditor/TextEditor.tsx +++ b/frontend/app-development/features/textEditor/TextEditor.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type { LangCode } from '@altinn/text-editor'; import { TextEditor as TextEditorImpl, defaultLangCode } from '@altinn/text-editor'; import { StudioPageSpinner } from '@studio/components'; -import { useLocalStorage } from 'app-shared/hooks/useLocalStorage'; +import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage'; import { useSearchParams } from 'react-router-dom'; import type { TextResourceIdMutation } from '@altinn/text-editor/types'; import { useLanguagesQuery, useTextResourcesQuery } from '../../hooks/queries'; diff --git a/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts b/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts index 093d82eba13..06563fde0e8 100644 --- a/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts +++ b/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts @@ -2,7 +2,7 @@ import { type UseMutateFunction, useMutation, useQueryClient } from '@tanstack/r import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { LayoutSetConfig, LayoutSets } from 'app-shared/types/api/LayoutSetsResponse'; -import { useLocalStorage } from 'app-shared/hooks/useLocalStorage'; +import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage'; import type { AddLayoutSetResponse, LayoutSetsResponse, diff --git a/frontend/app-development/layout/AppBar/appBarConfig.test.ts b/frontend/app-development/layout/AppBar/appBarConfig.test.ts index 4c8acd06435..66338edff79 100644 --- a/frontend/app-development/layout/AppBar/appBarConfig.test.ts +++ b/frontend/app-development/layout/AppBar/appBarConfig.test.ts @@ -1,6 +1,6 @@ import { RepositoryType } from 'app-shared/types/global'; import { getFilteredTopBarMenu, topBarMenuItem } from './appBarConfig'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { TopBarMenu } from 'app-shared/enums/TopBarMenu'; import type { TopBarMenuItem } from 'app-shared/types/TopBarMenuItem'; import { RoutePaths } from 'app-development/enums/RoutePaths'; diff --git a/frontend/app-preview/src/views/LandingPage.tsx b/frontend/app-preview/src/views/LandingPage.tsx index 9e469d1f0a8..8d6fe8edd63 100644 --- a/frontend/app-preview/src/views/LandingPage.tsx +++ b/frontend/app-preview/src/views/LandingPage.tsx @@ -3,7 +3,7 @@ import classes from './LandingPage.module.css'; import { useTranslation } from 'react-i18next'; import { usePreviewConnection } from 'app-shared/providers/PreviewConnectionContext'; import { useInstanceIdQuery, useRepoMetadataQuery, useUserQuery } from 'app-shared/hooks/queries'; -import { useLocalStorage } from 'app-shared/hooks/useLocalStorage'; +import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage'; import { AltinnHeader } from 'app-shared/components/altinnHeader'; import type { AltinnHeaderVariant } from 'app-shared/components/altinnHeader/types'; import { getRepositoryType } from 'app-shared/utils/repository'; diff --git a/frontend/dashboard/hooks/guards/useContextRedirectionGuard.ts b/frontend/dashboard/hooks/guards/useContextRedirectionGuard.ts index 94c3f555a2d..ff9cda04f11 100644 --- a/frontend/dashboard/hooks/guards/useContextRedirectionGuard.ts +++ b/frontend/dashboard/hooks/guards/useContextRedirectionGuard.ts @@ -4,7 +4,7 @@ import { useSelectedContext } from '../useSelectedContext'; import type { NavigateFunction } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'; import { SelectedContextType } from 'app-shared/navigation/main-header/Header'; -import { typedSessionStorage } from 'app-shared/utils/webStorage'; +import { typedSessionStorage } from '@studio/components/src/hooks/webStorage'; import { userHasAccessToSelectedContext } from 'dashboard/utils/userUtils'; export type UseRedirectionGuardResult = { diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 59c4a91a360..f2911c800b0 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -11,7 +11,7 @@ export type StudioResizableLayoutContainerProps = { layoutId: string; orientation: StudioResizableOrientation; children: React.ReactElement[]; - /*localStorageContext?: string;*/ + localStorageContext?: string; style?: React.CSSProperties; }; @@ -19,7 +19,7 @@ const StudioResizableLayoutContainer = ({ layoutId, children, orientation, - // localStorageContext = "default", + localStorageContext = 'default', style, }: StudioResizableLayoutContainerProps): React.ReactElement => { const elementRefs = useRef<(HTMLDivElement | null)[]>([]); @@ -27,7 +27,10 @@ const StudioResizableLayoutContainer = ({ elementRefs.current = elementRefs.current.slice(0, getValidChildren(children).length); }, [children]); - const { containerSizes, setContainerSizes } = useTrackContainerSizes(layoutId); + const { containerSizes, setContainerSizes } = useTrackContainerSizes( + layoutId, + localStorageContext, + ); const { resizeTo, resizeDelta, collapse } = useStudioResizableLayoutFunctions( orientation, elementRefs, diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts index 131912de1b6..a4882298b67 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts @@ -1,18 +1,21 @@ -// import { useLocalStorage } from "app-shared/hooks/useLocalStorage"; -import { useState } from 'react'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { useEffect, useState } from 'react'; -export const useTrackContainerSizes = (layoutId: string) => { +export const useTrackContainerSizes = (layoutId: string, localStorageContextKey: string) => { const [containerSizes, setContainerSizes] = useState([]); - // const [value, setValue, removeValue] = useLocalStorage(`studio:resizable-layout:${layoutId}:${localStorageContextKey}`, containerSizes); - // useEffect(() => { - // setContainerSizes(value); - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, []); - // - // useEffect(() => { - // setValue(containerSizes); - // }, [containerSizes, setValue]); - // + const [value, setValue] = useLocalStorage( + `studio:resizable-layout:${layoutId}:${localStorageContextKey}`, + containerSizes, + ); + useEffect(() => { + setContainerSizes(value); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setValue(containerSizes); + }, [containerSizes, setValue]); + return { containerSizes, setContainerSizes }; }; diff --git a/frontend/packages/shared/src/hooks/useLocalStorage.test.ts b/frontend/libs/studio-components/src/hooks/useLocalStorage.test.ts similarity index 91% rename from frontend/packages/shared/src/hooks/useLocalStorage.test.ts rename to frontend/libs/studio-components/src/hooks/useLocalStorage.test.ts index b51ccbd6638..fc288cb3312 100644 --- a/frontend/packages/shared/src/hooks/useLocalStorage.test.ts +++ b/frontend/libs/studio-components/src/hooks/useLocalStorage.test.ts @@ -1,6 +1,6 @@ -import { typedLocalStorage } from 'app-shared/utils/webStorage'; import { renderHook, waitFor } from '@testing-library/react'; -import { useLocalStorage } from 'app-shared/hooks/useLocalStorage'; +import { useLocalStorage } from './useLocalStorage'; +import { typedLocalStorage } from './webStorage'; describe('useLocalStorage', () => { it('Gives access to the stored value', () => { diff --git a/frontend/packages/shared/src/hooks/useLocalStorage.ts b/frontend/libs/studio-components/src/hooks/useLocalStorage.ts similarity index 89% rename from frontend/packages/shared/src/hooks/useLocalStorage.ts rename to frontend/libs/studio-components/src/hooks/useLocalStorage.ts index caf30542f04..9114c26a3a8 100644 --- a/frontend/packages/shared/src/hooks/useLocalStorage.ts +++ b/frontend/libs/studio-components/src/hooks/useLocalStorage.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -import type { TypedStorage } from 'app-shared/utils/webStorage'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import type { TypedStorage } from './webStorage'; +import { typedLocalStorage } from './webStorage'; const useWebStorage = ( typedStorage: TypedStorage, diff --git a/frontend/packages/shared/src/utils/webStorage.test.ts b/frontend/libs/studio-components/src/hooks/webStorage.test.ts similarity index 100% rename from frontend/packages/shared/src/utils/webStorage.test.ts rename to frontend/libs/studio-components/src/hooks/webStorage.test.ts diff --git a/frontend/packages/shared/src/utils/webStorage.ts b/frontend/libs/studio-components/src/hooks/webStorage.ts similarity index 100% rename from frontend/packages/shared/src/utils/webStorage.ts rename to frontend/libs/studio-components/src/hooks/webStorage.ts diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/RedirectToCreatePageButton/RedirectToCreatePageButton.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/RedirectToCreatePageButton/RedirectToCreatePageButton.tsx index a8468753c2a..d86a8722a92 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/RedirectToCreatePageButton/RedirectToCreatePageButton.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/RedirectToCreatePageButton/RedirectToCreatePageButton.tsx @@ -4,7 +4,7 @@ import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmen import { PackagesRouter } from 'app-shared/navigation/PackagesRouter'; import { PencilWritingIcon } from '@studio/icons'; import { StudioButton } from '@studio/components'; -import { useLocalStorage } from 'app-shared/hooks/useLocalStorage'; +import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage'; import { useTranslation } from 'react-i18next'; import { useBpmnApiContext } from '../../../../../contexts/BpmnApiContext'; import { RedirectBox } from '../../../../RedirectBox'; diff --git a/frontend/packages/shared/src/components/PreviewLimitationsInfo/PreviewLimitationsInfo.tsx b/frontend/packages/shared/src/components/PreviewLimitationsInfo/PreviewLimitationsInfo.tsx index ac9f8c59836..7bf89165e20 100644 --- a/frontend/packages/shared/src/components/PreviewLimitationsInfo/PreviewLimitationsInfo.tsx +++ b/frontend/packages/shared/src/components/PreviewLimitationsInfo/PreviewLimitationsInfo.tsx @@ -4,7 +4,7 @@ import cn from 'classnames'; import { useTranslation } from 'react-i18next'; import { Alert, LegacyPopover } from '@digdir/design-system-react'; import { XMarkIcon } from '@studio/icons'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { StudioButton } from '@studio/components'; export const PreviewLimitationsInfo = () => { diff --git a/frontend/packages/shared/src/hooks/useReactiveLocalStorage.ts b/frontend/packages/shared/src/hooks/useReactiveLocalStorage.ts index 3ac1b9882ab..67feaa4e06c 100644 --- a/frontend/packages/shared/src/hooks/useReactiveLocalStorage.ts +++ b/frontend/packages/shared/src/hooks/useReactiveLocalStorage.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; -import { useLocalStorage } from './useLocalStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; +import { useLocalStorage } from '@studio/components/src/hooks/useLocalStorage'; import { useEventListener } from './useEventListener'; export const useReactiveLocalStorage = ( diff --git a/frontend/packages/shared/src/utils/featureToggleUtils.test.ts b/frontend/packages/shared/src/utils/featureToggleUtils.test.ts index be58774057e..f7b97825739 100644 --- a/frontend/packages/shared/src/utils/featureToggleUtils.test.ts +++ b/frontend/packages/shared/src/utils/featureToggleUtils.test.ts @@ -1,4 +1,4 @@ -import { typedLocalStorage, typedSessionStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage, typedSessionStorage } from '@studio/components/src/hooks/webStorage'; import { addFeatureFlagToLocalStorage, removeFeatureFlagFromLocalStorage, diff --git a/frontend/packages/shared/src/utils/featureToggleUtils.ts b/frontend/packages/shared/src/utils/featureToggleUtils.ts index ee2584c5a15..b4a6fa3adbe 100644 --- a/frontend/packages/shared/src/utils/featureToggleUtils.ts +++ b/frontend/packages/shared/src/utils/featureToggleUtils.ts @@ -1,4 +1,4 @@ -import { typedLocalStorage, typedSessionStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage, typedSessionStorage } from '@studio/components/src/hooks/webStorage'; const featureFlagKey = 'featureFlags'; const persistFeatureKey = 'persistFeatureFlag'; diff --git a/frontend/packages/ux-editor-v3/src/App.test.tsx b/frontend/packages/ux-editor-v3/src/App.test.tsx index 4b16a3968e0..c67804a09b5 100644 --- a/frontend/packages/ux-editor-v3/src/App.test.tsx +++ b/frontend/packages/ux-editor-v3/src/App.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import { formLayoutSettingsMock, renderWithProviders } from './testing/mocks'; import { App } from './App'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { appStateMock } from './testing/stateMocks'; import type { AppContextProps } from './AppContext'; diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx b/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx index c52a77ec833..2de29610813 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx @@ -10,7 +10,7 @@ import { screen, waitFor } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { useTextResourcesQuery } from 'app-shared/hooks/queries/useTextResourcesQuery'; import { DEFAULT_LANGUAGE } from 'app-shared/constants'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { addFeatureFlagToLocalStorage } from 'app-shared/utils/featureToggleUtils'; import { app, org } from '@studio/testing/testids'; diff --git a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.test.tsx b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.test.tsx index 6a7b5b4e178..e9aafb9cf83 100644 --- a/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.test.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/DesignView/DesignView.test.tsx @@ -8,7 +8,7 @@ import { DragAndDrop } from 'app-shared/components/dragAndDrop'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import userEvent from '@testing-library/user-event'; import { queriesMock } from 'app-shared/mocks/queriesMock'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; import { diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index adb1b7e1d68..f9f5841d14b 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -149,7 +149,6 @@ export const FormDesigner = ({ return (
-
diff --git a/frontend/packages/ux-editor/src/App.test.tsx b/frontend/packages/ux-editor/src/App.test.tsx index 3e5deccddf7..e00d19a05c1 100644 --- a/frontend/packages/ux-editor/src/App.test.tsx +++ b/frontend/packages/ux-editor/src/App.test.tsx @@ -3,7 +3,7 @@ import { screen, waitForElementToBeRemoved } from '@testing-library/react'; import { formLayoutSettingsMock, renderWithProviders } from './testing/mocks'; import { App } from './App'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import type { AppContextProps } from './AppContext'; import ruleHandlerMock from './testing/ruleHandlerMock'; diff --git a/frontend/packages/ux-editor/src/components/TextResource/TextResource.test.tsx b/frontend/packages/ux-editor/src/components/TextResource/TextResource.test.tsx index 26a711cd164..7e0ae2e8d01 100644 --- a/frontend/packages/ux-editor/src/components/TextResource/TextResource.test.tsx +++ b/frontend/packages/ux-editor/src/components/TextResource/TextResource.test.tsx @@ -8,7 +8,7 @@ import { renderWithProviders } from '../../testing/mocks'; import { screen } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { DEFAULT_LANGUAGE } from 'app-shared/constants'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { appContextMock } from '../../testing/appContextMock'; diff --git a/frontend/packages/ux-editor/src/components/TextResource/TextResourceEditor/TextResourceValueEditor/TextResourceValueEditor.test.tsx b/frontend/packages/ux-editor/src/components/TextResource/TextResourceEditor/TextResourceValueEditor/TextResourceValueEditor.test.tsx index 17b1ac9dd46..966721c08d3 100644 --- a/frontend/packages/ux-editor/src/components/TextResource/TextResourceEditor/TextResourceValueEditor/TextResourceValueEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/TextResource/TextResourceEditor/TextResourceValueEditor/TextResourceValueEditor.test.tsx @@ -8,7 +8,7 @@ import { renderWithProviders } from '../../../../testing/mocks'; import { screen } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { DEFAULT_LANGUAGE } from 'app-shared/constants'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { appContextMock } from '../../../../testing/appContextMock'; diff --git a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.test.tsx b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.test.tsx index 3bde49dd736..008d933fd70 100644 --- a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.test.tsx +++ b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.test.tsx @@ -8,7 +8,7 @@ import { DragAndDrop } from 'app-shared/components/dragAndDrop'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import userEvent from '@testing-library/user-event'; import { queriesMock } from 'app-shared/mocks/queriesMock'; -import { typedLocalStorage } from 'app-shared/utils/webStorage'; +import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; import { diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index 4bb8f7b6b0c..d898d569651 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -12,7 +12,7 @@ import { ErrorPage } from '../components/ErrorPage'; import { StudioPageSpinner, StudioResizableLayout } from '@studio/components'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import { useRuleConfigQuery } from '../hooks/queries/useRuleConfigQuery'; -import { useInstanceIdQuery } from 'app-shared/hooks/queries'; +import { useInstanceIdQuery, useUserQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { HandleAdd, HandleMove } from 'app-shared/types/dndTypes'; import type { ComponentType } from 'app-shared/types/ComponentType'; @@ -33,6 +33,7 @@ import { FormDesignerToolbar } from './FormDesignerToolbar'; export const FormDesigner = (): JSX.Element => { const { org, app } = useStudioEnvironmentParams(); const { data: instanceId } = useInstanceIdQuery(org, app); + const { data: user } = useUserQuery(); const { selectedFormLayoutSetName, selectedFormLayoutName, refetchLayouts } = useAppContext(); const { data: formLayouts, isError: layoutFetchedError } = useFormLayoutsQuery( org, @@ -150,7 +151,7 @@ export const FormDesigner = (): JSX.Element => { @@ -159,17 +160,7 @@ export const FormDesigner = (): JSX.Element => { setHidePreview(resizing)}> - - - - - - - - + Date: Fri, 28 Jun 2024 09:16:21 +0200 Subject: [PATCH 15/44] Remove unnecessary ref proxying with imperativehandle --- .../StudioResizableLayoutElement.tsx | 12 ++---------- .../src/components/StudioResizableLayout/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx index 6bd9068319c..c05ab629a92 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx @@ -19,10 +19,9 @@ export type StudioResizableLayoutElementProps = { ref?: React.Ref; }; -const StudioResizableLayoutElement = forwardRef( +const StudioResizableLayoutElement = forwardRef( ( { - minimumSize = 0, index, collapsed, children, @@ -32,9 +31,6 @@ const StudioResizableLayoutElement = forwardRef( }: StudioResizableLayoutElementProps, ref, ) => { - const containerRef = useRef(null); - useImperativeHandle(ref, () => containerRef.current); - const { resizeDelta, collapse, orientation, containerSize } = useStudioResizableLayoutContext(index); @@ -50,11 +46,7 @@ const StudioResizableLayoutElement = forwardRef( return ( <> -
+
{collapsed} {children}
diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts index b5c809c1404..668e77c95b3 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts @@ -6,14 +6,14 @@ import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer import { StudioResizableLayoutContext } from './context/StudioResizableLayoutContext'; type StudioResizableLayoutComponent = { - Element: typeof StudioResizableLayoutElement; Container: typeof StudioResizableLayoutContainer; + Element: typeof StudioResizableLayoutElement; Context: typeof StudioResizableLayoutContext; }; export const StudioResizableLayout: StudioResizableLayoutComponent = { - Element: StudioResizableLayoutElement, Container: StudioResizableLayoutContainer, + Element: StudioResizableLayoutElement, Context: StudioResizableLayoutContext, }; From f0aa555d3c689e549f541ac91394e12299fcf680 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 12:24:00 +0200 Subject: [PATCH 16/44] Remove layoutId in favor of just using localStorageContextKey --- .../StudioResizableLayoutContainer.tsx | 12 ++++-------- .../StudioResizableLayoutElement.tsx | 6 +++--- .../hooks/useTrackContainerSizes.ts | 4 ++-- .../ux-editor/src/containers/FormDesigner.tsx | 3 +-- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index f2911c800b0..471a52c83c3 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -8,15 +8,14 @@ import { StudioResizableLayoutContext } from '../context/StudioResizableLayoutCo export type StudioResizableOrientation = 'horizontal' | 'vertical'; export type StudioResizableLayoutContainerProps = { - layoutId: string; - orientation: StudioResizableOrientation; - children: React.ReactElement[]; localStorageContext?: string; + orientation: StudioResizableOrientation; style?: React.CSSProperties; + + children: React.ReactElement[]; }; const StudioResizableLayoutContainer = ({ - layoutId, children, orientation, localStorageContext = 'default', @@ -27,10 +26,7 @@ const StudioResizableLayoutContainer = ({ elementRefs.current = elementRefs.current.slice(0, getValidChildren(children).length); }, [children]); - const { containerSizes, setContainerSizes } = useTrackContainerSizes( - layoutId, - localStorageContext, - ); + const { containerSizes, setContainerSizes } = useTrackContainerSizes(localStorageContext); const { resizeTo, resizeDelta, collapse } = useStudioResizableLayoutFunctions( orientation, elementRefs, diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx index c05ab629a92..ec9ee5060f1 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx @@ -23,6 +23,7 @@ const StudioResizableLayoutElement = forwardRef { - const { resizeDelta, collapse, orientation, containerSize } = + const { resizeTo, collapse, orientation, containerSize } = useStudioResizableLayoutContext(index); useEffect(() => { if (collapsed) { collapse(index); } else { - resizeDelta(index, 0); + resizeTo(index, minimumSize); } // disable linter as we only want to run this effect if the collapsed prop changes // eslint-disable-next-line react-hooks/exhaustive-deps @@ -47,7 +48,6 @@ const StudioResizableLayoutElement = forwardRef
- {collapsed} {children}
{hasNeighbour && ( diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts index a4882298b67..0d09328dec1 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts @@ -1,11 +1,11 @@ import { useLocalStorage } from '../../../hooks/useLocalStorage'; import { useEffect, useState } from 'react'; -export const useTrackContainerSizes = (layoutId: string, localStorageContextKey: string) => { +export const useTrackContainerSizes = (localStorageContextKey: string) => { const [containerSizes, setContainerSizes] = useState([]); const [value, setValue] = useLocalStorage( - `studio:resizable-layout:${layoutId}:${localStorageContextKey}`, + `studio:resizable-layout:${localStorageContextKey}`, containerSizes, ); useEffect(() => { diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index d898d569651..02b1391e75c 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -149,9 +149,8 @@ export const FormDesigner = (): JSX.Element => {
From bbaacbbe6bc6226163c70decee3591a5e6b3f72f Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 14:58:18 +0200 Subject: [PATCH 17/44] Add test for resizing and keyboard presses --- .../StudioResizableLayoutContainer.test.tsx | 69 +++++++++++++------ .../StudioResizableLayoutElement.tsx | 7 +- .../hooks/useKeyboardControls.test.ts | 38 ++++++++++ .../useStudioResizableLayoutMouseMovement.ts | 4 +- 4 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.test.ts diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx index 00909b57c57..89f40f80221 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx @@ -1,41 +1,68 @@ -import React from 'react'; +import React, { act } from 'react'; import type { StudioResizableLayoutContainerProps } from './StudioResizableLayoutContainer'; import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { StudioResizableLayoutElement } from '../StudioResizableLayoutElement/StudioResizableLayoutElement'; +import userEvent from '@testing-library/user-event'; describe('StudioResizableLayoutContainer', () => { - it('should render children', () => { + it('should render just one handle with two elements', () => { renderStudioResizableLayoutContainer(); - expect(screen.getByTestId('childone')).toBeInTheDocument(); - expect(screen.getByTestId('childtwo')).toBeInTheDocument(); + expect(screen.getAllByRole('separator').length).toBe(1); }); - it('should render just one handle with two children', () => { + it('should resize containers', () => { renderStudioResizableLayoutContainer(); - expect(screen.getAllByRole('separator').length).toBe(1); + const handle = screen.getByRole('separator'); + + dragHandle(handle, { clientX: 400 }, { clientX: 200 }); + + expect(screen.getAllByTestId('resizablelayoutelement')[0].style.flexGrow).toBe('0.5'); + expect(screen.getAllByTestId('resizablelayoutelement')[1].style.flexGrow).toBe('1.5'); + }); + + it('should not resize containers below minimum size', () => { + // minimum flexgrow should be minimumSize/containerSize=0.25 + renderStudioResizableLayoutContainer(); + const handle = screen.getByRole('separator'); + + dragHandle(handle, { clientX: 400 }, { clientX: 0 }); + expect(screen.getAllByTestId('resizablelayoutelement')[0].style.flexGrow).toBe('0.25'); + expect(screen.getAllByTestId('resizablelayoutelement')[1].style.flexGrow).toBe('1.75'); + + dragHandle(handle, { clientX: 0 }, { clientX: 800 }); + expect(screen.getAllByTestId('resizablelayoutelement')[0].style.flexGrow).toBe('1.75'); + expect(screen.getAllByTestId('resizablelayoutelement')[1].style.flexGrow).toBe('0.25'); }); }); +const dragHandle = ( + handle: HTMLElement, + from: { clientX?: number; clientY?: number }, + to: { clientX?: number; clientY?: number }, +) => { + fireEvent.mouseDown(handle, from); + fireEvent.mouseMove(handle, to); + fireEvent.mouseUp(handle, to); +}; + const renderStudioResizableLayoutContainer = ( props: Partial = {}, ) => { - const defaultProps: StudioResizableLayoutContainerProps = { - layoutId: 'test', - orientation: 'horizontal', - children: [], - }; + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + value: 400, + }); return render( - - -
- test1 -
+ + +
test1
- -
- test1 -
+ +
test1
, ); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx index ec9ee5060f1..0e8d02fa6d3 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx @@ -47,7 +47,12 @@ const StudioResizableLayoutElement = forwardRef -
+
{children}
{hasNeighbour && ( diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.test.ts new file mode 100644 index 00000000000..ceed840036d --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.test.ts @@ -0,0 +1,38 @@ +import { renderHook } from '@testing-library/react'; +import { useKeyboardControls } from './useKeyboardControls'; + +describe('useKeyboardControls', () => { + let onResize: jest.Mock; + let result: any; + + beforeEach(() => { + onResize = jest.fn(); + const { result: hookResult } = renderHook(() => useKeyboardControls(onResize)); + result = hookResult; + }); + + it('should call onResize with -10 when ArrowLeft is pressed', () => { + result.current.onKeyDown({ key: 'ArrowLeft' }); + expect(onResize).toHaveBeenCalledWith(-10); + }); + + it('should call onResize with -50 when ArrowLeft and Shift are pressed', () => { + result.current.onKeyDown({ key: 'ArrowUp', shiftKey: true }); + expect(onResize).toHaveBeenCalledWith(-50); + }); + + it('should call onResize with 10 when ArrowRight is pressed', () => { + result.current.onKeyDown({ key: 'ArrowRight' }); + expect(onResize).toHaveBeenCalledWith(10); + }); + + it('should call onResize with 50 when ArrowRight and Shift are pressed', () => { + result.current.onKeyDown({ key: 'ArrowDown', shiftKey: true }); + expect(onResize).toHaveBeenCalledWith(50); + }); + + it('should not call onResize when a key different from ArrowLeft, ArrowRight, ArrowUp or ArrowDown is pressed', () => { + result.current.onKeyDown({ key: 'k' }); + expect(onResize).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts index aa0379e6618..197890e5441 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts @@ -14,7 +14,7 @@ export const useStudioResizableLayoutMouseMovement = ( const mouseMove = useCallback( (event: MouseEvent) => { - const mousePos = orientation === 'horizontal' ? event.pageX : event.pageY; + const mousePos = orientation === 'horizontal' ? event.clientX : event.clientY; const mouseTotalDelta = mousePos - startMousePosition.current; const mouseDelta = mousePos - lastMousePosition.current; onMousePosChange(mouseDelta, mouseTotalDelta); @@ -37,7 +37,7 @@ export const useStudioResizableLayoutMouseMovement = ( if (event.button !== 0) return; event.preventDefault(); setIsResizing(true); - lastMousePosition.current = orientation === 'horizontal' ? event.pageX : event.pageY; + lastMousePosition.current = orientation === 'horizontal' ? event.clientX : event.clientY; startMousePosition.current = lastMousePosition.current; window.addEventListener('mousemove', mouseMove); window.addEventListener('mouseup', mouseUp); From 93ae303d1a6b5b7fedad8fa7d25bae3d0cefb351 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 14:59:13 +0200 Subject: [PATCH 18/44] Hide left side when container size is small, to avoid overlapping handles --- .../StudioResizableLayoutHandle.module.css | 7 +++++++ .../StudioResizableLayoutHandle.tsx | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css index b344dc5b255..279fdff723e 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.module.css @@ -29,3 +29,10 @@ .resizeHandleV:after { inset: -6px 0px; } + +.hideLeftSide { +} + +.hideLeftSide:after { + left: 0px; +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx index 62a9e875038..536aebc4ea0 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx @@ -16,7 +16,7 @@ export const StudioResizableLayoutHandle = ({ index, onResizing, }: StudioResizableLayoutHandleProps) => { - const { resizeDelta } = useStudioResizableLayoutContext(index); + const { resizeDelta, containerSize } = useStudioResizableLayoutContext(index); const { onMouseDown, isResizing } = useStudioResizableLayoutMouseMovement( orientation, (delta, _) => { @@ -33,7 +33,9 @@ export const StudioResizableLayoutHandle = ({
Date: Fri, 28 Jun 2024 15:05:41 +0200 Subject: [PATCH 19/44] Remove unused code --- .../StudioResizableLayoutContainer.test.tsx | 3 +-- .../StudioResizableLayoutElement.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx index 89f40f80221..9e9b013c539 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx @@ -1,9 +1,8 @@ -import React, { act } from 'react'; +import React from 'react'; import type { StudioResizableLayoutContainerProps } from './StudioResizableLayoutContainer'; import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer'; import { fireEvent, render, screen } from '@testing-library/react'; import { StudioResizableLayoutElement } from '../StudioResizableLayoutElement/StudioResizableLayoutElement'; -import userEvent from '@testing-library/user-event'; describe('StudioResizableLayoutContainer', () => { it('should render just one handle with two elements', () => { diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx index 0e8d02fa6d3..8731843ebd9 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutElement/StudioResizableLayoutElement.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; +import React, { forwardRef, useEffect } from 'react'; import classes from './StudioResizableLayoutElement.module.css'; import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; import { StudioResizableLayoutHandle } from '../StudioResizableLayoutHandle/StudioResizableLayoutHandle'; From 08e9b744339174c241ebeeefb72e20a891eb7870 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 15:32:47 +0200 Subject: [PATCH 20/44] Revert "Make preview collapsing also collapse the resizable layout element" This reverts commit 823884f4e997fa1c3ed6f6f3f87a1f3480ebbdc2. --- .../src/components/Preview/Preview.tsx | 7 +-- .../src/containers/FormDesigner.tsx | 45 ++++++++----------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx b/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx index 49c3c6c3e05..500d841ce27 100644 --- a/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Preview/Preview.tsx @@ -15,18 +15,13 @@ import { ViewToggler } from './ViewToggler/ViewToggler'; import { ArrowRightIcon } from '@studio/icons'; import { PreviewLimitationsInfo } from 'app-shared/components/PreviewLimitationsInfo/PreviewLimitationsInfo'; -export type PreviewProps = { - onCollapseToggle?: (collapsed: boolean) => void; -}; - -export const Preview = ({ onCollapseToggle }: PreviewProps) => { +export const Preview = () => { const { t } = useTranslation(); const [isPreviewHidden, setIsPreviewHidden] = useState(false); const layoutName = useSelector(selectedLayoutNameSelector); const noPageSelected = layoutName === 'default' || layoutName === undefined; const togglePreview = (): void => { - onCollapseToggle?.(!isPreviewHidden); setIsPreviewHidden((prev: boolean) => !prev); }; diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index f9f5841d14b..8954e4bef5a 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { Properties } from '../components/Properties'; import { DesignView } from './DesignView'; @@ -32,7 +32,7 @@ import { FormLayoutActions } from '../features/formDesigner/formLayout/formLayou import { Preview } from '../components/Preview'; import { setSelectedLayoutInLocalStorage } from '../utils/localStorageUtils'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; -import previewGet from '@studio/testing/mockend/src/routes/preview-get'; +import { StudioResizableLayoutRoot } from 'libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot'; import { FormDesignerToolbar } from './FormDesignerToolbar'; export interface FormDesignerProps { @@ -64,7 +64,6 @@ export const FormDesigner = ({ ); const [searchParams] = useSearchParams(); const { handleEdit } = useFormItemContext(); - const [previewCollapsed, setPreviewCollapsed] = useState(false); const layoutPagesOrder = formLayoutSettings?.pages.order; @@ -150,33 +149,27 @@ export const FormDesigner = ({
- - + + - - + + - - - - + + + + - - + + - - - - - setPreviewCollapsed(collapsed)} - /> - - + + + + + + +
From 2bd8ab078852989c5e95e8dc497eb2b88220b08e Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 15:33:52 +0200 Subject: [PATCH 21/44] Revert accidental v3 form editor edits --- .../src/components/Elements/Elements.tsx | 3 ++ .../Elements/LayoutSetsContainer.module.css | 2 ++ .../Elements/LayoutSetsContainer.tsx | 2 +- .../src/containers/FormDesigner.module.css | 1 + .../src/containers/FormDesigner.tsx | 29 ++++--------------- .../containers/FormDesignerToolbar.module.css | 6 ---- .../src/containers/FormDesignerToolbar.tsx | 13 --------- 7 files changed, 12 insertions(+), 44 deletions(-) delete mode 100644 frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css delete mode 100644 frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx index ff75c1d4de7..030a495a925 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Elements/Elements.tsx @@ -17,8 +17,10 @@ export const Elements = () => { const { org, app } = useStudioEnvironmentParams(); const selectedLayout: string = useSelector(selectedLayoutNameSelector); const { selectedLayoutSet } = useAppContext(); + const layoutSetsQuery = useLayoutSetsQuery(org, app); const { data: formLayoutSettings } = useFormLayoutSettingsQuery(org, app, selectedLayoutSet); const receiptName = formLayoutSettings?.receiptLayoutName; + const layoutSetNames = layoutSetsQuery?.data?.sets; const hideComponents = selectedLayout === 'default' || selectedLayout === undefined; @@ -26,6 +28,7 @@ export const Elements = () => { return (
+ {layoutSetNames && } {t('left_menu.components')} diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css index f2476301540..ebe13d456a7 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css +++ b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.module.css @@ -1,2 +1,4 @@ .dropDownContainer { + margin: var(--fds-spacing-5); + margin-bottom: 5px; } diff --git a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx index 09cfce45e56..08928286ec1 100644 --- a/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Elements/LayoutSetsContainer.tsx @@ -24,7 +24,7 @@ export function LayoutSetsContainer() { return (
onLayoutSetClick(event.target.value)} value={selectedLayoutSet} > diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css index 766e48e73a6..d606a86f042 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.module.css @@ -15,6 +15,7 @@ --properties-width-fraction: 2; --preview-width-fraction: 3; + flex-grow: 1; height: calc(100vh - var(--header-height)); overflow-y: hidden; } diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index 8954e4bef5a..1992ff48f50 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -10,7 +10,7 @@ import { useFormLayoutsQuery } from '../hooks/queries/useFormLayoutsQuery'; import { useFormLayoutSettingsQuery } from '../hooks/queries/useFormLayoutSettingsQuery'; import { useRuleModelQuery } from '../hooks/queries/useRuleModelQuery'; import { ErrorPage } from '../components/ErrorPage'; -import { StudioPageSpinner, StudioResizableLayout } from '@studio/components'; +import { StudioPageSpinner } from '@studio/components'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; import { useRuleConfigQuery } from '../hooks/queries/useRuleConfigQuery'; import { useInstanceIdQuery } from 'app-shared/hooks/queries'; @@ -32,8 +32,6 @@ import { FormLayoutActions } from '../features/formDesigner/formLayout/formLayou import { Preview } from '../components/Preview'; import { setSelectedLayoutInLocalStorage } from '../utils/localStorageUtils'; import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; -import { StudioResizableLayoutRoot } from 'libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutRoot'; -import { FormDesignerToolbar } from './FormDesignerToolbar'; export interface FormDesignerProps { selectedLayout: string; @@ -149,27 +147,10 @@ export const FormDesigner = ({
- - - - - - - - - - - - - - - - - - - - - + + + +
diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css deleted file mode 100644 index e1c0d8b41fc..00000000000 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.toolbar { - align-items: center; - display: flex; - padding: 8px; - border-bottom: 1px solid #c9c9c9; -} diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx deleted file mode 100644 index 9f0bf754b21..00000000000 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesignerToolbar.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { LayoutSetsContainer } from '../components/Elements/LayoutSetsContainer'; -import { useLayoutSetsQuery } from '../hooks/queries/useLayoutSetsQuery'; -import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import classes from './FormDesignerToolbar.module.css'; - -export const FormDesignerToolbar = () => { - const { org, app } = useStudioEnvironmentParams(); - const layoutSetsQuery = useLayoutSetsQuery(org, app); - const layoutSetNames = layoutSetsQuery?.data?.sets; - - return
{layoutSetNames && }
; -}; From 249bcfb5cab6ac79604754c79de46867517b65d6 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Fri, 28 Jun 2024 15:42:29 +0200 Subject: [PATCH 22/44] Remove layoutId from storybook entry and unnecessary imports --- .../StudioResizableLayout.stories.tsx | 6 +----- .../ux-editor/src/components/Elements/Elements.tsx | 9 ++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx index 70053a7c270..57cc244590a 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayout.stories.tsx @@ -18,7 +18,6 @@ const meta: Meta = { export const Preview: Story = (args): React.ReactElement => (
@@ -29,10 +28,7 @@ export const Preview: Story = (args): React.ReactElement => (
- +
lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx index 6cf6f6e0559..a0e8ecc8857 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx @@ -1,17 +1,16 @@ +import { Alert, Heading, Paragraph } from '@digdir/design-system-react'; import React from 'react'; +import { useAppContext } from '../../hooks'; import { ConfPageToolbar } from './ConfPageToolbar'; import { DefaultToolbar } from './DefaultToolbar'; -import { Alert, Heading, Paragraph } from '@digdir/design-system-react'; -import { useAppContext } from '../../hooks'; -import { LayoutSetsContainer } from './LayoutSetsContainer'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import classes from './Elements.module.css'; -import { useCustomReceiptLayoutSetName } from 'app-shared/hooks/useCustomReceiptLayoutSetName'; -import { useProcessTaskTypeQuery } from '../../hooks/queries/useProcessTaskTypeQuery'; import { StudioSpinner } from '@studio/components'; +import { useCustomReceiptLayoutSetName } from 'app-shared/hooks/useCustomReceiptLayoutSetName'; import { useTranslation } from 'react-i18next'; +import { useProcessTaskTypeQuery } from '../../hooks/queries/useProcessTaskTypeQuery'; export const Elements = (): React.ReactElement => { const { t } = useTranslation(); From ea4fbaf8f793b75b4733e61d221ae53ed3b6e31a Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Mon, 1 Jul 2024 08:46:08 +0200 Subject: [PATCH 23/44] Remove accordion top border for first item in list --- .../DesignView/PageAccordion/PageAccordion.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/containers/DesignView/PageAccordion/PageAccordion.module.css b/frontend/packages/ux-editor/src/containers/DesignView/PageAccordion/PageAccordion.module.css index cd2f4365f14..08dc35e2277 100644 --- a/frontend/packages/ux-editor/src/containers/DesignView/PageAccordion/PageAccordion.module.css +++ b/frontend/packages/ux-editor/src/containers/DesignView/PageAccordion/PageAccordion.module.css @@ -19,7 +19,7 @@ background-color: var(--fds-semantic-surface-neutral-default); } -.accordionItem { +.accordionItem:not(:first-child) { border-top: 1px solid var(--fds-semantic-border-neutral-subtle); } From 45574f9c3c5403db2778c05587b7e6277decafa9 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 08:07:21 +0200 Subject: [PATCH 24/44] Add maximumSize and move collapsing to css rules --- .../StudioResizableLayoutContainer.tsx | 11 +++-- .../StudioResizableLayoutElement.tsx | 28 +++++------ .../StudioResizableLayoutHandle.tsx | 3 +- .../classes/StudioResizableLayoutElement.ts | 4 ++ .../context/StudioResizableLayoutContext.ts | 1 - .../hooks/useKeyboardControls.ts | 2 +- .../hooks/useStudioResizableFunctions.ts | 48 +++++------------- .../hooks/useStudioResizableLayoutContext.ts | 14 ++++-- .../useStudioResizableLayoutMouseMovement.ts | 6 +-- .../hooks/useTrackContainerSizes.ts | 5 +- .../SchemaEditor/SchemaEditor.module.css | 1 + .../components/SchemaEditor/SchemaEditor.tsx | 49 ++++++++++++------- .../SchemaInspector.module.css | 1 - .../TypesInspector/TypesInspector.module.css | 1 - .../components/Elements/Elements.module.css | 3 ++ .../src/components/Elements/Elements.tsx | 15 ++++-- .../src/components/Preview/Preview.tsx | 21 +++----- .../ux-editor/src/containers/FormDesigner.tsx | 7 +-- 18 files changed, 112 insertions(+), 108 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 471a52c83c3..e08be8fd2cc 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -1,3 +1,4 @@ +import type { CSSProperties, ReactElement } from 'react'; import React, { Children, useEffect, useRef } from 'react'; import classes from './StudioResizableLayoutContainer.module.css'; import { type StudioResizableLayoutElementProps } from '../StudioResizableLayoutElement/StudioResizableLayoutElement'; @@ -10,9 +11,9 @@ export type StudioResizableOrientation = 'horizontal' | 'vertical'; export type StudioResizableLayoutContainerProps = { localStorageContext?: string; orientation: StudioResizableOrientation; - style?: React.CSSProperties; + style?: CSSProperties; - children: React.ReactElement[]; + children: ReactElement[]; }; const StudioResizableLayoutContainer = ({ @@ -20,14 +21,14 @@ const StudioResizableLayoutContainer = ({ orientation, localStorageContext = 'default', style, -}: StudioResizableLayoutContainerProps): React.ReactElement => { +}: StudioResizableLayoutContainerProps): ReactElement => { const elementRefs = useRef<(HTMLDivElement | null)[]>([]); useEffect(() => { elementRefs.current = elementRefs.current.slice(0, getValidChildren(children).length); }, [children]); const { containerSizes, setContainerSizes } = useTrackContainerSizes(localStorageContext); - const { resizeTo, resizeDelta, collapse } = useStudioResizableLayoutFunctions( + const { resizeTo, resizeDelta } = useStudioResizableLayoutFunctions( orientation, elementRefs, getValidChildren(children), @@ -47,7 +48,7 @@ const StudioResizableLayoutContainer = ({ return (
{ - const { resizeTo, collapse, orientation, containerSize } = - useStudioResizableLayoutContext(index); - - useEffect(() => { - if (collapsed) { - collapse(index); - } else { - resizeTo(index, minimumSize); - } - // disable linter as we only want to run this effect if the collapsed prop changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [collapsed]); + ): ReactElement => { + const { orientation, containerSize } = useStudioResizableLayoutContext(index); return ( <>
{children} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx index 536aebc4ea0..af905c76a60 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutHandle/StudioResizableLayoutHandle.tsx @@ -1,3 +1,4 @@ +import type { ReactElement } from 'react'; import React, { useEffect } from 'react'; import { useStudioResizableLayoutContext } from '../hooks/useStudioResizableLayoutContext'; import type { StudioResizableOrientation } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; @@ -15,7 +16,7 @@ export const StudioResizableLayoutHandle = ({ orientation, index, onResizing, -}: StudioResizableLayoutHandleProps) => { +}: StudioResizableLayoutHandleProps): ReactElement => { const { resizeDelta, containerSize } = useStudioResizableLayoutContext(index); const { onMouseDown, isResizing } = useStudioResizableLayoutMouseMovement( orientation, diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts index 3219b45584d..45a111a6566 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts @@ -27,6 +27,10 @@ export class StudioResizableLayoutArea { return this.reactElement.props.minimumSize || 0; } + public get maximumSize() { + return this.reactElement.props.maximumSize || Number.MAX_SAFE_INTEGER; + } + public get collapsedSize() { return this.reactElement.props.collapsedSize || 0; } diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts index 3d27e107352..03554273680 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/context/StudioResizableLayoutContext.ts @@ -6,7 +6,6 @@ export type StudioResizableLayoutContextProps = { containerSizes: number[]; resizeDelta: (index: number, size: number) => void; resizeTo: (index: number, size: number) => void; - collapse: (index: number) => void; }; export const StudioResizableLayoutContext = diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts index b7fa27aff0b..6614a59fcb6 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useKeyboardControls.ts @@ -1,7 +1,7 @@ export const useKeyboardControls = ( onResize: (delta: number) => void, ): { onKeyDown: (event: React.KeyboardEvent) => void } => { - const onKeyDown = (event: React.KeyboardEvent) => { + const onKeyDown = (event: React.KeyboardEvent): void => { if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { if (event.shiftKey) { onResize(-50); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts index b82e6e442aa..06f2dda76ef 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.ts @@ -5,7 +5,6 @@ import { StudioResizableLayoutArea } from '../classes/StudioResizableLayoutEleme type useResizableFunctionsReturnType = { resizeTo: (index: number, size: number) => void; resizeDelta: (index: number, size: number) => void; - collapse: (index: number) => void; }; export const useStudioResizableLayoutFunctions = ( @@ -14,7 +13,7 @@ export const useStudioResizableLayoutFunctions = ( children: any[], setContainerSize: (index: number, size: number) => void, ): useResizableFunctionsReturnType => { - const getElementNeighbour = (index: number) => { + const getElementNeighbour = (index: number): StudioResizableLayoutArea => { const neighbourIndex = elementRefs.current.length < index + 2 ? index - 1 : index + 1; return new StudioResizableLayoutArea( neighbourIndex, @@ -24,7 +23,7 @@ export const useStudioResizableLayoutFunctions = ( ); }; - const getElement = (index: number) => { + const getElement = (index: number): StudioResizableLayoutArea => { return new StudioResizableLayoutArea( index, elementRefs.current[index], @@ -37,14 +36,11 @@ export const useStudioResizableLayoutFunctions = ( element: StudioResizableLayoutArea, neighbour: StudioResizableLayoutArea, newSize: number, - ) => { + ): { newSize: number; neighbourNewSize: number } => { const totalSize = element.size + neighbour.size; - if (element.minimumSize > newSize) { - newSize = element.minimumSize; - } - if (neighbour.minimumSize > totalSize - newSize) { - newSize = totalSize - neighbour.minimumSize; - } + if (element.maximumSize < newSize) newSize = element.maximumSize; + if (element.minimumSize > newSize) newSize = element.minimumSize; + if (neighbour.minimumSize > totalSize - newSize) newSize = totalSize - neighbour.minimumSize; const neighbourNewSize = totalSize - newSize; return { newSize, neighbourNewSize }; }; @@ -54,34 +50,19 @@ export const useStudioResizableLayoutFunctions = ( neighbour: StudioResizableLayoutArea, resizeTo: number, ignoreMinimumSize: boolean = false, - ) => { + ): { containerFlexGrow: number; neighbourFlexGrow: number } => { const totalPixelSize = element.size + neighbour.size; const { newSize, neighbourNewSize } = ignoreMinimumSize ? { newSize: resizeTo, neighbourNewSize: totalPixelSize - resizeTo } : calculatePixelSizes(element, neighbour, resizeTo); const totalFlexGrow = element.flexGrow + neighbour.flexGrow; - const containerFlexGrow = totalFlexGrow * (newSize / totalPixelSize); - const neighbourFlexGrow = totalFlexGrow * (neighbourNewSize / totalPixelSize); + const containerFlexGrow = (newSize / totalPixelSize) * totalFlexGrow; + const neighbourFlexGrow = (neighbourNewSize / totalPixelSize) * totalFlexGrow; return { containerFlexGrow, neighbourFlexGrow }; }; - const forceSize = (index: number, size: number) => { - const element = getElement(index); - const neighbour = getElementNeighbour(index); - - const { containerFlexGrow, neighbourFlexGrow } = calculateFlexGrow( - element, - neighbour, - size, - true, - ); - - setContainerSize(index, containerFlexGrow); - setContainerSize(neighbour.index, neighbourFlexGrow); - }; - - const resizeTo = (index: number, size: number) => { + const resizeTo = (index: number, size: number): void => { const element = getElement(index); const neighbour = getElementNeighbour(index); @@ -95,15 +76,10 @@ export const useStudioResizableLayoutFunctions = ( setContainerSize(neighbour.index, neighbourFlexGrow); }; - const resizeDelta = (index: number, size: number) => { + const resizeDelta = (index: number, size: number): void => { const element = getElement(index); resizeTo(index, element.size + size); }; - const collapse = (index: number) => { - const element = getElement(index); - forceSize(index, element.collapsedSize); - }; - - return { resizeTo, resizeDelta, collapse }; + return { resizeTo, resizeDelta }; }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts index 104be40e16c..a04f6881f84 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutContext.ts @@ -1,10 +1,18 @@ import { useContext } from 'react'; +import type { StudioResizableLayoutContextProps } from '../context/StudioResizableLayoutContext'; import { StudioResizableLayoutContext } from '../context/StudioResizableLayoutContext'; -export const useStudioResizableLayoutContext = (index: number) => { - const { containerSizes, orientation, resizeDelta, resizeTo, collapse } = useContext( +interface useStudioResizableLayoutContextReturnType + extends Omit { + containerSize: number; +} + +export const useStudioResizableLayoutContext = ( + index: number, +): useStudioResizableLayoutContextReturnType => { + const { containerSizes, orientation, resizeDelta, resizeTo } = useContext( StudioResizableLayoutContext, ); const containerSize = containerSizes[index]; - return { containerSize, orientation, resizeDelta, resizeTo, collapse }; + return { containerSize, orientation, resizeDelta, resizeTo }; }; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts index 197890e5441..6456a5f63a7 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts @@ -13,7 +13,7 @@ export const useStudioResizableLayoutMouseMovement = ( const [isResizing, setIsResizing] = useState(false); const mouseMove = useCallback( - (event: MouseEvent) => { + (event: MouseEvent): void => { const mousePos = orientation === 'horizontal' ? event.clientX : event.clientY; const mouseTotalDelta = mousePos - startMousePosition.current; const mouseDelta = mousePos - lastMousePosition.current; @@ -24,7 +24,7 @@ export const useStudioResizableLayoutMouseMovement = ( ); const mouseUp = useCallback( - (_: MouseEvent) => { + (_: MouseEvent): void => { setIsResizing(false); window.removeEventListener('mousemove', mouseMove); window.removeEventListener('mouseup', mouseUp); @@ -33,7 +33,7 @@ export const useStudioResizableLayoutMouseMovement = ( ); const onMouseDown = useCallback( - (event: React.MouseEvent) => { + (event: React.MouseEvent): (() => void) => { if (event.button !== 0) return; event.preventDefault(); setIsResizing(true); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts index 0d09328dec1..239e593e2fb 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useTrackContainerSizes.ts @@ -1,5 +1,5 @@ import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; export const useTrackContainerSizes = (localStorageContextKey: string) => { const [containerSizes, setContainerSizes] = useState([]); @@ -8,7 +8,8 @@ export const useTrackContainerSizes = (localStorageContextKey: string) => { `studio:resizable-layout:${localStorageContextKey}`, containerSizes, ); - useEffect(() => { + + useMemo(() => { setContainerSizes(value); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css index 66ec5338b84..8510bcb9d53 100644 --- a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css +++ b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css @@ -11,6 +11,7 @@ background-color: #f7f7f7; overflow-x: clip; overflow-y: auto; + width: 100%; } .typeInfo { diff --git a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx index 42cf9b157da..abd5e970b35 100644 --- a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx +++ b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx @@ -9,9 +9,10 @@ import { DragAndDropTree } from 'app-shared/components/DragAndDropTree'; import { useMoveProperty } from './hooks/useMoveProperty'; import { useAddReference } from './hooks/useAddReference'; import { NodePanel } from '../NodePanel'; +import { StudioResizableLayout } from '@studio/components'; export const SchemaEditor = () => { - const { schemaModel, selectedTypePointer } = useSchemaEditorAppContext(); + const { schemaModel, selectedTypePointer, selectedNodePointer } = useSchemaEditorAppContext(); const moveProperty = useMoveProperty(); const addReference = useAddReference(); @@ -20,23 +21,33 @@ export const SchemaEditor = () => { const selectedType = selectedTypePointer && schemaModel.getNode(selectedTypePointer); return ( - <> - - -
- -
-
- - + + + + + + +
+ +
+
+ + + +
+
); }; diff --git a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css index abe735fe9a4..fe4a1c6dbde 100644 --- a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css +++ b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css @@ -2,7 +2,6 @@ display: flex; flex-direction: column; height: 100%; - width: 500px; } .root :global(.MuiAutocomplete-input) { diff --git a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css index 41fe32301c3..4456910f3d4 100644 --- a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css +++ b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css @@ -1,7 +1,6 @@ .root { box-sizing: border-box; padding: 24px 24px; - width: 280px; min-height: 100%; background: rgba(224, 224, 224, 0.3); border-right: 1px solid var(--fds-semantic-border-neutral-subtle); diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.module.css b/frontend/packages/ux-editor/src/components/Elements/Elements.module.css index f9b1ba5d0fe..13a812c4704 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.module.css +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.module.css @@ -16,6 +16,9 @@ margin: var(--fds-spacing-3); padding-bottom: 10px; border-bottom: 2px solid var(--semantic-surface-neutral-subtle-hover); + display: flex; + flex-direction: row; + justify-content: space-between; } .noPageSelected { diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx index a0e8ecc8857..bd188a6a5e8 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx @@ -7,10 +7,11 @@ import { DefaultToolbar } from './DefaultToolbar'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import classes from './Elements.module.css'; -import { StudioSpinner } from '@studio/components'; +import { StudioButton, StudioSpinner } from '@studio/components'; import { useCustomReceiptLayoutSetName } from 'app-shared/hooks/useCustomReceiptLayoutSetName'; import { useTranslation } from 'react-i18next'; import { useProcessTaskTypeQuery } from '../../hooks/queries/useProcessTaskTypeQuery'; +import { ShrinkIcon } from '@studio/icons'; export const Elements = (): React.ReactElement => { const { t } = useTranslation(); @@ -61,9 +62,15 @@ export const Elements = (): React.ReactElement => { return (
- - {t('left_menu.components')} - +
+ {t('left_menu.components')} + } + title={t('ux_editor.close_preview')} + // onClick={onCollapseToggle} + > +
{hideComponents ? ( {t('left_menu.no_components_selected')} diff --git a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx index 54f1426e5ba..b857a610eb8 100644 --- a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx +++ b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx @@ -10,32 +10,27 @@ import { Paragraph } from '@digdir/design-system-react'; import { StudioButton, StudioCenter } from '@studio/components'; import type { SupportedView } from './ViewToggler/ViewToggler'; import { ViewToggler } from './ViewToggler/ViewToggler'; -import { ArrowRightIcon } from '@studio/icons'; +import { ShrinkIcon } from '@studio/icons'; import { PreviewLimitationsInfo } from 'app-shared/components/PreviewLimitationsInfo/PreviewLimitationsInfo'; export type PreviewProps = { - onCollapseToggle?: (collapsed: boolean) => void; + collapsed: boolean; + onCollapseToggle: () => void; hidePreview?: boolean; }; -export const Preview = ({ onCollapseToggle, hidePreview }: PreviewProps) => { +export const Preview = ({ collapsed, onCollapseToggle, hidePreview }: PreviewProps) => { const { t } = useTranslation(); - const [isPreviewHidden, setIsPreviewHidden] = useState(false); const { selectedFormLayoutName } = useAppContext(); const noPageSelected = selectedFormLayoutName === 'default' || selectedFormLayoutName === undefined; - const togglePreview = (): void => { - onCollapseToggle?.(!isPreviewHidden); - setIsPreviewHidden((prev: boolean) => !prev); - }; - - return isPreviewHidden ? ( + return collapsed ? ( {t('ux_editor.open_preview')} @@ -43,10 +38,10 @@ export const Preview = ({ onCollapseToggle, hidePreview }: PreviewProps) => {
} + icon={} title={t('ux_editor.close_preview')} className={classes.closePreviewButton} - onClick={togglePreview} + onClick={onCollapseToggle} /> {noPageSelected ? ( diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index 02b1391e75c..22c32a8465c 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -63,7 +63,7 @@ export const FormDesigner = (): JSX.Element => { selectedFormLayoutSetName, ); const { handleEdit } = useFormItemContext(); - const [previewCollapsed, setPreviewCollapsed] = useState(false); + const [previewCollapsed, setPreviewCollapsed] = useState(true); const [hidePreview, setHidePreview] = useState(false); const t = useText(); @@ -152,7 +152,7 @@ export const FormDesigner = (): JSX.Element => { orientation='horizontal' localStorageContext={`form-designer-main:${user.id}:${org}`} > - + @@ -167,7 +167,8 @@ export const FormDesigner = (): JSX.Element => { minimumSize={400} > setPreviewCollapsed(collapsed)} + collapsed={previewCollapsed} + onCollapseToggle={() => setPreviewCollapsed((prev) => !prev)} hidePreview={hidePreview} /> From c13ea5cace3725a686e844c30ff3694bc2c98c92 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 08:22:46 +0200 Subject: [PATCH 25/44] Update preview test --- .../ux-editor/src/components/Preview/Preview.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx b/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx index 8466c45fcec..35dec03754e 100644 --- a/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx +++ b/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx @@ -91,7 +91,7 @@ describe('Preview', () => { const newSelectedFormLayoutName = 'test'; appContextMock.selectedFormLayoutName = newSelectedFormLayoutName; - view.rerender(); + view.rerender(); expect(appContextMock.previewIframeRef?.current?.src).toBe( 'http://localhost' + @@ -107,4 +107,4 @@ describe('Preview', () => { }); export const render = (options: Partial = {}) => - renderWithProviders(, options); + renderWithProviders(, options); From 96f57e691d69eed8674fcc20cdc6b96e29efccef Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 08:44:23 +0200 Subject: [PATCH 26/44] Make components left menu collapsible --- frontend/language/src/en.json | 2 ++ frontend/language/src/nb.json | 2 ++ .../components/Elements/Elements.module.css | 9 +++++-- .../src/components/Elements/Elements.tsx | 24 ++++++++++++++++--- .../toolbar/ToolbarItemComponent.module.css | 1 - .../ux-editor/src/containers/FormDesigner.tsx | 13 ++++++++-- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/frontend/language/src/en.json b/frontend/language/src/en.json index af9fc8fe1cc..9aa16667645 100644 --- a/frontend/language/src/en.json +++ b/frontend/language/src/en.json @@ -450,12 +450,14 @@ "language.za": "Zhuang", "language.zh": "Chinese", "language.zu": "Zulu", + "left_menu.close_components": "Close components", "left_menu.components": "Components", "left_menu.configure_layout_sets": "Convert form to handle multiple groups of pages", "left_menu.configure_layout_sets_info": "NB, this action will change the folder structure of your application. Read more at <0 href=\"{{layoutSetsDocs}}\" >Altinn Docs for details of what this involves.", "left_menu.configure_layout_sets_name": "Name of first group:", "left_menu.layout_sets": "Groups of pages", "left_menu.layout_sets_add": "Add new group", + "left_menu.open_components": "Open components", "left_menu.pages.invalid_page_data": "The page contains invalid data and cannot be shown. Please check all component IDs and references to components from groups.", "left_menu.pages_error_empty": "Can not be empty", "left_menu.pages_error_format": "Must consist of letters (a-z), numbers, or \"-\", \"_\" and \".\"", diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index b4b02f520ec..0eb704cc66a 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -605,6 +605,7 @@ "language.za": "Zhuang", "language.zh": "Kinesisk", "language.zu": "Zulu", + "left_menu.close_components": "Lukk komponenter", "left_menu.components": "Komponenter", "left_menu.configure_layout_sets": "Konverter skjema til å håndtere sidegrupper", "left_menu.configure_layout_sets_info": "Obs, ved å velge denne handlingen endrer du mappestrukturen i applikasjonen. Les mer om hva det vil si på <0 href=\"{{layoutSetsDocs}}\" >Altinn Docs.", @@ -613,6 +614,7 @@ "left_menu.layout_sets": "Sidegrupper", "left_menu.layout_sets_add": "Legg til ny sidegruppe", "left_menu.no_components_selected": "Velg en side for å se komponenter", + "left_menu.open_components": "Åpne komponenter", "left_menu.pages.invalid_page_data": "Siden inneholder ugyldig data, og kan ikke vises. Kontroller alle komponent-ID-er og referanser til komponenter fra grupper.", "left_menu.pages_error_empty": "Kan ikke være tom", "left_menu.pages_error_format": "Må bestå av bokstaver (a-z), tall, eller \"-\", \"_\" og \".\"", diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.module.css b/frontend/packages/ux-editor/src/components/Elements/Elements.module.css index 13a812c4704..70af74ffb9f 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.module.css +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.module.css @@ -14,11 +14,16 @@ .componentsHeader { margin: var(--fds-spacing-3); - padding-bottom: 10px; - border-bottom: 2px solid var(--semantic-surface-neutral-subtle-hover); display: flex; flex-direction: row; justify-content: space-between; + align-items: center; +} + +.openElementsButton { + writing-mode: vertical-lr; + text-transform: uppercase; + border-radius: 0; } .noPageSelected { diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx index bd188a6a5e8..303146a70f6 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx @@ -13,7 +13,12 @@ import { useTranslation } from 'react-i18next'; import { useProcessTaskTypeQuery } from '../../hooks/queries/useProcessTaskTypeQuery'; import { ShrinkIcon } from '@studio/icons'; -export const Elements = (): React.ReactElement => { +export interface ElementsProps { + collapsed: boolean; + onCollapseToggle: () => void; +} + +export const Elements = ({ collapsed, onCollapseToggle }: ElementsProps): React.ReactElement => { const { t } = useTranslation(); const { org, app } = useStudioEnvironmentParams(); const { selectedFormLayoutSetName, selectedFormLayoutName } = useAppContext(); @@ -60,6 +65,19 @@ export const Elements = (): React.ReactElement => { const shouldShowConfPageToolbar = selectedLayoutIsCustomReceipt || processTaskType === 'payment'; const confPageToolbarMode = selectedLayoutIsCustomReceipt ? 'receipt' : 'payment'; + if (collapsed) { + return ( + + {t('left_menu.open_components')} + + ); + } + return (
@@ -67,8 +85,8 @@ export const Elements = (): React.ReactElement => { } - title={t('ux_editor.close_preview')} - // onClick={onCollapseToggle} + title={t('left_menu.close_components')} + onClick={onCollapseToggle} >
{hideComponents ? ( diff --git a/frontend/packages/ux-editor/src/components/toolbar/ToolbarItemComponent.module.css b/frontend/packages/ux-editor/src/components/toolbar/ToolbarItemComponent.module.css index a0a79b06118..10cc2eca15a 100644 --- a/frontend/packages/ux-editor/src/components/toolbar/ToolbarItemComponent.module.css +++ b/frontend/packages/ux-editor/src/components/toolbar/ToolbarItemComponent.module.css @@ -17,7 +17,6 @@ .componentLabel { font-size: 14px; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index 22c32a8465c..63e3e56e810 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -64,6 +64,7 @@ export const FormDesigner = (): JSX.Element => { ); const { handleEdit } = useFormItemContext(); const [previewCollapsed, setPreviewCollapsed] = useState(true); + const [elementsCollapsed, setElementsCollapsed] = useState(false); const [hidePreview, setHidePreview] = useState(false); const t = useText(); @@ -152,8 +153,16 @@ export const FormDesigner = (): JSX.Element => { orientation='horizontal' localStorageContext={`form-designer-main:${user.id}:${org}`} > - - + + setElementsCollapsed((prev) => !prev)} + /> From 27777b6a937f66f336ece2fe712a8398ddfe78e6 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 09:00:12 +0200 Subject: [PATCH 27/44] Fix export of localStorage hooks for tests --- frontend/libs/studio-components/src/hooks/index.ts | 2 ++ .../packages/ux-editor-v3/src/components/TextResource.test.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/libs/studio-components/src/hooks/index.ts b/frontend/libs/studio-components/src/hooks/index.ts index d9493f4ad49..66baa1b2b61 100644 --- a/frontend/libs/studio-components/src/hooks/index.ts +++ b/frontend/libs/studio-components/src/hooks/index.ts @@ -1,2 +1,4 @@ export * from './useMediaQuery'; export * from './usePrevious'; +export * from './useLocalStorage'; +export * from './webStorage'; diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx b/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx index 2de29610813..c5d07cb73f1 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.test.tsx @@ -10,7 +10,7 @@ import { screen, waitFor } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { useTextResourcesQuery } from 'app-shared/hooks/queries/useTextResourcesQuery'; import { DEFAULT_LANGUAGE } from 'app-shared/constants'; -import { typedLocalStorage } from '@studio/components/src/hooks/webStorage'; +import { typedLocalStorage } from '@studio/components'; import { addFeatureFlagToLocalStorage } from 'app-shared/utils/featureToggleUtils'; import { app, org } from '@studio/testing/testids'; From 30b19af2a82e41512b8a4ea83f74cea7a1543a3e Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 09:04:14 +0200 Subject: [PATCH 28/44] Fix elements test --- .../ux-editor/src/components/Elements/Elements.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx index 27748910090..bedcda9afe9 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx @@ -101,7 +101,7 @@ const renderElements = ( ) => { return renderWithProviders( - + , { appContextProps, From 9509cbe0aee0b7b242eb7740b7e1a63877e76b99 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 09:30:45 +0200 Subject: [PATCH 29/44] Fix preview test collapsing --- .../src/components/Preview/Preview.test.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx b/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx index 35dec03754e..0f756774937 100644 --- a/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx +++ b/frontend/packages/ux-editor/src/components/Preview/Preview.test.tsx @@ -60,18 +60,22 @@ describe('Preview', () => { it('should be possible to toggle preview window', async () => { const user = userEvent.setup(); - render(); + const view = render(); const hidePreviewButton = screen.getByRole('button', { name: textMock('ux_editor.close_preview'), }); await user.click(hidePreviewButton); + expect(collapseToggle).toHaveBeenCalledTimes(1); + view.rerender(); expect(hidePreviewButton).not.toBeInTheDocument(); const showPreviewButton = screen.getByRole('button', { name: textMock('ux_editor.open_preview'), }); await user.click(showPreviewButton); + expect(collapseToggle).toHaveBeenCalledTimes(2); + view.rerender(); expect(showPreviewButton).not.toBeInTheDocument(); }); @@ -106,5 +110,11 @@ describe('Preview', () => { }); }); -export const render = (options: Partial = {}) => - renderWithProviders(, options); +const collapseToggle = jest.fn(); + +export const render = (options: Partial = {}) => { + return renderWithProviders( + , + options, + ); +}; From 352e75e4a03e432a7949fca7691fb9ef4c9b5ca9 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 09:48:25 +0200 Subject: [PATCH 30/44] Add test for collapsing elements view --- .../src/components/Elements/Elements.test.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx index bedcda9afe9..75aeb48d017 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.test.tsx @@ -92,8 +92,26 @@ describe('Elements', () => { ), ).toBeInTheDocument(); }); + + it('should collapse element when collapse button is clicked', async () => { + const view = renderElements(); + const collapseButton = screen.getByRole('button', { + name: textMock('left_menu.close_components'), + }); + collapseButton.click(); + expect(collapseToggle).toHaveBeenCalled(); + + view.rerender(); + expect(collapseButton).not.toBeInTheDocument(); + const openButton = screen.getByRole('button', { + name: textMock('left_menu.open_components'), + }); + openButton.click(); + expect(collapseToggle).toHaveBeenCalledTimes(2); + }); }); +const collapseToggle = jest.fn(); const renderElements = ( appContextProps?: Partial, queries?: Partial, @@ -101,7 +119,7 @@ const renderElements = ( ) => { return renderWithProviders( - + , { appContextProps, From 6cf2aec8f8f9bf98baf9211c3238288b215a41b3 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 10:53:14 +0200 Subject: [PATCH 31/44] Use exact matching to avoid potential false hits in playwright getbyrole --- .../testing/playwright/components/SettingsModal.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/testing/playwright/components/SettingsModal.ts b/frontend/testing/playwright/components/SettingsModal.ts index 37c43b13d36..af75a7cb375 100644 --- a/frontend/testing/playwright/components/SettingsModal.ts +++ b/frontend/testing/playwright/components/SettingsModal.ts @@ -14,13 +14,17 @@ export class SettingsModal extends BasePage { .getByRole('heading', { name: this.textMock('settings_modal.heading'), level: 1, + exact: true, }) .isVisible(); } public async clickOnCloseSettingsModalButton(): Promise { await this.page - .getByRole('button', { name: this.textMock('settings_modal.close_button_label') }) + .getByRole('button', { + name: this.textMock('settings_modal.close_button_label'), + exact: true, + }) .click(); } @@ -29,13 +33,14 @@ export class SettingsModal extends BasePage { .getByRole('heading', { name: this.textMock('settings_modal.heading'), level: 1, + exact: true, }) .isHidden(); } public async navigateToTab(tab: SettingsModalTab): Promise { await this.page - .getByRole('tab', { name: this.textMock(`settings_modal.left_nav_tab_${tab}`) }) + .getByRole('tab', { name: this.textMock(`settings_modal.left_nav_tab_${tab}`), exact: true }) .click(); } @@ -44,6 +49,7 @@ export class SettingsModal extends BasePage { .getByRole('heading', { name: this.textMock(`settings_modal.${tabHeading}_tab_heading`), level: 2, + exact: true, }) .isVisible(); } @@ -53,6 +59,7 @@ export class SettingsModal extends BasePage { .getByRole('heading', { name: this.textMock(`settings_modal.${tabHeading}_tab_heading`), level: 2, + exact: true, }) .isHidden(); } From 8f7c22ff9aad61eadc469c2120bc41295853006c Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 12:09:53 +0200 Subject: [PATCH 32/44] Add exact matching to other cases with false hits --- frontend/testing/playwright/pages/UiEditorPage.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/testing/playwright/pages/UiEditorPage.ts b/frontend/testing/playwright/pages/UiEditorPage.ts index a1dc554f48b..7a2b5a76c0d 100644 --- a/frontend/testing/playwright/pages/UiEditorPage.ts +++ b/frontend/testing/playwright/pages/UiEditorPage.ts @@ -161,6 +161,7 @@ export class UiEditorPage extends BasePage { await this.page .getByRole('button', { name: this.textMock('general.close'), + exact: true, }) .click(); } @@ -217,6 +218,7 @@ export class UiEditorPage extends BasePage { await this.page .getByRole('button', { name: this.textMock('general.close'), + exact: true, }) .click(); } From 1275262f705511fce32685ac78f8673e7992f0b4 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Tue, 2 Jul 2024 16:32:16 +0200 Subject: [PATCH 33/44] Add tests for resizablelayoutfunction and mousemovementhook --- .../StudioResizableLayoutContainer.tsx | 5 +- .../hooks/useStudioResizableFunctions.test.ts | 59 +++++++++++++++++++ ...StudioResizableLayoutMouseMovement.test.ts | 28 +++++++++ .../components/StudioResizableLayout/index.ts | 3 +- 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts create mode 100644 frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index e08be8fd2cc..8734dc35ab9 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -6,7 +6,10 @@ import { useStudioResizableLayoutFunctions } from '../hooks/useStudioResizableFu import { useTrackContainerSizes } from '../hooks/useTrackContainerSizes'; import { StudioResizableLayoutContext } from '../context/StudioResizableLayoutContext'; -export type StudioResizableOrientation = 'horizontal' | 'vertical'; +export const ORIENTATIONS = ['horizontal', 'vertical'] as const; +export type StudioResizableOrientation = (typeof ORIENTATIONS)[number]; +export const horizontal: StudioResizableOrientation = 'horizontal'; +export const vertical: StudioResizableOrientation = 'vertical'; export type StudioResizableLayoutContainerProps = { localStorageContext?: string; diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts new file mode 100644 index 00000000000..a392cd86d8b --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts @@ -0,0 +1,59 @@ +import { renderHook } from '@testing-library/react'; +import { useStudioResizableLayoutFunctions } from './useStudioResizableFunctions'; +import { horizontal } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; + +describe('useStudioResizableLayoutFunctions', () => { + let setContainerSize: jest.Mock; + let elementRefs: React.MutableRefObject; + let children: any[]; + + beforeEach(() => { + setContainerSize = jest.fn(); + elementRefs = { current: [document.createElement('div'), document.createElement('div')] }; + children = [ + { props: { collapsed: false, minimumSize: 0, maximumSize: 100, collapsedSize: 0 } }, + { props: { collapsed: false, minimumSize: 0, maximumSize: 100, collapsedSize: 0 } }, + ]; + }); + + it('should return resizeTo and resizeDelta functions', () => { + const { result } = renderFunctionsHook(elementRefs, children, setContainerSize); + + expect(result.current).toHaveProperty('resizeTo'); + expect(result.current).toHaveProperty('resizeDelta'); + }); + + it('should call setContainerSize when resizeTo is called', () => { + const { result } = renderFunctionsHook(elementRefs, children, setContainerSize); + + result.current.resizeTo(0, 100); + expect(setContainerSize).toHaveBeenCalled(); + }); + + it('should call resizeTo with correct parameters when resizeDelta is called', () => { + const { result } = renderFunctionsHook(elementRefs, children, setContainerSize); + + result.current.resizeDelta(0, 100); + expect(setContainerSize).toHaveBeenCalled(); + }); +}); + +function renderFunctionsHook( + elementRefs, + children: any[], + setContainerSize: jest.Mock, +): { result: any } { + return renderHook(() => + useStudioResizableLayoutFunctions(horizontal, elementRefs, children, setContainerSize), + ); +} + +function newFunction( + elementRefs, + children: any[], + setContainerSize: jest.Mock, +): { result: any } { + return renderHook(() => + useStudioResizableLayoutFunctions(horizontal, elementRefs, children, setContainerSize), + ); +} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts new file mode 100644 index 00000000000..e7d5ed64754 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts @@ -0,0 +1,28 @@ +import { act, renderHook } from '@testing-library/react'; +import { horizontal } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { useStudioResizableLayoutMouseMovement } from './useStudioResizableLayoutMouseMovement'; + +describe('useStudioResizableLayoutMouseMovement', () => { + it('should return onMouseDown and isResizing', () => { + const { result } = renderHook(() => + useStudioResizableLayoutMouseMovement(horizontal, jest.fn()), + ); + expect(result.current).toHaveProperty('onMouseDown'); + expect(result.current).toHaveProperty('isResizing'); + }); + + it('should call onMousePosChange when onMouseDown is called', () => { + const onMousePosChange = jest.fn(); + const { result } = renderHook(() => + useStudioResizableLayoutMouseMovement(horizontal, onMousePosChange), + ); + + const event = new MouseEvent('mousedown'); + act(() => { + result.current.onMouseDown(event); + }); + const mouseMoveEvent = new MouseEvent('mousemove'); + window.dispatchEvent(mouseMoveEvent); + expect(onMousePosChange).toHaveBeenCalled(); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts index 668e77c95b3..36a7fc6abf9 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/index.ts @@ -2,6 +2,7 @@ import { StudioResizableLayoutElement, type StudioResizableLayoutElementProps, } from './StudioResizableLayoutElement/StudioResizableLayoutElement'; +import type { StudioResizableOrientation } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { StudioResizableLayoutContainer } from './StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { StudioResizableLayoutContext } from './context/StudioResizableLayoutContext'; @@ -17,4 +18,4 @@ export const StudioResizableLayout: StudioResizableLayoutComponent = { Context: StudioResizableLayoutContext, }; -export type { StudioResizableLayoutElementProps as StudioResizableLayoutContainerProps }; +export type { StudioResizableLayoutElementProps, StudioResizableOrientation }; From 7cd17d52aa3eb32b0c49cd9a7cacf586fa65d506 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 4 Jul 2024 08:44:53 +0200 Subject: [PATCH 34/44] Fix event type in mouse movement test --- .../hooks/useStudioResizableFunctions.test.ts | 10 ----- ...StudioResizableLayoutMouseMovement.test.ts | 43 +++++++++++++++++-- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts index a392cd86d8b..12763cc2ca9 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts @@ -47,13 +47,3 @@ function renderFunctionsHook( useStudioResizableLayoutFunctions(horizontal, elementRefs, children, setContainerSize), ); } - -function newFunction( - elementRefs, - children: any[], - setContainerSize: jest.Mock, -): { result: any } { - return renderHook(() => - useStudioResizableLayoutFunctions(horizontal, elementRefs, children, setContainerSize), - ); -} diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts index e7d5ed64754..b4650c86820 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts @@ -1,6 +1,7 @@ import { act, renderHook } from '@testing-library/react'; import { horizontal } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { useStudioResizableLayoutMouseMovement } from './useStudioResizableLayoutMouseMovement'; +import type React from 'react'; describe('useStudioResizableLayoutMouseMovement', () => { it('should return onMouseDown and isResizing', () => { @@ -11,18 +12,54 @@ describe('useStudioResizableLayoutMouseMovement', () => { expect(result.current).toHaveProperty('isResizing'); }); - it('should call onMousePosChange when onMouseDown is called', () => { + it('should call onMousePosChange when mouse is moved', () => { const onMousePosChange = jest.fn(); const { result } = renderHook(() => useStudioResizableLayoutMouseMovement(horizontal, onMousePosChange), ); - const event = new MouseEvent('mousedown'); act(() => { - result.current.onMouseDown(event); + result.current.onMouseDown(mockMouseEvent); }); const mouseMoveEvent = new MouseEvent('mousemove'); window.dispatchEvent(mouseMoveEvent); expect(onMousePosChange).toHaveBeenCalled(); }); }); + +const mockMouseEvent: React.MouseEvent = { + nativeEvent: new MouseEvent('mousedown'), + type: 'mousedown', + button: 0, + buttons: 1, + altKey: false, + clientX: 0, + clientY: 0, + ctrlKey: false, + metaKey: false, + movementX: 0, + movementY: 0, + pageX: 0, + pageY: 0, + screenX: 0, + screenY: 0, + shiftKey: false, + relatedTarget: null, + currentTarget: null, + target: null, + detail: 0, + view: null, + bubbles: false, + cancelable: false, + defaultPrevented: false, + eventPhase: 0, + isTrusted: false, + timeStamp: 0, + stopPropagation: jest.fn(), + preventDefault: jest.fn(), + isDefaultPrevented: jest.fn(), + isPropagationStopped: jest.fn(), + persist: jest.fn(), + getModifierState: jest.fn(), + // Add other properties as needed +}; From 308c8983c5ce9b1236e2b619f7807d0f99427b6b Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 4 Jul 2024 10:32:33 +0200 Subject: [PATCH 35/44] Update resizablelayout tests to cover more cases --- .../StudioResizableLayoutContainer.test.tsx | 25 +++++++++++++++++-- .../classes/StudioResizableLayoutElement.ts | 9 +------ .../hooks/useStudioResizableFunctions.test.ts | 10 ++++++++ ...StudioResizableLayoutMouseMovement.test.ts | 18 ++++++++++++- .../useStudioResizableLayoutMouseMovement.ts | 5 ---- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx index 9e9b013c539..b23bfd8f31e 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.test.tsx @@ -33,6 +33,15 @@ describe('StudioResizableLayoutContainer', () => { expect(screen.getAllByTestId('resizablelayoutelement')[0].style.flexGrow).toBe('1.75'); expect(screen.getAllByTestId('resizablelayoutelement')[1].style.flexGrow).toBe('0.25'); }); + + it('should not resize containers above maximum size', () => { + renderStudioResizableLayoutContainer(600); + const handle = screen.getByRole('separator'); + + dragHandle(handle, { clientX: 400 }, { clientX: 800 }); + expect(screen.getAllByTestId('resizablelayoutelement')[0].style.flexGrow).toBe('1.5'); + expect(screen.getAllByTestId('resizablelayoutelement')[1].style.flexGrow).toBe('0.5'); + }); }); const dragHandle = ( @@ -46,6 +55,8 @@ const dragHandle = ( }; const renderStudioResizableLayoutContainer = ( + maximumSize = 800, + collapsed = false, props: Partial = {}, ) => { Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { @@ -57,10 +68,20 @@ const renderStudioResizableLayoutContainer = ( orientation='horizontal' {...props} > - +
test1
- +
test1
, diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts index 45a111a6566..54caa2d91c6 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/classes/StudioResizableLayoutElement.ts @@ -4,14 +4,7 @@ export class StudioResizableLayoutArea { public HTMLElementRef: HTMLElement, public reactElement: React.ReactElement, public orientation: 'horizontal' | 'vertical', - ) { - if (HTMLElementRef === undefined) { - throw new Error('Element is undefined'); - } - if (reactElement === undefined) { - throw new Error('React element is undefined'); - } - } + ) {} public get size() { return this.orientation === 'vertical' diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts index 12763cc2ca9..b70f2b50968 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableFunctions.test.ts @@ -36,6 +36,16 @@ describe('useStudioResizableLayoutFunctions', () => { result.current.resizeDelta(0, 100); expect(setContainerSize).toHaveBeenCalled(); }); + + it('should not resize when either element is collapsed', () => { + const { result } = renderFunctionsHook( + elementRefs, + { ...children, 0: { ...children[0], props: { ...children[0].props, collapsed: true } } }, + setContainerSize, + ); + result.current.resizeTo(0, 100); + expect(setContainerSize).not.toHaveBeenCalled(); + }); }); function renderFunctionsHook( diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts index b4650c86820..97d0d575fcf 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts @@ -1,5 +1,8 @@ import { act, renderHook } from '@testing-library/react'; -import { horizontal } from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; +import { + horizontal, + vertical, +} from '../StudioResizableLayoutContainer/StudioResizableLayoutContainer'; import { useStudioResizableLayoutMouseMovement } from './useStudioResizableLayoutMouseMovement'; import type React from 'react'; @@ -25,6 +28,19 @@ describe('useStudioResizableLayoutMouseMovement', () => { window.dispatchEvent(mouseMoveEvent); expect(onMousePosChange).toHaveBeenCalled(); }); + + it('should not start resizing if mouse button is not 0/LMB', () => { + const onMousePosChange = jest.fn(); + const { result } = renderHook(() => + useStudioResizableLayoutMouseMovement(horizontal, onMousePosChange), + ); + act(() => { + result.current.onMouseDown({ ...mockMouseEvent, button: 1 }); + }); + const mouseMoveEvent = new MouseEvent('mousemove'); + window.dispatchEvent(mouseMoveEvent); + expect(onMousePosChange).not.toHaveBeenCalled(); + }); }); const mockMouseEvent: React.MouseEvent = { diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts index 6456a5f63a7..65c8aaf9fe8 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.ts @@ -41,11 +41,6 @@ export const useStudioResizableLayoutMouseMovement = ( startMousePosition.current = lastMousePosition.current; window.addEventListener('mousemove', mouseMove); window.addEventListener('mouseup', mouseUp); - - return () => { - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - }; }, [mouseMove, mouseUp, orientation], ); From 5967c714fc5ba6d347d9d8ad6d77de84bd19eaf7 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Thu, 4 Jul 2024 10:42:35 +0200 Subject: [PATCH 36/44] Add test case for horizontal and vertical layout configuration --- ...StudioResizableLayoutMouseMovement.test.ts | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts index 97d0d575fcf..52632f4542c 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/hooks/useStudioResizableLayoutMouseMovement.test.ts @@ -15,25 +15,29 @@ describe('useStudioResizableLayoutMouseMovement', () => { expect(result.current).toHaveProperty('isResizing'); }); - it('should call onMousePosChange when mouse is moved', () => { - const onMousePosChange = jest.fn(); - const { result } = renderHook(() => - useStudioResizableLayoutMouseMovement(horizontal, onMousePosChange), - ); + it.each([horizontal, vertical])( + 'should call onMousePosChange when mouse is moved in a %p layout', + (orientation) => { + const onMousePosChange = jest.fn(); + const { result } = renderHook(() => + useStudioResizableLayoutMouseMovement(orientation, onMousePosChange), + ); - act(() => { - result.current.onMouseDown(mockMouseEvent); - }); - const mouseMoveEvent = new MouseEvent('mousemove'); - window.dispatchEvent(mouseMoveEvent); - expect(onMousePosChange).toHaveBeenCalled(); - }); + act(() => { + result.current.onMouseDown(mockMouseEvent); + }); + const mouseMoveEvent = new MouseEvent('mousemove'); + window.dispatchEvent(mouseMoveEvent); + expect(onMousePosChange).toHaveBeenCalled(); + }, + ); it('should not start resizing if mouse button is not 0/LMB', () => { const onMousePosChange = jest.fn(); const { result } = renderHook(() => useStudioResizableLayoutMouseMovement(horizontal, onMousePosChange), ); + act(() => { result.current.onMouseDown({ ...mockMouseEvent, button: 1 }); }); From c3030fb9b195411fc7f50b84476c69becc6b1cc5 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:08:33 +0200 Subject: [PATCH 37/44] Remove unused css file --- .../components/Elements/LayoutSetsContainer.module.css | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css deleted file mode 100644 index 278bfbed404..00000000000 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css +++ /dev/null @@ -1,9 +0,0 @@ -/* .dropDownContainer { */ -/* margin: var(--fds-spacing-5); */ -/* margin-bottom: 5px; */ -/* } */ -/**/ -/* .layoutSetsDropDown { */ -/* width: 100%; */ -/* text-overflow: ellipsis; */ -/* } */ From ec74e2cba3a0c76ddd5011dcb26048d5a67d6b61 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:14:50 +0200 Subject: [PATCH 38/44] Revert "Remove unused css file" This reverts commit c3030fb9b195411fc7f50b84476c69becc6b1cc5. --- .../components/Elements/LayoutSetsContainer.module.css | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css new file mode 100644 index 00000000000..278bfbed404 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css @@ -0,0 +1,9 @@ +/* .dropDownContainer { */ +/* margin: var(--fds-spacing-5); */ +/* margin-bottom: 5px; */ +/* } */ +/**/ +/* .layoutSetsDropDown { */ +/* width: 100%; */ +/* text-overflow: ellipsis; */ +/* } */ From 388670d49dad9db4de9b8e3cb5c937d29dfb8ebc Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:24:56 +0200 Subject: [PATCH 39/44] Remove unused css rules and fix css toolbar issue --- .../Elements/LayoutSetsContainer.module.css | 12 +++--------- .../src/components/Elements/LayoutSetsContainer.tsx | 3 +-- .../src/containers/FormDesignerToolbar.module.css | 2 -- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css index 278bfbed404..975fbf000e5 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.module.css @@ -1,9 +1,3 @@ -/* .dropDownContainer { */ -/* margin: var(--fds-spacing-5); */ -/* margin-bottom: 5px; */ -/* } */ -/**/ -/* .layoutSetsDropDown { */ -/* width: 100%; */ -/* text-overflow: ellipsis; */ -/* } */ +.layoutSetsDropDown { + min-width: 400px; +} diff --git a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx index e92f746624d..2bbd803624f 100644 --- a/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/LayoutSetsContainer.tsx @@ -37,13 +37,12 @@ export function LayoutSetsContainer() { if (!layoutSetNames) return null; return ( -
+
onLayoutSetClick(event.target.value)} value={selectedFormLayoutSetName} className={classes.layoutSetsDropDown} - style={{ minWidth: 400 }} > {layoutSetNames.map((set: string) => { return ( diff --git a/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css index 04cfa13ce87..e1c0d8b41fc 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css +++ b/frontend/packages/ux-editor/src/containers/FormDesignerToolbar.module.css @@ -3,6 +3,4 @@ display: flex; padding: 8px; border-bottom: 1px solid #c9c9c9; - box-sizing: border-box; - height: var(--subtoolbar-height); } From ae4d2f0911d26715af0ff99f49972f2ae3a54bcd Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:25:22 +0200 Subject: [PATCH 40/44] Simplify valid child checker --- .../StudioResizableLayoutContainer.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx index 8734dc35ab9..b37803f4f00 100644 --- a/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx +++ b/frontend/libs/studio-components/src/components/StudioResizableLayout/StudioResizableLayoutContainer/StudioResizableLayoutContainer.tsx @@ -69,12 +69,7 @@ const getValidChildren = ( string | React.JSXElementConstructor >[], ) => { - return Children.map(children, (child) => { - if (!child) { - return; - } - return child; - }).filter((child) => !!child); + return children.filter((child) => !!child); }; StudioResizableLayoutContainer.displayName = 'StudioResizableLayout.Container'; From 5b6d773ad0ccc22d6350351872a587dd585a5ebe Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:42:11 +0200 Subject: [PATCH 41/44] Add tests for collapsing components and preview in ux editor --- .../src/components/Elements/Elements.tsx | 1 + .../src/components/Preview/Preview.tsx | 1 + .../src/containers/FormDesigner.test.tsx | 32 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx index 303146a70f6..78e36bd4e3a 100644 --- a/frontend/packages/ux-editor/src/components/Elements/Elements.tsx +++ b/frontend/packages/ux-editor/src/components/Elements/Elements.tsx @@ -72,6 +72,7 @@ export const Elements = ({ collapsed, onCollapseToggle }: ElementsProps): React. variant='secondary' className={classes.openElementsButton} onClick={onCollapseToggle} + title={t('left_menu.open_components')} > {t('left_menu.open_components')} diff --git a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx index 2517e913da9..48a97b4a58c 100644 --- a/frontend/packages/ux-editor/src/components/Preview/Preview.tsx +++ b/frontend/packages/ux-editor/src/components/Preview/Preview.tsx @@ -30,6 +30,7 @@ export const Preview = ({ collapsed, onCollapseToggle, hidePreview }: PreviewPro size='small' variant='secondary' className={classes.openPreviewButton} + title={t('ux_editor.open_preview')} onClick={onCollapseToggle} > {t('ux_editor.open_preview')} diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.test.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.test.tsx index f422ccbcfb3..4bb3ba30717 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.test.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.test.tsx @@ -19,12 +19,14 @@ import { FormItemContext } from './FormItemContext'; import { formItemContextProviderMock } from '../testing/formItemContextMocks'; import { appContextMock } from '../testing/appContextMock'; import { app, org } from '@studio/testing/testids'; +import userEvent from '@testing-library/user-event'; // Test data: const defaultTexts: ITextResources = { [DEFAULT_LANGUAGE]: [], }; const dataModelName = undefined; +const user = userEvent.setup(); const render = () => { const queryClient = createQueryClientMock(); @@ -128,4 +130,34 @@ describe('FormDesigner', () => { expect(appContextMock.refetchLayouts).toHaveBeenCalledTimes(1); expect(appContextMock.refetchLayouts).toHaveBeenCalledWith('test-layout-set'); }); + + it('should be able to collapse and uncollapse components', async () => { + await waitForData(); + render(); + + await waitFor(() => + expect(screen.queryByText(textMock('ux_editor.loading_form_layout'))).not.toBeInTheDocument(), + ); + + await user.click(screen.getByTitle(textMock('left_menu.close_components'))); + expect(screen.getByTitle(textMock('left_menu.open_components'))).toBeInTheDocument(); + + await user.click(screen.getByTitle(textMock('left_menu.open_components'))); + expect(screen.getByTitle(textMock('left_menu.close_components'))).toBeInTheDocument(); + }); + + it('should be able to collapse and uncollapse preview', async () => { + await waitForData(); + render(); + + await waitFor(() => + expect(screen.queryByText(textMock('ux_editor.loading_form_layout'))).not.toBeInTheDocument(), + ); + + await user.click(screen.getByTitle(textMock('ux_editor.open_preview'))); + expect(screen.getByTitle(textMock('ux_editor.close_preview'))).toBeInTheDocument(); + + await user.click(screen.getByTitle(textMock('ux_editor.close_preview'))); + expect(screen.getByTitle(textMock('ux_editor.open_preview'))).toBeInTheDocument(); + }); }); From a0a67929deb31b3fa1324f786e3353a331d27663 Mon Sep 17 00:00:00 2001 From: Jonas Dyrlie Date: Wed, 10 Jul 2024 11:47:42 +0200 Subject: [PATCH 42/44] Add org and user to `localStorageContext` for datamodel `resizableLayout` --- .../src/components/SchemaEditor/SchemaEditor.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx index 1ea4a9910ee..a83995b9836 100644 --- a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx +++ b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx @@ -10,9 +10,13 @@ import { useMoveProperty } from './hooks/useMoveProperty'; import { useAddReference } from './hooks/useAddReference'; import { NodePanel } from '../NodePanel'; import { StudioResizableLayout } from '@studio/components'; +import { useUserQuery } from 'app-development/hooks/queries'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; export const SchemaEditor = () => { const { schemaModel, selectedTypePointer, selectedNodePointer } = useSchemaEditorAppContext(); + const { org } = useStudioEnvironmentParams(); + const { data: user } = useUserQuery(); const moveProperty = useMoveProperty(); const addReference = useAddReference(); @@ -26,9 +30,12 @@ export const SchemaEditor = () => { onMove={moveProperty} rootId={ROOT_POINTER} itemId={selectedTypePointer ?? null} - key={selectedType?.pointer} + key={selectedType?.pointer} > - +