Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import starlightLinksValidator from "starlight-links-validator";
import icon from "astro-icon";
import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react";

import { readdir } from "fs/promises";
import { fileURLToPath } from "url";

import rehypeTitleFigure from "rehype-title-figure";
import rehypeMermaid from "./src/plugins/rehype/mermaid.ts";
import rehypeAutolinkHeadings from "./src/plugins/rehype/autolink-headings.ts";
import rehypeExternalLinks from "./src/plugins/rehype/external-links.ts";
import rehypeHeadingSlugs from "./src/plugins/rehype/heading-slugs.ts";
import { fileURLToPath } from "url";

async function autogenSections() {
const sections = (
Expand Down
42 changes: 11 additions & 31 deletions src/components/Render.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,29 @@ const props = z.object({
let { file, product, params } = props.parse(Astro.props);

if (!product) {
const fromSlug = Astro.params.slug?.split("/")[0];

if (!fromSlug) {
throw new Error(
`[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
);
}
product = Astro.params.slug?.split("/")[0];
}

product = fromSlug;
if (!product) {
throw new Error(
`[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
);
}

const id = `${product}/${file}`;
const partial = await getEntry("partials", id);
const partial = await getEntry("partials", `${product}/${file}`);

if (!partial) {
throw new Error(
`[Render] Couldn't find "${id}" included on "${Astro.url.pathname}"`,
`[Render] Couldn't find partial: ${file}. Included on ${Astro.params.slug}`,
);
}

// We currently only enforce parameters if `params` is set in the frontmatter,
// until we can migrate existing `inputParameters` frontmatter to `params`.
if (partial.data.params) {
const expected = partial.data.params.sort();
const optional = expected.filter((p) => p.endsWith("?"));
const received = Object.keys(params ?? {}).sort();

const maximum = expected.length;
const minimum = maximum - optional.length;

if (
received.length < minimum ||
received.length > maximum ||
expected.some((p: string) => {
if (p.endsWith("?")) return false;

return !received.includes(p);
})
) {
const expected = partial.data.params;
if (!params)
throw new Error(
`[Render] Expected parameters ${JSON.stringify(expected)} but received parameters ${JSON.stringify(received)} for "${file}" included on "${Astro.url.pathname}"`,
`${file} included on ${Astro.params.slug} expected parameters: ${expected}, got none`,
);
}
}

const { Content } = await render(partial);
Expand Down
9 changes: 6 additions & 3 deletions src/content.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineCollection } from "astro:content";
import { z, defineCollection } from "astro:content";

import { docsLoader, i18nLoader } from "@astrojs/starlight/loaders";
import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
Expand All @@ -20,7 +20,6 @@ import {
warpReleasesSchema,
changelogsNextSchema,
fieldsSchema,
partialsSchema,
} from "~/schemas";

function contentLoader(name: string) {
Expand All @@ -37,6 +36,10 @@ function dataLoader(name: string) {
});
}

const partialSchema = z.object({
params: z.string().array().optional(),
});

export const collections = {
docs: defineCollection({
loader: docsLoader(),
Expand All @@ -58,7 +61,7 @@ export const collections = {
}),
partials: defineCollection({
loader: contentLoader("partials"),
schema: partialsSchema,
schema: partialSchema,
}),
glossary: defineCollection({
loader: dataLoader("glossary"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ The Microsoft Entra ID integration allows you to synchronize IdP groups and auto

<Render
file="access/enable-scim-on-dashboard"
params={{ idp: "Entra ID", supportgroups: "Support groups"}}
params={{ idp: "Entra ID", and: " and ", supportgroups: "Support groups"}}
/>

### 2. Configure SCIM in Entra ID
Expand Down
6 changes: 0 additions & 6 deletions src/content/docs/style-guide/components/markdown.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
title: Markdown
---

:::caution
This component does not use Astro to render the Markdown, as a result it cannot use components, `~/assets/` images or code blocks.

Where feasible, use [JSX](/style-guide/components/render/#properties-in-markdown-syntax) instead
:::

This component uses `marked` to turn the `text` prop into HTML. This is useful with, for example partial files that have variables you need to format.

```mdx live
Expand Down
178 changes: 47 additions & 131 deletions src/content/docs/style-guide/components/render.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,174 +2,90 @@
title: Render
---

import { Code, Details, Type, MetaInfo } from "~/components";
import { Steps } from "~/components";

The `Render` component allows us to include a "partial", a reusable Markdown snippet, onto a page.

It also accepts parameters that can be used as variables within the partial, so that even content which needs slight differences between usages can be turned into a partial.
It also accepts parameters that can be used as variables within the partial, so that even content
which needs slight differences between usages can be turned into a partial.

## Component

```mdx live
import { Render } from "~/components";

<Render file="simple-props" params={{
<Render file="hello" params={{
name: "world",
link: "/style-guide/components/render/"
}} />
```

### Inputs

- `file` <Type text="string" />

This should be the name of the partial, without the containing directory or file extension. For example, `/partials/style-guide/hello.mdx` would be `file="hello"`.

- `product` <Type text="string" /> <MetaInfo text="optional" />

By default, it will look for partials in the same product folder as the current page. You can use this to specify a different product.

:::caution
{/*
Hello, {props.name}!

When using the `Render` component inside partials, the original `product` is lost.
Hello `{props.name}`

For example, if there are three files:
Hello <code>{props.name}</code>

1. `docs/fundamentals/index.mdx`
2. `partials/dns/thing.mdx`
3. `partials/dns/thing2.mdx`
[link]({props.link})

`docs/fundamentals/index.mdx` uses `<Render file="thing" product="dns" />`

`partials/dns/thing.mdx` must use `<Render file="thing2" product="dns" />` as `product` cannot be inferred.

:::
<a href={props.link}>link</a>
*/}
```

- `params` <Type text="object" /> <MetaInfo text="optional" />
### Inputs

If you wish to substitute values inside your partial, you can use pass params which can be referenced in your partial. Refer to [properties](#properties).
**file** `string`: This should be the name of the partial, without the containing directory or file extension.
For example, `/partials/style-guide/hello.mdx` would be `file="hello"`.

## Properties
**product** `string` (optional): By default, it will look for partials in the same product folder as the current page.
You can use this to specify a different product.

### Defining expected properties in frontmatter
**params** `object` (optional): If you wish to substitute values inside your partial, you can use pass params which can be
referenced in your partial. Refer to [params](#params).

Anything defined in the `params` property of the `Render` component is available inside the partial, using [JavaScript expressions](https://mdxjs.com/docs/using-mdx/).
## Partials

To protect against required properties being missed, any partial that relies on `params` should also define `params` in the partial's frontmatter. This should be an array of strings, matching the property names you expect. If a property is optional, such as for [conditional content](#properties-to-render-content-conditionally), add a `?` to the end of the name.
Partials only have one optional frontmatter property, which is `params`. This takes an array of strings,
which should be the expected parameters. When this is defined, but those parameters are not passed, an error
will be thrown.

```mdx
```mdx title="/src/content/partials/style-guide/hello.mdx"
---
params:
- product
- deprecated?
- name
- foo
- bar
---
```

For each of the below examples, you can open the dropdown to view the partial's content.

### Properties as a plain string

The below example would render `Hello, world!`.

import simpleRaw from "~/content/partials/style-guide/simple-props.mdx?raw";

<Details header="simple-props.mdx">
<Code code={simpleRaw} lang="mdx" />
</Details>

```mdx live
import { Render } from "~/components";

<Render file="simple-props" params={{ name: "world" }} />
```

### Properties in Markdown syntax

When using JavaScript expressions, you are now "inside JSX" and cannot use traditional Markdown syntax. Similarly, you cannot use a JavaScript expression inside Markdown syntax.

Ideally, you should not use Markdown syntax, such as `**strong**` or `[text](link)`, with properties. If using JSX is not feasible, there is a [`Markdown`](/style-guide/components/markdown/) component that will take a `text` property.

The [MDX documentation](https://mdxjs.com/table-of-components/#components) includes a mapping of common Markdown syntax to their equivalent JSX elements.

#### Strong

import strongRaw from "~/content/partials/style-guide/strong-in-props.mdx?raw";

<Details header="strong-in-props.mdx">
<Code code={strongRaw} lang="mdx" />
</Details>
Hello, {props.name}!

```mdx live
import { Render } from "~/components";
Hello, {props.foo}!

<Render file="strong-in-props" params={{ do: "Text", dont: "**Text**" }} />
Hello, {props.bar}!
```

#### Links

import linkRaw from "~/content/partials/style-guide/link-in-props.mdx?raw";

<Details header="link-in-props.mdx">
<Code code={linkRaw} lang="mdx" />
</Details>
### Params

```mdx live
import { Render } from "~/components";
In the above example, you will notice there is:

<Render file="link-in-props" params={{
link: "/style-guide/components/render/#links"
}} />
```
<Steps>

#### Images

import imageRaw from "~/content/partials/style-guide/image-in-props.mdx?raw";

<Details header="image-in-props.mdx">
<Code code={imageRaw} lang="mdx" />
</Details>

```mdx live
import { Render } from "~/components";

<Render file="image-in-props" params={{ image: "/logo.svg" }} />
```
1. A `params` input on the `Render` component.
2. A `params` property in the frontmatter.
3. A reference to `props.name`.

#### Code blocks
</Steps>

import codeRaw from "~/content/partials/style-guide/code-in-props.mdx?raw";

<Details header="code-in-props.mdx">
<Code code={codeRaw} lang="mdx" />
</Details>

```mdx live
import { Render } from "~/components";

<Render file="code-in-props" params={{ code: "export const foo = 'bar';" }} />
```

### Properties to render content conditionally

Anything that you can represent in a JavaScript expression can be used in your conditional logic.

This may be the [and (`&&`) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) or [ternary (`? ... : ... `) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator), the below example uses both.

import optionalRaw from "~/content/partials/style-guide/optional-props.mdx?raw";

<Details header="optional-props.mdx">
<Code code={optionalRaw} lang="mdx" />
</Details>

```mdx live
import { Render } from "~/components";
#### Input

<Render file="optional-props" params={{ product: "Thing", deprecated: true }} />
When using the `params` input on the `Render` component, you can write a [JavaScript object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects)
which is later available inside the partial.

<hr />
#### Frontmatter

<Render file="optional-props" params={{ product: "Thing Two" }} />
The `params` frontmatter property on a partial expects an array of strings, containing the "expected parameters". This is so that if
your partial requires parameters to be passed, and none are passed, we can warn the user.

<hr />
#### Props

<Render file="optional-props" params={{ product: "Thing Three" }} />
```
The way that you can access these parameters is with the `props` object, surrounded in curly braces `{}`.
This is a [JavaScript expression within MDX](https://mdxjs.com/docs/using-mdx/#props).
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
params:
- idp
- supportgroups?
- and
- supportgroups
---

import { Markdown } from "~/components"
Expand All @@ -10,7 +11,7 @@ import { Markdown } from "~/components"

2. Find the {props.idp} integration and select **Edit**.

3. Turn on **Enable SCIM** {props.supportgroups && <span> and <strong>props.supportgroups</strong>.</span>}
3. Turn on **Enable SCIM**{props.and}**{props.supportgroups}**.

4. (Optional) Configure the following settings:

Expand Down
Loading
Loading