Skip to content

Commit 32cff51

Browse files
Feat(table): Add fullscreen mode support
1 parent c9d7d12 commit 32cff51

12 files changed

Lines changed: 462 additions & 81 deletions
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, expect, it } from "vitest";
2+
import { render } from "vitest-browser-react";
3+
import { I18nProvider } from "../i18n/I18nProvider";
4+
import { FullScreen } from "./FullScreen";
5+
import { FullScreenButton } from "./FullScreenButton";
6+
7+
function TestWrapper({ children }: { children: React.ReactNode }) {
8+
return (
9+
<I18nProvider>
10+
<FullScreen>
11+
<FullScreenButton />
12+
{children}
13+
</FullScreen>
14+
</I18nProvider>
15+
);
16+
}
17+
18+
describe("FullScreen", () => {
19+
it("should render element ouside of modal if not in fullscreen mode", async () => {
20+
const screen = await render(<p>Test Element</p>, {
21+
wrapper: TestWrapper,
22+
});
23+
24+
await expect
25+
.element(screen.getByRole("paragraph"))
26+
.toHaveTextContent("Test Element");
27+
await expect.element(screen.getByRole("dialog")).not.toBeInTheDocument();
28+
});
29+
30+
it("should render element in modal if in fullscreen mode", async () => {
31+
const screen = await render(<p>Test Element</p>, {
32+
wrapper: TestWrapper,
33+
});
34+
35+
await screen
36+
.getByRole("button", {
37+
name: "Passer en mode plein écran",
38+
})
39+
.click();
40+
41+
const dialog = screen.getByRole("dialog");
42+
await expect.element(dialog).toBeInTheDocument();
43+
44+
await expect
45+
.element(dialog.getByRole("paragraph"))
46+
.toHaveTextContent("Test Element");
47+
});
48+
49+
it("should close modal when clicking on close button", async () => {
50+
const screen = await render(<p>Test Element</p>, {
51+
wrapper: TestWrapper,
52+
});
53+
54+
await screen
55+
.getByRole("button", {
56+
name: "Passer en mode plein écran",
57+
})
58+
.click();
59+
60+
const dialog = screen.getByRole("dialog");
61+
await expect.element(dialog).toBeInTheDocument();
62+
63+
await screen
64+
.getByRole("button", {
65+
name: "Quitter le mode plein écran",
66+
})
67+
.click();
68+
69+
await expect.element(dialog).not.toBeInTheDocument();
70+
});
71+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen";
2+
import Dialog from "@mui/material/Dialog";
3+
import DialogContent from "@mui/material/DialogContent";
4+
import IconButton from "@mui/material/IconButton";
5+
import Tooltip from "@mui/material/Tooltip";
6+
import type * as React from "react";
7+
import { useTranslation } from "react-i18next";
8+
import { FullScreenContextProvider } from "./FullScreenContext";
9+
import { useFullScreenContext } from "./useFullScreenContext";
10+
11+
export function Switch({ children }: FullScreenProps) {
12+
const { t } = useTranslation();
13+
const { isFullScreen, exitFullScreen } = useFullScreenContext();
14+
15+
const closeButtonLabel = t("fullScreen.exit");
16+
17+
if (!isFullScreen) {
18+
return children;
19+
}
20+
return (
21+
<Dialog fullScreen open onClose={exitFullScreen}>
22+
<Tooltip title={closeButtonLabel}>
23+
<IconButton
24+
onClick={exitFullScreen}
25+
aria-label={closeButtonLabel}
26+
size="small"
27+
sx={{
28+
position: "fixed",
29+
top: 16,
30+
right: 16,
31+
backgroundColor: "background.paper",
32+
}}
33+
>
34+
<CloseFullscreenIcon />
35+
</IconButton>
36+
</Tooltip>
37+
<DialogContent>{children}</DialogContent>
38+
</Dialog>
39+
);
40+
}
41+
42+
export function FullScreen(props: FullScreenProps) {
43+
return (
44+
<FullScreenContextProvider>
45+
<Switch {...props} />
46+
</FullScreenContextProvider>
47+
);
48+
}
49+
50+
type FullScreenProps = {
51+
children: React.ReactNode;
52+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { render } from "vitest-browser-react";
3+
import { I18nProvider } from "../i18n/I18nProvider";
4+
import { FullScreenButton } from "./FullScreenButton";
5+
import { useFullScreenContext } from "./useFullScreenContext";
6+
7+
vi.mock("./useFullScreenContext");
8+
9+
describe("FullScreenButton", () => {
10+
it("should open fullscreen on click", async () => {
11+
const enterFullScreen = vi.fn();
12+
const exitFullScreen = vi.fn();
13+
vi.mocked(useFullScreenContext).mockReturnValue({
14+
isFullScreen: false,
15+
enterFullScreen,
16+
exitFullScreen,
17+
});
18+
const screen = await render(<FullScreenButton />, {
19+
wrapper({ children }) {
20+
return <I18nProvider>{children}</I18nProvider>;
21+
},
22+
});
23+
24+
await screen
25+
.getByRole("button", {
26+
name: "Passer en mode plein écran",
27+
})
28+
.click();
29+
30+
expect(enterFullScreen).toHaveBeenCalled();
31+
expect(exitFullScreen).not.toHaveBeenCalled();
32+
});
33+
34+
it("should not render button when already in fullscreen", async () => {
35+
const enterFullScreen = vi.fn();
36+
const exitFullScreen = vi.fn();
37+
vi.mocked(useFullScreenContext).mockReturnValue({
38+
isFullScreen: true,
39+
enterFullScreen,
40+
exitFullScreen,
41+
});
42+
43+
const screen = await render(<FullScreenButton />, {
44+
wrapper({ children }) {
45+
return <I18nProvider>{children}</I18nProvider>;
46+
},
47+
});
48+
49+
await expect
50+
.element(
51+
screen.getByRole("button", {
52+
name: "Passer en mode plein écran",
53+
}),
54+
)
55+
.not.toBeInTheDocument();
56+
});
57+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import OpenInFullIcon from "@mui/icons-material/OpenInFull";
2+
import IconButton from "@mui/material/IconButton";
3+
import Tooltip from "@mui/material/Tooltip";
4+
import { useTranslation } from "react-i18next";
5+
import { useFullScreenContext } from "./useFullScreenContext";
6+
7+
export function FullScreenButton() {
8+
const { t } = useTranslation();
9+
const { isFullScreen, enterFullScreen } = useFullScreenContext();
10+
11+
const label = t("fullScreen.enter");
12+
13+
if (isFullScreen) {
14+
return null;
15+
}
16+
17+
return (
18+
<Tooltip title={label}>
19+
<IconButton
20+
color="primary"
21+
onClick={enterFullScreen}
22+
aria-label={label}
23+
size="small"
24+
>
25+
<OpenInFullIcon />
26+
</IconButton>
27+
</Tooltip>
28+
);
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { createContext, useMemo, useState } from "react";
2+
3+
export type FullScreenContextValue = {
4+
isFullScreen: boolean;
5+
enterFullScreen: () => void;
6+
exitFullScreen: () => void;
7+
};
8+
9+
export const FullScreenContext = createContext<FullScreenContextValue | null>(
10+
null,
11+
);
12+
13+
export function FullScreenContextProvider({
14+
children,
15+
}: FullScreenContextProviderProps) {
16+
const [isFullScreen, setIsFullScreen] = useState(false);
17+
18+
const value = useMemo<FullScreenContextValue>(
19+
() => ({
20+
isFullScreen,
21+
enterFullScreen() {
22+
setIsFullScreen(true);
23+
},
24+
exitFullScreen() {
25+
setIsFullScreen(false);
26+
},
27+
}),
28+
[isFullScreen],
29+
);
30+
31+
return (
32+
<FullScreenContext.Provider value={value}>
33+
{children}
34+
</FullScreenContext.Provider>
35+
);
36+
}
37+
38+
export type FullScreenContextProviderProps = {
39+
children: React.ReactNode;
40+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useContext } from "react";
2+
import { FullScreenContext } from "./FullScreenContext";
3+
4+
export function useFullScreenContext() {
5+
const context = useContext(FullScreenContext);
6+
if (!context) {
7+
throw new Error(
8+
"useFullScreenContext must be used within a FullScreenContextProvider",
9+
);
10+
}
11+
12+
return context;
13+
}

packages/react-tei/src/i18n/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,8 @@ export const en: Translation = {
7777
science_metrix: "Science-Metrix Category",
7878
scopus: "Scopus Category",
7979
},
80+
fullScreen: {
81+
enter: "Enter full screen mode",
82+
exit: "Exit full screen mode",
83+
},
8084
};

packages/react-tei/src/i18n/locales/fr.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export const fr = {
7676
science_metrix: "Catégorie Science-Metrix",
7777
scopus: "Catégorie Scopus",
7878
},
79+
fullScreen: {
80+
enter: "Passer en mode plein écran",
81+
exit: "Quitter le mode plein écran",
82+
},
7983
};
8084

8185
export type Translation = typeof fr;

packages/react-tei/src/tags/Table.spec.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,57 @@ describe("Table", () => {
129129

130130
expect(screen.getByRole("paragraph")).not.toBeInTheDocument();
131131
});
132+
133+
it("should support fullscreen mode", async () => {
134+
const jsonDocument: DocumentJson = {
135+
tag: "table",
136+
attributes: { "@xml:id": "t3" },
137+
value: [
138+
{ tag: "head", attributes: { "@type": "label" }, value: "Table 3" },
139+
{
140+
tag: "row",
141+
attributes: { "@role": "label" },
142+
value: [
143+
{ tag: "cell", attributes: {}, value: "H1" },
144+
{ tag: "cell", attributes: {}, value: "H2" },
145+
],
146+
},
147+
{
148+
tag: "row",
149+
attributes: { "@role": "data" },
150+
value: [
151+
{ tag: "cell", attributes: {}, value: "D1" },
152+
{ tag: "cell", attributes: {}, value: "D2" },
153+
],
154+
},
155+
],
156+
};
157+
158+
const screen = await render(<Table data={jsonDocument} />, {
159+
wrapper: ({ children }) => (
160+
<TagCatalogProvider tagCatalog={tagCatalog}>
161+
{children}
162+
</TagCatalogProvider>
163+
),
164+
});
165+
166+
const fullScreenButton = screen.getByRole("button", {
167+
name: "Passer en mode plein écran",
168+
});
169+
expect(fullScreenButton).toBeVisible();
170+
171+
await fullScreenButton.click();
172+
173+
const dialog = screen.getByRole("dialog");
174+
expect(dialog).toBeVisible();
175+
176+
const exitFullScreenButton = screen.getByRole("button", {
177+
name: "Quitter le mode plein écran",
178+
});
179+
expect(exitFullScreenButton).toBeVisible();
180+
181+
await exitFullScreenButton.click();
182+
183+
expect(dialog).not.toBeInTheDocument();
184+
});
132185
});

0 commit comments

Comments
 (0)