mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-09 12:57:45 +00:00
migrate tabs markdown component to jsx
This commit is contained in:
@@ -68,7 +68,7 @@ import SignUp from "src/page-components/collections/framework-field-guide/segmen
|
|||||||
enableColorChangeListeners();
|
enableColorChangeListeners();
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
import { enableTabs } from "../../../utils/markdown/scripts/tabs";
|
import { enableTabs } from "../../../utils/markdown/tabs/tabs-script";
|
||||||
enableTabs();
|
enableTabs();
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { GetPictureResult } from "@astrojs/image/dist/lib/get-picture";
|
import { GetPictureResult } from "@astrojs/image/dist/lib/get-picture";
|
||||||
|
|
||||||
export interface IFramePlaceholderProps {
|
export interface IFramePlaceholderProps {
|
||||||
width: number;
|
width: string;
|
||||||
height: number;
|
height: string;
|
||||||
src: string;
|
src: string;
|
||||||
pageTitle: string;
|
pageTitle: string;
|
||||||
pageIcon: GetPictureResult;
|
pageIcon: GetPictureResult;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const iFrameClickToRun = () => {
|
|||||||
[...iframeButtons].forEach((el) => {
|
[...iframeButtons].forEach((el) => {
|
||||||
el.addEventListener("click", () => {
|
el.addEventListener("click", () => {
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(iframe as any).loading = "lazy";
|
(iframe as any).loading = "lazy";
|
||||||
iframe.src = el.parentElement.dataset.iframeurl;
|
iframe.src = el.parentElement.dataset.iframeurl;
|
||||||
iframe.style.width = el.parentElement.style.width;
|
iframe.style.width = el.parentElement.style.width;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Root } from "hast";
|
import { Root, Element } from "hast";
|
||||||
import { Plugin } from "unified";
|
import { Plugin } from "unified";
|
||||||
|
|
||||||
import { visit } from "unist-util-visit";
|
import { visit } from "unist-util-visit";
|
||||||
@@ -141,8 +141,8 @@ export const rehypeUnicornIFrameClickToRun: Plugin<
|
|||||||
Root
|
Root
|
||||||
> = () => {
|
> = () => {
|
||||||
return async (tree) => {
|
return async (tree) => {
|
||||||
const iframeNodes: any[] = [];
|
const iframeNodes: Element[] = [];
|
||||||
visit(tree, (node: any) => {
|
visit(tree, (node: Element) => {
|
||||||
if (node.tagName === "iframe") {
|
if (node.tagName === "iframe") {
|
||||||
iframeNodes.push(node);
|
iframeNodes.push(node);
|
||||||
}
|
}
|
||||||
@@ -153,13 +153,13 @@ export const rehypeUnicornIFrameClickToRun: Plugin<
|
|||||||
const width = iframeNode.properties.width ?? EMBED_SIZE.w;
|
const width = iframeNode.properties.width ?? EMBED_SIZE.w;
|
||||||
const height = iframeNode.properties.height ?? EMBED_SIZE.h;
|
const height = iframeNode.properties.height ?? EMBED_SIZE.h;
|
||||||
const info: PageInfo = (await fetchPageInfo(
|
const info: PageInfo = (await fetchPageInfo(
|
||||||
iframeNode.properties.src
|
iframeNode.properties.src.toString()
|
||||||
).catch(() => null)) || { icon: await fetchDefaultPageIcon() };
|
).catch(() => null)) || { icon: await fetchDefaultPageIcon() };
|
||||||
|
|
||||||
const iframeReplacement = IFramePlaceholder({
|
const iframeReplacement = IFramePlaceholder({
|
||||||
width,
|
width: width.toString(),
|
||||||
height,
|
height: height.toString(),
|
||||||
src: iframeNode.properties.src,
|
src: iframeNode.properties.src.toString(),
|
||||||
pageTitle: info.title || "",
|
pageTitle: info.title || "",
|
||||||
pageIcon: info.icon,
|
pageIcon: info.icon,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "./tabs";
|
export * from "./rehype-transform";
|
||||||
|
|||||||
164
src/utils/markdown/tabs/rehype-transform.ts
Normal file
164
src/utils/markdown/tabs/rehype-transform.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import { Root } from "hast";
|
||||||
|
import replaceAllBetween from "unist-util-replace-all-between";
|
||||||
|
import { Plugin } from "unified";
|
||||||
|
import { getHeaderNodeId, slugs } from "rehype-slug-custom-id";
|
||||||
|
import { Element, Node, Text } from "hast";
|
||||||
|
import { TabInfo, Tabs } from "./tabs";
|
||||||
|
import { toString } from "hast-util-to-string";
|
||||||
|
|
||||||
|
const isNodeHeading = (n: Element) =>
|
||||||
|
n.type === "element" && /h[1-6]/.exec(n.tagName);
|
||||||
|
|
||||||
|
const findLargestHeading = (nodes: Element[]) => {
|
||||||
|
let largestSize = Infinity;
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!isNodeHeading(node)) continue;
|
||||||
|
const size = parseInt(node.tagName.substring(1), 10);
|
||||||
|
largestSize = Math.min(largestSize, size);
|
||||||
|
}
|
||||||
|
return largestSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNodeLargestHeading = (n: Element, largestSize: number) =>
|
||||||
|
isNodeHeading(n) && parseInt(n.tagName.substring(1), 10) === largestSize;
|
||||||
|
|
||||||
|
const getApproxLineCount = (nodes: Node[], inParagraph?: boolean): number => {
|
||||||
|
let lines = 0;
|
||||||
|
|
||||||
|
for (const n of nodes) {
|
||||||
|
const isInParagraph =
|
||||||
|
inParagraph || (n.type === "element" && (n as Element).tagName === "p");
|
||||||
|
|
||||||
|
// recurse through child nodes
|
||||||
|
if ("children" in n) {
|
||||||
|
lines += getApproxLineCount(n.children as Node[], isInParagraph);
|
||||||
|
}
|
||||||
|
// assume that any div/p/br causes a line break
|
||||||
|
if (
|
||||||
|
n.type === "element" &&
|
||||||
|
["div", "p", "br"].includes((n as Element).tagName)
|
||||||
|
)
|
||||||
|
lines++;
|
||||||
|
// assume that any image or embed could add ~10 lines
|
||||||
|
if (
|
||||||
|
n.type === "element" &&
|
||||||
|
["img", "svg", "iframe"].includes((n as Element).tagName)
|
||||||
|
)
|
||||||
|
lines += 10;
|
||||||
|
// approximate line wraps in <p> tag, assuming ~100 chars per line
|
||||||
|
if (
|
||||||
|
isInParagraph &&
|
||||||
|
n.type === "text" &&
|
||||||
|
typeof (n as Text).value === "string"
|
||||||
|
)
|
||||||
|
lines += Math.floor((n as Text).value.length / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RehypeTabsProps {
|
||||||
|
injectSubheaderProps?: boolean;
|
||||||
|
tabSlugifyProps?: Parameters<typeof getHeaderNodeId>[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin to add Docsify's tab support.
|
||||||
|
* @see https://jhildenbiddle.github.io/docsify-tabs/
|
||||||
|
*
|
||||||
|
* Given that syntax, output the following:
|
||||||
|
* ```
|
||||||
|
* <div class="tabs">
|
||||||
|
* <ul role="tablist">
|
||||||
|
* <li role="tab">Header Contents</li>
|
||||||
|
* </ul>
|
||||||
|
* <div role="tabpanel">Body contents</div>
|
||||||
|
* </div>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To align with React Tabs package:
|
||||||
|
* @see https://github.com/reactjs/react-tabs
|
||||||
|
*/
|
||||||
|
export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
|
||||||
|
injectSubheaderProps = false,
|
||||||
|
tabSlugifyProps = {},
|
||||||
|
}) => {
|
||||||
|
return (tree) => {
|
||||||
|
const replaceTabNodes = (nodes: Node[]) => {
|
||||||
|
let sectionStarted = false;
|
||||||
|
const largestSize = findLargestHeading(nodes as Element[]);
|
||||||
|
const tabs: TabInfo[] = [];
|
||||||
|
|
||||||
|
for (const localNode of nodes as Element[]) {
|
||||||
|
if (!sectionStarted && !isNodeLargestHeading(localNode, largestSize)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sectionStarted = true;
|
||||||
|
|
||||||
|
// If this is a heading, start a new tab entry...
|
||||||
|
if (isNodeLargestHeading(localNode, largestSize)) {
|
||||||
|
// Make sure that all tabs labeled "thing" aren't also labeled "thing2"
|
||||||
|
slugs.reset();
|
||||||
|
const { id: headerSlug } = getHeaderNodeId(
|
||||||
|
localNode,
|
||||||
|
tabSlugifyProps
|
||||||
|
);
|
||||||
|
|
||||||
|
tabs.push({
|
||||||
|
slug: headerSlug,
|
||||||
|
name: toString(localNode),
|
||||||
|
contents: [],
|
||||||
|
headers: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For any other heading found in the tab contents, append to the nested headers array
|
||||||
|
if (isNodeHeading(localNode) && injectSubheaderProps) {
|
||||||
|
const lastTab = tabs.at(-1);
|
||||||
|
|
||||||
|
// Store the related tab ID in the attributes of the header
|
||||||
|
localNode.properties["data-tabname"] = lastTab.slug;
|
||||||
|
|
||||||
|
// Add header ID to array
|
||||||
|
tabs.at(-1).headers.push(localNode.properties.id.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, append the node as tab content
|
||||||
|
tabs.at(-1).contents.push(localNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if the set of tabs should use a constant height (via the "tabs-small" class)
|
||||||
|
const tabHeights = tabs.map(({ contents }) =>
|
||||||
|
getApproxLineCount(contents)
|
||||||
|
);
|
||||||
|
const isSmall =
|
||||||
|
// all tabs must be <= 30 approx. lines (less than the height of most desktop viewports)
|
||||||
|
Math.max(...tabHeights) <= 30 &&
|
||||||
|
// the max difference between tab heights must be under 15 lines
|
||||||
|
Math.max(...tabHeights) - Math.min(...tabHeights) <= 15;
|
||||||
|
|
||||||
|
return [
|
||||||
|
Tabs({
|
||||||
|
tabs,
|
||||||
|
isSmall,
|
||||||
|
}),
|
||||||
|
] as Node[];
|
||||||
|
};
|
||||||
|
|
||||||
|
replaceAllBetween(
|
||||||
|
tree,
|
||||||
|
{ type: "raw", value: "<!-- tabs:start -->" } as never,
|
||||||
|
{ type: "raw", value: "<!-- tabs:end -->" } as never,
|
||||||
|
replaceTabNodes
|
||||||
|
);
|
||||||
|
replaceAllBetween(
|
||||||
|
tree,
|
||||||
|
{ type: "comment", value: " tabs:start " } as never,
|
||||||
|
{ type: "comment", value: " tabs:end " } as never,
|
||||||
|
replaceTabNodes
|
||||||
|
);
|
||||||
|
return tree;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { enableTabs } from "./tabs";
|
import { enableTabs } from "./tabs-script";
|
||||||
|
|
||||||
const tabsHtml = `
|
const tabsHtml = `
|
||||||
<ul role="tablist" class="tabs__tab-list">
|
<ul role="tablist" class="tabs__tab-list">
|
||||||
@@ -142,7 +142,7 @@ export const enableTabs = () => {
|
|||||||
if (!heading) return;
|
if (!heading) return;
|
||||||
|
|
||||||
for (const tabEntry of tabEntries)
|
for (const tabEntry of tabEntries)
|
||||||
for (const [_, tab] of tabEntry) {
|
for (const [, tab] of tabEntry) {
|
||||||
// If the tab is hidden and the heading is contained within the tab
|
// If the tab is hidden and the heading is contained within the tab
|
||||||
if (
|
if (
|
||||||
tab.panel.hasAttribute("aria-hidden") &&
|
tab.panel.hasAttribute("aria-hidden") &&
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
import { Root } from "hast";
|
|
||||||
import replaceAllBetween from "unist-util-replace-all-between";
|
|
||||||
import { Parent, Node } from "unist";
|
|
||||||
import { Plugin } from "unified";
|
|
||||||
import { getHeaderNodeId, slugs } from "rehype-slug-custom-id";
|
|
||||||
|
|
||||||
interface ElementNode extends Parent {
|
|
||||||
tagName: string;
|
|
||||||
properties: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TextNode extends Node {
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNodeHeading = (n: ElementNode) =>
|
|
||||||
n.type === "element" && /h[1-6]/.exec(n.tagName);
|
|
||||||
|
|
||||||
const findLargestHeading = (nodes: ElementNode[]) => {
|
|
||||||
let largestSize = Infinity;
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (!isNodeHeading(node)) continue;
|
|
||||||
const size = parseInt(node.tagName.substr(1), 10);
|
|
||||||
largestSize = Math.min(largestSize, size);
|
|
||||||
}
|
|
||||||
return largestSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNodeLargestHeading = (n: ElementNode, largestSize: number) =>
|
|
||||||
isNodeHeading(n) && parseInt(n.tagName.substr(1), 10) === largestSize;
|
|
||||||
|
|
||||||
const getApproxLineCount = (n: Node, inParagraph?: boolean): number => {
|
|
||||||
inParagraph ||= n.type === "element" && (n as ElementNode).tagName === "p";
|
|
||||||
let lines = 0;
|
|
||||||
|
|
||||||
// recurse through child nodes
|
|
||||||
if ("children" in n) {
|
|
||||||
for (const child of (n as Parent).children)
|
|
||||||
lines += getApproxLineCount(child, inParagraph);
|
|
||||||
}
|
|
||||||
// assume that any div/p/br causes a line break
|
|
||||||
if (
|
|
||||||
n.type === "element" &&
|
|
||||||
["div", "p", "br"].includes((n as ElementNode).tagName)
|
|
||||||
)
|
|
||||||
lines++;
|
|
||||||
// assume that any image or embed could add ~10 lines
|
|
||||||
if (
|
|
||||||
n.type === "element" &&
|
|
||||||
["img", "svg", "iframe"].includes((n as ElementNode).tagName)
|
|
||||||
)
|
|
||||||
lines += 10;
|
|
||||||
// approximate line wraps in <p> tag, assuming ~100 chars per line
|
|
||||||
if (
|
|
||||||
inParagraph &&
|
|
||||||
n.type === "text" &&
|
|
||||||
typeof (n as TextNode).value === "string"
|
|
||||||
)
|
|
||||||
lines += Math.floor((n as TextNode).value.length / 100);
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RehypeTabsProps {
|
|
||||||
injectSubheaderProps?: boolean;
|
|
||||||
tabSlugifyProps?: Parameters<typeof getHeaderNodeId>[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin to add Docsify's tab support.
|
|
||||||
* @see https://jhildenbiddle.github.io/docsify-tabs/
|
|
||||||
*
|
|
||||||
* Given that syntax, output the following:
|
|
||||||
* ```
|
|
||||||
* <div class="tabs">
|
|
||||||
* <ul role="tablist">
|
|
||||||
* <li role="tab">Header Contents</li>
|
|
||||||
* </ul>
|
|
||||||
* <div role="tabpanel">Body contents</div>
|
|
||||||
* </div>
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* To align with React Tabs package:
|
|
||||||
* @see https://github.com/reactjs/react-tabs
|
|
||||||
*/
|
|
||||||
export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
|
|
||||||
injectSubheaderProps = false,
|
|
||||||
tabSlugifyProps = {},
|
|
||||||
}) => {
|
|
||||||
return (tree) => {
|
|
||||||
const replaceTabNodes = (nodes: Node[]) => {
|
|
||||||
let sectionStarted = false;
|
|
||||||
|
|
||||||
const largestSize = findLargestHeading(nodes as ElementNode[]);
|
|
||||||
|
|
||||||
const tabsContainer = {
|
|
||||||
type: "element",
|
|
||||||
tagName: "div",
|
|
||||||
properties: {
|
|
||||||
class: "tabs",
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: "element",
|
|
||||||
tagName: "ul",
|
|
||||||
properties: {
|
|
||||||
role: "tablist",
|
|
||||||
class: "tabs__tab-list",
|
|
||||||
},
|
|
||||||
children: [] as ElementNode[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const localNode of nodes as ElementNode[]) {
|
|
||||||
if (!sectionStarted && !isNodeLargestHeading(localNode, largestSize)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sectionStarted = true;
|
|
||||||
|
|
||||||
if (isNodeLargestHeading(localNode, largestSize)) {
|
|
||||||
// Make sure that all tabs labeled "thing" aren't also labeled "thing2"
|
|
||||||
slugs.reset();
|
|
||||||
const { id: headerSlug } = getHeaderNodeId(
|
|
||||||
localNode,
|
|
||||||
tabSlugifyProps
|
|
||||||
);
|
|
||||||
|
|
||||||
// - 1 because the tabs are part of the header
|
|
||||||
const idx = tabsContainer.children.length - 1;
|
|
||||||
|
|
||||||
const header = {
|
|
||||||
type: "element",
|
|
||||||
tagName: "li",
|
|
||||||
children: localNode.children,
|
|
||||||
properties: {
|
|
||||||
role: "tab",
|
|
||||||
class: "tabs__tab",
|
|
||||||
"data-tabname": headerSlug,
|
|
||||||
"aria-selected": idx === 0 ? "true" : "false",
|
|
||||||
"aria-controls": `panel-${idx}`,
|
|
||||||
id: `tab-${idx}`,
|
|
||||||
tabIndex: idx === 0 ? "0" : "-1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const contents = {
|
|
||||||
type: "element",
|
|
||||||
tagName: "div",
|
|
||||||
children: [],
|
|
||||||
properties: {
|
|
||||||
id: `panel-${idx}`,
|
|
||||||
role: "tabpanel",
|
|
||||||
class: "tabs__tab-panel",
|
|
||||||
tabindex: 0,
|
|
||||||
"aria-labelledby": `tab-${idx}`,
|
|
||||||
...(idx === 0 ? {} : { "aria-hidden": "true" }),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
tabsContainer.children[0].children.push(header);
|
|
||||||
|
|
||||||
tabsContainer.children.push(contents);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNodeHeading(localNode) && injectSubheaderProps) {
|
|
||||||
// This is `tagName: tab`
|
|
||||||
const lastTab =
|
|
||||||
tabsContainer.children[0].children[
|
|
||||||
tabsContainer.children[0].children.length - 1
|
|
||||||
];
|
|
||||||
|
|
||||||
// Store the related tab ID in the attributes of the header
|
|
||||||
localNode.properties["data-tabname"] =
|
|
||||||
// Get the last tab's `data-tabname` property
|
|
||||||
lastTab.properties["data-tabname"];
|
|
||||||
|
|
||||||
// Add header ID to array
|
|
||||||
lastTab.properties["data-headers"] = JSON.stringify(
|
|
||||||
JSON.parse(lastTab.properties["data-headers"] ?? "[]").concat(
|
|
||||||
localNode.properties.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push into last `tab-panel`
|
|
||||||
tabsContainer.children[tabsContainer.children.length - 1].children.push(
|
|
||||||
localNode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if all tabs contain <=30 lines
|
|
||||||
// if so, "tabs-small" class makes the container use a constant height
|
|
||||||
const isSmallTab = tabsContainer.children.every(
|
|
||||||
(n) => getApproxLineCount(n) <= 30
|
|
||||||
);
|
|
||||||
if (isSmallTab) tabsContainer.properties.class += " tabs-small";
|
|
||||||
|
|
||||||
return [tabsContainer];
|
|
||||||
};
|
|
||||||
|
|
||||||
replaceAllBetween(
|
|
||||||
tree,
|
|
||||||
{ type: "raw", value: "<!-- tabs:start -->" } as never,
|
|
||||||
{ type: "raw", value: "<!-- tabs:end -->" } as never,
|
|
||||||
replaceTabNodes
|
|
||||||
);
|
|
||||||
replaceAllBetween(
|
|
||||||
tree,
|
|
||||||
{ type: "comment", value: " tabs:start " } as never,
|
|
||||||
{ type: "comment", value: " tabs:end " } as never,
|
|
||||||
replaceTabNodes
|
|
||||||
);
|
|
||||||
return tree;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
53
src/utils/markdown/tabs/tabs.tsx
Normal file
53
src/utils/markdown/tabs/tabs.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import { Node } from "hast";
|
||||||
|
|
||||||
|
export interface TabInfo {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
contents: Node[];
|
||||||
|
|
||||||
|
// array of header slugs that are inside the header contents, for URL hash behavior
|
||||||
|
headers: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabsProps {
|
||||||
|
tabs: TabInfo[];
|
||||||
|
isSmall: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @jsxImportSource hastscript */
|
||||||
|
export function Tabs({ tabs, isSmall }: TabsProps) {
|
||||||
|
return (
|
||||||
|
<div class={classNames("tabs", isSmall && "tabs-small")}>
|
||||||
|
<ul role="tablist" class="tabs__tab-list">
|
||||||
|
{tabs.map(({ slug, name, headers }, index) => (
|
||||||
|
<li
|
||||||
|
id={`tab-${index}`}
|
||||||
|
role="tab"
|
||||||
|
class="tabs__tab"
|
||||||
|
data-tabname={slug}
|
||||||
|
data-headers={JSON.stringify(headers)}
|
||||||
|
aria-selected={index === 0}
|
||||||
|
aria-controls={`panel-${index}`}
|
||||||
|
tabIndex={index === 0 ? 0 : -1}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{tabs.map(({ contents }, index) => (
|
||||||
|
<div
|
||||||
|
id={`panel-${index}`}
|
||||||
|
role="tabpanel"
|
||||||
|
class="tabs__tab-panel"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-labelledby={`tab-${index}`}
|
||||||
|
aria-hidden={index === 0 ? undefined : true}
|
||||||
|
>
|
||||||
|
{contents}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ if (post.collection && post.order) {
|
|||||||
mediumZoom(".post-body img:not([data-nozoom])");
|
mediumZoom(".post-body img:not([data-nozoom])");
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
import { enableTabs } from "../../utils/markdown/scripts/tabs";
|
import { enableTabs } from "../../utils/markdown/tabs/tabs-script";
|
||||||
enableTabs();
|
enableTabs();
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user