setOffsetHeight(element?.offsetHeight ?? 0)}
+ ref={element => {
+ setElement(element);
+ setOffsetHeight(element?.offsetHeight ?? 0);
+ }}
data-testid={testId}
>
;
+
+export default {
+ title: 'displays/Banner',
+ component: Banner,
+ args: {
+ children:
+ 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quis, hic voluptatibus, aliquam totam distinctio doloribus veniam iusto eos, nemo quas temporibus sapiente? Nemo eum ipsum fugit exercitationem a reprehenderit assumenda?',
+ withClose: DEFAULT_WITH_CLOSE,
+ withIcon: DEFAULT_WITH_ICON,
+ variant: DEFAULT_VARIANT,
+ },
+ argTypes: {
+ ...addInlineRadio('variant', BANNER_VARIANTS),
+ },
+} as Meta;
+
+export const All: Story = {
+ render: ({ children, ...args }) => (
+
+ {BANNER_VARIANTS.map(variant => (
+
+ {children}
+
+ ))}
+
+ ),
+
+ parameters: {
+ controls: { exclude: ['variant'] },
+ },
+};
diff --git a/src/components/displays/Banner/__tests__/Banner.test.tsx b/src/components/displays/Banner/__tests__/Banner.test.tsx
new file mode 100644
index 0000000..a7b8904
--- /dev/null
+++ b/src/components/displays/Banner/__tests__/Banner.test.tsx
@@ -0,0 +1,88 @@
+import {
+ expectNotToBeVisibleInTheDocument,
+ expectToBeVisibleInTheDocument,
+} from '@gatewatcher/bistoury/utils-tests';
+import type { TestId } from '@gatewatcher/bistoury/utils-types';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import type { BannerProps } from '..';
+import Banner from '..';
+
+describe('Banner', () => {
+ const TEST_ID: TestId = 'banner';
+ const content = 'content';
+
+ const renderComponent = ({
+ children = content,
+ ...props
+ }: Partial = {}) =>
+ render(
+
+ {children}
+ ,
+ );
+
+ const user = userEvent.setup();
+
+ it('should render', async () => {
+ renderComponent();
+ await expectToBeVisibleInTheDocument(TEST_ID);
+ });
+
+ it('should have content', async () => {
+ renderComponent();
+ await expectToBeVisibleInTheDocument(content, screen.findByText);
+ });
+
+ it('should have icons', async () => {
+ renderComponent();
+ await waitFor(async () => {
+ const icons = await screen.findAllByRole('img');
+ expect(icons.length).toBe(2);
+ });
+ });
+
+ it('should have no icons', async () => {
+ renderComponent({ withClose: false, withIcon: false });
+ const icons = screen.queryAllByRole('img');
+ expect(icons.length).toBe(0);
+ });
+
+ it('should have only info icon', async () => {
+ renderComponent({ withClose: false });
+ await expectToBeVisibleInTheDocument('info');
+ await expectNotToBeVisibleInTheDocument('close');
+ });
+
+ it('should have only close icon', async () => {
+ renderComponent({ withIcon: false });
+ await expectToBeVisibleInTheDocument('close');
+ await expectNotToBeVisibleInTheDocument('info');
+ });
+
+ it('should close', async () => {
+ const onClose = vi.fn();
+ renderComponent({ onClose });
+ const close = await screen.findByTestId('close');
+ await user.click(close);
+
+ expect(onClose).toHaveBeenCalledTimes(1);
+
+ await waitFor(async () => {
+ await expectNotToBeVisibleInTheDocument(TEST_ID);
+ });
+ });
+
+ it('should render paragraph', async () => {
+ renderComponent({ children: 'a string' });
+ await expectToBeVisibleInTheDocument('paragraph');
+ await expectToBeVisibleInTheDocument('a string', screen.findByText);
+ });
+
+ it('should render custom children', async () => {
+ renderComponent({ children: children
});
+ await expectToBeVisibleInTheDocument('children');
+ await expectToBeVisibleInTheDocument('children', screen.findByText);
+ });
+});
diff --git a/src/components/displays/Banner/constants.tsx b/src/components/displays/Banner/constants.tsx
new file mode 100644
index 0000000..1d66bc7
--- /dev/null
+++ b/src/components/displays/Banner/constants.tsx
@@ -0,0 +1,18 @@
+import { MESSAGE_TYPES } from '@/constants';
+import type { IconName } from '@/skin/displays';
+
+import type { BannerVariant } from './types';
+
+export const BANNER_VARIANTS = MESSAGE_TYPES;
+
+export const DEFAULT_VARIANT: BannerVariant = 'info';
+export const DEFAULT_WITH_ICON = true;
+export const DEFAULT_WITH_CLOSE = true;
+
+export const BANNER_ICONS: Record = {
+ info: 'CircleInfo',
+ danger: 'CircleWarning',
+ success: 'CircleCheck',
+ warning: 'CircleWarning',
+ error: 'CircleWarning',
+};
diff --git a/src/components/displays/Banner/index.tsx b/src/components/displays/Banner/index.tsx
new file mode 100644
index 0000000..319cafb
--- /dev/null
+++ b/src/components/displays/Banner/index.tsx
@@ -0,0 +1,98 @@
+import { isString } from '@gatewatcher/bistoury/utils-lang';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+import { useState } from 'react';
+import { animated, useTransition } from 'react-spring';
+
+import { ANIMATION_SHARED_CONFIG } from '@/constants';
+import { InternalButtonClose } from '@/skin/actions/buttons/ButtonClose';
+import { Icon } from '@/skin/displays';
+import { Stack } from '@/skin/layout';
+import { useThemeContext } from '@/skin/navigation/Theme';
+import { InternalParagraph } from '@/skin/typography/Paragraph';
+import { getColor } from '@/utils';
+
+import {
+ BANNER_ICONS,
+ DEFAULT_VARIANT,
+ DEFAULT_WITH_CLOSE,
+ DEFAULT_WITH_ICON,
+} from './constants';
+import type { BannerVariant } from './types';
+
+import styles from './styles.module.scss';
+
+export type BannerProps = DataTestId & {
+ children: ReactNode;
+ onClose?: () => void;
+ withClose?: boolean;
+ withIcon?: boolean;
+ variant?: BannerVariant;
+};
+
+const Banner = ({
+ children,
+ 'data-testid': testId = 'banner',
+ onClose,
+ variant = DEFAULT_VARIANT,
+ withClose = DEFAULT_WITH_CLOSE,
+ withIcon = DEFAULT_WITH_ICON,
+}: BannerProps) => {
+ const { theme } = useThemeContext();
+ const [isOpened, setIsOpened] = useState(true);
+ const transition = useTransition(isOpened, {
+ from: { opacity: 1 },
+ enter: { opacity: 1 },
+ leave: { opacity: 0 },
+ config: ANIMATION_SHARED_CONFIG,
+ });
+
+ const handleClose = () => {
+ setIsOpened(!isOpened);
+ onClose?.();
+ };
+
+ return transition(
+ (transitionStyles, item) =>
+ item && (
+
+ {withIcon && (
+
+ )}
+
+ {isString(children) ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
+
+ {withClose && (
+
+ )}
+
+ ),
+ );
+};
+
+export default Banner;
diff --git a/src/components/displays/Banner/styles.module.scss b/src/components/displays/Banner/styles.module.scss
new file mode 100644
index 0000000..dd3d471
--- /dev/null
+++ b/src/components/displays/Banner/styles.module.scss
@@ -0,0 +1,21 @@
+@import '@/styles/mixins';
+
+.Banner {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ padding: var(--spacing-6);
+}
+
+.paragraph {
+ width: 100%;
+ margin: 0;
+}
+
+.icon {
+ margin-right: var(--spacing-6);
+}
+
+.close {
+ margin-left: var(--spacing-6);
+}
diff --git a/src/components/displays/Banner/types.tsx b/src/components/displays/Banner/types.tsx
new file mode 100644
index 0000000..080f0cf
--- /dev/null
+++ b/src/components/displays/Banner/types.tsx
@@ -0,0 +1,3 @@
+import type { BANNER_VARIANTS } from './constants';
+
+export type BannerVariant = typeof BANNER_VARIANTS[number];
diff --git a/src/components/displays/Carousel/index.tsx b/src/components/displays/Carousel/index.tsx
index bff795a..3cf6719 100644
--- a/src/components/displays/Carousel/index.tsx
+++ b/src/components/displays/Carousel/index.tsx
@@ -7,8 +7,8 @@ import {
useState,
} from 'react';
+import { useOnResizeElement } from '@/hooks';
import { ButtonIcon } from '@/skin/actions';
-import { useOnResizeElement } from '@/skin/displays/panels/DrawerV2/PanelLayout/hooks';
import { Stack } from '@/skin/layout';
import type { Gap } from '@/skin/layout/Grid/types';
diff --git a/src/components/displays/Code/styles.module.scss b/src/components/displays/Code/styles.module.scss
index 4e5aee2..be5bdf8 100644
--- a/src/components/displays/Code/styles.module.scss
+++ b/src/components/displays/Code/styles.module.scss
@@ -3,8 +3,9 @@
.Code {
display: inline-block;
padding: var(--spacing-1) var(--spacing-2) var(--spacing-2);
- border-radius: var(--border-radius-small);
- background-color: var(--color-neutral-100);
+ border: solid 1px var(--color-neutral-100);
+ border-radius: var(--border-radius-regular);
+ background-color: var(--color-neutral-50);
&.block {
display: block;
@@ -13,6 +14,7 @@
}
@include on-dark-theme {
+ border: solid 1px var(--color-neutral-600);
background-color: var(--color-neutral-700);
}
}
diff --git a/src/components/displays/CodeBlock/CodeBlock.stories.tsx b/src/components/displays/CodeBlock/CodeBlock.stories.tsx
new file mode 100644
index 0000000..434a27b
--- /dev/null
+++ b/src/components/displays/CodeBlock/CodeBlock.stories.tsx
@@ -0,0 +1,38 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import CodeBlock from '.';
+
+type Story = StoryObj;
+
+export default {
+ title: 'displays/CodeBlock',
+ component: CodeBlock,
+} as Meta;
+
+const typescript = `let message: string = "Hello, World!";
+console.log(message);`;
+
+const yaml = `name: John Doe
+age: 30
+isAdmin: false`;
+
+export const Default: Story = {
+ args: {
+ code: typescript,
+ language: 'Typescript',
+ },
+};
+
+export const Yaml: Story = {
+ args: {
+ code: yaml,
+ language: 'YAML',
+ },
+};
+
+export const Typescript: Story = {
+ args: {
+ code: typescript,
+ language: 'Typescript',
+ },
+};
diff --git a/src/components/displays/CodeBlock/Prism.ts b/src/components/displays/CodeBlock/Prism.ts
new file mode 100644
index 0000000..87c7839
--- /dev/null
+++ b/src/components/displays/CodeBlock/Prism.ts
@@ -0,0 +1,79 @@
+import Prism from 'prismjs';
+import 'prismjs/components/prism-apacheconf';
+import 'prismjs/components/prism-applescript';
+import 'prismjs/components/prism-asm6502';
+import 'prismjs/components/prism-awk';
+import 'prismjs/components/prism-bash';
+import 'prismjs/components/prism-basic';
+import 'prismjs/components/prism-batch';
+import 'prismjs/components/prism-brainfuck';
+import 'prismjs/components/prism-c';
+import 'prismjs/components/prism-cmake';
+import 'prismjs/components/prism-cobol';
+import 'prismjs/components/prism-cpp';
+import 'prismjs/components/prism-csharp';
+import 'prismjs/components/prism-cshtml';
+import 'prismjs/components/prism-csp';
+import 'prismjs/components/prism-css';
+import 'prismjs/components/prism-css-extras';
+import 'prismjs/components/prism-csv';
+import 'prismjs/components/prism-cypher';
+import 'prismjs/components/prism-diff';
+import 'prismjs/components/prism-dns-zone-file';
+import 'prismjs/components/prism-docker';
+import 'prismjs/components/prism-excel-formula';
+import 'prismjs/components/prism-firestore-security-rules';
+import 'prismjs/components/prism-gherkin';
+import 'prismjs/components/prism-git';
+import 'prismjs/components/prism-glsl';
+import 'prismjs/components/prism-go';
+import 'prismjs/components/prism-go-module';
+import 'prismjs/components/prism-gradle';
+import 'prismjs/components/prism-graphql';
+import 'prismjs/components/prism-http';
+import 'prismjs/components/prism-io';
+import 'prismjs/components/prism-java';
+import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-javastacktrace';
+import 'prismjs/components/prism-jq';
+import 'prismjs/components/prism-js-extras';
+import 'prismjs/components/prism-js-templates';
+import 'prismjs/components/prism-json';
+import 'prismjs/components/prism-jsstacktrace';
+import 'prismjs/components/prism-jsx';
+import 'prismjs/components/prism-keepalived';
+import 'prismjs/components/prism-kotlin';
+import 'prismjs/components/prism-latex';
+import 'prismjs/components/prism-less';
+import 'prismjs/components/prism-log';
+import 'prismjs/components/prism-lua';
+import 'prismjs/components/prism-makefile';
+import 'prismjs/components/prism-markup';
+import 'prismjs/components/prism-markup-templating';
+import 'prismjs/components/prism-mermaid';
+import 'prismjs/components/prism-mongodb';
+import 'prismjs/components/prism-nginx';
+import 'prismjs/components/prism-objectivec';
+import 'prismjs/components/prism-perl';
+import 'prismjs/components/prism-php';
+import 'prismjs/components/prism-php-extras';
+import 'prismjs/components/prism-python';
+import 'prismjs/components/prism-r';
+import 'prismjs/components/prism-regex';
+import 'prismjs/components/prism-ruby';
+import 'prismjs/components/prism-rust';
+import 'prismjs/components/prism-sass';
+import 'prismjs/components/prism-scala';
+import 'prismjs/components/prism-scss';
+import 'prismjs/components/prism-shell-session';
+import 'prismjs/components/prism-sql';
+import 'prismjs/components/prism-swift';
+import 'prismjs/components/prism-tsx';
+import 'prismjs/components/prism-typescript';
+import 'prismjs/components/prism-uri';
+import 'prismjs/components/prism-visual-basic';
+import 'prismjs/components/prism-wasm';
+import 'prismjs/components/prism-yaml';
+import 'prismjs/components/prism-zig';
+
+export { Prism };
diff --git a/src/components/displays/CodeBlock/index.tsx b/src/components/displays/CodeBlock/index.tsx
new file mode 100644
index 0000000..bbf5286
--- /dev/null
+++ b/src/components/displays/CodeBlock/index.tsx
@@ -0,0 +1,54 @@
+import { Highlight, themes } from 'prism-react-renderer';
+
+import { CopyToClipboard } from '@/skin/actions';
+import { Stack } from '@/skin/layout';
+import { useThemeContext } from '@/skin/navigation';
+import { Text } from '@/skin/typography';
+
+import { Prism } from './Prism';
+
+import styles from './styles.module.scss';
+
+export type CodeBlockProps = {
+ code: string;
+ language: string;
+};
+
+const CodeBlock = ({ code, language }: CodeBlockProps) => {
+ const { theme } = useThemeContext();
+ return (
+
+ {({ style, tokens, getTokenProps, getLineProps }) => {
+ return (
+
+
+ {language}
+
+
+
+ {tokens.map((line, i) => (
+
+ {line.map((token, key) => (
+
+ ))}
+
+ ))}
+
+
+ );
+ }}
+
+ );
+};
+
+export default CodeBlock;
diff --git a/src/components/displays/CodeBlock/styles.module.scss b/src/components/displays/CodeBlock/styles.module.scss
new file mode 100644
index 0000000..fb06eb8
--- /dev/null
+++ b/src/components/displays/CodeBlock/styles.module.scss
@@ -0,0 +1,36 @@
+@import '@/styles/mixins';
+
+.InlineCode {
+ padding: var(--spacing-1) var(--spacing-2);
+ border-radius: var(--border-radius-regular);
+ background-color: var(--color-neutral-400-a25);
+ font-size: 78%;
+ overflow-x: auto;
+}
+
+.CodeBlock {
+ display: flex;
+ overflow: hidden;
+ flex-direction: column;
+ border: 1px solid var(--color-neutral-200);
+ border-radius: var(--border-radius-large);
+ background-color: var(--body-background-color) !important;
+
+ &Inner {
+ padding: var(--spacing-5);
+ overflow-x: auto;
+ }
+
+ @include on-dark-theme {
+ border: 1px solid var(--color-neutral-600);
+ }
+
+ &Head {
+ padding: var(--spacing-5) var(--spacing-6);
+ background-color: var(--color-neutral-50);
+
+ @include on-dark-theme {
+ background-color: var(--color-neutral-700);
+ }
+ }
+}
diff --git a/src/components/displays/Drawer/__tests__/Drawer.test.tsx b/src/components/displays/Drawer/__tests__/Drawer.test.tsx
index fe5f9e7..e409440 100644
--- a/src/components/displays/Drawer/__tests__/Drawer.test.tsx
+++ b/src/components/displays/Drawer/__tests__/Drawer.test.tsx
@@ -12,7 +12,8 @@ import { renderWithRouter } from '@/tests';
import type { DrawerProps } from '..';
import Drawer from '..';
-import { Panels, drawerPersistence } from '../..';
+import { drawerPersistence } from '../..';
+import Panels from '../../Panels';
import { useDrawer } from '../hooks/useDrawer';
import DrawerProvider from '../provider';
diff --git a/src/components/displays/Drawer/__tests__/Test.tsx b/src/components/displays/Drawer/__tests__/Test.tsx
index 130560a..e4350f4 100644
--- a/src/components/displays/Drawer/__tests__/Test.tsx
+++ b/src/components/displays/Drawer/__tests__/Test.tsx
@@ -1,8 +1,8 @@
+import { useDrawerPersistence } from '@/skin/displays/drawerPanels/DrawerV2';
import { Layout } from '@/skin/layout';
import Drawer from '..';
import { Card, drawerPersistence } from '../..';
-import { useDrawerPersistence } from '../../panels/DrawerV2';
import { useDrawer } from '../hooks/useDrawer';
export type DrawerTestProps = {
diff --git a/src/components/displays/Drawer/compounds/Content.tsx b/src/components/displays/Drawer/compounds/Content.tsx
index df6c785..f85bf2b 100644
--- a/src/components/displays/Drawer/compounds/Content.tsx
+++ b/src/components/displays/Drawer/compounds/Content.tsx
@@ -1,28 +1,28 @@
import { classNames } from '@gatewatcher/bistoury/utils-dom';
import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
-import type { ReactNode } from 'react';
-import { forwardRef } from 'react';
+import type { ReactNode, Ref } from 'react';
import styles from '../styles.module.scss';
export type DrawerContentProps = DataTestId & {
children: ReactNode;
fitContent?: boolean;
+ ref?: Ref;
};
-const Content = forwardRef(
- (
- { children, fitContent = false, 'data-testid': testId = 'drawer-content' },
- ref,
- ) => (
-
- {children}
-
- ),
+const Content = ({
+ children,
+ fitContent = false,
+ 'data-testid': testId = 'drawer-content',
+ ref,
+}: DrawerContentProps) => (
+
+ {children}
+
);
export default Content;
diff --git a/src/components/displays/Drawer/compounds/HeaderSticky.tsx b/src/components/displays/Drawer/compounds/HeaderSticky.tsx
index 5084b07..8c1d21a 100644
--- a/src/components/displays/Drawer/compounds/HeaderSticky.tsx
+++ b/src/components/displays/Drawer/compounds/HeaderSticky.tsx
@@ -11,7 +11,7 @@ import styles from '../styles.module.scss';
export type DrawerHeaderStickyProps = DataTestId & {
children: ReactNode;
customContent?: (isSticky: boolean) => ReactElement;
- parentRef: RefObject;
+ parentRef: RefObject;
} & Omit;
const HeaderSticky = ({
diff --git a/src/components/displays/Drawer/index.tsx b/src/components/displays/Drawer/index.tsx
index e917e1e..921003b 100644
--- a/src/components/displays/Drawer/index.tsx
+++ b/src/components/displays/Drawer/index.tsx
@@ -10,7 +10,8 @@ import { useLocation, useSearchParams } from 'react-router-dom';
import { Stack } from '@/skin/layout';
-import { type ImperativePanelHandle, Panels } from '..';
+import Panels from '../Panels';
+import type { ImperativePanelHandle } from '../Panels/compounds/PanelsItem';
import Actions from './compounds/Actions';
import Body from './compounds/Body';
import Close from './compounds/Close';
diff --git a/src/components/displays/Markdown/Mardown.stories.tsx b/src/components/displays/Markdown/Mardown.stories.tsx
index 21f509a..227cd2c 100644
--- a/src/components/displays/Markdown/Mardown.stories.tsx
+++ b/src/components/displays/Markdown/Mardown.stories.tsx
@@ -391,6 +391,19 @@ age: 30
isAdmin: false
\`\`\`
+And here is a Mermaid code example:
+\`\`\`mermaid
+flowchart TD
+ A["Inputs (Logs, SIEM, API, User)"] --> B["LangChain Orchestration, Parsing, Tools"]
+ B --> C["LangGraph Advanced Workflow & Logic"]
+ B --> D["LLM OpenAI (GPT-4, GPT-4o)"]
+ B --> E["Cybersecurity Tools (Enrichment, Actions)"]
+ B --> F["Langfuse Monitoring & Logs"]
+ D --> G["Outputs (Report, Alert, Action)"]
+ E --> G
+ F --> G
+\`\`\`
+

`;
diff --git a/src/components/displays/Markdown/index.tsx b/src/components/displays/Markdown/index.tsx
index 0b8cfb5..720b150 100644
--- a/src/components/displays/Markdown/index.tsx
+++ b/src/components/displays/Markdown/index.tsx
@@ -1,16 +1,14 @@
-import { Highlight, themes } from 'prism-react-renderer';
import type { ComponentProps } from 'react';
import type { Options } from 'react-markdown';
import ReactMarkdown, { defaultUrlTransform } from 'react-markdown';
import remarkGfm from 'remark-gfm';
-import { CopyToClipboard, Link } from '@/skin/actions';
-import { Stack } from '@/skin/layout';
-import { useThemeContext } from '@/skin/navigation/Theme';
-import { Paragraph, Text, Title } from '@/skin/typography';
+import { Link } from '@/skin/actions';
+import { Paragraph, Title } from '@/skin/typography';
+import CodeBlock from '../CodeBlock';
import Divider from '../Divider';
-import { Prism } from './Prism';
+import MermaidViewer from '../MermaidViewer';
import styles from './styles.module.scss';
@@ -26,8 +24,6 @@ export const InternalMarkdown = ({
children,
components,
}: MarkdownProps & InternalMarkdownProps) => {
- const { theme } = useThemeContext();
-
return (
= 1;
if (isMultiline) {
- return (
-
- {({ style, tokens, getTokenProps, getLineProps }) => {
- return (
-
-
- {match?.[1] ?? ''}
-
-
-
- {tokens.map((line, i) => (
-
- {line.map((token, key) => (
-
- ))}
-
- ))}
-
-
- );
- }}
-
- );
+ if (language === 'mermaid') {
+ return ;
+ } else {
+ return ;
+ }
} else {
return {children};
}
@@ -146,4 +113,6 @@ export const InternalMarkdown = ({
const Markdown = (props: MarkdownProps) => ;
+Markdown.Mermaid = MermaidViewer.Tabs;
+
export default Markdown;
diff --git a/src/components/displays/Markdown/styles.module.scss b/src/components/displays/Markdown/styles.module.scss
index 24127b7..a462922 100644
--- a/src/components/displays/Markdown/styles.module.scss
+++ b/src/components/displays/Markdown/styles.module.scss
@@ -8,33 +8,6 @@
overflow-x: auto;
}
-.CodeBlock {
- display: flex;
- overflow: hidden;
- flex-direction: column;
- border: 1px solid var(--color-neutral-200);
- border-radius: var(--border-radius-large);
- background-color: var(--body-background-color) !important;
-
- &Inner {
- padding: var(--spacing-5);
- overflow-x: auto;
- }
-
- @include on-dark-theme {
- border: 1px solid var(--color-neutral-600);
- }
-
- &Head {
- padding: var(--spacing-5) var(--spacing-6);
- background-color: var(--color-neutral-50);
-
- @include on-dark-theme {
- background-color: var(--color-neutral-700);
- }
- }
-}
-
.table {
width: 100%;
min-width: min-content;
diff --git a/src/components/displays/MermaidViewer/MermaidViewer.stories.tsx b/src/components/displays/MermaidViewer/MermaidViewer.stories.tsx
new file mode 100644
index 0000000..77230fd
--- /dev/null
+++ b/src/components/displays/MermaidViewer/MermaidViewer.stories.tsx
@@ -0,0 +1,47 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import MermaidViewer from '.';
+
+const code = `
+flowchart TD
+ A["Inputs (Logs, SIEM, API, User)"] --> B["LangChain Orchestration, Parsing, Tools"]
+ B --> C["LangGraph Advanced Workflow & Logic"]
+ B --> D["LLM OpenAI (GPT-4, GPT-4o)"]
+ B --> E["Cybersecurity Tools (Enrichment, Actions)"]
+ B --> F["Langfuse Monitoring & Logs"]
+ D --> G["Outputs (Report, Alert, Action)"]
+ E --> G
+ F --> G
+`;
+
+const invalidCode = `
+// no comments allowed in mermaid code
+flowchart TD
+ A["Inputs (Logs, SIEM, API, User)"] --> B["LangChain Orchestration, Parsing, Tools"]
+ B --> C["LangGraph Advanced Workflow & Logic"]
+ B --> D["LLM OpenAI (GPT-4, GPT-4o)"]
+ B --> E["Cybersecurity Tools (Enrichment, Actions)"]
+ B --> F["Langfuse Monitoring & Logs"]
+ D --> G["Outputs (Report, Alert, Action)"]
+ E --> G
+ F --> G
+`;
+
+type Story = StoryObj;
+
+export default {
+ title: 'displays/MermaidViewer',
+ component: MermaidViewer,
+} as Meta;
+
+export const Default: Story = {
+ args: {
+ code,
+ },
+};
+
+export const InvalidCode: Story = {
+ args: {
+ code: invalidCode,
+ },
+};
diff --git a/src/components/displays/MermaidViewer/compounds/MermaidTabs.tsx b/src/components/displays/MermaidViewer/compounds/MermaidTabs.tsx
new file mode 100644
index 0000000..72ca0ca
--- /dev/null
+++ b/src/components/displays/MermaidViewer/compounds/MermaidTabs.tsx
@@ -0,0 +1,31 @@
+import { CodeBlock, MermaidViewer, Tabs } from '@/skin/displays';
+import { Stack } from '@/skin/layout';
+
+export type MermaidTabsProps = { code: string };
+
+const MermaidTabs = ({ code }: MermaidTabsProps) => {
+ return (
+
+
+ Diagram
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default MermaidTabs;
diff --git a/src/components/displays/MermaidViewer/constants.ts b/src/components/displays/MermaidViewer/constants.ts
new file mode 100644
index 0000000..ba60897
--- /dev/null
+++ b/src/components/displays/MermaidViewer/constants.ts
@@ -0,0 +1,11 @@
+export const DEFAULT_RENDERING_ERROR_MESSAGE = 'Mermaid code is not valid';
+export const RENDERING_ERROR_MESSAGE_TEST_ID = 'rendering-error-message';
+
+export const SVG_PAN_ZOOM_DEFAULT_OPTIONS: SvgPanZoom.Options = {
+ zoomEnabled: true,
+ panEnabled: true,
+ fit: true,
+ center: true,
+ minZoom: 0.2,
+ maxZoom: 5,
+};
diff --git a/src/components/displays/MermaidViewer/index.tsx b/src/components/displays/MermaidViewer/index.tsx
new file mode 100644
index 0000000..54eb9f4
--- /dev/null
+++ b/src/components/displays/MermaidViewer/index.tsx
@@ -0,0 +1,98 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import { generateUniqId } from '@gatewatcher/bistoury/utils-lang';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import mermaid from 'mermaid';
+import { useRef, useState } from 'react';
+import { createRoot } from 'react-dom/client';
+import svgPanZoom from 'svg-pan-zoom';
+
+import { CircularLoader } from '@/skin/feedback';
+import { Stack } from '@/skin/layout';
+import { Text } from '@/skin/typography';
+
+import MermaidTabs from './compounds/MermaidTabs';
+import {
+ DEFAULT_RENDERING_ERROR_MESSAGE,
+ RENDERING_ERROR_MESSAGE_TEST_ID,
+ SVG_PAN_ZOOM_DEFAULT_OPTIONS,
+} from './constants';
+
+import styles from './styles.module.scss';
+
+mermaid.initialize({
+ suppressErrorRendering: true,
+});
+
+export type MermaidViewerProps = DataTestId & {
+ code: string;
+ renderingErrorMessage?: string;
+ svgPanZoomOptions?: SvgPanZoom.Options;
+};
+
+const MermaidViewer = ({
+ code,
+ renderingErrorMessage = DEFAULT_RENDERING_ERROR_MESSAGE,
+ svgPanZoomOptions = SVG_PAN_ZOOM_DEFAULT_OPTIONS,
+ 'data-testid': TestId = 'mermaid-container',
+}: MermaidViewerProps) => {
+ const [hasRenderingError, setHasRenderingError] = useState(false);
+ const id = useRef(generateUniqId());
+
+ const initializeMermaid = async (element: HTMLElement) => {
+ if (element) {
+ const loader = (
+
+
+
+ );
+ const root = createRoot(element);
+ root.render(loader);
+
+ try {
+ const { svg, bindFunctions } = await mermaid.render(
+ `mermaid-diagram-${id.current}`,
+ code,
+ );
+ element.innerHTML = svg;
+ bindFunctions?.(element);
+ setHasRenderingError(false);
+ } catch (error) {
+ setHasRenderingError(true);
+ }
+
+ try {
+ const svgElement = document.querySelector(`#${id.current} > svg`);
+ const isSVGElement = svgElement instanceof SVGElement;
+
+ if (svgElement && isSVGElement) {
+ svgPanZoom(svgElement, svgPanZoomOptions);
+
+ if (svgElement instanceof SVGGraphicsElement) {
+ const bbox = svgElement.getBBox();
+ svgElement.setAttribute('width', `${bbox.width}`);
+ svgElement.setAttribute('height', `${bbox.height}`);
+ }
+ }
+ } catch (error) {
+ return;
+ }
+ }
+ };
+
+ return hasRenderingError ? (
+
+ {renderingErrorMessage}
+
+ ) : (
+
+ );
+};
+
+MermaidViewer.Tabs = MermaidTabs;
+
+export default MermaidViewer;
diff --git a/src/components/displays/MermaidViewer/styles.module.scss b/src/components/displays/MermaidViewer/styles.module.scss
new file mode 100644
index 0000000..c447b52
--- /dev/null
+++ b/src/components/displays/MermaidViewer/styles.module.scss
@@ -0,0 +1,4 @@
+.mermaidDiagramContainer {
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/components/displays/Navigator/Navigator.stories.tsx b/src/components/displays/Navigator/Navigator.stories.tsx
new file mode 100644
index 0000000..cce5857
--- /dev/null
+++ b/src/components/displays/Navigator/Navigator.stories.tsx
@@ -0,0 +1,312 @@
+import { faker } from '@faker-js/faker';
+import type { Meta, StoryFn, StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { withRouter } from 'storybook-addon-remix-react-router';
+
+import { Button } from '@/skin/actions';
+import { Stack } from '@/skin/layout';
+import { Paragraph, Title } from '@/skin/typography';
+
+import type { NavigatorProps } from '.';
+import Navigator from '.';
+import type { NavId } from './types';
+
+faker.seed(42);
+
+type Story = StoryObj;
+
+const getDefaultSideNav = (
+ {
+ withLongText,
+ withFooter,
+ }: { withLongText?: boolean; withFooter?: boolean } = {
+ withLongText: false,
+ withFooter: true,
+ },
+) => {
+ return (
+
+
+
+
+ {withLongText
+ ? 'Item 1 loooooooooooooooooooooooooooooooooong'
+ : 'Item 1'}
+
+ Item 2
+
+
+
+ Item 1
+ Item 2
+
+
+
+
+ Item 1
+
+ Item 2
+
+
+
+ {withFooter && (
+
+
+
+ )}
+
+ );
+};
+
+const getDefaultChildren = (
+ {
+ withLongText,
+ withFooter,
+ }: { withLongText?: boolean; withFooter?: boolean } = {
+ withLongText: false,
+ withFooter: true,
+ },
+) => {
+ return (
+ <>
+ {getDefaultSideNav({ withLongText, withFooter })}
+
+
+
+
+ <>
+ Content 1 for group 1
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+ <>
+ Content 2 for group 1
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+
+
+
+ <>
+ Content 1 for group 2
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+ <>
+ Content 2 for group 2
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+
+
+
+ <>
+ Content 1 for group 3
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+ <>
+ Content 2 for group 3
+ {faker.lorem.paragraphs(5)}
+ >
+
+
+
+ >
+ );
+};
+
+export default {
+ title: 'displays/Navigator',
+ component: Navigator,
+ args: {
+ children: getDefaultChildren(),
+ onNavItemChange: item => {
+ console.log(`nav item change`, item);
+ },
+ },
+} as Meta;
+
+const Template: StoryFn = ({
+ children,
+ ...args
+}: NavigatorProps) => {children};
+
+export const Default: Story = {
+ render: Template,
+ decorators: [withRouter],
+};
+
+export const WithLongSideNav: Story = {
+ render: ({ ...args }) => {
+ return (
+
+
+
+ );
+ },
+};
+
+export const WithLongText: Story = {
+ render: Template,
+ args: {
+ children: getDefaultChildren({ withLongText: true }),
+ },
+};
+
+export const WithDefaultActiveItem: Story = {
+ render: Template,
+ args: {
+ defaultSection: 1,
+ defaultNavItem: 1,
+ },
+};
+
+export const Controlled: Story = {
+ render: ({ ...args }) => {
+ const [currentSection, setCurrentSection] = useState(0);
+ const [currentNavItem, setCurrentNavItem] = useState(1);
+
+ const onClick = (sectionId: NavId, navItemId: NavId) => {
+ setCurrentSection(sectionId);
+ setCurrentNavItem(navItemId);
+ };
+
+ const onNavItemChange = (item: { sectionId: NavId; navItemId: NavId }) => {
+ setCurrentSection(item.sectionId);
+ setCurrentNavItem(item.navItemId);
+ };
+
+ return (
+
+ Starts at First Group Second Item
+ {`Group ${Number(currentSection) + 1} - Item ${
+ Number(currentNavItem) + 1
+ }`}
+
+
+
+
+
+
+
+ );
+ },
+};
+
+export const Ungrouped: Story = {
+ render: Template,
+ args: {
+ children: (
+ <>
+
+
+
+ First Item
+ Second Item
+ Third Item
+ Fourth Item
+
+
+
+
+
+
+
+ First Panel Content
+
+
+ Second Panel Content
+
+
+ Third Panel Content
+
+
+ Fourth Panel Content
+
+
+
+ >
+ ),
+ },
+};
+
+export const WithRouter: Story = {
+ render: Template,
+ args: {
+ children: (
+ <>
+
+
+
+
+ Users
+
+
+ Admin
+
+
+
+
+
+
+ >
+ ),
+ },
+ decorators: [
+ Story => (
+
+
+ } path="/">
+
+ Users
+ {faker.lorem.paragraphs(5)}
+
+ }
+ path="/users"
+ />
+
+ Admin
+ {faker.lorem.paragraphs(5)}
+
+ }
+ path="/admin"
+ />
+
+
+
+ ),
+ ],
+ parameters: {
+ controls: { exclude: ['defaultTab'] },
+ },
+};
diff --git a/src/components/displays/Navigator/__tests__/Navigator.test.tsx b/src/components/displays/Navigator/__tests__/Navigator.test.tsx
new file mode 100644
index 0000000..19be16e
--- /dev/null
+++ b/src/components/displays/Navigator/__tests__/Navigator.test.tsx
@@ -0,0 +1,136 @@
+import {
+ expectNotToBeVisibleInTheDocument,
+ expectToBeVisibleInTheDocument,
+} from '@gatewatcher/bistoury/utils-tests';
+import type { TestId } from '@gatewatcher/bistoury/utils-types';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { renderWithRouter } from '@/tests';
+
+import type { NavigatorProps } from '..';
+import Navigator from '..';
+
+describe('Navigator', () => {
+ const TEST_ID: TestId = 'navigator';
+
+ const user = userEvent.setup();
+
+ const renderComponent = ({
+ children,
+ ...props
+ }: Partial
= {}) =>
+ renderWithRouter(
+
+ {children || (
+ <>
+
+
+
+ Item1
+ Item2
+
+
+ Item3
+ Item4
+
+
+
+
+
+
+ Content1
+ Content2
+
+
+ Content3
+ Content4
+
+
+ >
+ )}
+ ,
+ );
+
+ const getNavItems = async () => [
+ ...(await screen.findAllByTestId('nav-item')),
+ await screen.findByTestId('nav-item-active'),
+ ];
+ const getPanels = async () => await screen.findAllByTestId('navigator-panel');
+
+ it('should render', async () => {
+ renderComponent();
+ await expectToBeVisibleInTheDocument(TEST_ID);
+ });
+
+ it('should render four nav items', async () => {
+ renderComponent();
+ const navItems = await getNavItems();
+ expect(navItems).toHaveLength(4);
+ });
+
+ it('should render only one panel', async () => {
+ renderComponent();
+ const panels = await getPanels();
+ expect(panels).toHaveLength(1);
+ await expectToBeVisibleInTheDocument('Content1', screen.findByText);
+ });
+
+ it('should have active className for current nav item', async () => {
+ renderComponent();
+ const activeTab = await screen.findByTestId('nav-item-active');
+ expect(activeTab).toHaveClass('active');
+ });
+
+ it('should have default nav item', async () => {
+ renderComponent({ defaultSection: 1, defaultNavItem: 1 });
+ await expectToBeVisibleInTheDocument('Content4', screen.findByText);
+ });
+
+ it('should have disabled tab', async () => {
+ renderComponent({
+ children: (
+
+
+
+ Item1
+
+ Item2
+
+
+
+
+ ),
+ });
+ expect(await screen.findByTestId('nav-item-disabled')).toHaveClass(
+ 'disabled',
+ );
+ });
+
+ it('should switch nav item', async () => {
+ renderComponent();
+
+ await expectToBeVisibleInTheDocument('Content1', screen.findByText);
+ const otherNavItem = await screen.findByText('Item2');
+ await user.click(otherNavItem);
+
+ await expectNotToBeVisibleInTheDocument('Content1', screen.queryByText);
+ await expectToBeVisibleInTheDocument('Content2', screen.findByText);
+ });
+
+ it('should call onNavItemChange', async () => {
+ const onNavItemChange = vi.fn();
+ renderComponent({ onNavItemChange });
+
+ const otherTab = await screen.findByText('Item4');
+ await user.click(otherTab);
+
+ expect(onNavItemChange).toHaveBeenNthCalledWith(1, {
+ sectionId: 1,
+ navItemId: 1,
+ });
+ });
+});
diff --git a/src/components/displays/Navigator/compounds/NavItem.tsx b/src/components/displays/Navigator/compounds/NavItem.tsx
new file mode 100644
index 0000000..b43bc49
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/NavItem.tsx
@@ -0,0 +1,103 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import { isDefined } from '@gatewatcher/bistoury/utils-lang';
+import { suffixTestId } from '@gatewatcher/bistoury/utils-tests';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+import { useMemo } from 'react';
+import type { To } from 'react-router-dom';
+
+import { Button } from '@/skin/actions';
+import { Stack } from '@/skin/layout';
+import { Text } from '@/skin/typography';
+
+import TextIcon from '../../TextIcon';
+import type { IconName } from '../../icons/types';
+import { useNavigatorContext } from '../context';
+import type { NavId, NavIdProps } from '../types';
+import NavLink from './NavLink';
+
+import styles from '../styles.module.scss';
+
+export type NavItemProps = NavIdProps &
+ DataTestId & {
+ children: ReactNode;
+ disabled?: boolean;
+ icon?: IconName;
+ to?: To;
+ sectionId?: NavId;
+ };
+
+const NavItem = ({
+ children,
+ 'data-testid': testId = 'nav-item',
+ disabled,
+ icon,
+ to,
+ id,
+ sectionId,
+}: NavItemProps) => {
+ const {
+ currentSection,
+ currentNavItem,
+ setCurrentSection,
+ setCurrentNavItem,
+ onNavItemChange,
+ } = useNavigatorContext();
+ const isActive = useMemo(
+ () => currentSection === sectionId && currentNavItem === id,
+ [currentSection, currentNavItem, sectionId, id],
+ );
+ const navItemTestId = isActive ? suffixTestId(testId, 'active') : testId;
+
+ const handleClick = () => {
+ if (disabled || !isDefined(id) || !isDefined(sectionId)) return;
+
+ setCurrentSection(sectionId);
+ setCurrentNavItem(id);
+ onNavItemChange({ sectionId, navItemId: id });
+ };
+
+ const getContent = () => {
+ return icon ? (
+
+ {children}
+
+ ) : (
+ {children}
+ );
+ };
+
+ return (
+
+ {to ? (
+
+ {getContent()}
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default NavItem;
diff --git a/src/components/displays/Navigator/compounds/NavLink.tsx b/src/components/displays/Navigator/compounds/NavLink.tsx
new file mode 100644
index 0000000..a249514
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/NavLink.tsx
@@ -0,0 +1,60 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+import { useEffect } from 'react';
+import type { To } from 'react-router-dom';
+import {
+ NavLink as SkinNavLink,
+ useMatch,
+ useResolvedPath,
+} from 'react-router-dom';
+
+import styles from '../styles.module.scss';
+
+export type NavLinkProps = DataTestId & {
+ children: ReactNode;
+ disabled?: boolean;
+ onClick: () => void;
+ ref?: React.Ref;
+ to: To;
+};
+
+const NavLink = ({
+ 'data-testid': testid,
+ disabled,
+ onClick,
+ children,
+ ref,
+ to,
+}: NavLinkProps) => {
+ const path = useResolvedPath(to);
+ const match = useMatch(`${path.pathname}/*`);
+
+ useEffect(() => {
+ if (match) {
+ onClick();
+ }
+ }, [match, onClick]);
+
+ return (
+
+ classNames(
+ styles.NavItem,
+ styles.NavItemLink,
+ isActive && styles.active,
+ disabled && styles.disabled,
+ )
+ }
+ data-testid={testid}
+ onClick={onClick}
+ tabIndex={disabled ? -1 : undefined}
+ to={to}
+ >
+ {children}
+
+ );
+};
+
+export default NavLink;
diff --git a/src/components/displays/Navigator/compounds/Outlet.tsx b/src/components/displays/Navigator/compounds/Outlet.tsx
new file mode 100644
index 0000000..230d57d
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/Outlet.tsx
@@ -0,0 +1,7 @@
+import { Outlet } from 'react-router-dom';
+
+const NavigatorOutlet = () => {
+ return ;
+};
+
+export default NavigatorOutlet;
diff --git a/src/components/displays/Navigator/compounds/Panel.tsx b/src/components/displays/Navigator/compounds/Panel.tsx
new file mode 100644
index 0000000..7fa03aa
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/Panel.tsx
@@ -0,0 +1,25 @@
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+
+import type { NavIdProps } from '../types';
+
+import styles from '../styles.module.scss';
+
+export type NavigatorPanelProps = NavIdProps &
+ DataTestId & {
+ children: ReactNode;
+ path?: string;
+ };
+
+const NavigatorPanel = ({
+ children,
+ 'data-testid': testId = 'navigator-panel',
+}: NavigatorPanelProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default NavigatorPanel;
diff --git a/src/components/displays/Navigator/compounds/PanelGroup.tsx b/src/components/displays/Navigator/compounds/PanelGroup.tsx
new file mode 100644
index 0000000..8a7fbac
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/PanelGroup.tsx
@@ -0,0 +1,26 @@
+import type { ReactElement } from 'react';
+import { Children } from 'react';
+
+import { useNavigatorContext } from '../context';
+import type { NavIdProps } from '../types';
+import type { NavigatorPanelProps } from './Panel';
+
+export type NavigatorPanelGroupProps = NavIdProps & {
+ children:
+ | ReactElement
+ | ReactElement[];
+};
+
+const NavigatorPanelGroup = ({ children }: NavigatorPanelGroupProps) => {
+ const { currentNavItem } = useNavigatorContext();
+ return (
+ <>
+ {Children.map(children, item => item).find(
+ (item, index) =>
+ item.props.id === currentNavItem || currentNavItem === index,
+ )}
+ >
+ );
+};
+
+export default NavigatorPanelGroup;
diff --git a/src/components/displays/Navigator/compounds/PanelList.tsx b/src/components/displays/Navigator/compounds/PanelList.tsx
new file mode 100644
index 0000000..5ead5d9
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/PanelList.tsx
@@ -0,0 +1,25 @@
+import { Children, type ReactElement } from 'react';
+
+import { useNavigatorContext } from '../context';
+import type { NavigatorPanelGroupProps } from './PanelGroup';
+
+export type NavigatorPanelListProps = {
+ children:
+ | ReactElement
+ | ReactElement[];
+};
+
+const NavigatorPanelList = ({ children }: NavigatorPanelListProps) => {
+ const { currentSection } = useNavigatorContext();
+
+ return (
+ <>
+ {Children.map(children, item => item).find(
+ (item, index) =>
+ item.props.id === currentSection || currentSection === index,
+ )}
+ >
+ );
+};
+
+export default NavigatorPanelList;
diff --git a/src/components/displays/Navigator/compounds/Section.tsx b/src/components/displays/Navigator/compounds/Section.tsx
new file mode 100644
index 0000000..4bcd999
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/Section.tsx
@@ -0,0 +1,52 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactElement } from 'react';
+import { Children, cloneElement } from 'react';
+
+import { Stack } from '@/skin/layout';
+import { Text } from '@/skin/typography';
+
+import type { NavIdProps } from '../types';
+import type { NavItemProps } from './NavItem';
+
+import styles from '../styles.module.scss';
+
+export type SectionProps = NavIdProps &
+ DataTestId & {
+ children: ReactElement | ReactElement[];
+ title?: string;
+ };
+
+const Section = ({
+ 'data-testid': testId = 'section',
+ children,
+ id,
+ title,
+}: SectionProps) => {
+ return (
+
+ {title}
+
+
+ {Children.map(children, (item, index) => (
+
+ {cloneElement(item, { id: item.props.id ?? index, sectionId: id })}
+
+ ))}
+
+
+ );
+};
+
+export default Section;
diff --git a/src/components/displays/Navigator/compounds/SideNav.tsx b/src/components/displays/Navigator/compounds/SideNav.tsx
new file mode 100644
index 0000000..e9a10be
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/SideNav.tsx
@@ -0,0 +1,58 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+import { Children, isValidElement } from 'react';
+
+import { Stack } from '@/skin/layout';
+import { Title } from '@/skin/typography';
+
+import SideNavBody from './SideNavBody';
+import SideNavFooter from './SideNavFooter';
+
+import styles from '../styles.module.scss';
+
+export type SideNavProps = DataTestId & {
+ children: ReactNode;
+ title?: string | ReactNode;
+};
+
+const SideNav = ({
+ 'data-testid': testId = 'side-nav',
+ children,
+ title,
+}: SideNavProps) => {
+ const titleElement =
+ typeof title === 'string' ? (
+
+ {title}
+
+ ) : (
+ title
+ );
+
+ let sideNavBody: ReactNode = null;
+ let sideNavFooter: ReactNode = null;
+
+ Children.forEach(children, child => {
+ if (!isValidElement(child)) return;
+
+ if (child.type === SideNavBody) {
+ sideNavBody = child;
+ } else if (child.type === SideNavFooter) {
+ sideNavFooter = child;
+ }
+ });
+
+ return (
+
+
+ {titleElement}
+ {sideNavBody}
+
+
+ {sideNavFooter}
+
+ );
+};
+
+export default SideNav;
diff --git a/src/components/displays/Navigator/compounds/SideNavBody.tsx b/src/components/displays/Navigator/compounds/SideNavBody.tsx
new file mode 100644
index 0000000..1ae5202
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/SideNavBody.tsx
@@ -0,0 +1,32 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactElement } from 'react';
+import { Children, cloneElement } from 'react';
+
+import { Stack } from '@/skin/layout';
+
+import type { SectionProps } from './Section';
+
+import style from '../styles.module.scss';
+
+export type SideNavBodyProps = DataTestId & {
+ children: ReactElement | ReactElement[];
+};
+
+const SideNavBody = ({
+ 'data-testid': testId = 'section-list',
+ children,
+}: SideNavBodyProps) => {
+ return (
+
+ {Children.map(children, (item, index) =>
+ cloneElement(item, {
+ id: item.props.id ?? index,
+ key: item.props.id ?? index,
+ }),
+ )}
+
+ );
+};
+
+export default SideNavBody;
diff --git a/src/components/displays/Navigator/compounds/SideNavFooter.tsx b/src/components/displays/Navigator/compounds/SideNavFooter.tsx
new file mode 100644
index 0000000..da34196
--- /dev/null
+++ b/src/components/displays/Navigator/compounds/SideNavFooter.tsx
@@ -0,0 +1,24 @@
+import { classNames } from '@gatewatcher/bistoury/utils-dom';
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+
+import { Stack } from '@/skin/layout';
+
+import styles from '../styles.module.scss';
+
+export type SideNavFooterProps = DataTestId & {
+ children: ReactNode;
+};
+
+const SideNavFooter = ({
+ children,
+ 'data-testid': testId = 'sidenav-footer',
+}: SideNavFooterProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default SideNavFooter;
diff --git a/src/components/displays/Navigator/constants.ts b/src/components/displays/Navigator/constants.ts
new file mode 100644
index 0000000..bf4da15
--- /dev/null
+++ b/src/components/displays/Navigator/constants.ts
@@ -0,0 +1,5 @@
+import type { NavId } from './types';
+
+export const DEFAULT_SECTION: NavId = 0;
+export const DEFAULT_NAV_ITEM: NavId = 0;
+export const DEFAULT_ON_NAV_ITEM_CHANGE = () => {};
diff --git a/src/components/displays/Navigator/context.tsx b/src/components/displays/Navigator/context.tsx
new file mode 100644
index 0000000..1b31c68
--- /dev/null
+++ b/src/components/displays/Navigator/context.tsx
@@ -0,0 +1,21 @@
+import { createContext, useContext } from 'react';
+
+import type { NavId } from './types';
+
+export type NavigatorContextType = {
+ currentSection: NavId;
+ currentNavItem: NavId;
+ onNavItemChange: (activeItem: { sectionId: NavId; navItemId: NavId }) => void;
+ setCurrentSection: (id: NavId) => void;
+ setCurrentNavItem: (id: NavId) => void;
+};
+
+export const NavigatorContext = createContext({
+ currentSection: 0,
+ currentNavItem: 0,
+ onNavItemChange: () => {},
+ setCurrentSection: () => {},
+ setCurrentNavItem: () => {},
+});
+
+export const useNavigatorContext = () => useContext(NavigatorContext);
diff --git a/src/components/displays/Navigator/index.tsx b/src/components/displays/Navigator/index.tsx
new file mode 100644
index 0000000..6931a21
--- /dev/null
+++ b/src/components/displays/Navigator/index.tsx
@@ -0,0 +1,74 @@
+import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
+import type { ReactNode } from 'react';
+import { useState } from 'react';
+
+import { Stack } from '@/skin/layout';
+
+import NavItem from './compounds/NavItem';
+import NavigatorOutlet from './compounds/Outlet';
+import NavigatorPanel from './compounds/Panel';
+import NavigatorPanelGroup from './compounds/PanelGroup';
+import NavigatorPanelList from './compounds/PanelList';
+import Section from './compounds/Section';
+import SideNav from './compounds/SideNav';
+import SideNavBody from './compounds/SideNavBody';
+import SideNavFooter from './compounds/SideNavFooter';
+import {
+ DEFAULT_NAV_ITEM,
+ DEFAULT_ON_NAV_ITEM_CHANGE,
+ DEFAULT_SECTION,
+} from './constants';
+import type { NavigatorContextType } from './context';
+import { NavigatorContext } from './context';
+import type { CurrentActiveItem, DefaultActiveItem, NavId } from './types';
+
+import styles from './styles.module.scss';
+
+export type NavigatorProps = DataTestId &
+ DefaultActiveItem &
+ CurrentActiveItem & {
+ children: ReactNode;
+ onNavItemChange?: NavigatorContextType['onNavItemChange'];
+ };
+
+const Navigator = ({
+ children,
+ 'data-testid': testId = 'tabs',
+ defaultSection = DEFAULT_SECTION,
+ defaultNavItem = DEFAULT_NAV_ITEM,
+ onNavItemChange = DEFAULT_ON_NAV_ITEM_CHANGE,
+ ...props
+}: NavigatorProps) => {
+ const [currentSection, setCurrentSection] = useState(defaultSection);
+ const [currentNavItem, setCurrentNavItem] = useState(defaultNavItem);
+
+ const contextValue: NavigatorContextType = {
+ currentSection: props.currentSection ?? currentSection,
+ currentNavItem: props.currentNavItem ?? currentNavItem,
+ onNavItemChange,
+ setCurrentSection,
+ setCurrentNavItem,
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+Navigator.SideNav = SideNav;
+Navigator.SideNavBody = SideNavBody;
+Navigator.SideNavFooter = SideNavFooter;
+Navigator.Section = Section;
+Navigator.NavItem = NavItem;
+
+Navigator.PanelList = NavigatorPanelList;
+Navigator.PanelGroup = NavigatorPanelGroup;
+Navigator.Panel = NavigatorPanel;
+
+Navigator.Outlet = NavigatorOutlet;
+
+export default Navigator;
diff --git a/src/components/displays/Navigator/styles.module.scss b/src/components/displays/Navigator/styles.module.scss
new file mode 100644
index 0000000..27a7ac1
--- /dev/null
+++ b/src/components/displays/Navigator/styles.module.scss
@@ -0,0 +1,116 @@
+@import '@/styles/mixins';
+
+.ContextNav {
+ width: 100%;
+ height: 100%;
+ gap: var(--spacing-7);
+}
+
+.SideNav {
+ position: relative;
+ width: fit-content;
+ min-width: 250px;
+ max-width: 300px;
+ flex: 0 0 auto;
+ flex-direction: column;
+ justify-content: space-between;
+
+ padding: var(--spacing-7);
+ padding-bottom: var(--spacing-4);
+ border: 1px solid var(--color-neutral-200);
+ border-radius: var(--border-radius-large);
+
+ @include on-dark-theme {
+ border: 1px solid var(--color-neutral-700);
+ }
+
+ & > div {
+ overflow-y: auto;
+ }
+}
+
+.SideNavBody {
+ flex-direction: column;
+ padding-bottom: var(--spacing-4);
+ gap: var(--spacing-7);
+}
+
+.SideNavFooter {
+ position: sticky;
+ flex-grow: 0;
+ flex-shrink: 0;
+ padding: var(--spacing-4) var(--spacing-7);
+ padding-bottom: 0;
+ border-top: 1px solid var(--color-neutral-200);
+ background-color: var(--body-background-color);
+ margin-inline: calc(-1 * var(--spacing-7));
+
+ @include on-dark-theme {
+ border-top: 1px solid var(--color-neutral-700);
+ }
+}
+
+.Section {
+ gap: var(--spacing-2);
+ @include child('Text') {
+ color: var(--color-neutral-400);
+ }
+
+ & > span {
+ overflow-x: hidden;
+ white-space: nowrap;
+ }
+}
+
+.NavItemList {
+ padding: 0;
+ margin: 0;
+ gap: var(--spacing-2);
+ list-style: none;
+}
+
+.NavItemContainer {
+ padding: var(--spacing-4) var(--spacing-5);
+ border-radius: var(--border-radius-large);
+
+ &:hover {
+ background-color: var(--color-neutral-50);
+
+ @include on-dark-theme {
+ background-color: var(--color-neutral-800);
+ }
+ }
+
+ &.active {
+ background-color: var(--color-neutral-50);
+
+ @include on-dark-theme {
+ background-color: var(--color-neutral-800);
+ }
+ }
+}
+
+.NavItem {
+ width: 100%;
+ color: var(--color-neutral-600);
+ overflow-x: hidden;
+ white-space: nowrap;
+
+ &.active {
+ font-weight: var(--font-weight-medium);
+ }
+
+ &.disabled {
+ pointer-events: none;
+
+ @include disabled;
+ }
+}
+
+.NavItemLink {
+ text-decoration: none;
+}
+
+.NavigatorPanel {
+ width: 100%;
+}
diff --git a/src/components/displays/Navigator/types.ts b/src/components/displays/Navigator/types.ts
new file mode 100644
index 0000000..379dd19
--- /dev/null
+++ b/src/components/displays/Navigator/types.ts
@@ -0,0 +1,13 @@
+export type NavId = string | number;
+
+export type NavIdProps = {
+ id?: NavId;
+};
+
+export type DefaultActiveItem =
+ | { defaultSection?: undefined; defaultNavItem?: undefined }
+ | { defaultSection: NavId; defaultNavItem: NavId };
+
+export type CurrentActiveItem =
+ | { currentSection?: undefined; currentNavItem?: undefined }
+ | { currentSection: NavId; currentNavItem: NavId };
diff --git a/src/components/displays/panels/Panels.stories.tsx b/src/components/displays/Panels/Panels.stories.tsx
similarity index 100%
rename from src/components/displays/panels/Panels.stories.tsx
rename to src/components/displays/Panels/Panels.stories.tsx
diff --git a/src/components/displays/panels/PanelsGroupContext.tsx b/src/components/displays/Panels/PanelsGroupContext.tsx
similarity index 100%
rename from src/components/displays/panels/PanelsGroupContext.tsx
rename to src/components/displays/Panels/PanelsGroupContext.tsx
diff --git a/src/components/displays/panels/ResizeHandler.tsx b/src/components/displays/Panels/ResizeHandler.tsx
similarity index 100%
rename from src/components/displays/panels/ResizeHandler.tsx
rename to src/components/displays/Panels/ResizeHandler.tsx
diff --git a/src/components/displays/panels/__tests__/PanelsItem.test.tsx b/src/components/displays/Panels/__tests__/PanelsItem.test.tsx
similarity index 100%
rename from src/components/displays/panels/__tests__/PanelsItem.test.tsx
rename to src/components/displays/Panels/__tests__/PanelsItem.test.tsx
diff --git a/src/components/displays/panels/__tests__/PanelsResizeHandle.test.tsx b/src/components/displays/Panels/__tests__/PanelsResizeHandle.test.tsx
similarity index 100%
rename from src/components/displays/panels/__tests__/PanelsResizeHandle.test.tsx
rename to src/components/displays/Panels/__tests__/PanelsResizeHandle.test.tsx
diff --git a/src/components/displays/panels/compounds/PanelsGroup.tsx b/src/components/displays/Panels/compounds/PanelsGroup.tsx
similarity index 100%
rename from src/components/displays/panels/compounds/PanelsGroup.tsx
rename to src/components/displays/Panels/compounds/PanelsGroup.tsx
diff --git a/src/components/displays/panels/compounds/PanelsItem.tsx b/src/components/displays/Panels/compounds/PanelsItem.tsx
similarity index 88%
rename from src/components/displays/panels/compounds/PanelsItem.tsx
rename to src/components/displays/Panels/compounds/PanelsItem.tsx
index 07226f4..9d47bfe 100644
--- a/src/components/displays/panels/compounds/PanelsItem.tsx
+++ b/src/components/displays/Panels/compounds/PanelsItem.tsx
@@ -1,19 +1,8 @@
// Forked from https://github.com/bvaughn/react-resizable-panels
import { useIsomorphicEffect, useUniqueId } from '@gatewatcher/bistoury/hooks';
import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
-import type {
- CSSProperties,
- ElementType,
- ForwardedRef,
- ReactNode,
-} from 'react';
-import {
- forwardRef,
- useContext,
- useEffect,
- useImperativeHandle,
- useRef,
-} from 'react';
+import type { CSSProperties, ElementType, ReactNode, Ref } from 'react';
+import { useContext, useEffect, useImperativeHandle, useRef } from 'react';
import { PanelsGroupContext } from '../PanelsGroupContext';
import type { PanelOnCollapse, PanelOnResize } from '../types';
@@ -31,6 +20,7 @@ export type PanelsItemProps = DataTestId & {
order?: number | null;
style?: CSSProperties;
as?: ElementType;
+ ref?: Ref;
};
export type ImperativePanelHandle = {
@@ -41,12 +31,12 @@ export type ImperativePanelHandle = {
resize: (percentage: number) => void;
};
-const PanelsItemWithForwardedRef = ({
+const PanelsItem = ({
children = null,
className: classNameFromProps = '',
collapsible = false,
defaultSize = null,
- forwardedRef,
+ ref,
id: idFromProps = null,
maxSize = 100,
minSize = 10,
@@ -56,9 +46,7 @@ const PanelsItemWithForwardedRef = ({
style: styleFromProps = {},
as: Component = 'div',
'data-testid': testId = 'panels-item',
-}: PanelsItemProps & {
- forwardedRef: ForwardedRef;
-}) => {
+}: PanelsItemProps) => {
const context = useContext(PanelsGroupContext);
if (context === null) {
throw Error(
@@ -147,7 +135,7 @@ const PanelsItemWithForwardedRef = ({
});
useImperativeHandle(
- forwardedRef,
+ ref,
() => ({
collapse: () => collapsePanel(panelId),
expand: () => expandPanel(panelId),
@@ -181,12 +169,6 @@ const PanelsItemWithForwardedRef = ({
);
};
-const PanelsItem = forwardRef(
- (props: PanelsItemProps, ref: ForwardedRef) => (
-
- ),
-);
-
function parseSizeFromStyle(style: CSSProperties): number {
const { flexGrow } = style;
if (typeof flexGrow === 'string') {
diff --git a/src/components/displays/panels/compounds/PanelsResizeHandle.module.scss b/src/components/displays/Panels/compounds/PanelsResizeHandle.module.scss
similarity index 100%
rename from src/components/displays/panels/compounds/PanelsResizeHandle.module.scss
rename to src/components/displays/Panels/compounds/PanelsResizeHandle.module.scss
diff --git a/src/components/displays/panels/compounds/PanelsResizeHandle.tsx b/src/components/displays/Panels/compounds/PanelsResizeHandle.tsx
similarity index 100%
rename from src/components/displays/panels/compounds/PanelsResizeHandle.tsx
rename to src/components/displays/Panels/compounds/PanelsResizeHandle.tsx
diff --git a/src/components/displays/panels/constants.ts b/src/components/displays/Panels/constants.ts
similarity index 100%
rename from src/components/displays/panels/constants.ts
rename to src/components/displays/Panels/constants.ts
diff --git a/src/components/displays/panels/hooks/useWindowSplitterBehavior.ts b/src/components/displays/Panels/hooks/useWindowSplitterBehavior.ts
similarity index 100%
rename from src/components/displays/panels/hooks/useWindowSplitterBehavior.ts
rename to src/components/displays/Panels/hooks/useWindowSplitterBehavior.ts
diff --git a/src/components/displays/panels/index.tsx b/src/components/displays/Panels/index.tsx
similarity index 84%
rename from src/components/displays/panels/index.tsx
rename to src/components/displays/Panels/index.tsx
index 6dc5d5d..7b10575 100644
--- a/src/components/displays/panels/index.tsx
+++ b/src/components/displays/Panels/index.tsx
@@ -3,14 +3,13 @@ import type { ReactNode } from 'react';
import { memo } from 'react';
import PanelsGroup from './compounds/PanelsGroup';
-import PanelsItem, { ImperativePanelHandle } from './compounds/PanelsItem';
+import PanelsItem from './compounds/PanelsItem';
import PanelsResizeHandle from './compounds/PanelsResizeHandle';
export type PanelsProps = DataTestId & {
children: ReactNode;
};
-export type { ImperativePanelHandle };
const Panels = ({
children,
'data-testid': testId = 'panels',
diff --git a/src/components/displays/panels/types.ts b/src/components/displays/Panels/types.ts
similarity index 100%
rename from src/components/displays/panels/types.ts
rename to src/components/displays/Panels/types.ts
diff --git a/src/components/displays/panels/utils/coordinates.ts b/src/components/displays/Panels/utils/coordinates.ts
similarity index 100%
rename from src/components/displays/panels/utils/coordinates.ts
rename to src/components/displays/Panels/utils/coordinates.ts
diff --git a/src/components/displays/panels/utils/cursor.ts b/src/components/displays/Panels/utils/cursor.ts
similarity index 100%
rename from src/components/displays/panels/utils/cursor.ts
rename to src/components/displays/Panels/utils/cursor.ts
diff --git a/src/components/displays/panels/utils/group.ts b/src/components/displays/Panels/utils/group.ts
similarity index 95%
rename from src/components/displays/panels/utils/group.ts
rename to src/components/displays/Panels/utils/group.ts
index 9189381..c43140b 100644
--- a/src/components/displays/panels/utils/group.ts
+++ b/src/components/displays/Panels/utils/group.ts
@@ -1,6 +1,4 @@
import { PRECISION } from '../constants';
-import type { PanelOnResize } from '../types';
-import type { PanelOnCollapse } from '../types';
import type { InitialDragState, PanelData, ResizeEvent } from '../types';
export function adjustByDelta(
@@ -118,22 +116,21 @@ export function adjustByDelta(
}
export function callPanelCallbacks(
- panelsArray: PanelData[],
+ panelsArray: (PanelData | undefined)[],
prevSizes: number[],
nextSizes: number[],
) {
nextSizes.forEach((nextSize, index) => {
const prevSize = prevSizes[index];
if (prevSize !== nextSize) {
- const { callbacksRef, collapsible } = panelsArray[index];
- const { onCollapse, onResize } = callbacksRef.current as {
- onCollapse: PanelOnCollapse | null;
- onResize: PanelOnResize | null;
- };
-
- if (onResize) {
- onResize(nextSize);
+ const panel = panelsArray[index];
+ if (!panel) {
+ return;
}
+ const { callbacksRef, collapsible } = panel;
+ const { onCollapse, onResize } = callbacksRef.current;
+
+ onResize?.(nextSize);
if (collapsible && onCollapse) {
// Falsy check handles both previous size of 0
diff --git a/src/components/displays/panels/utils/serialization.ts b/src/components/displays/Panels/utils/serialization.ts
similarity index 100%
rename from src/components/displays/panels/utils/serialization.ts
rename to src/components/displays/Panels/utils/serialization.ts
diff --git a/src/components/displays/ScoreIndicator/ScoreIndicator.stories.tsx b/src/components/displays/ScoreIndicator/ScoreIndicator.stories.tsx
index 859383a..aeb3a37 100644
--- a/src/components/displays/ScoreIndicator/ScoreIndicator.stories.tsx
+++ b/src/components/displays/ScoreIndicator/ScoreIndicator.stories.tsx
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/locale/en';
+import { faker } from '@faker-js/faker';
import type { Meta, StoryObj } from '@storybook/react';
import { Stack } from '@/skin/layout';
diff --git a/src/components/displays/ShimmerEffect/ShimmerEffect.stories.tsx b/src/components/displays/ShimmerEffect/ShimmerEffect.stories.tsx
index 2960a60..9b3ea98 100644
--- a/src/components/displays/ShimmerEffect/ShimmerEffect.stories.tsx
+++ b/src/components/displays/ShimmerEffect/ShimmerEffect.stories.tsx
@@ -34,7 +34,7 @@ export const ShimmerColorChange: Story = {
}, 6_000);
}, []);
return (
-
+
);
},
args: {
@@ -45,9 +45,9 @@ export const ShimmerColorChange: Story = {
export const WithShimmerText: Story = {
render: args => {
return (
-
+
This is Shimmer Text
-
+
);
},
};
diff --git a/src/components/displays/Stepper/Stepper.stories.tsx b/src/components/displays/Stepper/Stepper.stories.tsx
index 95243c2..e53832c 100644
--- a/src/components/displays/Stepper/Stepper.stories.tsx
+++ b/src/components/displays/Stepper/Stepper.stories.tsx
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/locale/en';
+import { faker } from '@faker-js/faker';
import type { Meta, StoryObj } from '@storybook/react';
import { ButtonAsync } from '@/skin/actions';
diff --git a/src/components/displays/Tabs/Tabs.stories.tsx b/src/components/displays/Tabs/Tabs.stories.tsx
index 6d72942..79c9554 100644
--- a/src/components/displays/Tabs/Tabs.stories.tsx
+++ b/src/components/displays/Tabs/Tabs.stories.tsx
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/locale/en';
+import { faker } from '@faker-js/faker';
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { withRouter } from 'storybook-addon-remix-react-router';
diff --git a/src/components/displays/Tabs/compounds/InternalNavLinkTitle.tsx b/src/components/displays/Tabs/compounds/InternalNavLinkTitle.tsx
index 3740848..f5f42e9 100644
--- a/src/components/displays/Tabs/compounds/InternalNavLinkTitle.tsx
+++ b/src/components/displays/Tabs/compounds/InternalNavLinkTitle.tsx
@@ -1,7 +1,7 @@
import { classNames } from '@gatewatcher/bistoury/utils-dom';
import type { DataTestId } from '@gatewatcher/bistoury/utils-types';
-import type { ReactNode } from 'react';
-import { forwardRef, useEffect } from 'react';
+import type { ReactNode, Ref } from 'react';
+import { useEffect } from 'react';
import type { To } from 'react-router-dom';
import { NavLink, useMatch, useResolvedPath } from 'react-router-dom';
@@ -13,48 +13,49 @@ export type InternalNavLinkTitleProps = DataTestId & {
children: ReactNode;
disabled?: boolean;
onClick: () => void;
+ ref?: Ref;
to: To;
variant: TitleListVariant;
};
-const InternalNavLinkTitle = forwardRef<
- HTMLAnchorElement,
- InternalNavLinkTitleProps
->(
- (
- { 'data-testid': testid, disabled, onClick, children, to, variant },
- ref,
- ) => {
- const path = useResolvedPath(to);
- const match = useMatch(`${path.pathname}/*`);
+const InternalNavLinkTitle = ({
+ 'data-testid': testid,
+ disabled,
+ onClick,
+ children,
+ ref,
+ to,
+ variant,
+}: InternalNavLinkTitleProps) => {
+ const path = useResolvedPath(to);
+ const match = useMatch(`${path.pathname}/*`);
- useEffect(() => {
- if (match) {
- onClick();
- }
- }, [match, onClick]);
+ useEffect(() => {
+ if (match) {
+ onClick();
+ }
+ }, [match, onClick]);
- return (
-
- classNames(
- styles.Title,
- styles.TitleLink,
- styles[variant],
- isActive && styles.active,
- disabled && styles.disabled,
- )
- }
- data-testid={testid}
- onClick={onClick}
- tabIndex={disabled ? -1 : undefined}
- to={to}
- >
- {children}
-
- );
- },
-);
+ return (
+
+ classNames(
+ styles.Title,
+ styles.TitleLink,
+ styles[variant],
+ isActive && styles.active,
+ disabled && styles.disabled,
+ )
+ }
+ data-testid={testid}
+ onClick={onClick}
+ tabIndex={disabled ? -1 : undefined}
+ to={to}
+ >
+ {children}
+
+ );
+};
export default InternalNavLinkTitle;
diff --git a/src/components/displays/date/DateTime/__tests__/DateTime.test.tsx b/src/components/displays/date/DateTime/__tests__/DateTime.test.tsx
index 953ebb7..6624639 100644
--- a/src/components/displays/date/DateTime/__tests__/DateTime.test.tsx
+++ b/src/components/displays/date/DateTime/__tests__/DateTime.test.tsx
@@ -6,6 +6,8 @@ import type { TestId } from '@gatewatcher/bistoury/utils-types';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { DATE_LONG_FORMAT } from '@/skin/displays/date/DateTime/constants';
+
import type { DateTimeProps } from '..';
import DateTime from '..';
@@ -67,4 +69,13 @@ describe('DateTime', () => {
await user.click(await screen.findByTestId('date-time-trigger'));
expect(onClick).toHaveBeenCalled();
});
+
+ it('should not display the tooltip', async () => {
+ renderComponent({
+ tooltipFormat: DATE_LONG_FORMAT,
+ withTooltip: false,
+ });
+ await trigger();
+ await expect(screen.findByText('22/09/2023 13:00:00')).rejects.toThrow();
+ });
});
diff --git a/src/components/displays/date/DateTime/index.tsx b/src/components/displays/date/DateTime/index.tsx
index ad69358..c25a3d5 100644
--- a/src/components/displays/date/DateTime/index.tsx
+++ b/src/components/displays/date/DateTime/index.tsx
@@ -24,6 +24,7 @@ export type DateTimeBaseProps = {
mode?: DateMode;
tooltipFormat?: string;
withEllipsis?: boolean;
+ withTooltip?: boolean;
};
export type DateTimeProps = DataTestId &
@@ -45,6 +46,7 @@ export const InternalDateTime = ({
tooltipFormat,
variant,
withEllipsis = false,
+ withTooltip = true,
...textProps
}: DateTimeProps & InternalDateTimeProps) => {
const isAbsolute = mode === 'absolute';
@@ -59,6 +61,7 @@ export const InternalDateTime = ({
+ Exclude
> & {
format?: Format;
};
diff --git a/src/components/displays/date/DateTimeRelative/index.tsx b/src/components/displays/date/DateTimeRelative/index.tsx
index 21d503b..f20364b 100644
--- a/src/components/displays/date/DateTimeRelative/index.tsx
+++ b/src/components/displays/date/DateTimeRelative/index.tsx
@@ -4,7 +4,7 @@ import { DATE_LONG_FORMAT } from '../DateTime/constants';
export type DateTimeRelativeProps = Omit<
DateTimeProps,
- Exclude
+ Exclude
>;
const DateTimeRelative = ({
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/PanelLayout.stories.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/PanelLayout.stories.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/PanelLayout.stories.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/PanelLayout.stories.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/components/DrawerPanel.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/DrawerPanel.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/components/DrawerPanel.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/DrawerPanel.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/components/LayoutProvider.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/LayoutProvider.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/components/LayoutProvider.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/LayoutProvider.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/components/MainPanel.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/MainPanel.tsx
similarity index 77%
rename from src/components/displays/panels/DrawerV2/PanelLayout/components/MainPanel.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/MainPanel.tsx
index e096d5f..13bdd4e 100644
--- a/src/components/displays/panels/DrawerV2/PanelLayout/components/MainPanel.tsx
+++ b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/MainPanel.tsx
@@ -6,16 +6,21 @@ import styles from '../styles.module.scss';
export type ContentPanelProps = DataTestId & {
children: ReactNode;
innerWidth: CSSProperties['width'];
+ innerHeight?: CSSProperties['height'];
};
const MainPanel = ({
'data-testid': testId = 'main-panel',
children,
innerWidth,
+ innerHeight,
}: ContentPanelProps) => {
return (
-
+
{children}
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/components/ResizeHandle.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/ResizeHandle.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/components/ResizeHandle.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/components/ResizeHandle.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/compounds/Close.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Close.tsx
similarity index 89%
rename from src/components/displays/panels/DrawerV2/PanelLayout/compounds/Close.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Close.tsx
index 1134f23..c96fd76 100644
--- a/src/components/displays/panels/DrawerV2/PanelLayout/compounds/Close.tsx
+++ b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Close.tsx
@@ -9,7 +9,7 @@ type CloseProps = {
closeOnEscapeKeyPress?: boolean;
};
-const Close = ({ closeOnEscapeKeyPress }: CloseProps) => {
+const Close = ({ closeOnEscapeKeyPress = true }: CloseProps) => {
const { onClose } = useDrawerV2();
const escapeKeyPressed = useKeyPressed('Escape');
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/compounds/Content.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Content.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/compounds/Content.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Content.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/compounds/Maximize.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Maximize.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/compounds/Maximize.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/compounds/Maximize.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/__tests__/useDragHandle.test.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/__tests__/useDragHandle.test.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/__tests__/useDragHandle.test.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/__tests__/useDragHandle.test.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/__tests__/utils.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/__tests__/utils.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/__tests__/utils.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/__tests__/utils.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/index.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/index.ts
similarity index 63%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/index.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/index.ts
index 2cb6d98..451e8aa 100644
--- a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/index.ts
+++ b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/index.ts
@@ -1,4 +1,3 @@
export { useDragHandle } from './useDragHandle';
-export { useOnResizeElement } from './useOnResizeElement';
export { type UseDragHandleParams } from './types';
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/BlurOnFocusLost.stories.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/BlurOnFocusLost.stories.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/BlurOnFocusLost.stories.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/BlurOnFocusLost.stories.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/DragZone.stories.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/DragZone.stories.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/DragZone.stories.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/DragZone.stories.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/DraggableDivs.stories.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/DraggableDivs.stories.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/DraggableDivs.stories.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/DraggableDivs.stories.tsx
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/styles.module.scss b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/styles.module.scss
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/stories/styles.module.scss
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/stories/styles.module.scss
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/types.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/types.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/types.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/types.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/hooks/useDragHandle.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/useDragHandle.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/hooks/useDragHandle.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/hooks/useDragHandle.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/index.tsx b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/index.tsx
similarity index 96%
rename from src/components/displays/panels/DrawerV2/PanelLayout/index.tsx
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/index.tsx
index 73cddc6..92bd095 100644
--- a/src/components/displays/panels/DrawerV2/PanelLayout/index.tsx
+++ b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/index.tsx
@@ -7,12 +7,13 @@ import {
useReducer,
} from 'react';
+import { useOnResizeElement } from '@/hooks';
+
import DrawerPanel from './components/DrawerPanel';
import LayoutProvider from './components/LayoutProvider';
import MainPanel from './components/MainPanel';
import Close from './compounds/Close';
import Maximize from './compounds/Maximize';
-import { useOnResizeElement } from './hooks';
import { actions, drawerV2Reducer } from './reducer';
import styles from './styles.module.scss';
@@ -29,6 +30,7 @@ export type PanelLayoutProps = {
onCloseDrawer?: () => void;
showDrawer?: boolean;
onResize?: (width: number) => void;
+ mainPanelHeight?: CSSProperties['height'];
};
const PanelLayout = ({
@@ -43,6 +45,7 @@ const PanelLayout = ({
onCloseDrawer = () => {},
showDrawer,
onResize = () => {},
+ mainPanelHeight,
}: PanelLayoutProps) => {
const [state, dispatch] = useReducer(drawerV2Reducer, {
computedMaxWidth: 0,
@@ -109,6 +112,7 @@ const PanelLayout = ({
? contentInnerWidth
: Math.min(contentInnerWidth, state.containerWidth)
}
+ innerHeight={mainPanelHeight}
>
{mainContent}
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/reducer/actions.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/actions.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/reducer/actions.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/actions.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/reducer/index.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/index.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/reducer/index.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/index.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/reducer/reducer.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/reducer.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/reducer/reducer.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/reducer.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/reducer/types.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/types.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/reducer/types.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/reducer/types.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/styles.module.scss b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/styles.module.scss
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/styles.module.scss
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/styles.module.scss
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/utils/__tests__/utils.test.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/__tests__/utils.test.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/utils/__tests__/utils.test.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/__tests__/utils.test.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/utils/drawerPanelWidth.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/drawerPanelWidth.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/utils/drawerPanelWidth.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/drawerPanelWidth.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/utils/index.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/index.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/utils/index.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/index.ts
diff --git a/src/components/displays/panels/DrawerV2/PanelLayout/utils/types.ts b/src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/types.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/PanelLayout/utils/types.ts
rename to src/components/displays/drawerPanels/DrawerV2/PanelLayout/utils/types.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/Provider.stories.tsx b/src/components/displays/drawerPanels/DrawerV2/Provider/Provider.stories.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/Provider.stories.tsx
rename to src/components/displays/drawerPanels/DrawerV2/Provider/Provider.stories.tsx
diff --git a/src/components/displays/panels/DrawerV2/Provider/__tests__/Provider.test.tsx b/src/components/displays/drawerPanels/DrawerV2/Provider/__tests__/Provider.test.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/__tests__/Provider.test.tsx
rename to src/components/displays/drawerPanels/DrawerV2/Provider/__tests__/Provider.test.tsx
diff --git a/src/components/displays/panels/DrawerV2/Provider/__tests__/utils.tsx b/src/components/displays/drawerPanels/DrawerV2/Provider/__tests__/utils.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/__tests__/utils.tsx
rename to src/components/displays/drawerPanels/DrawerV2/Provider/__tests__/utils.tsx
diff --git a/src/components/displays/panels/DrawerV2/Provider/components/DrawerProvider.tsx b/src/components/displays/drawerPanels/DrawerV2/Provider/components/DrawerProvider.tsx
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/components/DrawerProvider.tsx
rename to src/components/displays/drawerPanels/DrawerV2/Provider/components/DrawerProvider.tsx
diff --git a/src/components/displays/panels/DrawerV2/Provider/hooks/useCurrentDrawer.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useCurrentDrawer.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/hooks/useCurrentDrawer.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useCurrentDrawer.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerOptions.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerOptions.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerOptions.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerOptions.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerPersistence.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerPersistence.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerPersistence.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerPersistence.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerSearchParams.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerSearchParams.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerSearchParams.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerSearchParams.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerV2.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerV2.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/hooks/useDrawerV2.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/hooks/useDrawerV2.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/index.tsx b/src/components/displays/drawerPanels/DrawerV2/Provider/index.tsx
similarity index 80%
rename from src/components/displays/panels/DrawerV2/Provider/index.tsx
rename to src/components/displays/drawerPanels/DrawerV2/Provider/index.tsx
index 5667b52..401fe22 100644
--- a/src/components/displays/panels/DrawerV2/Provider/index.tsx
+++ b/src/components/displays/drawerPanels/DrawerV2/Provider/index.tsx
@@ -4,6 +4,7 @@ import {
type ReactNode,
useCallback,
useEffect,
+ useMemo,
useRef,
useState,
} from 'react';
@@ -11,6 +12,7 @@ import { useLocation } from 'react-router-dom';
import DrawerProvider from './components/DrawerProvider';
import { useDrawerOptions } from './hooks/useDrawerOptions';
+import { useDrawerPersistence } from './hooks/useDrawerPersistence';
import { useDrawerSearchParams } from './hooks/useDrawerSearchParams';
import type { UseDrawerV2Options } from './hooks/useDrawerV2';
import type { DrawerMatches } from './types';
@@ -32,6 +34,9 @@ export type DrawerV2ProviderProps = {
const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
const { pathname } = useLocation();
const drawerSearchParams = useDrawerSearchParams();
+
+ const persistence = useDrawerPersistence();
+
const [currentId, setCurrentId] = useState(() => {
const id = drawerSearchParams.getId();
return id && matches[id] ? id : null;
@@ -44,13 +49,18 @@ const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
);
const currentPathnameRef = useRef(currentId ? pathname : '');
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const openedOnMount = useMemo(() => drawerSearchParams.getId(), []);
+
const optionsStore = useDrawerOptions();
- const { keepOn, closeOn, encode, clean } = optionsStore.get(currentId ?? '');
+ const optionsFromStore = optionsStore.get(currentId ?? '');
+ const { keepOn, closeOn, clean, encode } = optionsFromStore;
const canBeKeptOpen =
(!keepOn || keepOn.test(pathname)) && !closeOn?.test(pathname);
const persistenceRef = useRef({ encode, clean });
+
useEffect(() => {
persistenceRef.current = { encode, clean };
}, [clean, encode]);
@@ -65,13 +75,16 @@ const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
setCurrentId(id);
setProps(props);
setContent(getContentResult.content);
+
if (options) {
optionsStore.register(id, options);
}
- if (id !== currentId) {
- persistenceRef.current.clean?.();
+
+ if (!!currentId && id !== currentId) {
+ options?.clean?.();
}
- persistenceRef.current.encode?.(id, props as SearchParamsObject);
+
+ options?.encode?.(id, props as SearchParamsObject);
currentPathnameRef.current = pathname;
},
[currentId, matches, optionsStore, pathname],
@@ -81,11 +94,9 @@ const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
setCurrentId(null);
setProps(null);
setContent(null);
- if (drawerSearchParams.getId()) {
- persistenceRef.current.clean?.();
- }
+ persistenceRef.current.clean?.();
currentPathnameRef.current = '';
- }, [drawerSearchParams]);
+ }, []);
const closeWithId = useCallback(
(id: string) => {
@@ -98,6 +109,8 @@ const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
[closeCurrent, currentId],
);
+ const isOpened = !!currentId;
+
useEffect(() => {
if (
currentId &&
@@ -108,20 +121,14 @@ const Provider = ({ children, matches }: DrawerV2ProviderProps) => {
} else {
currentPathnameRef.current = pathname;
}
- }, [
- canBeKeptOpen,
- closeCurrent,
- currentId,
- drawerSearchParams,
- optionsStore,
- pathname,
- ]);
+ }, [canBeKeptOpen, closeCurrent, currentId, pathname]);
useEffect(() => {
- if (currentId && !drawerSearchParams.getId()) {
- persistenceRef.current.encode?.(currentId, props as SearchParamsObject);
+ if (openedOnMount && !isOpened) {
+ persistence.clean();
}
- }, [currentId, props, pathname, drawerSearchParams, optionsStore]);
+ //eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [openedOnMount, isOpened]);
return (
{
close: closeCurrent,
content,
currentId,
- isOpened: !!currentId,
+ isOpened,
options: currentId ? optionsStore.get(currentId) : {},
})
: children}
diff --git a/src/components/displays/panels/DrawerV2/Provider/types.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/types.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/types.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/types.ts
diff --git a/src/components/displays/panels/DrawerV2/Provider/utils.ts b/src/components/displays/drawerPanels/DrawerV2/Provider/utils.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/Provider/utils.ts
rename to src/components/displays/drawerPanels/DrawerV2/Provider/utils.ts
diff --git a/src/components/displays/panels/DrawerV2/constants.ts b/src/components/displays/drawerPanels/DrawerV2/constants.ts
similarity index 100%
rename from src/components/displays/panels/DrawerV2/constants.ts
rename to src/components/displays/drawerPanels/DrawerV2/constants.ts
diff --git a/src/components/displays/panels/DrawerV2/index.tsx b/src/components/displays/drawerPanels/DrawerV2/index.tsx
similarity index 97%
rename from src/components/displays/panels/DrawerV2/index.tsx
rename to src/components/displays/drawerPanels/DrawerV2/index.tsx
index f896965..00d9ffb 100644
--- a/src/components/displays/panels/DrawerV2/index.tsx
+++ b/src/components/displays/drawerPanels/DrawerV2/index.tsx
@@ -28,7 +28,7 @@ DrawerV2.SubHeader = ({
);
DrawerV2.Body = ({
- 'data-testid': testId = 'drawer-sub-body',
+ 'data-testid': testId = 'drawer-body',
...rest
}: BodyProps) => ;
diff --git a/src/components/displays/panels/PanelContentLayout/PanelContentLayout.stories.tsx b/src/components/displays/drawerPanels/PanelContentLayout/PanelContentLayout.stories.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/PanelContentLayout.stories.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/PanelContentLayout.stories.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/components/StickyProvider.tsx b/src/components/displays/drawerPanels/PanelContentLayout/components/StickyProvider.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/components/StickyProvider.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/components/StickyProvider.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/Actions.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/Actions.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/Actions.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/Actions.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/Body.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/Body.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/Body.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/Body.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/Footer.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/Footer.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/Footer.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/Footer.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/Header.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/Header.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/Header.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/Header.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/SubHeader.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/SubHeader.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/SubHeader.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/SubHeader.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/compounds/Title.tsx b/src/components/displays/drawerPanels/PanelContentLayout/compounds/Title.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/compounds/Title.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/compounds/Title.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/index.tsx b/src/components/displays/drawerPanels/PanelContentLayout/index.tsx
similarity index 100%
rename from src/components/displays/panels/PanelContentLayout/index.tsx
rename to src/components/displays/drawerPanels/PanelContentLayout/index.tsx
diff --git a/src/components/displays/panels/PanelContentLayout/styles.module.scss b/src/components/displays/drawerPanels/PanelContentLayout/styles.module.scss
similarity index 94%
rename from src/components/displays/panels/PanelContentLayout/styles.module.scss
rename to src/components/displays/drawerPanels/PanelContentLayout/styles.module.scss
index c06e4be..6573fb7 100644
--- a/src/components/displays/panels/PanelContentLayout/styles.module.scss
+++ b/src/components/displays/drawerPanels/PanelContentLayout/styles.module.scss
@@ -17,7 +17,7 @@
.Body {
flex-grow: 1;
- padding: var(--spacing-9) var(--spacing-9) var(--spacing-9) var(--spacing-9);
+ padding: 0 var(--spacing-9) var(--spacing-9) var(--spacing-9);
background-color: var(--body-background-color);
&.fitContent {
@@ -46,7 +46,7 @@
.SubHeader {
width: 100%;
- padding: 0 var(--spacing-9);
+ padding: 0 var(--spacing-9) var(--spacing-9);
}
.Header {
diff --git a/src/components/displays/panels/SidePanel/Panel.tsx b/src/components/displays/drawerPanels/SidePanel/Panel.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/Panel.tsx
rename to src/components/displays/drawerPanels/SidePanel/Panel.tsx
diff --git a/src/components/displays/panels/SidePanel/PanelLayout.tsx b/src/components/displays/drawerPanels/SidePanel/PanelLayout.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/PanelLayout.tsx
rename to src/components/displays/drawerPanels/SidePanel/PanelLayout.tsx
diff --git a/src/components/displays/panels/SidePanel/Provider.tsx b/src/components/displays/drawerPanels/SidePanel/Provider.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/Provider.tsx
rename to src/components/displays/drawerPanels/SidePanel/Provider.tsx
diff --git a/src/components/displays/panels/SidePanel/SidePanel.stories.tsx b/src/components/displays/drawerPanels/SidePanel/SidePanel.stories.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/SidePanel.stories.tsx
rename to src/components/displays/drawerPanels/SidePanel/SidePanel.stories.tsx
diff --git a/src/components/displays/panels/SidePanel/__tests__/SidePanel.test.tsx b/src/components/displays/drawerPanels/SidePanel/__tests__/SidePanel.test.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/__tests__/SidePanel.test.tsx
rename to src/components/displays/drawerPanels/SidePanel/__tests__/SidePanel.test.tsx
diff --git a/src/components/displays/panels/SidePanel/__tests__/utils.tsx b/src/components/displays/drawerPanels/SidePanel/__tests__/utils.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/__tests__/utils.tsx
rename to src/components/displays/drawerPanels/SidePanel/__tests__/utils.tsx
diff --git a/src/components/displays/panels/SidePanel/compounds/Close.tsx b/src/components/displays/drawerPanels/SidePanel/compounds/Close.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/compounds/Close.tsx
rename to src/components/displays/drawerPanels/SidePanel/compounds/Close.tsx
diff --git a/src/components/displays/panels/SidePanel/compounds/Content.tsx b/src/components/displays/drawerPanels/SidePanel/compounds/Content.tsx
similarity index 100%
rename from src/components/displays/panels/SidePanel/compounds/Content.tsx
rename to src/components/displays/drawerPanels/SidePanel/compounds/Content.tsx
diff --git a/src/components/displays/panels/SidePanel/index.ts b/src/components/displays/drawerPanels/SidePanel/index.ts
similarity index 100%
rename from src/components/displays/panels/SidePanel/index.ts
rename to src/components/displays/drawerPanels/SidePanel/index.ts
diff --git a/src/components/displays/panels/SidePanel/styles.module.scss b/src/components/displays/drawerPanels/SidePanel/styles.module.scss
similarity index 100%
rename from src/components/displays/panels/SidePanel/styles.module.scss
rename to src/components/displays/drawerPanels/SidePanel/styles.module.scss
diff --git a/src/components/displays/floating/Dropdown/Dropdown.stories.tsx b/src/components/displays/floating/Dropdown/Dropdown.stories.tsx
index 2f5905b..4b99c26 100644
--- a/src/components/displays/floating/Dropdown/Dropdown.stories.tsx
+++ b/src/components/displays/floating/Dropdown/Dropdown.stories.tsx
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/locale/fr';
+import { faker } from '@faker-js/faker';
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import { withRouter } from 'storybook-addon-remix-react-router';
diff --git a/src/components/displays/floating/Floating/FloatingElement/index.tsx b/src/components/displays/floating/Floating/FloatingElement/index.tsx
index 421ab26..4e166d4 100644
--- a/src/components/displays/floating/Floating/FloatingElement/index.tsx
+++ b/src/components/displays/floating/Floating/FloatingElement/index.tsx
@@ -9,7 +9,6 @@ import { isFunction } from '@gatewatcher/bistoury/utils-lang';
import { suffixTestId } from '@gatewatcher/bistoury/utils-tests';
import type { Modify, TestId } from '@gatewatcher/bistoury/utils-types';
import type { MouseEvent, Ref } from 'react';
-import { forwardRef } from 'react';
import { Stack } from '@/skin/layout';
@@ -28,126 +27,123 @@ export type FloatingElementProps = Modify<
context: FloatingContext;
maxHeight?: number;
middlewareData: MiddlewareData;
+ ref?: Ref;
x: number | null;
y: number | null;
};
-const FloatingElement = forwardRef(
- (
- {
- arrowClassName,
- arrowRef,
- className,
- content: contentProps,
- context,
- 'data-testid': baseTestId,
- duration = DEFAULT_DURATION,
- isDisabled,
- maxHeight,
- middlewareData,
- padding,
- placement,
- strategy,
- withArrow,
- withSmoothAnimation,
- x,
- y,
- ...rest
- },
- ref,
- ) => {
- const positionComputed = x !== null && y !== null;
+const FloatingElement = ({
+ arrowClassName,
+ arrowRef,
+ className,
+ content: contentProps,
+ context,
+ 'data-testid': baseTestId,
+ duration = DEFAULT_DURATION,
+ isDisabled,
+ maxHeight,
+ middlewareData,
+ padding,
+ placement,
+ ref,
+ strategy,
+ withArrow,
+ withSmoothAnimation,
+ x,
+ y,
+ ...rest
+}: FloatingElementProps) => {
+ const positionComputed = x !== null && y !== null;
- const ctx = useFloatingContext();
+ const ctx = useFloatingContext();
- const direction = placement.split('-')[0];
- const isVertical = direction === 'top' || direction === 'bottom';
- const isHorizontal = direction === 'left' || direction === 'right';
+ const direction = placement.split('-')[0];
+ const isVertical = direction === 'top' || direction === 'bottom';
+ const isHorizontal = direction === 'left' || direction === 'right';
- const transition = useTransitionStyles(context, {
- duration: DURATIONS_MS[duration],
- initial: {
- ...(withSmoothAnimation && {
- transform: isVertical
- ? `translateY(${10 * (direction === 'top' ? 1 : -1)}px)`
- : `translateX(${10 * (direction === 'left' ? 1 : -1)}px)`,
- }),
- opacity: 0,
- },
- });
+ const transition = useTransitionStyles(context, {
+ duration: DURATIONS_MS[duration],
+ initial: {
+ ...(withSmoothAnimation && {
+ transform: isVertical
+ ? `translateY(${10 * (direction === 'top' ? 1 : -1)}px)`
+ : `translateX(${10 * (direction === 'left' ? 1 : -1)}px)`,
+ }),
+ opacity: 0,
+ },
+ });
- if (isDisabled) {
- return null;
- }
+ if (isDisabled) {
+ return null;
+ }
- const content = isFunction(contentProps) ? contentProps(ctx) : contentProps;
+ const content = isFunction(contentProps) ? contentProps(ctx) : contentProps;
- const handleClick = (ev: MouseEvent) => {
- ev.stopPropagation();
- };
+ const handleClick = (ev: MouseEvent) => {
+ ev.stopPropagation();
+ };
- return (
- <>
- {transition.isMounted && content && (
-
+ {transition.isMounted && content && (
+
+
-
- {content}
-
+ {content}
+
- {withArrow && (
-
- )}
-
- )}
- >
- );
- },
-);
+ {withArrow && (
+
+ )}
+
+ )}
+ >
+ );
+};
export default FloatingElement;
diff --git a/src/components/displays/floating/Floating/TriggerElement/index.tsx b/src/components/displays/floating/Floating/TriggerElement/index.tsx
index 70daf15..95de382 100644
--- a/src/components/displays/floating/Floating/TriggerElement/index.tsx
+++ b/src/components/displays/floating/Floating/TriggerElement/index.tsx
@@ -1,7 +1,7 @@
import { classNames } from '@gatewatcher/bistoury/utils-dom';
import { suffixTestId } from '@gatewatcher/bistoury/utils-tests';
import type { HTMLAttributes, MouseEvent, ReactElement, Ref } from 'react';
-import { cloneElement, forwardRef } from 'react';
+import { cloneElement } from 'react';
import type { FloatingProps } from '..';
@@ -13,54 +13,51 @@ export type TriggerElementProps = HTMLAttributes &
children: ReactElement;
className?: string;
withMinWidthTrigger?: boolean;
+ ref?: Ref;
};
-const TriggerElement = forwardRef(
- (
- {
- children,
- 'data-testid': baseTestId,
- className,
- withStopPropagation,
- withMinWidthTrigger,
- ...rest
- }: TriggerElementProps,
- ref,
- ) => {
- const isString = typeof children.type === 'string';
-
- const divOnClick = (event: MouseEvent) => {
- if (withStopPropagation) {
- event.stopPropagation();
- }
- rest.onClick?.(event);
- };
-
- const testId = suffixTestId(baseTestId, 'trigger');
+const TriggerElement = ({
+ children,
+ 'data-testid': baseTestId,
+ className,
+ withStopPropagation,
+ withMinWidthTrigger,
+ ref,
+ ...rest
+}: TriggerElementProps) => {
+ const isString = typeof children.type === 'string';
+
+ const divOnClick = (event: MouseEvent) => {
+ if (withStopPropagation) {
+ event.stopPropagation();
+ }
+ rest.onClick?.(event);
+ };
- return isString ? (
- cloneElement(children, {
- 'data-testid': testId,
- ref,
- className: classNames(className),
- ...rest,
- })
- ) : (
- }
- className={classNames(
- styles.TriggerElement,
- className,
- withMinWidthTrigger && styles.MinWidth,
- )}
- data-testid={testId}
- {...rest}
- onClick={divOnClick}
- >
- {children}
-
- );
- },
-);
+ const testId = suffixTestId(baseTestId, 'trigger');
+
+ return isString ? (
+ cloneElement(children, {
+ 'data-testid': testId,
+ ref,
+ className: classNames(className),
+ ...rest,
+ } as Partial)
+ ) : (
+ }
+ className={classNames(
+ styles.TriggerElement,
+ className,
+ withMinWidthTrigger && styles.MinWidth,
+ )}
+ data-testid={testId}
+ {...rest}
+ onClick={divOnClick}
+ >
+ {children}
+
+ );
+};
export default TriggerElement;
diff --git a/src/components/displays/floating/Modal/index.tsx b/src/components/displays/floating/Modal/index.tsx
index b0e063a..e26c1f7 100644
--- a/src/components/displays/floating/Modal/index.tsx
+++ b/src/components/displays/floating/Modal/index.tsx
@@ -1,7 +1,7 @@
import { FloatingFocusManager, useTransitionStyles } from '@floating-ui/react';
import { classNames, stylesToCamelCase } from '@gatewatcher/bistoury/utils-dom';
import type { ReactElement, Ref } from 'react';
-import { Fragment, forwardRef, useState } from 'react';
+import { Fragment, useState } from 'react';
import Backdrop from '../Backdrop';
import type { FloatingProps } from '../Floating';
@@ -65,64 +65,60 @@ const Modal = (props: ModalProps) => {
type WrapperComponentType = FloatingWrapperType &
Required>;
-const WrapperComponent = forwardRef(
- (
- {
- ctx,
- children,
- duration,
- 'data-testid': testId,
- scrollOn,
- size,
- withBackdrop,
- }: WrapperComponentType,
- ref,
- ) => {
- const transition = useTransitionStyles(ctx, {
- duration,
- });
+const WrapperComponent = ({
+ ctx,
+ children,
+ duration,
+ 'data-testid': testId,
+ scrollOn,
+ size,
+ withBackdrop,
+ ref,
+}: WrapperComponentType) => {
+ const transition = useTransitionStyles(ctx, {
+ duration,
+ });
- const slideTransition = useTransitionStyles(ctx, {
- duration,
- initial: {
- transform: 'translateY(-26px)',
- },
- });
+ const slideTransition = useTransitionStyles(ctx, {
+ duration,
+ initial: {
+ transform: 'translateY(-26px)',
+ },
+ });
- return (
- <>
-
+
+
- }
+ className={classNames(
+ styles.Modal,
+ styles.size,
+ stylesToCamelCase(styles, 'size', size),
+ stylesToCamelCase(styles, 'scroll', 'on', scrollOn),
+ )}
+ data-testid={testId}
+ role="dialog"
>
- }
- className={classNames(
- styles.Modal,
- styles.size,
- stylesToCamelCase(styles, 'size', size),
- stylesToCamelCase(styles, 'scroll', 'on', scrollOn),
- )}
- data-testid={testId}
- role="dialog"
- >
- {children}
-
-
-
- >
- );
- },
-);
+ {children}
+
+
+
+ >
+ );
+};
const ModalContent = ({
content,
diff --git a/src/components/displays/index.ts b/src/components/displays/index.ts
index 7edff86..975655a 100644
--- a/src/components/displays/index.ts
+++ b/src/components/displays/index.ts
@@ -1,13 +1,23 @@
import type { StatusIndicatorProps } from '@/skin/displays/StatusIndicator';
import StatusIndicator from '@/skin/displays/StatusIndicator';
+import DrawerV2 from '@/skin/displays/drawerPanels/DrawerV2';
+import {
+ DRAWER_CLOSE_EVERYWHERE,
+ DRAWER_KEEP_EVERYWHERE,
+} from '@/skin/displays/drawerPanels/DrawerV2/constants';
+import SidePanel, {
+ type SidePanelProps,
+} from '@/skin/displays/drawerPanels/SidePanel';
import Accordion, { type AccordionProps } from './Accordion';
import Avatar, { type AvatarProps } from './Avatar';
import AvatarUsername, { type AvatarUsernameProps } from './AvatarUsername';
import Badge, { type BadgeProps } from './Badge';
+import Banner, { type BannerProps } from './Banner';
import Carousel, { type CarouselProps } from './Carousel';
import Changelog, { type ChangelogProps } from './Changelog';
import Code, { type CodeProps } from './Code';
+import CodeBlock, { type CodeBlockProps } from './CodeBlock';
import ColorIndicator, { type ColorIndicatorProps } from './ColorIndicator';
import Comments, { type CommentsProps } from './Comments';
import Divider, { type DividerProps } from './Divider';
@@ -22,8 +32,11 @@ import KeyValue, { type KeyValueProps } from './KeyValue';
import KeyValueDisplay, { type KeyValueDisplayProps } from './KeyValueDisplay';
import Label, { type LabelProps } from './Label';
import Markdown, { type MarkdownProps } from './Markdown';
+import MermaidViewer, { type MermaidViewerProps } from './MermaidViewer';
+import Navigator, { type NavigatorProps } from './Navigator';
import ObfuscatedText, { type ObfuscatedTextProps } from './ObfuscatedText';
import ObjectGrid, { type ObjectGridProps } from './ObjectGrid';
+import Panels, { type PanelsProps } from './Panels';
import Pill, { type PillProps } from './Pill';
import PoweredByGatewatcher, {
type PoweredByGatewatcherProps,
@@ -61,6 +74,7 @@ import EllipsisDataModal, {
import EllipsisDataPopover, {
type EllipsisDataPopoverProps,
} from './ellipsis/EllipsisDataPopover';
+import Backdrop, { type BackdropProps } from './floating/Backdrop';
import Dropdown, { type DropdownProps } from './floating/Dropdown';
import Modal, { type ModalProps } from './floating/Modal';
import Popover, { type PopoverProps } from './floating/Popover';
@@ -70,13 +84,6 @@ import IconAttachment, {
type IconAttachmentProps,
} from './icons/IconAttachment';
import IconContained, { type IconContainedProps } from './icons/IconContained';
-import Panels, { ImperativePanelHandle, PanelsProps } from './panels';
-import DrawerV2 from './panels/DrawerV2';
-import {
- DRAWER_CLOSE_EVERYWHERE,
- DRAWER_KEEP_EVERYWHERE,
-} from './panels/DrawerV2/constants';
-import SidePanel, { type SidePanelProps } from './panels/SidePanel';
import SelectableTree, {
type SelectableTreeProps,
} from './tree/SelectableTree';
@@ -89,9 +96,9 @@ export {
type DrawerMatches,
type UseDrawerV2Options,
type UseDrawerV2Return,
-} from './panels/DrawerV2';
+} from '@/skin/displays/drawerPanels/DrawerV2';
-export { useSidePanel } from './panels/SidePanel/Provider';
+export { useSidePanel } from '@/skin/displays/drawerPanels/SidePanel/Provider';
export type { IconName } from './icons/types';
export type { IllustrationName } from './Illustration/types';
export type { TreeNodeId } from './tree/SelectableTree/types';
@@ -105,7 +112,9 @@ export {
Accordion,
Avatar,
AvatarUsername,
+ Backdrop,
Badge,
+ Banner,
Card,
CardSelectable,
Carousel,
@@ -113,6 +122,7 @@ export {
Chip,
ChipCustom,
Code,
+ CodeBlock,
ColorIndicator,
Comments,
DateTime,
@@ -138,7 +148,9 @@ export {
KeyValueDisplay,
Label,
Markdown,
+ MermaidViewer,
Modal,
+ Navigator,
ObfuscatedText,
ObjectGrid,
Panels,
@@ -169,7 +181,9 @@ export type {
AccordionProps,
AvatarProps,
AvatarUsernameProps,
+ BackdropProps,
BadgeProps,
+ BannerProps,
CardProps,
CardSelectableProps,
CarouselProps,
@@ -177,6 +191,7 @@ export type {
ChipCustomProps,
ChipProps,
CodeProps,
+ CodeBlockProps,
ColorIndicatorProps,
CommentsProps,
DateTimeAbsoluteProps,
@@ -196,13 +211,14 @@ export type {
IconContainedProps,
IconProps,
IllustrationProps,
- ImperativePanelHandle,
InfoTooltipProps,
KeyValueDisplayProps,
KeyValueProps,
LabelProps,
MarkdownProps,
+ MermaidViewerProps,
ModalProps,
+ NavigatorProps,
ObfuscatedTextProps,
ObjectGridProps,
PanelsProps,
diff --git a/src/components/displays/tree/SelectableTree/SelectableTree.stories.tsx b/src/components/displays/tree/SelectableTree/SelectableTree.stories.tsx
index 587c4c5..95c313d 100644
--- a/src/components/displays/tree/SelectableTree/SelectableTree.stories.tsx
+++ b/src/components/displays/tree/SelectableTree/SelectableTree.stories.tsx
@@ -1,4 +1,4 @@
-import { faker } from '@faker-js/faker/locale/en';
+import { faker } from '@faker-js/faker';
import type { Meta, StoryObj } from '@storybook/react';
import { Chip } from '@/skin/displays';
diff --git a/src/components/displays/tree/SelectableTree/utils.ts b/src/components/displays/tree/SelectableTree/utils.ts
index 0b6d7aa..a52f3a1 100644
--- a/src/components/displays/tree/SelectableTree/utils.ts
+++ b/src/components/displays/tree/SelectableTree/utils.ts
@@ -1,4 +1,5 @@
import { Children } from 'react';
+import type { JSX } from 'react';
import type { NodeDataType, TreeNodeId } from './types';
diff --git a/src/components/feedback/Breadcrumb/index.tsx b/src/components/feedback/Breadcrumb/index.tsx
index 83a3df7..9369601 100644
--- a/src/components/feedback/Breadcrumb/index.tsx
+++ b/src/components/feedback/Breadcrumb/index.tsx
@@ -32,9 +32,12 @@ const Breadcrumb = ({
))}