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 new file mode 100644 index 000000000..59131da93 --- /dev/null +++ b/src/components/MDXComponents/ExpandableImage.tsx @@ -0,0 +1,98 @@ +import * as React from "react"; +import { useI18next } from "gatsby-plugin-react-i18next"; + +const IMAGE_LABELS = { + en: { + expand: "Expand image", + collapse: "Collapse image", + }, + zh: { + expand: "Expand image", + collapse: "Collapse image", + }, + ja: { + expand: "Expand image", + collapse: "Collapse image", + }, +} 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 [open, setOpen] = React.useState(false); + const { language } = useI18next(); + 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 new file mode 100644 index 000000000..1966ad582 --- /dev/null +++ b/src/components/MDXComponents/ExpandableTable.tsx @@ -0,0 +1,94 @@ +import * as React from "react"; +import { useI18next } from "gatsby-plugin-react-i18next"; + +const TABLE_LABELS = { + en: { + expand: "Expand table", + collapse: "Collapse table", + }, + zh: { + expand: "Expand table", + collapse: "Collapse table", + }, + ja: { + expand: "Expand table", + collapse: "Collapse table", + }, +} 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 [open, setOpen] = React.useState(false); + const { language } = useI18next(); + 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/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..39570c1a0 100644 --- a/src/styles/docTemplate.css +++ b/src/styles/docTemplate.css @@ -44,6 +44,92 @@ margin: auto; } + .expandable-toggle-button { + display: block; + width: fit-content; + margin: 0 0 8px auto; + 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-image { + display: block; + margin: 16px 0; + } + + .expandable-image > img { + max-width: 100%; + height: auto; + } + + .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-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-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; + } + + .expandable-modal-scroll { + max-height: calc(100vh - 120px); + overflow: auto; + } + + .expandable-table-modal-content table { + margin: 0; + min-width: 100%; + } + + .expandable-image-modal-content .expandable-modal-scroll { + text-align: center; + } + + .expandable-modal-image { + max-width: none; + height: auto; + margin: 0 auto; + } + code { background-color: var(--tiui-palette-carbon-200); border-radius: var(--tiui-shape-border-radius);