✨(frontend) add resizable left panel on desktop with persistence
mainlayout and leftpanel updated with resizable panel saved in localstorage Signed-off-by: Cyril <c.gromoff@gmail.com> ✨(frontend) show full nested doc names with horizontal scroll support horizontal overflow enabled and opacity used for sticky actions visibility Signed-off-by: Cyril <c.gromoff@gmail.com> ✨(frontend) show full nested doc names with horizontal scroll support horizontal overflow enabled and opacity used for sticky actions visibility Signed-off-by: Cyril <c.gromoff@gmail.com> ✨(frontend) add resizable-panels lib also used in our shared ui kit needed for adaptable ui consistent with our shared ui kit components Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -75,3 +75,6 @@ db.sqlite3
|
|||||||
.vscode/
|
.vscode/
|
||||||
*.iml
|
*.iml
|
||||||
.devcontainer
|
.devcontainer
|
||||||
|
|
||||||
|
# Cursor rules
|
||||||
|
.cursorrules
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ and this project adheres to
|
|||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- 🐛(frontend) fix duplicate document entries in grid #1479
|
- 🐛(frontend) fix duplicate document entries in grid #1479
|
||||||
|
- 🐛(frontend) show full nested doc names with ajustable bar #1456
|
||||||
|
|
||||||
## [3.8.2] - 2025-10-17
|
## [3.8.2] - 2025-10-17
|
||||||
|
|
||||||
@@ -30,7 +31,6 @@ and this project adheres to
|
|||||||
|
|
||||||
- 🔥(backend) remove treebeard form for the document admin #1470
|
- 🔥(backend) remove treebeard form for the document admin #1470
|
||||||
|
|
||||||
|
|
||||||
## [3.8.0] - 2025-10-14
|
## [3.8.0] - 2025-10-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -38,6 +38,10 @@ and this project adheres to
|
|||||||
- ✨(frontend) add pdf block to the editor #1293
|
- ✨(frontend) add pdf block to the editor #1293
|
||||||
- ✨List and restore deleted docs #1450
|
- ✨List and restore deleted docs #1450
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛(frontend) show full nested doc names with ajustable bar #1456
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- ♻️(frontend) Refactor Auth component for improved redirection logic #1461
|
- ♻️(frontend) Refactor Auth component for improved redirection logic #1461
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import { createDoc } from './utils-common';
|
||||||
|
|
||||||
test.describe('Left panel desktop', () => {
|
test.describe('Left panel desktop', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -11,6 +13,53 @@ test.describe('Left panel desktop', () => {
|
|||||||
await expect(page.getByTestId('home-button')).toBeVisible();
|
await expect(page.getByTestId('home-button')).toBeVisible();
|
||||||
await expect(page.getByTestId('new-doc-button')).toBeVisible();
|
await expect(page.getByTestId('new-doc-button')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('checks resize handle is present and functional on document page', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// On home page, resize handle should NOT be present
|
||||||
|
let resizeHandle = page.locator('[data-panel-resize-handle-id]');
|
||||||
|
await expect(resizeHandle).toBeHidden();
|
||||||
|
|
||||||
|
// Create and navigate to a document
|
||||||
|
await createDoc(page, 'doc-resize-test', browserName, 1);
|
||||||
|
|
||||||
|
// Now resize handle should be visible on document page
|
||||||
|
resizeHandle = page.locator('[data-panel-resize-handle-id]').first();
|
||||||
|
await expect(resizeHandle).toBeVisible();
|
||||||
|
|
||||||
|
const leftPanel = page.getByTestId('left-panel-desktop');
|
||||||
|
await expect(leftPanel).toBeVisible();
|
||||||
|
|
||||||
|
// Get initial panel width
|
||||||
|
const initialBox = await leftPanel.boundingBox();
|
||||||
|
expect(initialBox).not.toBeNull();
|
||||||
|
|
||||||
|
// Get handle position
|
||||||
|
const handleBox = await resizeHandle.boundingBox();
|
||||||
|
expect(handleBox).not.toBeNull();
|
||||||
|
|
||||||
|
// Test resize by dragging the handle
|
||||||
|
await page.mouse.move(
|
||||||
|
handleBox!.x + handleBox!.width / 2,
|
||||||
|
handleBox!.y + handleBox!.height / 2,
|
||||||
|
);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(
|
||||||
|
handleBox!.x + 100,
|
||||||
|
handleBox!.y + handleBox!.height / 2,
|
||||||
|
);
|
||||||
|
await page.mouse.up();
|
||||||
|
|
||||||
|
// Wait for resize to complete
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
// Verify the panel has been resized
|
||||||
|
const newBox = await leftPanel.boundingBox();
|
||||||
|
expect(newBox).not.toBeNull();
|
||||||
|
expect(newBox!.width).toBeGreaterThan(initialBox!.width);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Left panel mobile', () => {
|
test.describe('Left panel mobile', () => {
|
||||||
@@ -47,4 +96,12 @@ test.describe('Left panel mobile', () => {
|
|||||||
await expect(languageButton).toBeInViewport();
|
await expect(languageButton).toBeInViewport();
|
||||||
await expect(logoutButton).toBeInViewport();
|
await expect(logoutButton).toBeInViewport();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('checks resize handle is not present on mobile', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// Verify the resize handle is NOT present on mobile
|
||||||
|
const resizeHandle = page.locator('[data-panel-resize-handle-id]');
|
||||||
|
await expect(resizeHandle).toBeHidden();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"react-dom": "*",
|
"react-dom": "*",
|
||||||
"react-i18next": "15.7.3",
|
"react-i18next": "15.7.3",
|
||||||
"react-intersection-observer": "9.16.0",
|
"react-intersection-observer": "9.16.0",
|
||||||
|
"react-resizable-panels": "3.0.6",
|
||||||
"react-select": "5.10.2",
|
"react-select": "5.10.2",
|
||||||
"styled-components": "6.1.19",
|
"styled-components": "6.1.19",
|
||||||
"use-debounce": "10.0.6",
|
"use-debounce": "10.0.6",
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
aria-label={`${t('Open document {{title}}', { title: docTitle })}`}
|
aria-label={`${t('Open document {{title}}', { title: docTitle })}`}
|
||||||
$css={css`
|
$css={css`
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
min-width: 0;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Box $width="16px" $height="16px">
|
<Box $width="16px" $height="16px">
|
||||||
@@ -180,8 +181,10 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Text $css={ItemTextCss} $size="sm" $variation="1000">
|
<Text $css={ItemTextCss} $size="sm" $variation="1000">
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
|||||||
/* Remove outline from TreeViewItem wrapper elements */
|
/* Remove outline from TreeViewItem wrapper elements */
|
||||||
.c__tree-view--row {
|
.c__tree-view--row {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
@@ -241,7 +240,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus-within {
|
&:focus-visible {
|
||||||
.doc-tree-root-item-actions {
|
.doc-tree-root-item-actions {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,10 @@ export const LeftPanel = () => {
|
|||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<Box
|
<Box
|
||||||
data-testid="left-panel-desktop"
|
data-testid="left-panel-desktop"
|
||||||
$css={`
|
$css={css`
|
||||||
height: calc(100vh - ${HEADER_HEIGHT}px);
|
height: calc(100vh - ${HEADER_HEIGHT}px);
|
||||||
width: 300px;
|
width: 100%;
|
||||||
min-width: 300px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-right: 1px solid ${colorsTokens['greyscale-200']};
|
|
||||||
background-color: ${colorsTokens['greyscale-000']};
|
background-color: ${colorsTokens['greyscale-000']};
|
||||||
`}
|
`}
|
||||||
className="--docs--left-panel-desktop"
|
className="--docs--left-panel-desktop"
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
ImperativePanelHandle,
|
||||||
|
Panel,
|
||||||
|
PanelGroup,
|
||||||
|
PanelResizeHandle,
|
||||||
|
} from 'react-resizable-panels';
|
||||||
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
|
interface PanelStyleProps {
|
||||||
|
$isResizing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PanelStyle = createGlobalStyle<PanelStyleProps>`
|
||||||
|
${({ $isResizing }) => $isResizing && `body * { transition: none !important; }`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Convert a target pixel width to a percentage of the current viewport width.
|
||||||
|
// react-resizable-panels expects sizes in %, not px.
|
||||||
|
const calculateDefaultSize = (targetWidth: number) => {
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
return (targetWidth / windowWidth) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResizableLeftPanelProps = {
|
||||||
|
leftPanel: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
minPanelSizePx?: number;
|
||||||
|
maxPanelSizePx?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResizableLeftPanel = ({
|
||||||
|
leftPanel,
|
||||||
|
children,
|
||||||
|
minPanelSizePx = 300,
|
||||||
|
maxPanelSizePx = 450,
|
||||||
|
}: ResizableLeftPanelProps) => {
|
||||||
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
const ref = useRef<ImperativePanelHandle>(null);
|
||||||
|
const resizeTimeoutRef = useRef<number | undefined>(undefined);
|
||||||
|
|
||||||
|
const [minPanelSize, setMinPanelSize] = useState(0);
|
||||||
|
const [maxPanelSize, setMaxPanelSize] = useState(0);
|
||||||
|
|
||||||
|
// Single resize listener that handles both panel size updates and transition disabling
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
// Update panel sizes (px -> %)
|
||||||
|
const min = Math.round(calculateDefaultSize(minPanelSizePx));
|
||||||
|
const max = Math.round(
|
||||||
|
Math.min(calculateDefaultSize(maxPanelSizePx), 40),
|
||||||
|
);
|
||||||
|
setMinPanelSize(min);
|
||||||
|
setMaxPanelSize(max);
|
||||||
|
|
||||||
|
// Temporarily disable transitions to avoid flicker
|
||||||
|
setIsResizing(true);
|
||||||
|
if (resizeTimeoutRef.current) {
|
||||||
|
clearTimeout(resizeTimeoutRef.current);
|
||||||
|
}
|
||||||
|
resizeTimeoutRef.current = window.setTimeout(() => {
|
||||||
|
setIsResizing(false);
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeTimeoutRef.current) {
|
||||||
|
clearTimeout(resizeTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [minPanelSizePx, maxPanelSizePx]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PanelStyle $isResizing={isResizing} />
|
||||||
|
<PanelGroup
|
||||||
|
autoSaveId="docs-left-panel-persistence"
|
||||||
|
direction="horizontal"
|
||||||
|
>
|
||||||
|
<Panel
|
||||||
|
ref={ref}
|
||||||
|
order={0}
|
||||||
|
defaultSize={minPanelSize}
|
||||||
|
minSize={minPanelSize}
|
||||||
|
maxSize={maxPanelSize}
|
||||||
|
>
|
||||||
|
{leftPanel}
|
||||||
|
</Panel>
|
||||||
|
<PanelResizeHandle
|
||||||
|
style={{
|
||||||
|
borderRightWidth: '1px',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
borderRightColor: colorsTokens['greyscale-200'],
|
||||||
|
width: '1px',
|
||||||
|
cursor: 'col-resize',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Panel order={1}>{children}</Panel>
|
||||||
|
</PanelGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from './LeftPanel';
|
export * from './LeftPanel';
|
||||||
|
export * from './ResizableLeftPanel';
|
||||||
|
|||||||
@@ -6,23 +6,21 @@ import { Box } from '@/components';
|
|||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Header } from '@/features/header';
|
import { Header } from '@/features/header';
|
||||||
import { HEADER_HEIGHT } from '@/features/header/conf';
|
import { HEADER_HEIGHT } from '@/features/header/conf';
|
||||||
import { LeftPanel } from '@/features/left-panel';
|
import { LeftPanel, ResizableLeftPanel } from '@/features/left-panel';
|
||||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
|
|
||||||
|
import { MAIN_LAYOUT_ID } from './conf';
|
||||||
|
|
||||||
type MainLayoutProps = {
|
type MainLayoutProps = {
|
||||||
backgroundColor?: 'white' | 'grey';
|
backgroundColor?: 'white' | 'grey';
|
||||||
|
enableResizablePanel?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MainLayout({
|
export function MainLayout({
|
||||||
children,
|
children,
|
||||||
backgroundColor = 'white',
|
backgroundColor = 'white',
|
||||||
|
enableResizablePanel = false,
|
||||||
}: PropsWithChildren<MainLayoutProps>) {
|
}: PropsWithChildren<MainLayoutProps>) {
|
||||||
const { isDesktop } = useResponsiveStore();
|
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
|
||||||
const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="--docs--main-layout">
|
<Box className="--docs--main-layout">
|
||||||
<Header />
|
<Header />
|
||||||
@@ -30,33 +28,90 @@ export function MainLayout({
|
|||||||
$direction="row"
|
$direction="row"
|
||||||
$margin={{ top: `${HEADER_HEIGHT}px` }}
|
$margin={{ top: `${HEADER_HEIGHT}px` }}
|
||||||
$width="100%"
|
$width="100%"
|
||||||
|
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
||||||
>
|
>
|
||||||
<LeftPanel />
|
<MainLayoutContent
|
||||||
<Box
|
backgroundColor={backgroundColor}
|
||||||
as="main"
|
enableResizablePanel={enableResizablePanel}
|
||||||
role="main"
|
|
||||||
aria-label={t('Main content')}
|
|
||||||
id={MAIN_LAYOUT_ID}
|
|
||||||
$align="center"
|
|
||||||
$flex={1}
|
|
||||||
$width="100%"
|
|
||||||
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
|
||||||
$padding={{
|
|
||||||
all: isDesktop ? 'base' : '0',
|
|
||||||
}}
|
|
||||||
$background={
|
|
||||||
currentBackgroundColor === 'white'
|
|
||||||
? colorsTokens['greyscale-000']
|
|
||||||
: colorsTokens['greyscale-050']
|
|
||||||
}
|
|
||||||
$css={css`
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: clip;
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</MainLayoutContent>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MainLayoutContentProps {
|
||||||
|
backgroundColor: 'white' | 'grey';
|
||||||
|
enableResizablePanel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MainLayoutContent({
|
||||||
|
children,
|
||||||
|
backgroundColor,
|
||||||
|
enableResizablePanel = false,
|
||||||
|
}: PropsWithChildren<MainLayoutContentProps>) {
|
||||||
|
const { isDesktop } = useResponsiveStore();
|
||||||
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor;
|
||||||
|
|
||||||
|
const mainContent = (
|
||||||
|
<Box
|
||||||
|
as="main"
|
||||||
|
role="main"
|
||||||
|
aria-label={t('Main content')}
|
||||||
|
id={MAIN_LAYOUT_ID}
|
||||||
|
$align="center"
|
||||||
|
$flex={1}
|
||||||
|
$width="100%"
|
||||||
|
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
||||||
|
$padding={{
|
||||||
|
all: isDesktop ? 'base' : '0',
|
||||||
|
}}
|
||||||
|
$background={
|
||||||
|
currentBackgroundColor === 'white'
|
||||||
|
? colorsTokens['greyscale-000']
|
||||||
|
: colorsTokens['greyscale-050']
|
||||||
|
}
|
||||||
|
$css={css`
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: clip;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDesktop) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LeftPanel />
|
||||||
|
{mainContent}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableResizablePanel) {
|
||||||
|
return (
|
||||||
|
<ResizableLeftPanel leftPanel={<LeftPanel />}>
|
||||||
|
{mainContent}
|
||||||
|
</ResizableLeftPanel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
$css={css`
|
||||||
|
width: 300px;
|
||||||
|
min-width: 300px;
|
||||||
|
border-right: 1px solid ${colorsTokens['greyscale-200']};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<LeftPanel />
|
||||||
|
</Box>
|
||||||
|
{mainContent}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function DocLayout() {
|
|||||||
return subPageToTree(doc.results);
|
return subPageToTree(doc.results);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MainLayout>
|
<MainLayout enableResizablePanel={true}>
|
||||||
<DocPage id={id} />
|
<DocPage id={id} />
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
</TreeProvider>
|
</TreeProvider>
|
||||||
|
|||||||
@@ -13793,6 +13793,11 @@ react-resizable-panels@2.1.7:
|
|||||||
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz#afd29d8a3d708786a9f95183a38803c89f13c2e7"
|
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz#afd29d8a3d708786a9f95183a38803c89f13c2e7"
|
||||||
integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==
|
integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==
|
||||||
|
|
||||||
|
react-resizable-panels@3.0.6:
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-3.0.6.tgz#8183132ea13a09821e9c93962ed49f240cdcfd3f"
|
||||||
|
integrity sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==
|
||||||
|
|
||||||
react-select@5.10.2:
|
react-select@5.10.2:
|
||||||
version "5.10.2"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127"
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127"
|
||||||
|
|||||||
Reference in New Issue
Block a user