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) => ( + + ))} +
+
+ ) + } + +
+ + + + 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