diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.module.css deleted file mode 100644 index 1784221be59..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.module.css +++ /dev/null @@ -1,14 +0,0 @@ -.buttonTab { - all: unset; - display: var(--tab-display); - align-items: var(--tab-align-items); - gap: var(--tab-icon-title-gap); - cursor: var(--tab-cursor); - width: var(--tab-width); -} - -.icon { - display: flex; - align-items: center; - font-size: var(--fds-spacing-8); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.tsx deleted file mode 100644 index dbfa66e2764..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/StudioButtonTab.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import type { StudioButtonTabType } from '../types/StudioMenuTabType'; -import { StudioMenuTab } from '../StudioMenuTab'; -import { StudioMenuTabContainer } from '../StudioMenuTabContainer'; -import { useStudioContentMenuContext } from '../context/StudioContentMenuContext'; -import classes from './StudioButtonTab.module.css'; - -type StudioButtonTabProps = { - contentTab: StudioButtonTabType; -}; - -export function StudioButtonTab({ - contentTab, -}: StudioButtonTabProps): React.ReactElement { - const { selectedTabId, onChangeTab } = useStudioContentMenuContext(); - - return ( - onChangeTab(contentTab.tabId)} - > - - - ); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/index.ts deleted file mode 100644 index f8d49aa8d9f..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioButtonTab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioButtonTab } from './StudioButtonTab'; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.module.css index 81214c4d5dd..86263d1d238 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.module.css +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.module.css @@ -1,4 +1,12 @@ -.tabsContainer { - background-color: var(--fds-semantic-surface-action-second-subtle); +.menuContainer { height: 100%; + background-color: var(--fds-semantic-surface-action-second-subtle); +} + +.tabsContainer { + display: flex; + flex-direction: column; + gap: var(--fds-border_width-default); + background-color: var(--fds-semantic-border-action-second-subtle); + padding: var(--fds-border_width-default); } diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.stories.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.stories.tsx index 1ae65fb7dc0..e20a4b6c1d6 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.stories.tsx @@ -14,15 +14,15 @@ const meta: Meta> = { control: 'object', description: 'Array of button menu tabs with icons, names, and ids.', table: { - type: { summary: 'StudioButtonTabType[]' }, + type: { summary: 'StudioContentMenuButtonTabProps[]' }, }, }, linkTabs: { control: 'object', description: - 'Array of link menu tabs with icons, names, and ids. Prop `to` defines the url to navigate to.', + 'Array of link menu tabs with icons, names, and ids. Provide an optional link-element to return `props` in renderTab.', table: { - type: { summary: 'StudioLinkTabType[]' }, + type: { summary: 'StudioContentMenuLinkTabProps[]' }, }, }, selectedTabId: { @@ -65,7 +65,7 @@ Preview.args = { tabId: 'tabAsLink', tabName: 'Gå til Designsystemet', icon: , - to: 'https://next.storybook.designsystemet.no', + renderTab: (props) => , }, ], onChangeTab: () => {}, diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.test.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.test.tsx index 09038e656c6..ef5c3954c17 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.test.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import { StudioContentMenu } from './'; -import type { StudioMenuTabType } from './types/StudioMenuTabType'; import type { StudioContentMenuWrapperProps } from './StudioContentMenuWrapper'; +import type { StudioContentMenuButtonTabProps } from './StudioContentMenuButtonTab'; type StudioMenuTabName = 'tab1' | 'tab2' | 'tab3'; @@ -11,14 +11,14 @@ const onChangeTabMock = jest.fn(); const tab1Name = 'My tab'; const tab1Id: StudioMenuTabName = 'tab1'; -const tab1: StudioMenuTabType = { +const tab1: StudioContentMenuButtonTabProps = { tabName: tab1Name, tabId: tab1Id, icon: , }; const tab2Name = 'My second tab'; const tab2Id: StudioMenuTabName = 'tab2'; -const tab2: StudioMenuTabType = { +const tab2: StudioContentMenuButtonTabProps = { tabName: tab2Name, tabId: tab2Id, icon: , @@ -27,18 +27,18 @@ const tab2: StudioMenuTabType = { describe('StudioContentMenu', () => { afterEach(jest.clearAllMocks); - it('renders an empty contentMenu when there is no provided tabs', () => { - renderStudioContentMenu({ buttonTabs: [] }); - const emptyMenu = screen.getByRole('tablist'); - expect(emptyMenu).toBeInTheDocument(); - }); - it('renders first tab as selected if selectedTab is not provided', () => { renderStudioContentMenu({ buttonTabs: [tab1, tab2], }); const firstTab = screen.getByRole('tab', { name: tab1Name }); - expect(firstTab).toHaveClass('selectedTab'); + expect(firstTab).toHaveClass('selected'); + }); + + it('renders an empty contentMenu when there is no provided tabs', () => { + renderStudioContentMenu({ buttonTabs: [] }); + const emptyMenu = screen.getByRole('tablist'); + expect(emptyMenu).toBeInTheDocument(); }); it('renders the title and icon of a given menu tab', () => { @@ -63,13 +63,11 @@ describe('StudioContentMenu', () => { linkTabs: [ { ...tab1, - to: link, + renderTab: (props) => , }, ], }); - const menuTab = screen.getByRole('tab', { name: tab1Name }); - const linkTab = screen.getByRole('link', { name: tab1Name }); - expect(menuTab).toBeInTheDocument(); + const linkTab = screen.getByRole('tab', { name: tab1Name }); expect(linkTab).toBeInTheDocument(); expect(linkTab).toHaveAttribute('href', link); }); @@ -148,23 +146,6 @@ describe('StudioContentMenu', () => { expect(onChangeTabMock).toHaveBeenCalledTimes(1); expect(onChangeTabMock).toHaveBeenCalledWith(tab1Id); }); - - it('calls onChangeTab when clicking on a menu tab with link', async () => { - const link = 'url-link'; - const user = userEvent.setup(); - renderStudioContentMenu({ - linkTabs: [ - { - ...tab1, - to: link, - }, - ], - }); - const menuTab = screen.getByRole('tab', { name: tab1Name }); - await user.click(menuTab); - expect(onChangeTabMock).toHaveBeenCalledTimes(1); - expect(onChangeTabMock).toHaveBeenCalledWith(tab1Id); - }); }); const renderStudioContentMenu = ({ @@ -174,10 +155,21 @@ const renderStudioContentMenu = ({ render( {buttonTabs.map((buttonTab) => ( - + ))} {linkTabs.map((linkTab) => ( - + ))} , ); diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.tsx index 8de765eeee5..deb4ceef71d 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenu.tsx @@ -14,17 +14,25 @@ function StudioContentMenuForwarded( ref: React.Ref, ): ReactElement { const firstTabId = getFirstTabId(children); - const [selectedTab, setSelectedTab] = useState(selectedTabId ?? firstTabId); + const [selectedTab, setSelectedTab] = useState(selectedTabId ?? firstTabId); + const handleChangeTab = (tabId: TabId) => { onChangeTab(tabId); setSelectedTab(tabId); }; + const isTabSelected = (tabId: TabId) => selectedTab === tabId; + return ( -
- - {children} - +
+
+ + {children} + +
); } @@ -36,5 +44,5 @@ export const StudioContentMenu = forwardRef { return Children.toArray(children).filter((child): child is ReactElement => React.isValidElement(child), - )[0]?.props.contentTab.tabId; + )[0]?.props.tabId; }; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.module.css new file mode 100644 index 00000000000..a0f7dbe287d --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.module.css @@ -0,0 +1,6 @@ +.buttonTab { + all: unset; + display: var(--tab-display); + align-items: var(--tab-align-items); + width: var(--tab-width); +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.tsx new file mode 100644 index 00000000000..b08f457d793 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/StudioContentMenuButtonTab.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import type { ReactNode } from 'react'; +import { useTabProps } from '../hooks/useTabProps'; +import { StudioButton } from '@studio/components'; + +export type StudioContentMenuButtonTabProps = { + icon: ReactNode; + tabName: string; + tabId: TabId; +}; + +export function StudioContentMenuButtonTab({ + icon, + tabName, + tabId, +}: StudioContentMenuButtonTabProps): React.ReactElement { + const props = useTabProps(icon, tabName, tabId); + + return ; +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/index.ts new file mode 100644 index 00000000000..4487fc8824d --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuButtonTab/index.ts @@ -0,0 +1,2 @@ +export { StudioContentMenuButtonTab } from './StudioContentMenuButtonTab'; +export type { StudioContentMenuButtonTabProps } from './StudioContentMenuButtonTab'; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/StudioContentMenuLinkTab.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/StudioContentMenuLinkTab.tsx new file mode 100644 index 00000000000..7d4d2df1190 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/StudioContentMenuLinkTab.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import type { ReactNode } from 'react'; +import { useTabProps } from '../hooks/useTabProps'; +import { StudioButton } from '@studio/components'; + +export type StudioContentMenuLinkTabProps = { + icon: ReactNode; + tabName: string; + tabId: TabId; + renderTab: (props: React.HTMLAttributes) => React.ReactElement; +}; + +export function StudioContentMenuLinkTab({ + icon, + tabName, + tabId, + renderTab, +}: StudioContentMenuLinkTabProps): React.ReactElement { + const props = useTabProps(icon, tabName, tabId); + + return {renderTab(props)}; +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/index.ts new file mode 100644 index 00000000000..49029d77d44 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuLinkTab/index.ts @@ -0,0 +1,2 @@ +export { StudioContentMenuLinkTab } from './StudioContentMenuLinkTab'; +export type { StudioContentMenuLinkTabProps } from './StudioContentMenuLinkTab'; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.module.css index d6e23110149..ef296e30ece 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.module.css +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.module.css @@ -1,4 +1,4 @@ .contentMenuWrapper { height: 300px; - width: 200px; + width: 20vw; } diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.tsx index f20fc7725da..a5ff855a37c 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioContentMenuWrapper.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { StudioContentMenu } from './'; import classes from './StudioContentMenuWrapper.module.css'; -import type { StudioButtonTabType, StudioLinkTabType } from './types/StudioMenuTabType'; +import type { StudioContentMenuButtonTabProps } from './StudioContentMenuButtonTab'; +import type { StudioContentMenuLinkTabProps } from './StudioContentMenuLinkTab'; export type StudioContentMenuWrapperProps = { - buttonTabs: StudioButtonTabType[]; - linkTabs: StudioLinkTabType[]; + buttonTabs: StudioContentMenuButtonTabProps[]; + linkTabs: StudioContentMenuLinkTabProps[]; selectedTabId: TabId; onChangeTab: (tabId: TabId) => void; }; @@ -19,10 +20,27 @@ export function StudioContentMenuWrapper({ return (
- - - - + + + +
); diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.module.css deleted file mode 100644 index 75337cf4120..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.linkTab { - gap: var(--tab-icon-title-gap); -} - -.linkIcon { - display: flex; - align-items: center; - font-size: var(--fds-spacing-8); - color: var(--fds-semantic-text-neutral-default); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.tsx deleted file mode 100644 index ca8d38aba73..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/StudioLinkTab.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import type { StudioLinkTabType } from '../types/StudioMenuTabType'; -import classes from './StudioLinkTab.module.css'; -import { StudioMenuTab } from '../StudioMenuTab'; -import { StudioLink } from '@studio/components'; -import { StudioMenuTabContainer } from '../StudioMenuTabContainer'; -import { useStudioContentMenuContext } from '../context/StudioContentMenuContext'; - -type StudioLinkTabProps = { - contentTab: StudioLinkTabType; -}; - -export function StudioLinkTab({ - contentTab, -}: StudioLinkTabProps): React.ReactElement { - const { selectedTabId, onChangeTab } = useStudioContentMenuContext(); - - return ( - onChangeTab(contentTab.tabId)} - > - -
{contentTab.icon}
- -
-
- ); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/index.ts deleted file mode 100644 index 8a35bd72e73..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioLinkTab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioLinkTab } from './StudioLinkTab'; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.module.css index 6197246e4ef..f48508dd446 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.module.css +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.module.css @@ -1,3 +1,10 @@ +.tabIcon { + display: flex; + align-items: center; + font-size: var(--fds-spacing-8); + color: var(--fds-semantic-text-neutral-default); +} + .tabTitle { overflow: hidden; text-overflow: ellipsis; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.tsx index 521e112c774..842952acba5 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTab/StudioMenuTab.tsx @@ -1,16 +1,20 @@ import classes from './StudioMenuTab.module.css'; import { StudioParagraph } from '@studio/components'; +import type { ReactNode } from 'react'; import React from 'react'; -import type { StudioMenuTabType } from '../types/StudioMenuTabType'; -type MenuTabContentProps = { - contentTab: StudioMenuTabType; +type StudioMenuTabProps = { + icon: ReactNode; + tabName: string; }; -export function StudioMenuTab({ contentTab }: MenuTabContentProps) { +export function StudioMenuTab({ icon, tabName }: StudioMenuTabProps) { return ( - - {contentTab.tabName} - + <> +
{icon}
+ + {tabName} + + ); } diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.module.css deleted file mode 100644 index 0083c6937bb..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.module.css +++ /dev/null @@ -1,36 +0,0 @@ -:root { - --tab-icon-title-gap: var(--fds-spacing-2); - --tab-display: flex; - --tab-align-items: center; - --tab-cursor: pointer; - --tab-width: 100%; -} - -.selectedTab, -.tab { - display: var(--tab-display); - align-items: var(--tab-align-items); - gap: var(--tab-icon-title-gap); - box-sizing: border-box; - border: solid 1px var(--fds-semantic-border-action-second-subtle); - padding: var(--fds-spacing-3); - width: var(--tab-width); - background-color: var(--fds-semantic-surface-action-second-subtle); - cursor: var(--tab-cursor); -} - -.selectedTab { - border-left: var(--fds-border_radius-interactive) solid var(--semantic-surface-action-default); - background-color: white; -} - -.tab:hover, -.selectedTab:hover { - background-color: var(--fds-semantic-surface-action-no_fill-hover); -} - -.tab:focus, -.selectedTab:focus { - outline: none; /* Remove default focus outline */ - border-color: var(--fds-border_width-tab_focus) var(--fds-semantic-surface-focus-default); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.tsx deleted file mode 100644 index 04ca5492920..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/StudioMenuTabContainer.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import type { ReactElement, ReactNode } from 'react'; -import classes from './StudioMenuTabContainer.module.css'; -import { moveFocus } from '../utils/dom-utils'; -import type { StudioMenuTabType } from '../types/StudioMenuTabType'; - -type StudioMenuTabProps = { - children: ReactNode; - contentTab: StudioMenuTabType; - isTabSelected: boolean; - onClick: (tabId: TabId) => void; -}; - -export function StudioMenuTabContainer({ - children, - contentTab, - isTabSelected, - onClick, -}: StudioMenuTabProps): ReactElement { - const handleKeyDown = (event: React.KeyboardEvent) => { - moveFocus(event); - if (event.key === 'Enter') { - event.preventDefault(); - onClick(contentTab.tabId); - } - }; - - return ( -
onClick(contentTab.tabId)} - role='tab' - tabIndex={isTabSelected ? 0 : -1} - onKeyDown={handleKeyDown} - title={contentTab.tabName} - > - {children} -
- ); -} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/index.ts deleted file mode 100644 index b77e4d1fdaa..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/StudioMenuTabContainer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { StudioMenuTabContainer } from './StudioMenuTabContainer'; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/context/StudioContentMenuContext.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/context/StudioContentMenuContext.tsx index f6cf1a282d9..450a52bd27a 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/context/StudioContentMenuContext.tsx +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/context/StudioContentMenuContext.tsx @@ -1,11 +1,11 @@ import React, { type ReactNode, createContext, useContext } from 'react'; export type StudioContentMenuContextProps = { - selectedTabId: TabId; + isTabSelected: (tabId: TabId) => boolean; onChangeTab: (tabId: TabId) => void; }; -export const StudioContentMenuContext = +const StudioContentMenuContext = createContext>>(undefined); export type StudioContentMenuContextProviderProps = { @@ -14,11 +14,11 @@ export type StudioContentMenuContextProviderProps = { export function StudioContentMenuContextProvider({ children, - selectedTabId, + isTabSelected, onChangeTab, }: Partial>) { return ( - + {children} ); diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.module.css b/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.module.css new file mode 100644 index 00000000000..316350e2078 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.module.css @@ -0,0 +1,28 @@ +.tab { + display: flex; + align-items: center; + justify-content: flex-start; + box-sizing: border-box; + gap: var(--fds-spacing-2); + padding: var(--fds-spacing-3); + width: 100%; + background-color: var(--fds-semantic-surface-action-second-subtle); + cursor: pointer; + border-left: var(--fds-sizing-1) solid transparent; + border-width: 0 0 0 var(--fds-sizing-1); + border-radius: 0; +} + +.tab.selected { + border-left: var(--fds-sizing-1) solid var(--semantic-surface-action-default); + background-color: white; +} + +.tab:hover, +.selectedTab:hover { + background-color: var(--fds-semantic-surface-action-no_fill-hover); +} + +.tab:focus { + z-index: 1; +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.tsx new file mode 100644 index 00000000000..d1c6b6df440 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/hooks/useTabProps.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react'; +import React from 'react'; +import { useStudioContentMenuContext } from '../context/StudioContentMenuContext'; +import type { HTMLTabElement } from '../utils/dom-utils'; +import { moveFocus } from '../utils/dom-utils'; +import classes from './useTabProps.module.css'; +import { StudioMenuTab } from '../StudioMenuTab'; +import cn from 'classnames'; + +export function useTabProps(icon: ReactNode, tabName: string, tabId: TabId) { + const { isTabSelected, onChangeTab } = useStudioContentMenuContext(); + + const handleKeyDown = (event: React.KeyboardEvent) => { + moveFocus(event); + }; + + const props: React.HTMLAttributes = { + className: cn(classes.tab, isTabSelected(tabId) ? classes.selected : null), + role: 'tab', + tabIndex: isTabSelected(tabId) ? 0 : -1, + onClick: () => onChangeTab(tabId), + onKeyDown: handleKeyDown, + children: , + title: tabName, + }; + + return props; +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/index.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/index.ts index 2212a50d19b..de949b46705 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/index.ts +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/index.ts @@ -1,18 +1,15 @@ -export type { StudioMenuTabType } from '../StudioContentMenu/types/StudioMenuTabType'; +export type { StudioContentMenuButtonTabProps } from './StudioContentMenuButtonTab'; +export type { StudioContentMenuLinkTabProps } from './StudioContentMenuLinkTab'; import { StudioContentMenu as StudioContentMenuRoot } from './StudioContentMenu'; -import { StudioButtonTab } from './StudioButtonTab'; -import { StudioLinkTab } from './StudioLinkTab'; +import { StudioContentMenuButtonTab } from './StudioContentMenuButtonTab'; +import { StudioContentMenuLinkTab } from './StudioContentMenuLinkTab'; type StudioContentMenuComponent = typeof StudioContentMenuRoot & { - ButtonTab: typeof StudioButtonTab; - LinkTab: typeof StudioLinkTab; + ButtonTab: typeof StudioContentMenuButtonTab; + LinkTab: typeof StudioContentMenuLinkTab; }; export const StudioContentMenu = StudioContentMenuRoot as StudioContentMenuComponent; -StudioContentMenu.ButtonTab = StudioButtonTab; -StudioContentMenu.LinkTab = StudioLinkTab; - -//StudioContentMenu.displayName = 'StudioContentMenu'; -//StudioContentMenu.ButtonTab.displayName = 'StudioContentMenu.ButtonTab'; -//StudioContentMenu.LinkTab.displayName = 'StudioContentMenu.LinkTab'; +StudioContentMenu.ButtonTab = StudioContentMenuButtonTab; +StudioContentMenu.LinkTab = StudioContentMenuLinkTab; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/types/StudioMenuTabType.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/types/StudioMenuTabType.ts deleted file mode 100644 index 84b65d3ad24..00000000000 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/types/StudioMenuTabType.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ReactNode } from 'react'; - -export type StudioButtonTabType = { - icon: ReactNode; - tabName: string; - tabId: TabId; -}; - -export type StudioLinkTabType = StudioButtonTabType & { - to: string; -}; - -export type StudioMenuTabType = - | StudioButtonTabType - | StudioLinkTabType; diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.test.tsx b/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.test.tsx new file mode 100644 index 00000000000..d2eedddce3e --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.test.tsx @@ -0,0 +1,46 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import type { HTMLTabElement } from './dom-utils'; +import { moveFocus } from './dom-utils'; + +describe('moveFocus', () => { + it('moves focus to the next tab when ArrowDown is pressed', () => { + renderTabList(); + simulateFocusMove('ArrowDown', 'tab-1', 'tab-2'); + simulateFocusMove('ArrowDown', 'tab-2', 'tab-3'); + }); + + it('moves focus to the previous tab when ArrowUp is pressed', () => { + renderTabList(); + simulateFocusMove('ArrowUp', 'tab-3', 'tab-2'); + simulateFocusMove('ArrowUp', 'tab-2', 'tab-1'); + }); +}); + +const renderTabList = () => { + render( +
+ + + +
, + ); +}; + +function simulateFocusMove(key: string, initialTabId: string, expectedFocusedTabId: string) { + const initialTab = screen.getByTestId(initialTabId); + initialTab.focus(); + const event = { + key, + currentTarget: initialTab, + preventDefault: jest.fn(), + } as unknown as React.KeyboardEvent; + moveFocus(event); + expect(screen.getByTestId(expectedFocusedTabId)).toHaveFocus(); +} diff --git a/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.ts b/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.ts index 65003257df5..133f833a0ee 100644 --- a/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.ts +++ b/frontend/libs/studio-components/src/components/StudioContentMenu/utils/dom-utils.ts @@ -1,6 +1,8 @@ import type React from 'react'; -export function moveFocus(event: React.KeyboardEvent) { +export type HTMLTabElement = HTMLAnchorElement | HTMLButtonElement; + +export function moveFocus(event: React.KeyboardEvent) { const nextTab = getNextTab(event); if (nextTab) { event.preventDefault(); @@ -10,7 +12,7 @@ export function moveFocus(event: React.KeyboardEvent) { } } -function getNextTab({ key, currentTarget }: React.KeyboardEvent) { +function getNextTab({ key, currentTarget }: React.KeyboardEvent) { const tablist = getParentTablist(currentTarget); const tabs = getTabs(tablist); switch (key) { @@ -23,7 +25,7 @@ function getNextTab({ key, currentTarget }: React.KeyboardEvent) } } -function getTabElementAbove(tabs: HTMLDivElement[], currentTab: HTMLDivElement) { +function getTabElementAbove(tabs: HTMLTabElement[], currentTab: HTMLTabElement) { const currentIndex = tabs.indexOf(currentTab); if (currentIndex > 0) { return tabs[currentIndex - 1]; @@ -31,18 +33,18 @@ function getTabElementAbove(tabs: HTMLDivElement[], currentTab: HTMLDivElement) return null; } -function getTabElementBelow(tabs: HTMLDivElement[], currentTab: HTMLDivElement) { +function getTabElementBelow(tabs: HTMLTabElement[], currentTab: HTMLTabElement) { const currentIndex = tabs.indexOf(currentTab); if (currentIndex < tabs.length - 1) { - return tabs[currentIndex + 1] as HTMLDivElement; + return tabs[currentIndex + 1] as HTMLTabElement; } return null; } -function getTabs(tablist: HTMLDivElement): HTMLDivElement[] { - return Array.from(tablist.querySelectorAll('[role="tab"]')) as HTMLDivElement[]; +function getTabs(tablist: HTMLDivElement): HTMLTabElement[] { + return Array.from(tablist.querySelectorAll('[role="tab"]')) as HTMLTabElement[]; } -function getParentTablist(element: HTMLDivElement): HTMLDivElement | null { +function getParentTablist(element: HTMLTabElement): HTMLDivElement | null { return element.closest('[role="tablist"]'); } diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.test.tsx index 76ed63ecd6c..e6323e92ae5 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.test.tsx @@ -6,6 +6,7 @@ import { mockPagesConfig } from '../../mocks/mockPagesConfig'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { RouterContext } from '../contexts/RouterContext'; import type { PageName } from '../types/PageName'; +import { BrowserRouter } from 'react-router-dom'; const navigateMock = jest.fn(); @@ -13,7 +14,7 @@ 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'), @@ -52,8 +53,10 @@ describe('ContentLibrary', () => { const renderContentLibrary = (currentPage: PageName = undefined) => { render( - - - , + + + + + , ); }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.tsx index 9937587edc4..de6561398f2 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/ContentLibrary.tsx @@ -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 { LibraryContent } from './LibraryContent/LibraryContent'; type ContentLibraryProps = { pages: PagesConfig; @@ -34,22 +33,11 @@ function ContentLibraryForPage({ router.configuredRoutes.get(currentPage); if (!Component) return 404 Page Not Found; // Show the NotFound page from app-dev instead - const componentPropsAreExternal = currentPage !== 'landingPage'; - - const componentProps: Required[T] = - componentPropsAreExternal && (pages[currentPage].props as Required[T]); - return (
-
- -
- -
- -
+
); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.module.css b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.module.css similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.module.css rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.module.css diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.test.tsx similarity index 95% rename from frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.test.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.test.tsx index a597e5ea34a..2927a6de35f 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.test.tsx @@ -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'; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/InfoBox.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/InfoBox.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/index.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/index.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/index.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/infoBoxConfigs.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/infoBoxConfigs.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/InfoBox/infoBoxConfigs.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/InfoBox/infoBoxConfigs.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/LibraryContent.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/LibraryContent.tsx new file mode 100644 index 00000000000..dd0c4453648 --- /dev/null +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/LibraryContent.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import classes from '../ContentLibrary.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 LibraryContentProps = { + Component: PageComponent[T]>; + pages: PagesConfig; + currentPage: PageName; +}; + +export function LibraryContent({ + Component, + pages, + currentPage, +}: LibraryContentProps) { + const componentPropsAreExternal = currentPage !== 'landingPage'; + + const componentProps: Required[T] = + componentPropsAreExternal && (pages[currentPage].props as Required[T]); + + return ( +
+ +
+ +
+ +
+ ); +} + +const getAllPageNamesFromPagesConfig = (pages: PagesConfig): PageName[] => { + const customPages = Object.keys(pages) as PageName[]; + return ['landingPage', ...customPages]; +}; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.test.tsx similarity index 52% rename from frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.test.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.test.tsx index 711ac30d150..c2da32bea5d 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.test.tsx @@ -3,16 +3,21 @@ import { render, 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 { BrowserRouter } from 'react-router-dom'; 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(); }); @@ -20,24 +25,21 @@ describe('PagesRouter', () => { 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( - - - , + + + + + , ); }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.tsx new file mode 100644 index 00000000000..ae0e5641d17 --- /dev/null +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/PagesRouter.tsx @@ -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 { getContentTabs } = useContentTabs(); + + const handleNavigation = (pageToNavigateTo: PageName) => { + navigate(pageToNavigateTo); + }; + + return ( + + {pageNames.map((pageName) => ( + + ))} + + ); +} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/index.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/index.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/PagesRouter/index.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/CodeList.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/CodeList.test.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/CodeList.test.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/CodeList.test.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/CodeList.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/CodeList.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/CodeList.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/CodeList.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/index.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/CodeList/index.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/CodeList/index.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/Images.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/Images.test.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/Images.test.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/Images.test.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/Images.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/Images.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/Images.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/Images.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/index.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/Images/index.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/Images/index.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.module.css b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.module.css similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.module.css rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.module.css diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.test.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.test.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.test.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.tsx similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/LandingPage.tsx rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/LandingPage.tsx diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/index.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/index.ts similarity index 100% rename from frontend/libs/studio-content-library/src/ContentLibrary/pages/LandingPage/index.ts rename to frontend/libs/studio-content-library/src/ContentLibrary/LibraryContent/pages/LandingPage/index.ts diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.module.css b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.module.css index e7fec6680ac..25d5304ea77 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.module.css +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.module.css @@ -1,13 +1,9 @@ .libraryHeading { - padding: var(--fds-spacing-4); - border-bottom: solid 1px var(--fds-semantic-border-neutral-subtle); -} - -.libraryLandingPageNavigation { display: flex; align-items: center; gap: var(--fds-spacing-1); - width: fit-content; + padding: var(--fds-spacing-4); + border-bottom: solid 1px var(--fds-semantic-border-neutral-subtle); } .headingIcon { diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.test.tsx index 85dbbfa2553..e21e190d908 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.test.tsx @@ -8,25 +8,14 @@ import { RouterContext } from '../../contexts/RouterContext'; const navigateMock = jest.fn(); describe('LibraryHeader', () => { - it('renders the landingPage header', () => { + it('renders the contentLibrary 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(); }); }); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.tsx index 787b7813936..299ac11b248 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryHeader/LibraryHeader.tsx @@ -9,10 +9,8 @@ export function LibraryHeader(): React.ReactElement { return (
-
- - {t('app_content_library.library_heading')} -
+ + {t('app_content_library.library_heading')}
); } diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.tsx deleted file mode 100644 index 8bd5be23535..00000000000 --- a/frontend/libs/studio-content-library/src/ContentLibrary/PagesRouter/PagesRouter.tsx +++ /dev/null @@ -1,26 +0,0 @@ -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 = { - currentPage: PageName; -}; - -export function PagesRouter({ currentPage }: PagesRouterProps): React.ReactElement { - const { navigate } = useRouterContext(); - const { getContentTabs } = useContentTabs(); - - const handleNavigation = (pageToNavigateTo: PageName) => { - navigate(pageToNavigateTo); - }; - - return ( - - {getContentTabs().map((contentTab) => ( - - ))} - - ); -} diff --git a/frontend/libs/studio-content-library/src/config/ContentResourceLibraryImpl.test.tsx b/frontend/libs/studio-content-library/src/config/ContentResourceLibraryImpl.test.tsx index 131bd3bedb4..d5d1cd1143a 100644 --- a/frontend/libs/studio-content-library/src/config/ContentResourceLibraryImpl.test.tsx +++ b/frontend/libs/studio-content-library/src/config/ContentResourceLibraryImpl.test.tsx @@ -3,6 +3,7 @@ import { render, screen } from '@testing-library/react'; import type { PagesConfig } from '../types/PagesProps'; import { ResourceContentLibraryImpl } from './ContentResourceLibraryImpl'; import { textMock } from '@studio/testing/mocks/i18nMock'; +import { BrowserRouter } from 'react-router-dom'; describe('ContentResourceLibraryImpl', () => { it('renders ContentResourceLibraryImpl with given pages', () => { @@ -51,5 +52,5 @@ describe('ContentResourceLibraryImpl', () => { const renderContentResourceLibraryImpl = (pages: PagesConfig) => { const contentResourceLibraryImpl = new ResourceContentLibraryImpl({ pages }); const { getContentResourceLibrary } = contentResourceLibraryImpl; - render(
{getContentResourceLibrary()}
); + render({getContentResourceLibrary()}); }; diff --git a/frontend/libs/studio-content-library/src/hooks/useLibraryMenuContentTabs.tsx b/frontend/libs/studio-content-library/src/hooks/useLibraryMenuContentTabs.tsx index cbae1a3c2b3..5f1ad56766f 100644 --- a/frontend/libs/studio-content-library/src/hooks/useLibraryMenuContentTabs.tsx +++ b/frontend/libs/studio-content-library/src/hooks/useLibraryMenuContentTabs.tsx @@ -1,35 +1,40 @@ import React from 'react'; import type { PageName } from '../types/PageName'; import { BookIcon, CodeListsIcon, ImageIcon } from '@studio/icons'; -import type { StudioMenuTabType } from '@studio/components'; +import type { StudioContentMenuLinkTabProps } from '@studio/components'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { pageRouterQueryParamKey } from '../utils/router/QueryParamsRouter'; + +type TabDictionary = Record>; type useContentTabsReturnType = { - getContentTabs: () => StudioMenuTabType[]; + getContentTabs: () => TabDictionary; }; export const useContentTabs = (): useContentTabsReturnType => { const { t } = useTranslation(); - const getContentTabs = (): StudioMenuTabType[] => [ - { + + const getContentTabs = (): TabDictionary => ({ + landingPage: { tabName: t('app_content_library.landing_page.page_name'), tabId: 'landingPage', icon: , - to: '', + renderTab: (props) => , }, - { - icon: , - tabId: 'codeList', + codeList: { tabName: t('app_content_library.code_lists.page_name'), - to: '', + tabId: 'codeList', + icon: , + renderTab: (props) => , }, - { - icon: , - tabId: 'images', + images: { tabName: t('app_content_library.images.page_name'), - to: '', + tabId: 'images', + icon: , + renderTab: (props) => , }, - ]; + }); return { getContentTabs }; }; diff --git a/frontend/libs/studio-content-library/src/utils/router/QueryParamsRouter.ts b/frontend/libs/studio-content-library/src/utils/router/QueryParamsRouter.ts index 23c21e495da..3ebdb611691 100644 --- a/frontend/libs/studio-content-library/src/utils/router/QueryParamsRouter.ts +++ b/frontend/libs/studio-content-library/src/utils/router/QueryParamsRouter.ts @@ -1,6 +1,6 @@ import type { PageName } from '../../types/PageName'; -const pageRouterQueryParamKey: string = 'currentLibraryRoute'; +export const pageRouterQueryParamKey: string = 'currentLibraryRoute'; export interface QueryParamsRouter { currentRoute: PageName; diff --git a/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.test.ts b/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.test.ts index f5d0c9e9752..96d83a6d2c3 100644 --- a/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.test.ts +++ b/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.test.ts @@ -1,6 +1,6 @@ import { RouterRouteMapperImpl } from './RouterRouteMapper'; -import { LandingPage } from '../../ContentLibrary/pages/LandingPage'; -import { CodeList } from '../../ContentLibrary/pages/CodeList'; +import { LandingPage } from '../../ContentLibrary/LibraryContent/pages/LandingPage'; +import { CodeList } from '../../ContentLibrary/LibraryContent/pages/CodeList'; import { mockPagesConfig } from '../../../mocks/mockPagesConfig'; describe('RouterRouteMapperImpl', () => { diff --git a/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.ts b/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.ts index e05fca14698..8f91aca1cce 100644 --- a/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.ts +++ b/frontend/libs/studio-content-library/src/utils/router/RouterRouteMapper.ts @@ -1,9 +1,9 @@ import { type ComponentProps, type ReactElement } from 'react'; -import { CodeList } from '../../ContentLibrary/pages/CodeList'; +import { CodeList } from '../../ContentLibrary/LibraryContent/pages/CodeList'; import type { PageName } from '../../types/PageName'; -import { LandingPage } from '../../ContentLibrary/pages/LandingPage'; +import { LandingPage } from '../../ContentLibrary/LibraryContent/pages/LandingPage'; import type { PagesConfig } from '../../types/PagesProps'; -import { Images } from '../../ContentLibrary/pages/Images'; +import { Images } from '../../ContentLibrary/LibraryContent/pages/Images'; type PageProps = | ComponentProps