Skip to content

Commit b393f38

Browse files
authored
[Docs Site] Refactor partials property validation and docs (#19721)
* [Docs Site] Refactor partials property validation and docs * update partial examples * update anchor in markdown component aside
1 parent bf21ed4 commit b393f38

File tree

15 files changed

+300
-86
lines changed

15 files changed

+300
-86
lines changed

astro.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import starlightLinksValidator from "starlight-links-validator";
88
import icon from "astro-icon";
99
import sitemap from "@astrojs/sitemap";
1010
import react from "@astrojs/react";
11+
1112
import { readdir } from "fs/promises";
13+
import { fileURLToPath } from "url";
1214

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

2021
async function autogenSections() {
2122
const sections = (

src/components/Render.astro

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,49 @@ const props = z.object({
1313
let { file, product, params } = props.parse(Astro.props);
1414
1515
if (!product) {
16-
product = Astro.params.slug?.split("/")[0];
17-
}
16+
const fromSlug = Astro.params.slug?.split("/")[0];
1817
19-
if (!product) {
20-
throw new Error(
21-
`[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
22-
);
18+
if (!fromSlug) {
19+
throw new Error(
20+
`[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
21+
);
22+
}
23+
24+
product = fromSlug;
2325
}
2426
25-
const partial = await getEntry("partials", `${product}/${file}`);
27+
const id = `${product}/${file}`;
28+
const partial = await getEntry("partials", id);
2629
2730
if (!partial) {
2831
throw new Error(
29-
`[Render] Couldn't find partial: ${file}. Included on ${Astro.params.slug}`,
32+
`[Render] Couldn't find "${id}" included on "${Astro.url.pathname}"`,
3033
);
3134
}
3235
36+
// We currently only enforce parameters if `params` is set in the frontmatter,
37+
// until we can migrate existing `inputParameters` frontmatter to `params`.
3338
if (partial.data.params) {
34-
const expected = partial.data.params;
35-
if (!params)
39+
const expected = partial.data.params.sort();
40+
const optional = expected.filter((p) => p.endsWith("?"));
41+
const received = Object.keys(params ?? {}).sort();
42+
43+
const maximum = expected.length;
44+
const minimum = maximum - optional.length;
45+
46+
if (
47+
received.length < minimum ||
48+
received.length > maximum ||
49+
expected.some((p: string) => {
50+
if (p.endsWith("?")) return false;
51+
52+
return !received.includes(p);
53+
})
54+
) {
3655
throw new Error(
37-
`${file} included on ${Astro.params.slug} expected parameters: ${expected}, got none`,
56+
`[Render] Expected parameters ${JSON.stringify(expected)} but received parameters ${JSON.stringify(received)} for "${file}" included on "${Astro.url.pathname}"`,
3857
);
58+
}
3959
}
4060
4161
const { Content } = await render(partial);

src/content.config.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { z, defineCollection } from "astro:content";
1+
import { defineCollection } from "astro:content";
22

33
import { docsLoader, i18nLoader } from "@astrojs/starlight/loaders";
44
import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
@@ -20,6 +20,7 @@ import {
2020
warpReleasesSchema,
2121
changelogsNextSchema,
2222
fieldsSchema,
23+
partialsSchema,
2324
} from "~/schemas";
2425

2526
function contentLoader(name: string) {
@@ -36,10 +37,6 @@ function dataLoader(name: string) {
3637
});
3738
}
3839

39-
const partialSchema = z.object({
40-
params: z.string().array().optional(),
41-
});
42-
4340
export const collections = {
4441
docs: defineCollection({
4542
loader: docsLoader(),
@@ -61,7 +58,7 @@ export const collections = {
6158
}),
6259
partials: defineCollection({
6360
loader: contentLoader("partials"),
64-
schema: partialSchema,
61+
schema: partialsSchema,
6562
}),
6663
glossary: defineCollection({
6764
loader: dataLoader("glossary"),

src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ The Microsoft Entra ID integration allows you to synchronize IdP groups and auto
120120

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

126126
### 2. Configure SCIM in Entra ID

src/content/docs/style-guide/components/markdown.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
title: Markdown
33
---
44

5+
:::caution
6+
This component does not use Astro to render the Markdown, as a result it cannot use components, `~/assets/` images or code blocks.
7+
8+
Where feasible, use [JSX](/style-guide/components/render/#properties-in-markdown-syntax) instead
9+
:::
10+
511
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.
612

713
```mdx live

src/content/docs/style-guide/components/render.mdx

Lines changed: 131 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,90 +2,174 @@
22
title: Render
33
---
44

5-
import { Steps } from "~/components";
5+
import { Code, Details, Type, MetaInfo } from "~/components";
66

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

9-
It also accepts parameters that can be used as variables within the partial, so that even content
10-
which needs slight differences between usages can be turned into a partial.
9+
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.
1110

1211
## Component
1312

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

17-
<Render file="hello" params={{
16+
<Render file="simple-props" params={{
1817
name: "world",
19-
link: "/style-guide/components/render/"
2018
}} />
21-
{/*
22-
Hello, {props.name}!
19+
```
2320

24-
Hello `{props.name}`
21+
### Inputs
2522

26-
Hello <code>{props.name}</code>
23+
- `file` <Type text="string" />
2724

28-
[link]({props.link})
25+
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"`.
2926

30-
<a href={props.link}>link</a>
31-
*/}
32-
```
27+
- `product` <Type text="string" /> <MetaInfo text="optional" />
3328

34-
### Inputs
29+
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.
30+
31+
:::caution
32+
33+
When using the `Render` component inside partials, the original `product` is lost.
34+
35+
For example, if there are three files:
36+
37+
1. `docs/fundamentals/index.mdx`
38+
2. `partials/dns/thing.mdx`
39+
3. `partials/dns/thing2.mdx`
3540

36-
**file** `string`: This should be the name of the partial, without the containing directory or file extension.
37-
For example, `/partials/style-guide/hello.mdx` would be `file="hello"`.
41+
`docs/fundamentals/index.mdx` uses `<Render file="thing" product="dns" />`
3842

39-
**product** `string` (optional): By default, it will look for partials in the same product folder as the current page.
40-
You can use this to specify a different product.
43+
`partials/dns/thing.mdx` must use `<Render file="thing2" product="dns" />` as `product` cannot be inferred.
4144

42-
**params** `object` (optional): If you wish to substitute values inside your partial, you can use pass params which can be
43-
referenced in your partial. Refer to [params](#params).
45+
:::
4446

45-
## Partials
47+
- `params` <Type text="object" /> <MetaInfo text="optional" />
4648

47-
Partials only have one optional frontmatter property, which is `params`. This takes an array of strings,
48-
which should be the expected parameters. When this is defined, but those parameters are not passed, an error
49-
will be thrown.
49+
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).
5050

51-
```mdx title="/src/content/partials/style-guide/hello.mdx"
51+
## Properties
52+
53+
### Defining expected properties in frontmatter
54+
55+
Anything defined in the `params` property of the `Render` component is available inside the partial, using [JavaScript expressions](https://mdxjs.com/docs/using-mdx/).
56+
57+
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.
58+
59+
```mdx
5260
---
5361
params:
54-
- name
55-
- foo
56-
- bar
62+
- product
63+
- deprecated?
5764
---
65+
```
66+
67+
For each of the below examples, you can open the dropdown to view the partial's content.
68+
69+
### Properties as a plain string
70+
71+
The below example would render `Hello, world!`.
72+
73+
import simpleRaw from "~/content/partials/style-guide/simple-props.mdx?raw";
74+
75+
<Details header="simple-props.mdx">
76+
<Code code={simpleRaw} lang="mdx" />
77+
</Details>
78+
79+
```mdx live
80+
import { Render } from "~/components";
81+
82+
<Render file="simple-props" params={{ name: "world" }} />
83+
```
84+
85+
### Properties in Markdown syntax
86+
87+
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.
88+
89+
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.
90+
91+
The [MDX documentation](https://mdxjs.com/table-of-components/#components) includes a mapping of common Markdown syntax to their equivalent JSX elements.
92+
93+
#### Strong
94+
95+
import strongRaw from "~/content/partials/style-guide/strong-in-props.mdx?raw";
5896

59-
Hello, {props.name}!
97+
<Details header="strong-in-props.mdx">
98+
<Code code={strongRaw} lang="mdx" />
99+
</Details>
60100

61-
Hello, {props.foo}!
101+
```mdx live
102+
import { Render } from "~/components";
62103

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

66-
### Params
107+
#### Links
108+
109+
import linkRaw from "~/content/partials/style-guide/link-in-props.mdx?raw";
110+
111+
<Details header="link-in-props.mdx">
112+
<Code code={linkRaw} lang="mdx" />
113+
</Details>
67114

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

70-
<Steps>
118+
<Render file="link-in-props" params={{
119+
link: "/style-guide/components/render/#links"
120+
}} />
121+
```
71122

72-
1. A `params` input on the `Render` component.
73-
2. A `params` property in the frontmatter.
74-
3. A reference to `props.name`.
123+
#### Images
124+
125+
import imageRaw from "~/content/partials/style-guide/image-in-props.mdx?raw";
126+
127+
<Details header="image-in-props.mdx">
128+
<Code code={imageRaw} lang="mdx" />
129+
</Details>
130+
131+
```mdx live
132+
import { Render } from "~/components";
133+
134+
<Render file="image-in-props" params={{ image: "/logo.svg" }} />
135+
```
75136

76-
</Steps>
137+
#### Code blocks
77138

78-
#### Input
139+
import codeRaw from "~/content/partials/style-guide/code-in-props.mdx?raw";
140+
141+
<Details header="code-in-props.mdx">
142+
<Code code={codeRaw} lang="mdx" />
143+
</Details>
144+
145+
```mdx live
146+
import { Render } from "~/components";
147+
148+
<Render file="code-in-props" params={{ code: "export const foo = 'bar';" }} />
149+
```
150+
151+
### Properties to render content conditionally
152+
153+
Anything that you can represent in a JavaScript expression can be used in your conditional logic.
154+
155+
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.
156+
157+
import optionalRaw from "~/content/partials/style-guide/optional-props.mdx?raw";
158+
159+
<Details header="optional-props.mdx">
160+
<Code code={optionalRaw} lang="mdx" />
161+
</Details>
162+
163+
```mdx live
164+
import { Render } from "~/components";
79165

80-
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)
81-
which is later available inside the partial.
166+
<Render file="optional-props" params={{ product: "Thing", deprecated: true }} />
82167

83-
#### Frontmatter
168+
<hr />
84169

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

88-
#### Props
172+
<hr />
89173

90-
The way that you can access these parameters is with the `props` object, surrounded in curly braces `{}`.
91-
This is a [JavaScript expression within MDX](https://mdxjs.com/docs/using-mdx/#props).
174+
<Render file="optional-props" params={{ product: "Thing Three" }} />
175+
```

src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
params:
33
- idp
4-
- and
5-
- supportgroups
4+
- supportgroups?
65
---
76

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

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

14-
3. Turn on **Enable SCIM**{props.and}**{props.supportgroups}**.
13+
3. Turn on **Enable SCIM** {props.supportgroups && <span> and <strong>props.supportgroups</strong>.</span>}
1514

1615
4. (Optional) Configure the following settings:
1716

0 commit comments

Comments
 (0)