Skip to content

Commit

Permalink
feat: Use StudioContentMenu in ContentLibrary (#13927)
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Nov 6, 2024
1 parent 70f57bf commit 95a0859
Show file tree
Hide file tree
Showing 40 changed files with 212 additions and 225 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { AppContentLibrary } from './AppContentLibrary';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { renderWithProviders } from '../../test/mocks';

describe('AppContentLibrary', () => {
it('renders the AppContentLibrary with codeLists and images resources', () => {
Expand All @@ -20,5 +21,5 @@ describe('AppContentLibrary', () => {
});

const renderAppContentLibrary = () => {
render(<AppContentLibrary />);
renderWithProviders()(<AppContentLibrary />);
};
3 changes: 2 additions & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
"app_content_library.images.page_name": "Bilder",
"app_content_library.info_box.title": "En kort beskrivelse om bruk av og hensikt med ressursen i bibliotket.",
"app_content_library.landing_page.description": "Når du utvikler skjemaer, er det nyttig å samle ulike filer og ressurser på ett sted. I biblioteket kan du laste opp ting andre har laget som du har bruk for, eller selv lage det du trenger til de tjenestene du utvikler.",
"app_content_library.landing_page.page_name": "Bibliotek",
"app_content_library.landing_page.page_name": "Om biblioteket",
"app_content_library.landing_page.title": "Med biblioteket kan du effektivt utvikle mer konsekvente tjenester",
"app_content_library.library_heading": "Bibliotek",
"app_create_release.build_version": "Bygg versjon",
"app_create_release.check_status": "Sjekker status på appen din...",
"app_create_release.loading": "Laster...",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,9 @@
.libraryContainer {
background-color: var(--fds-semantic-background-default);
border-radius: var(--fds-border_radius-xlarge);
border-bottom: solid 1px var(--fds-semantic-border-neutral-subtle);
border: solid 1px var(--fds-semantic-border-neutral-subtle);
overflow: hidden;
min-height: 80%;
max-height: 100%;
width: 100%;
}

.component {
padding: var(--fds-spacing-10);
}

.libraryContent {
display: grid;
grid-template-columns: 0.7fr 3.3fr 1fr;
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React from 'react';
import { ContentLibrary } from './ContentLibrary';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { mockPagesConfig } from '../../mocks/mockPagesConfig';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { RouterContext } from '../contexts/RouterContext';
import type { PageName } from '../types/PageName';
import { renderWithBrowserRouter } from '../../test-utils/renderWithBrowserRouter';

const navigateMock = jest.fn();

describe('ContentLibrary', () => {
it('renders the ContentLibrary with landingPage by default', () => {
renderContentLibrary();
const libraryHeader = screen.getByRole('heading', {
name: textMock('app_content_library.landing_page.page_name'),
name: textMock('app_content_library.library_heading'),
});
const landingPageTitle = screen.getByRole('heading', {
name: textMock('app_content_library.landing_page.title'),
Expand Down Expand Up @@ -51,7 +52,7 @@ describe('ContentLibrary', () => {
});

const renderContentLibrary = (currentPage: PageName = undefined) => {
render(
renderWithBrowserRouter(
<RouterContext.Provider value={{ currentPage, navigate: navigateMock }}>
<ContentLibrary pages={mockPagesConfig} />
</RouterContext.Provider>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import type { PageComponent } from '../utils/router/RouterRouteMapper';
import { RouterRouteMapperImpl } from '../utils/router/RouterRouteMapper';
import type { PagePropsMap, PagesConfig } from '../types/PagesProps';
import classes from './ContentLibrary.module.css';
import { InfoBox } from './InfoBox';
import { PagesRouter } from './PagesRouter';
import { LibraryHeader } from './LibraryHeader';
import { StudioHeading } from '@studio/components';
import type { PageName } from '../types/PageName';
import { LibraryBody } from './LibraryBody';

type ContentLibraryProps = {
pages: PagesConfig;
Expand All @@ -19,37 +18,25 @@ export function ContentLibrary({ pages }: ContentLibraryProps): React.ReactEleme
return <ContentLibraryForPage pages={pages} currentPage={currentPage} />;
}

type ContentLibraryForPageProps<T extends PageName = 'landingPage'> = {
type ContentLibraryForPageProps<T extends PageName> = {
pages: PagesConfig;
currentPage: T;
};

function ContentLibraryForPage<T extends PageName = 'landingPage'>({
function ContentLibraryForPage<T extends PageName>({
pages,
currentPage,
}: ContentLibraryForPageProps<T>): React.ReactElement {
const router = new RouterRouteMapperImpl(pages);

const Component: PageComponent<Required<PagePropsMap>[T]> =
router.configuredRoutes.get(currentPage);
const Component: PageComponent<PagePropsMap<T>> = router.configuredRoutes.get(currentPage);
if (!Component) return <StudioHeading>404 Page Not Found</StudioHeading>; // Show the NotFound page from app-dev instead

const componentPropsAreExternal = currentPage !== 'landingPage';

const componentProps: Required<PagePropsMap>[T] =
componentPropsAreExternal && (pages[currentPage].props as Required<PagePropsMap>[T]);

return (
<div className={classes.libraryBackground}>
<div className={classes.libraryContainer}>
<LibraryHeader />
<div className={classes.libraryContent}>
<PagesRouter pageNames={Object.keys(pages) as PageName[]} />
<div className={classes.component}>
<Component {...componentProps} />
</div>
<InfoBox pageName={currentPage} />
</div>
<LibraryBody<T> Component={Component} pages={pages} currentPage={currentPage} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
.infoBoxContainer {
border-radius: var(--fds-border_radius-large);
border: 1px solid var(--fds-semantic-border-neutral-subtle);
width: 15vw;
overflow: hidden;
height: fit-content;
margin: var(--fds-spacing-10);
margin: var(--fds-spacing-8);
}

.infoBoxContainer img {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { InfoBox } from './InfoBox';
import { render, screen } from '@testing-library/react';
import { textMock } from '@studio/testing/mocks/i18nMock';
import type { PageName } from '../../types/PageName';
import type { PageName } from '../../../types/PageName';
import { infoBoxConfigs } from './infoBoxConfigs';

const pageNameMock: PageName = 'codeList';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import classes from './InfoBox.module.css';
import { StudioParagraph } from '@studio/components';
import { useTranslation } from 'react-i18next';
import type { PageName } from '../../types/PageName';
import type { PageName } from '../../../types/PageName';
import { infoBoxConfigs } from './infoBoxConfigs';

type InfoBoxProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PageName } from '../../types/PageName';
import type { InfoBoxProps } from '../../types/InfoBoxProps';
import type { PageName } from '../../../types/PageName';
import type { InfoBoxProps } from '../../../types/InfoBoxProps';

export type InfoBoxConfigs = Partial<{ [T in PageName]: InfoBoxProps }>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.libraryContent {
display: grid;
grid-template-columns: 2fr 7fr 3fr;
height: 100%;
}

.component {
padding: var(--fds-spacing-10);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import classes from './LibraryBody.module.css';
import { PagesRouter } from './PagesRouter';
import { InfoBox } from './InfoBox';
import type { PagePropsMap, PagesConfig } from '../../types/PagesProps';
import type { PageName } from '../../types/PageName';
import type { PageComponent } from '../../utils/router/RouterRouteMapper';

type LibraryBodyProps<T extends PageName> = {
Component: PageComponent<PagePropsMap<T>>;
pages: PagesConfig;
currentPage: T;
};

export function LibraryBody<T extends PageName>({
Component,
pages,
currentPage,
}: LibraryBodyProps<T>) {
const componentProps: PagePropsMap<T> = getComponentProps(pages, currentPage);

return (
<div className={classes.libraryContent}>
<PagesRouter pageNames={getAllPageNamesFromPagesConfig(pages)} />
<Page<T> Component={Component} componentProps={componentProps} currentPage={currentPage} />
</div>
);
}

type PageProps<T extends PageName> = {
Component: PageComponent<PagePropsMap<T>>;
componentProps: PagePropsMap<T>;
currentPage: T;
};

function Page<T extends PageName>({ Component, componentProps, currentPage }: PageProps<T>) {
return (
<>
<div className={classes.component}>
<Component {...componentProps} />
</div>
<InfoBox pageName={currentPage} />
</>
);
}

const getComponentProps = <T extends PageName>(
pages: PagesConfig,
currentPage: T,
): PagePropsMap<T> => {
if (currentPage === 'landingPage') return {} as PagePropsMap<T>;
return pages[currentPage].props;
};

const getAllPageNamesFromPagesConfig = (pages: PagesConfig): PageName[] => {
const customPages = Object.keys(pages) as PageName[];
return ['landingPage', ...customPages];
};
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PagesRouter } from './PagesRouter';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { RouterContext } from '../../contexts/RouterContext';
import type { PageName } from '../../types/PageName';
import { RouterContext } from '../../../contexts/RouterContext';
import type { PageName } from '../../../types/PageName';
import { renderWithBrowserRouter } from '../../../../test-utils/renderWithBrowserRouter';

const navigateMock = jest.fn();

describe('PagesRouter', () => {
it('renders the pages as navigation titles', () => {
renderPagesRouter();
const codeListNavTitle = screen.getByText(textMock('app_content_library.code_lists.page_name'));
const imagesNavTitle = screen.getByText(textMock('app_content_library.images.page_name'));
const codeListNavTitle = screen.getByRole('tab', {
name: textMock('app_content_library.code_lists.page_name'),
});
const imagesNavTitle = screen.getByRole('tab', {
name: textMock('app_content_library.images.page_name'),
});
expect(codeListNavTitle).toBeInTheDocument();
expect(imagesNavTitle).toBeInTheDocument();
});

it('calls navigate from RouterContext when clicking on a page that is not selected', async () => {
const user = userEvent.setup();
renderPagesRouter();
const imagesNavTitle = screen.getByText(textMock('app_content_library.images.page_name'));
const imagesNavTitle = screen.getByRole('tab', {
name: textMock('app_content_library.images.page_name'),
});
await user.click(imagesNavTitle);
expect(navigateMock).toHaveBeenCalledTimes(1);
expect(navigateMock).toHaveBeenCalledWith('images');
});

it('returns null if trying to render an unknown pageName', () => {
const pageName: string = 'unknownPageName';
renderPagesRouter([pageName as PageName]);
const navTitle = screen.queryByText(pageName);
expect(navTitle).not.toBeInTheDocument();
});
});

const renderPagesRouter = (pageNames: PageName[] = ['codeList', 'images']) => {
render(
renderWithBrowserRouter(
<RouterContext.Provider value={{ currentPage: 'codeList', navigate: navigateMock }}>
<PagesRouter pageNames={pageNames} />
</RouterContext.Provider>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { useRouterContext } from '../../../contexts/RouterContext';
import type { PageName } from '../../../types/PageName';
import { useContentTabs } from '../../../hooks/useLibraryMenuContentTabs';
import { StudioContentMenu } from '@studio/components';

type PagesRouterProps = {
pageNames: PageName[];
};

export function PagesRouter({ pageNames }: PagesRouterProps): React.ReactElement {
const { navigate, currentPage } = useRouterContext();
const contentTabs = useContentTabs();

const handleNavigation = (pageToNavigateTo: PageName) => {
navigate(pageToNavigateTo);
};

return (
<StudioContentMenu selectedTabId={currentPage} onChangeTab={handleNavigation}>
{pageNames.map((pageName) => (
<StudioContentMenu.LinkTab
key={contentTabs[pageName].tabId}
icon={contentTabs[pageName].icon}
tabId={contentTabs[pageName].tabId}
tabName={contentTabs[pageName].tabName}
renderTab={contentTabs[pageName].renderTab}
/>
))}
</StudioContentMenu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PagesRouter } from './PagesRouter';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { LibraryBody } from './LibraryBody';
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.libraryHeading {
display: flex;
align-items: center;
gap: var(--fds-spacing-1);
padding: var(--fds-spacing-4);
border-bottom: solid 1px var(--fds-semantic-border-neutral-subtle);
}

.libraryLandingPageNavigation {
.headingIcon {
display: flex;
align-items: center;
gap: var(--fds-spacing-1);
cursor: pointer;
width: fit-content;
color: var(--fds-semantic-text-neutral-default);
font-size: var(--fds-sizing-10);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,19 @@ import React from 'react';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { LibraryHeader } from './LibraryHeader';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RouterContext } from '../../contexts/RouterContext';

const navigateMock = jest.fn();

describe('LibraryHeader', () => {
it('renders the landingPage header', () => {
it('renders the content library header', () => {
renderLibraryHeader();
const landingPageIcon = screen.getByRole('img');
const landingPageHeader = screen.getByRole('heading', {
name: textMock('app_content_library.landing_page.page_name'),
const libraryIcon = screen.getByRole('img');
const libraryHeader = screen.getByRole('heading', {
name: textMock('app_content_library.library_heading'),
});
expect(landingPageIcon).toBeInTheDocument();
expect(landingPageHeader).toBeInTheDocument();
});

it('calls navigate from useRouterContext when clicking on the header', async () => {
const user = userEvent.setup();
renderLibraryHeader();
const landingPageHeader = screen.getByRole('heading', {
name: textMock('app_content_library.landing_page.page_name'),
});
await user.click(landingPageHeader);
expect(navigateMock).toHaveBeenCalledTimes(1);
expect(navigateMock).toHaveBeenCalledWith('landingPage');
expect(libraryIcon).toBeInTheDocument();
expect(libraryHeader).toBeInTheDocument();
});
});

Expand Down
Loading

0 comments on commit 95a0859

Please sign in to comment.