From 10bb8a42260dd10c6211586f19baecc696a46baa Mon Sep 17 00:00:00 2001 From: houfaxin Date: Wed, 4 Mar 2026 10:38:53 +0800 Subject: [PATCH 1/4] Add expandable image and table MDX components Introduce ExpandableImage and ExpandableTable MDX components with i18n labels (en/zh/ja) and toggle buttons. Components use useI18next for localized button text, manage expanded state, and expose aria-expanded for accessibility; ExpandableImage prevents default/propagation on the toggle click. Export the components as MDX shortcodes (img and table) in the MDXComponents index and add corresponding styles in docTemplate.css: collapsed containers have a max-height of 360px with overflow and border, and an expanded state removes the max-height. These changes enable collapsing large tables/images by default while allowing users to expand them. --- .../MDXComponents/ExpandableImage.tsx | 54 ++++++++++++++++++ .../MDXComponents/ExpandableTable.tsx | 52 +++++++++++++++++ src/components/MDXComponents/index.tsx | 2 + src/styles/docTemplate.css | 56 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 src/components/MDXComponents/ExpandableImage.tsx create mode 100644 src/components/MDXComponents/ExpandableTable.tsx diff --git a/src/components/MDXComponents/ExpandableImage.tsx b/src/components/MDXComponents/ExpandableImage.tsx new file mode 100644 index 000000000..dc75832f5 --- /dev/null +++ b/src/components/MDXComponents/ExpandableImage.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { useI18next } from "gatsby-plugin-react-i18next"; + +const IMAGE_LABELS = { + en: { + expand: "Expand image", + collapse: "Collapse image", + }, + zh: { + expand: "展开图片", + collapse: "收起图片", + }, + ja: { + expand: "画像を拡大", + collapse: "画像を折りたたむ", + }, +} as const; + +function getImageLabel(language: string, expanded: boolean) { + const lang = language.startsWith("zh") + ? "zh" + : language.startsWith("ja") + ? "ja" + : "en"; + return expanded ? IMAGE_LABELS[lang].collapse : IMAGE_LABELS[lang].expand; +} + +export function ExpandableImage( + props: React.ImgHTMLAttributes +) { + const [expanded, setExpanded] = React.useState(false); + const { language } = useI18next(); + const label = getImageLabel(language, expanded); + + return ( + + + + + + + ); +} diff --git a/src/components/MDXComponents/ExpandableTable.tsx b/src/components/MDXComponents/ExpandableTable.tsx new file mode 100644 index 000000000..5aa5ab261 --- /dev/null +++ b/src/components/MDXComponents/ExpandableTable.tsx @@ -0,0 +1,52 @@ +import * as React from "react"; +import { useI18next } from "gatsby-plugin-react-i18next"; + +const TABLE_LABELS = { + en: { + expand: "Expand table", + collapse: "Collapse table", + }, + zh: { + expand: "展开表格", + collapse: "收起表格", + }, + ja: { + expand: "表を展開", + collapse: "表を折りたたむ", + }, +} as const; + +function getTableLabel(language: string, expanded: boolean) { + const lang = language.startsWith("zh") + ? "zh" + : language.startsWith("ja") + ? "ja" + : "en"; + return expanded ? TABLE_LABELS[lang].collapse : TABLE_LABELS[lang].expand; +} + +export function ExpandableTable( + props: React.TableHTMLAttributes +) { + const [expanded, setExpanded] = React.useState(false); + const { language } = useI18next(); + const label = getTableLabel(language, expanded); + + return ( +
+ +
+ + + + ); +} diff --git a/src/components/MDXComponents/index.tsx b/src/components/MDXComponents/index.tsx index eedefe32d..95e95ac9b 100644 --- a/src/components/MDXComponents/index.tsx +++ b/src/components/MDXComponents/index.tsx @@ -34,3 +34,5 @@ export { } from "components/MDXComponents/developer"; export { TargetLink as Link } from "./Link"; +export { ExpandableTable as table } from "./ExpandableTable"; +export { ExpandableImage as img } from "./ExpandableImage"; diff --git a/src/styles/docTemplate.css b/src/styles/docTemplate.css index 806d57da9..2e0fabee6 100644 --- a/src/styles/docTemplate.css +++ b/src/styles/docTemplate.css @@ -44,6 +44,62 @@ margin: auto; } + .expandable-toggle-button { + margin: 0 0 8px; + padding: 0; + border: 0; + background: none; + color: var(--tiui-palette-secondary); + text-decoration: underline; + text-underline-offset: 2px; + font-size: 14px; + line-height: 1.4; + cursor: pointer; + } + + .expandable-table { + margin: 16px 0; + } + + .expandable-table-container { + max-height: 360px; + overflow: auto; + border: 1px solid var(--tiui-palette-carbon-300); + border-radius: var(--tiui-shape-border-radius); + transition: max-height 0.2s ease; + } + + .expandable-table-container.expanded { + max-height: none; + } + + .expandable-table-container table { + margin: 0; + } + + .expandable-image { + display: block; + margin: 16px 0; + } + + .expandable-image-frame { + display: block; + max-height: 360px; + overflow: auto; + border: 1px solid var(--tiui-palette-carbon-300); + border-radius: var(--tiui-shape-border-radius); + transition: max-height 0.2s ease; + } + + .expandable-image-frame.expanded { + max-height: none; + } + + .expandable-image-frame img { + max-width: 100%; + height: auto; + } + code { background-color: var(--tiui-palette-carbon-200); border-radius: var(--tiui-shape-border-radius); From 3db96375ed487371c754da46b5d53b14602f6089 Mon Sep 17 00:00:00 2001 From: houfaxin Date: Wed, 4 Mar 2026 20:49:42 +0800 Subject: [PATCH 2/4] refine code --- .husky/pre-commit | 11 ++- .../MDXComponents/ExpandableImage.tsx | 74 +++++++++++++++---- .../MDXComponents/ExpandableTable.tsx | 72 ++++++++++++++---- src/styles/docTemplate.css | 72 ++++++++++++------ 4 files changed, 176 insertions(+), 53 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index f23377e95..965e0ac22 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,13 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -yarn run lint-staged +if command -v yarn >/dev/null 2>&1; then + yarn run lint-staged +elif command -v corepack >/dev/null 2>&1; then + corepack yarn run lint-staged +elif command -v npm >/dev/null 2>&1; then + npm exec -- lint-staged +else + echo "Error: yarn/corepack/npm not found in PATH" + exit 127 +fi diff --git a/src/components/MDXComponents/ExpandableImage.tsx b/src/components/MDXComponents/ExpandableImage.tsx index dc75832f5..59131da93 100644 --- a/src/components/MDXComponents/ExpandableImage.tsx +++ b/src/components/MDXComponents/ExpandableImage.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import * as React from "react"; import { useI18next } from "gatsby-plugin-react-i18next"; const IMAGE_LABELS = { @@ -7,12 +7,12 @@ const IMAGE_LABELS = { collapse: "Collapse image", }, zh: { - expand: "展开图片", - collapse: "收起图片", + expand: "Expand image", + collapse: "Collapse image", }, ja: { - expand: "画像を拡大", - collapse: "画像を折りたたむ", + expand: "Expand image", + collapse: "Collapse image", }, } as const; @@ -28,27 +28,71 @@ function getImageLabel(language: string, expanded: boolean) { export function ExpandableImage( props: React.ImgHTMLAttributes ) { - const [expanded, setExpanded] = React.useState(false); + const [open, setOpen] = React.useState(false); const { language } = useI18next(); - const label = getImageLabel(language, expanded); + const expandLabel = getImageLabel(language, false); + const collapseLabel = getImageLabel(language, true); + + React.useEffect(() => { + if (!open) return; + + const originalOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setOpen(false); + } + }; + + document.addEventListener("keydown", onKeyDown); + + return () => { + document.body.style.overflow = originalOverflow; + document.removeEventListener("keydown", onKeyDown); + }; + }, [open]); return ( - +
- - - - + + {open && ( +
setOpen(false)} + > +
event.stopPropagation()} + > + +
+ +
+
+
+ )} +
); } diff --git a/src/components/MDXComponents/ExpandableTable.tsx b/src/components/MDXComponents/ExpandableTable.tsx index 5aa5ab261..1966ad582 100644 --- a/src/components/MDXComponents/ExpandableTable.tsx +++ b/src/components/MDXComponents/ExpandableTable.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import * as React from "react"; import { useI18next } from "gatsby-plugin-react-i18next"; const TABLE_LABELS = { @@ -7,12 +7,12 @@ const TABLE_LABELS = { collapse: "Collapse table", }, zh: { - expand: "展开表格", - collapse: "收起表格", + expand: "Expand table", + collapse: "Collapse table", }, ja: { - expand: "表を展開", - collapse: "表を折りたたむ", + expand: "Expand table", + collapse: "Collapse table", }, } as const; @@ -28,25 +28,67 @@ function getTableLabel(language: string, expanded: boolean) { export function ExpandableTable( props: React.TableHTMLAttributes ) { - const [expanded, setExpanded] = React.useState(false); + const [open, setOpen] = React.useState(false); const { language } = useI18next(); - const label = getTableLabel(language, expanded); + const expandLabel = getTableLabel(language, false); + const collapseLabel = getTableLabel(language, true); + + React.useEffect(() => { + if (!open) return; + + const originalOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setOpen(false); + } + }; + + document.addEventListener("keydown", onKeyDown); + + return () => { + document.body.style.overflow = originalOverflow; + document.removeEventListener("keydown", onKeyDown); + }; + }, [open]); return (
-
-
- +
+ {open && ( +
setOpen(false)} + > +
event.stopPropagation()} + > + +
+
+ + + + )} ); } diff --git a/src/styles/docTemplate.css b/src/styles/docTemplate.css index 2e0fabee6..826695acd 100644 --- a/src/styles/docTemplate.css +++ b/src/styles/docTemplate.css @@ -61,43 +61,71 @@ margin: 16px 0; } - .expandable-table-container { - max-height: 360px; - overflow: auto; - border: 1px solid var(--tiui-palette-carbon-300); - border-radius: var(--tiui-shape-border-radius); - transition: max-height 0.2s ease; + .expandable-image { + display: block; + margin: 16px 0; } - .expandable-table-container.expanded { - max-height: none; + .expandable-image > img { + max-width: 100%; + height: auto; } - .expandable-table-container table { - margin: 0; + .expandable-modal-backdrop { + position: fixed; + inset: 0; + z-index: 1300; + display: flex; + align-items: center; + justify-content: center; + background: rgba(14, 35, 54, 0.6); + padding: 24px; + box-sizing: border-box; } - .expandable-image { - display: block; - margin: 16px 0; + .expandable-modal-content { + width: min(1200px, 100%); + max-height: 100%; + background: #fff; + border-radius: var(--tiui-shape-border-radius); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + padding: 12px 12px 16px; + box-sizing: border-box; } - .expandable-image-frame { + .expandable-modal-collapse { + margin-left: auto; + margin-bottom: 8px; + padding: 0; + border: 0; + background: none; + color: var(--tiui-palette-secondary); + text-decoration: underline; + text-underline-offset: 2px; + font-size: 14px; + line-height: 1.4; + cursor: pointer; display: block; - max-height: 360px; + } + + .expandable-modal-scroll { + max-height: calc(100vh - 120px); overflow: auto; - border: 1px solid var(--tiui-palette-carbon-300); - border-radius: var(--tiui-shape-border-radius); - transition: max-height 0.2s ease; } - .expandable-image-frame.expanded { - max-height: none; + .expandable-table-modal-content table { + margin: 0; + min-width: 100%; } - .expandable-image-frame img { - max-width: 100%; + .expandable-image-modal-content .expandable-modal-scroll { + text-align: center; + } + + .expandable-modal-image { + max-width: none; height: auto; + margin: 0 auto; } code { From f9ebbaa5f7caede922d730127c30ab8399f6cd58 Mon Sep 17 00:00:00 2001 From: houfaxin Date: Wed, 4 Mar 2026 21:08:50 +0800 Subject: [PATCH 3/4] Update docTemplate.css --- src/styles/docTemplate.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/styles/docTemplate.css b/src/styles/docTemplate.css index 826695acd..39570c1a0 100644 --- a/src/styles/docTemplate.css +++ b/src/styles/docTemplate.css @@ -45,7 +45,9 @@ } .expandable-toggle-button { - margin: 0 0 8px; + display: block; + width: fit-content; + margin: 0 0 8px auto; padding: 0; border: 0; background: none; From 3521a6b7ea177ba51e856854cc7e3d3b48162a15 Mon Sep 17 00:00:00 2001 From: houfaxin Date: Thu, 5 Mar 2026 07:43:01 +0800 Subject: [PATCH 4/4] Add fullscreen icons and improve image modal Add open/close fullscreen icons to ExpandableImage and ExpandableTable by importing MdOpenInFull and MdCloseFullscreen and wrapping button labels in a .expandable-button-content element (with inline-flex alignment and gap). Update styles to constrain modal images (max-width: 100%; max-height: calc(100vh - 180px); object-fit: contain) so expanded content fits the viewport and doesn't overflow. --- src/components/MDXComponents/ExpandableImage.tsx | 11 +++++++++-- src/components/MDXComponents/ExpandableTable.tsx | 11 +++++++++-- src/styles/docTemplate.css | 10 +++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/MDXComponents/ExpandableImage.tsx b/src/components/MDXComponents/ExpandableImage.tsx index 59131da93..a54eaf5f7 100644 --- a/src/components/MDXComponents/ExpandableImage.tsx +++ b/src/components/MDXComponents/ExpandableImage.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { useI18next } from "gatsby-plugin-react-i18next"; +import { MdCloseFullscreen, MdOpenInFull } from "react-icons/md"; const IMAGE_LABELS = { en: { @@ -65,7 +66,10 @@ export function ExpandableImage( }} aria-expanded={open} > - {expandLabel} + + {open && ( @@ -85,7 +89,10 @@ export function ExpandableImage( className="expandable-modal-collapse" onClick={() => setOpen(false)} > - {collapseLabel} + +
diff --git a/src/components/MDXComponents/ExpandableTable.tsx b/src/components/MDXComponents/ExpandableTable.tsx index 1966ad582..4cc87c347 100644 --- a/src/components/MDXComponents/ExpandableTable.tsx +++ b/src/components/MDXComponents/ExpandableTable.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { useI18next } from "gatsby-plugin-react-i18next"; +import { MdCloseFullscreen, MdOpenInFull } from "react-icons/md"; const TABLE_LABELS = { en: { @@ -61,7 +62,10 @@ export function ExpandableTable( onClick={() => setOpen(true)} aria-expanded={open} > - {expandLabel} + +
{open && ( @@ -81,7 +85,10 @@ export function ExpandableTable( className="expandable-modal-collapse" onClick={() => setOpen(false)} > - {collapseLabel} + +
diff --git a/src/styles/docTemplate.css b/src/styles/docTemplate.css index 39570c1a0..60a339a66 100644 --- a/src/styles/docTemplate.css +++ b/src/styles/docTemplate.css @@ -59,6 +59,12 @@ cursor: pointer; } + .expandable-button-content { + display: inline-flex; + align-items: center; + gap: 4px; + } + .expandable-table { margin: 16px 0; } @@ -125,9 +131,11 @@ } .expandable-modal-image { - max-width: none; + max-width: 100%; + max-height: calc(100vh - 180px); height: auto; margin: 0 auto; + object-fit: contain; } code {