Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions packages/react-tei/src/fullscreen/FullScreen.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";
import { I18nProvider } from "../i18n/I18nProvider";
import { FullScreen } from "./FullScreen";
import { FullScreenButton } from "./FullScreenButton";

function TestWrapper({ children }: { children: React.ReactNode }) {
return (
<I18nProvider>
<FullScreen>
<FullScreenButton />
{children}
</FullScreen>
</I18nProvider>
);
}

describe("FullScreen", () => {
it("should render element ouside of modal if not in fullscreen mode", async () => {
const screen = await render(<p>Test Element</p>, {
wrapper: TestWrapper,
});

await expect
.element(screen.getByRole("paragraph"))
.toHaveTextContent("Test Element");
await expect.element(screen.getByRole("dialog")).not.toBeInTheDocument();
});

it("should render element in modal if in fullscreen mode", async () => {
const screen = await render(<p>Test Element</p>, {
wrapper: TestWrapper,
});

await screen
.getByRole("button", {
name: "Passer en mode plein écran",
})
.click();

const dialog = screen.getByRole("dialog");
await expect.element(dialog).toBeInTheDocument();

await expect
.element(dialog.getByRole("paragraph"))
.toHaveTextContent("Test Element");
});

it("should close modal when clicking on close button", async () => {
const screen = await render(<p>Test Element</p>, {
wrapper: TestWrapper,
});

await screen
.getByRole("button", {
name: "Passer en mode plein écran",
})
.click();

const dialog = screen.getByRole("dialog");
await expect.element(dialog).toBeInTheDocument();

await screen
.getByRole("button", {
name: "Quitter le mode plein écran",
})
.click();

await expect.element(dialog).not.toBeInTheDocument();
});
});
52 changes: 52 additions & 0 deletions packages/react-tei/src/fullscreen/FullScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen";
import Dialog from "@mui/material/Dialog";
import DialogContent from "@mui/material/DialogContent";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import type * as React from "react";
import { useTranslation } from "react-i18next";
import { FullScreenContextProvider } from "./FullScreenContext";
import { useFullScreenContext } from "./useFullScreenContext";

export function Switch({ children }: FullScreenProps) {
const { t } = useTranslation();
const { isFullScreen, exitFullScreen } = useFullScreenContext();

const closeButtonLabel = t("fullScreen.exit");

if (!isFullScreen) {
return children;
}
return (
<Dialog fullScreen open onClose={exitFullScreen}>
<Tooltip title={closeButtonLabel}>
<IconButton
onClick={exitFullScreen}
aria-label={closeButtonLabel}
size="small"
sx={{
position: "fixed",
top: 16,
right: 16,
backgroundColor: "background.paper",
}}
>
<CloseFullscreenIcon />
</IconButton>
</Tooltip>
<DialogContent>{children}</DialogContent>
</Dialog>
);
}

export function FullScreen(props: FullScreenProps) {
return (
<FullScreenContextProvider>
<Switch {...props} />
</FullScreenContextProvider>
);
}

type FullScreenProps = {
children: React.ReactNode;
};
57 changes: 57 additions & 0 deletions packages/react-tei/src/fullscreen/FullScreenButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it, vi } from "vitest";
import { render } from "vitest-browser-react";
import { I18nProvider } from "../i18n/I18nProvider";
import { FullScreenButton } from "./FullScreenButton";
import { useFullScreenContext } from "./useFullScreenContext";

vi.mock("./useFullScreenContext");

describe("FullScreenButton", () => {
it("should open fullscreen on click", async () => {
const enterFullScreen = vi.fn();
const exitFullScreen = vi.fn();
vi.mocked(useFullScreenContext).mockReturnValue({
isFullScreen: false,
enterFullScreen,
exitFullScreen,
});
const screen = await render(<FullScreenButton />, {
wrapper({ children }) {
return <I18nProvider>{children}</I18nProvider>;
},
});

await screen
.getByRole("button", {
name: "Passer en mode plein écran",
})
.click();

expect(enterFullScreen).toHaveBeenCalled();
expect(exitFullScreen).not.toHaveBeenCalled();
});

it("should not render button when already in fullscreen", async () => {
const enterFullScreen = vi.fn();
const exitFullScreen = vi.fn();
vi.mocked(useFullScreenContext).mockReturnValue({
isFullScreen: true,
enterFullScreen,
exitFullScreen,
});

const screen = await render(<FullScreenButton />, {
wrapper({ children }) {
return <I18nProvider>{children}</I18nProvider>;
},
});

await expect
.element(
screen.getByRole("button", {
name: "Passer en mode plein écran",
}),
)
.not.toBeInTheDocument();
});
});
29 changes: 29 additions & 0 deletions packages/react-tei/src/fullscreen/FullScreenButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import OpenInFullIcon from "@mui/icons-material/OpenInFull";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { useTranslation } from "react-i18next";
import { useFullScreenContext } from "./useFullScreenContext";

export function FullScreenButton() {
const { t } = useTranslation();
const { isFullScreen, enterFullScreen } = useFullScreenContext();

const label = t("fullScreen.enter");

if (isFullScreen) {
return null;
}

return (
<Tooltip title={label}>
<IconButton
color="primary"
onClick={enterFullScreen}
aria-label={label}
size="small"
>
<OpenInFullIcon />
</IconButton>
</Tooltip>
);
}
40 changes: 40 additions & 0 deletions packages/react-tei/src/fullscreen/FullScreenContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createContext, useMemo, useState } from "react";

export type FullScreenContextValue = {
isFullScreen: boolean;
enterFullScreen: () => void;
exitFullScreen: () => void;
};

export const FullScreenContext = createContext<FullScreenContextValue | null>(
null,
);

export function FullScreenContextProvider({
children,
}: FullScreenContextProviderProps) {
const [isFullScreen, setIsFullScreen] = useState(false);

const value = useMemo<FullScreenContextValue>(
() => ({
isFullScreen,
enterFullScreen() {
setIsFullScreen(true);
},
exitFullScreen() {
setIsFullScreen(false);
},
}),
[isFullScreen],
);

return (
<FullScreenContext.Provider value={value}>
{children}
</FullScreenContext.Provider>
);
}

export type FullScreenContextProviderProps = {
children: React.ReactNode;
};
13 changes: 13 additions & 0 deletions packages/react-tei/src/fullscreen/useFullScreenContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from "react";
import { FullScreenContext } from "./FullScreenContext";

export function useFullScreenContext() {
const context = useContext(FullScreenContext);
if (!context) {
throw new Error(
"useFullScreenContext must be used within a FullScreenContextProvider",
);
}

return context;
}
4 changes: 4 additions & 0 deletions packages/react-tei/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,8 @@ export const en: Translation = {
science_metrix: "Science-Metrix Category",
scopus: "Scopus Category",
},
fullScreen: {
enter: "Enter full screen mode",
exit: "Exit full screen mode",
},
};
4 changes: 4 additions & 0 deletions packages/react-tei/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export const fr = {
science_metrix: "Catégorie Science-Metrix",
scopus: "Catégorie Scopus",
},
fullScreen: {
enter: "Passer en mode plein écran",
exit: "Quitter le mode plein écran",
},
};

export type Translation = typeof fr;
57 changes: 56 additions & 1 deletion packages/react-tei/src/tags/Table.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";

import { I18nProvider } from "../i18n/I18nProvider";
import type { DocumentJson } from "../parser/document";
import { Table } from "./Table";
import { TagCatalogProvider } from "./TagCatalogProvider";
Expand Down Expand Up @@ -129,4 +129,59 @@ describe("Table", () => {

expect(screen.getByRole("paragraph")).not.toBeInTheDocument();
});

it("should support fullscreen mode", async () => {
const jsonDocument: DocumentJson = {
tag: "table",
attributes: { "@xml:id": "t3" },
value: [
{ tag: "head", attributes: { "@type": "label" }, value: "Table 3" },
{
tag: "row",
attributes: { "@role": "label" },
value: [
{ tag: "cell", attributes: {}, value: "H1" },
{ tag: "cell", attributes: {}, value: "H2" },
],
},
{
tag: "row",
attributes: { "@role": "data" },
value: [
{ tag: "cell", attributes: {}, value: "D1" },
{ tag: "cell", attributes: {}, value: "D2" },
],
},
],
};

const screen = await render(<Table data={jsonDocument} />, {
wrapper: ({ children }) => (
<I18nProvider>
<TagCatalogProvider tagCatalog={tagCatalog}>
{children}
</TagCatalogProvider>
</I18nProvider>
),
});

const fullScreenButton = screen.getByRole("button", {
name: "Passer en mode plein écran",
});
expect(fullScreenButton).toBeVisible();

await fullScreenButton.click();

const dialog = screen.getByRole("dialog");
expect(dialog).toBeVisible();

const exitFullScreenButton = screen.getByRole("button", {
name: "Quitter le mode plein écran",
});
expect(exitFullScreenButton).toBeVisible();

await exitFullScreenButton.click();

expect(dialog).not.toBeInTheDocument();
});
});
Loading