From 623b577319b1dea2d6c42f1b680139fb858d85d6 Mon Sep 17 00:00:00 2001
From: Chris Swithinbank
Date: Fri, 12 May 2023 23:07:42 +0200
Subject: Add tab components (#38)
---
.changeset/slimy-icons-itch.md | 5 +
docs/astro.config.mjs | 6 +-
docs/src/content/docs/getting-started.md | 59 ---------
docs/src/content/docs/getting-started.mdx | 83 ++++++++++++
docs/src/content/docs/guides/components.mdx | 60 +++++++++
packages/starlight/components.ts | 2 +
packages/starlight/package.json | 5 +-
packages/starlight/user-components/TabItem.astro | 17 +++
packages/starlight/user-components/Tabs.astro | 148 ++++++++++++++++++++++
packages/starlight/user-components/rehype-tabs.ts | 82 ++++++++++++
pnpm-lock.yaml | 6 +
11 files changed, 410 insertions(+), 63 deletions(-)
create mode 100644 .changeset/slimy-icons-itch.md
delete mode 100644 docs/src/content/docs/getting-started.md
create mode 100644 docs/src/content/docs/getting-started.mdx
create mode 100644 docs/src/content/docs/guides/components.mdx
create mode 100644 packages/starlight/components.ts
create mode 100644 packages/starlight/user-components/TabItem.astro
create mode 100644 packages/starlight/user-components/Tabs.astro
create mode 100644 packages/starlight/user-components/rehype-tabs.ts
diff --git a/.changeset/slimy-icons-itch.md b/.changeset/slimy-icons-itch.md
new file mode 100644
index 00000000..a46bf26f
--- /dev/null
+++ b/.changeset/slimy-icons-itch.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': patch
+---
+
+Add tab components for use in MDX.
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 41223fe0..721600ac 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -37,9 +37,9 @@ export default defineConfig({
},
{
label: 'Guides',
- items: [
- { label: 'Internationalization (i18n)', link: 'guides/i18n' },
- ],
+ autogenerate: {
+ directory: 'guides',
+ },
},
{
label: 'Reference',
diff --git a/docs/src/content/docs/getting-started.md b/docs/src/content/docs/getting-started.md
deleted file mode 100644
index b157bc83..00000000
--- a/docs/src/content/docs/getting-started.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: Getting Started
-description: Learn how to start building your next documentation site with Starlight by Astro.
----
-
-:::caution[Work in progress]
-Starlight is in early development so expect bugs and changes as we develop it.
-If you find something that’s not working, [open an issue on GitHub](https://github.com/withastro/starlight/issues/new/choose) or let us know on [Discord](https://astro.build/chat).
-:::
-
-Welcome to Starlight, an intuitive and user-friendly framework ideal for documentation websites. In this introductory guide, we will explore the main features and benefits of Starlight.
-
-## Getting started with Starlight
-
-Starlight is built on top of the [Astro](https://astro.build) all-in-one framework. You can create a new Astro + Starlight project using the following command:
-
-```sh
-# create a new project with npm
-npm create astro --template starlight
-```
-
-This will create a new project directory with all the necessary files and configurations for your site.
-
-## What’s in the box?
-
-Starlight includes all the features you need to get a documentation site up and running quickly:
-
-- Easy-to-read typographic styles
-- Syntax highlighting for code blocks
-- Simple-to-configure navigation menus
-- Built-in site search
-- [Internationalization features](/guides/i18n)
-- Mix components (from any framework!) and content in MDX
-- Support for custom styles
-
-With these features, you can create rich and engaging documentation that is easy to read and understand.
-
-## Creating content with Starlight
-
-Starlight supports authoring content in Markdown and MDX.
-
-Adding new pages is done by adding a new `.md` or `.mdx` file to `src/content/docs/`. For example, `src/content/docs/hello-world.md` will be available on your site at `/hello-world`.
-
-All Starlight pages share a common set of frontmatter properties you can set to control how the page appears:
-
-```md
----
-title: Hello, World!
-description: This is a page in my Starlight-powered site
----
-```
-
-If you forget anything important, Starlight will let you know.
-
-## Deploying your Starlight website
-
-Once you have created and customized your Starlight website, you can deploy it to a web server or hosting platform of your choice. Astro provides built-in support for several popular hosting platforms, including Netlify, Vercel, and GitHub Pages, which means you can deploy your website with just a few simple commands.
-
-[Learn about deploying an Astro site in the Astro docs.](https://docs.astro.build/en/guides/deploy/)
diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx
new file mode 100644
index 00000000..9a261ba1
--- /dev/null
+++ b/docs/src/content/docs/getting-started.mdx
@@ -0,0 +1,83 @@
+---
+title: Getting Started
+description: Learn how to start building your next documentation site with Starlight by Astro.
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+:::caution[Work in progress]
+Starlight is in early development so expect bugs and changes as we develop it.
+If you find something that’s not working, [open an issue on GitHub](https://github.com/withastro/starlight/issues/new/choose) or let us know on [Discord](https://astro.build/chat).
+:::
+
+Welcome to Starlight, an intuitive and user-friendly framework ideal for documentation websites. In this introductory guide, we will explore the main features and benefits of Starlight.
+
+## Getting started with Starlight
+
+Starlight is built on top of the [Astro](https://astro.build) all-in-one framework. You can create a new Astro + Starlight project using the following command:
+
+
+
+
+```sh
+# create a new project with npm
+npm create astro --template starlight
+```
+
+
+
+
+```sh
+# create a new project with pnpm
+pnpm create astro --template starlight
+```
+
+
+
+
+```sh
+# create a new project with pnpm
+yarn create astro --template starlight
+```
+
+
+
+
+This will create a new project directory with all the necessary files and configurations for your site.
+
+## What’s in the box?
+
+Starlight includes all the features you need to get a documentation site up and running quickly:
+
+- Easy-to-read typographic styles
+- Syntax highlighting for code blocks
+- Simple-to-configure navigation menus
+- Built-in site search
+- [Internationalization features](/guides/i18n)
+- Mix components (from any framework!) and content in MDX
+- Support for custom styles
+
+With these features, you can create rich and engaging documentation that is easy to read and understand.
+
+## Creating content with Starlight
+
+Starlight supports authoring content in Markdown and MDX.
+
+Adding new pages is done by adding a new `.md` or `.mdx` file to `src/content/docs/`. For example, `src/content/docs/hello-world.md` will be available on your site at `/hello-world`.
+
+All Starlight pages share a common set of frontmatter properties you can set to control how the page appears:
+
+```md
+---
+title: Hello, World!
+description: This is a page in my Starlight-powered site
+---
+```
+
+If you forget anything important, Starlight will let you know.
+
+## Deploying your Starlight website
+
+Once you have created and customized your Starlight website, you can deploy it to a web server or hosting platform of your choice. Astro provides built-in support for several popular hosting platforms, including Netlify, Vercel, and GitHub Pages, which means you can deploy your website with just a few simple commands.
+
+[Learn about deploying an Astro site in the Astro docs.](https://docs.astro.build/en/guides/deploy/)
diff --git a/docs/src/content/docs/guides/components.mdx b/docs/src/content/docs/guides/components.mdx
new file mode 100644
index 00000000..fd13d091
--- /dev/null
+++ b/docs/src/content/docs/guides/components.mdx
@@ -0,0 +1,60 @@
+---
+title: Components
+description: Using components in MDX with Starlight.
+---
+
+Components let you easily re-use a piece of UI or styling consistently.
+Examples might include a link card or a YouTube embed.
+Starlight supports the use of components in [MDX](https://mdxjs.com/) files and provides some common components for you to use.
+
+## Using a component
+
+You can use a component by importing it into your MDX file and then calling it as a JSX tag.
+These look like HTML tags but start with an uppercase letter matching the name in your `import` statement:
+
+```mdx
+---
+# src/content/docs/index.mdx
+title: Welcome to my docs
+---
+
+import SomeComponent from '../../components/SomeComponent.astro';
+import AnotherComponent from '../../components/AnotherComponent.astro';
+
+
+
+
+ Components can also contain **nested content**.
+
+```
+
+Because Starlight is powered by Astro, you can use components built with any UI framework in your MDX files.
+Learn more about [using components in MDX](https://docs.astro.build/en/guides/markdown-content/#using-components-in-mdx) in the Astro docs.
+
+## Built-in components
+
+Starlight provides built-in components for common documentation use cases.
+These components are available from the `@astrojs/starlight/components` package.
+
+### Tabs
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+You can display a tabbed interface using the `` and `` components.
+Each `` must have a `label` to display to users.
+
+```mdx
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+
+ Sirius, Vega, Betelgeuse
+ Io, Europa, Ganymede
+
+```
+
+The code above generates the following tabs on the page:
+
+
+ Sirius, Vega, Betelgeuse
+ Io, Europa, Ganymede
+
diff --git a/packages/starlight/components.ts b/packages/starlight/components.ts
new file mode 100644
index 00000000..1e25b6cc
--- /dev/null
+++ b/packages/starlight/components.ts
@@ -0,0 +1,2 @@
+export { default as Tabs } from './user-components/Tabs.astro';
+export { default as TabItem } from './user-components/TabItem.astro';
diff --git a/packages/starlight/package.json b/packages/starlight/package.json
index e70295e4..509ddfdb 100644
--- a/packages/starlight/package.json
+++ b/packages/starlight/package.json
@@ -21,6 +21,7 @@
"type": "module",
"exports": {
".": "./index.ts",
+ "./components": "./components.ts",
"./schema": "./schema.ts",
"./types": "./types.ts",
"./index.astro": "./index.astro",
@@ -41,9 +42,11 @@
"execa": "^7.1.1",
"hastscript": "^7.2.0",
"pagefind": "^0.12.0",
+ "rehype": "^12.0.1",
"remark-directive": "^2.0.1",
"unified": "^10.1.2",
"unist-util-remove": "^3.1.1",
- "unist-util-visit": "^4.1.2"
+ "unist-util-visit": "^4.1.2",
+ "vfile": "^5.3.7"
}
}
diff --git a/packages/starlight/user-components/TabItem.astro b/packages/starlight/user-components/TabItem.astro
new file mode 100644
index 00000000..be52c6f6
--- /dev/null
+++ b/packages/starlight/user-components/TabItem.astro
@@ -0,0 +1,17 @@
+---
+import { TabItemTagname } from './rehype-tabs';
+
+interface Props {
+ label: string;
+}
+
+const { label } = Astro.props;
+
+if (!label) {
+ throw new Error('Missing prop `label` on `` component.');
+}
+---
+
+
+
+
diff --git a/packages/starlight/user-components/Tabs.astro b/packages/starlight/user-components/Tabs.astro
new file mode 100644
index 00000000..dc4bc744
--- /dev/null
+++ b/packages/starlight/user-components/Tabs.astro
@@ -0,0 +1,148 @@
+---
+import { processPanels } from './rehype-tabs';
+
+const panelHtml = await Astro.slots.render('default');
+const { html, panels } = processPanels(panelHtml);
+---
+
+
+ {
+ panels && (
+
+
+ {panels.map(({ label, panelId, tabId }, idx) => (
+ -
+
+ {label}
+
+
+ ))}
+
+
+ )
+ }
+
+
+
+
+
+
diff --git a/packages/starlight/user-components/rehype-tabs.ts b/packages/starlight/user-components/rehype-tabs.ts
new file mode 100644
index 00000000..e3421856
--- /dev/null
+++ b/packages/starlight/user-components/rehype-tabs.ts
@@ -0,0 +1,82 @@
+import { rehype } from 'rehype';
+import { CONTINUE, SKIP, visit } from 'unist-util-visit';
+
+interface Panel {
+ panelId: string;
+ tabId: string;
+ label: string;
+}
+
+declare module 'vfile' {
+ interface DataMap {
+ panels: Panel[];
+ }
+}
+
+export const TabItemTagname = 'starlight-tab-item';
+
+let count = 0;
+const getIDs = () => {
+ const id = count++;
+ return { panelId: 'tab-panel-' + id, tabId: 'tab-' + id };
+};
+
+/**
+ * Rehype processor to extract tab panel data and turn each
+ * `` into a `` with the necessary
+ * attributes.
+ */
+const tabsProcessor = rehype()
+ .data('settings', { fragment: true })
+ .use(function tabs() {
+ return (tree, file) => {
+ file.data.panels = [];
+ let isFirst = true;
+ visit(tree, 'element', (node) => {
+ if (node.tagName !== TabItemTagname || !node.properties) {
+ return CONTINUE;
+ }
+
+ const { dataLabel } = node.properties;
+ const ids = getIDs();
+ file.data.panels?.push({
+ ...ids,
+ label: String(dataLabel),
+ });
+
+ // Remove `` props
+ delete node.properties.dataLabel;
+ // Turn into `` with required attributes
+ node.tagName = 'section';
+ node.properties.id = ids.panelId;
+ node.properties['aria-labelledby'] = ids.tabId;
+ node.properties.role = 'tabpanel';
+ node.properties.tabindex = -1;
+ // Hide all panels except the first
+ // TODO: make initially visible tab configurable
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ node.properties.hidden = true;
+ }
+
+ // Skip over the tab panel’s children.
+ return SKIP;
+ });
+ };
+ });
+
+/**
+ * Process tab panel items to extract data for the tab links and format
+ * each tab panel correctly.
+ * @param html Inner HTML passed to the `` component.
+ */
+export const processPanels = (html: string) => {
+ const file = tabsProcessor.processSync({ value: html });
+ return {
+ /** Data for each tab panel. */
+ panels: file.data.panels,
+ /** Processed HTML for the tab panels. */
+ html: file.toString(),
+ };
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9e074675..d397e16d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,6 +46,9 @@ importers:
pagefind:
specifier: ^0.12.0
version: 0.12.0
+ rehype:
+ specifier: ^12.0.1
+ version: 12.0.1
remark-directive:
specifier: ^2.0.1
version: 2.0.1
@@ -58,6 +61,9 @@ importers:
unist-util-visit:
specifier: ^4.1.2
version: 4.1.2
+ vfile:
+ specifier: ^5.3.7
+ version: 5.3.7
devDependencies:
'@types/node':
specifier: ^18.15.11
--
cgit