diff --git a/astro.config.ts b/astro.config.ts
index d7099a81d7f5ff..3e59ae1d5edd96 100644
--- a/astro.config.ts
+++ b/astro.config.ts
@@ -8,14 +8,15 @@ 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 = (
diff --git a/src/components/Render.astro b/src/components/Render.astro
index fd854e3aee7ab2..8583134d2de073 100644
--- a/src/components/Render.astro
+++ b/src/components/Render.astro
@@ -13,29 +13,49 @@ const props = z.object({
let { file, product, params } = props.parse(Astro.props);
if (!product) {
- product = Astro.params.slug?.split("/")[0];
-}
+ const fromSlug = Astro.params.slug?.split("/")[0];
-if (!product) {
- throw new Error(
- `[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
- );
+ if (!fromSlug) {
+ throw new Error(
+ `[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`,
+ );
+ }
+
+ product = fromSlug;
}
-const partial = await getEntry("partials", `${product}/${file}`);
+const id = `${product}/${file}`;
+const partial = await getEntry("partials", id);
if (!partial) {
throw new Error(
- `[Render] Couldn't find partial: ${file}. Included on ${Astro.params.slug}`,
+ `[Render] Couldn't find "${id}" included on "${Astro.url.pathname}"`,
);
}
+// 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;
- if (!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);
+ })
+ ) {
throw new Error(
- `${file} included on ${Astro.params.slug} expected parameters: ${expected}, got none`,
+ `[Render] Expected parameters ${JSON.stringify(expected)} but received parameters ${JSON.stringify(received)} for "${file}" included on "${Astro.url.pathname}"`,
);
+ }
}
const { Content } = await render(partial);
diff --git a/src/content.config.ts b/src/content.config.ts
index 5f8f1f01d0f4bb..3a7e99ec2fcd66 100644
--- a/src/content.config.ts
+++ b/src/content.config.ts
@@ -1,4 +1,4 @@
-import { z, defineCollection } from "astro:content";
+import { defineCollection } from "astro:content";
import { docsLoader, i18nLoader } from "@astrojs/starlight/loaders";
import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
@@ -20,6 +20,7 @@ import {
warpReleasesSchema,
changelogsNextSchema,
fieldsSchema,
+ partialsSchema,
} from "~/schemas";
function contentLoader(name: string) {
@@ -36,10 +37,6 @@ function dataLoader(name: string) {
});
}
-const partialSchema = z.object({
- params: z.string().array().optional(),
-});
-
export const collections = {
docs: defineCollection({
loader: docsLoader(),
@@ -61,7 +58,7 @@ export const collections = {
}),
partials: defineCollection({
loader: contentLoader("partials"),
- schema: partialSchema,
+ schema: partialsSchema,
}),
glossary: defineCollection({
loader: dataLoader("glossary"),
diff --git a/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx b/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx
index 51d66ac873662d..e22cde97e6ad46 100644
--- a/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx
+++ b/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx
@@ -120,7 +120,7 @@ The Microsoft Entra ID integration allows you to synchronize IdP groups and auto
### 2. Configure SCIM in Entra ID
diff --git a/src/content/docs/style-guide/components/markdown.mdx b/src/content/docs/style-guide/components/markdown.mdx
index 38a51a2bfc435c..a40df4d0024c33 100644
--- a/src/content/docs/style-guide/components/markdown.mdx
+++ b/src/content/docs/style-guide/components/markdown.mdx
@@ -2,6 +2,12 @@
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
diff --git a/src/content/docs/style-guide/components/render.mdx b/src/content/docs/style-guide/components/render.mdx
index 23069da3b383d6..57060bbe4d84a5 100644
--- a/src/content/docs/style-guide/components/render.mdx
+++ b/src/content/docs/style-guide/components/render.mdx
@@ -2,90 +2,174 @@
title: Render
---
-import { Steps } from "~/components";
+import { Code, Details, Type, MetaInfo } 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";
-
-{/*
-Hello, {props.name}!
+```
-Hello `{props.name}`
+### Inputs
-Hello {props.name}
+- `file`
-[link]({props.link})
+ 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"`.
-link
-*/}
-```
+- `product`
-### Inputs
+ 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
+
+ When using the `Render` component inside partials, the original `product` is lost.
+
+ For example, if there are three files:
+
+ 1. `docs/fundamentals/index.mdx`
+ 2. `partials/dns/thing.mdx`
+ 3. `partials/dns/thing2.mdx`
-**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"`.
+ `docs/fundamentals/index.mdx` uses ``
-**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.
+ `partials/dns/thing.mdx` must use `` as `product` cannot be inferred.
-**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).
+ :::
-## Partials
+- `params`
-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.
+ 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).
-```mdx title="/src/content/partials/style-guide/hello.mdx"
+## Properties
+
+### Defining expected properties in frontmatter
+
+Anything defined in the `params` property of the `Render` component is available inside the partial, using [JavaScript expressions](https://mdxjs.com/docs/using-mdx/).
+
+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.
+
+```mdx
---
params:
- - name
- - foo
- - bar
+ - product
+ - deprecated?
---
+```
+
+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";
+
+
+
+
+
+```mdx live
+import { Render } from "~/components";
+
+
+```
+
+### 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";
-Hello, {props.name}!
+
+
+
-Hello, {props.foo}!
+```mdx live
+import { Render } from "~/components";
-Hello, {props.bar}!
+
```
-### Params
+#### Links
+
+import linkRaw from "~/content/partials/style-guide/link-in-props.mdx?raw";
+
+
+
+
-In the above example, you will notice there is:
+```mdx live
+import { Render } from "~/components";
-
+
+```
-1. A `params` input on the `Render` component.
-2. A `params` property in the frontmatter.
-3. A reference to `props.name`.
+#### Images
+
+import imageRaw from "~/content/partials/style-guide/image-in-props.mdx?raw";
+
+
+
+
+
+```mdx live
+import { Render } from "~/components";
+
+
+```
-
+#### Code blocks
-#### Input
+import codeRaw from "~/content/partials/style-guide/code-in-props.mdx?raw";
+
+
+
+
+
+```mdx live
+import { Render } from "~/components";
+
+
+```
+
+### 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";
+
+
+
+
+
+```mdx live
+import { Render } from "~/components";
-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.
+
-#### Frontmatter
+
-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.
+
-#### Props
+
-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).
+
+```
\ No newline at end of file
diff --git a/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx b/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx
index 3878ec8c43e369..ccd70df213801f 100644
--- a/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx
+++ b/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx
@@ -1,8 +1,7 @@
---
params:
- idp
- - and
- - supportgroups
+ - supportgroups?
---
import { Markdown } from "~/components"
@@ -11,7 +10,7 @@ import { Markdown } from "~/components"
2. Find the {props.idp} integration and select **Edit**.
-3. Turn on **Enable SCIM**{props.and}**{props.supportgroups}**.
+3. Turn on **Enable SCIM** {props.supportgroups && and props.supportgroups.}
4. (Optional) Configure the following settings:
diff --git a/src/content/partials/style-guide/code-in-props.mdx b/src/content/partials/style-guide/code-in-props.mdx
new file mode 100644
index 00000000000000..12bd2c918db135
--- /dev/null
+++ b/src/content/partials/style-guide/code-in-props.mdx
@@ -0,0 +1,30 @@
+---
+params:
+ - code
+---
+
+import { Code } from "~/components";
+
+#### Inline
+
+**Don't do this!**
+
+`{props.code}`
+
+**Do this!**
+
+{props.code}
+
+
+
+#### Codeblocks
+
+**Don't do this!**
+
+```js
+{props.code}
+```
+
+**Do this!**
+
+
\ No newline at end of file
diff --git a/src/content/partials/style-guide/hello.mdx b/src/content/partials/style-guide/hello.mdx
deleted file mode 100644
index 2fefdcfb8641b7..00000000000000
--- a/src/content/partials/style-guide/hello.mdx
+++ /dev/null
@@ -1,14 +0,0 @@
----
-params:
- - name
----
-
-Hello, {props.name}!
-
-Hello `{props.name}`
-
-Hello {props.name}
-
-[link]({props.link}) (href="%7Bprops.link%7D")
-
-link (href="/style-guide/components/render/")
\ No newline at end of file
diff --git a/src/content/partials/style-guide/image-in-props.mdx b/src/content/partials/style-guide/image-in-props.mdx
new file mode 100644
index 00000000000000..cca4506f76e98c
--- /dev/null
+++ b/src/content/partials/style-guide/image-in-props.mdx
@@ -0,0 +1,12 @@
+---
+params:
+ - image
+---
+
+**Don't do this!**
+
+``
+
+**Do this!**
+
+
\ No newline at end of file
diff --git a/src/content/partials/style-guide/link-in-props.mdx b/src/content/partials/style-guide/link-in-props.mdx
new file mode 100644
index 00000000000000..75e80a9ba4bab7
--- /dev/null
+++ b/src/content/partials/style-guide/link-in-props.mdx
@@ -0,0 +1,16 @@
+---
+params:
+ - link
+---
+
+**Don't do this!**
+
+This will link to `/style-guide/components/%7Bprops.link%7D`.
+
+[Markdown link]({props.link})
+
+**Do this!**
+
+This will link to `style-guide/components/render/#links`.
+
+JSX link
\ No newline at end of file
diff --git a/src/content/partials/style-guide/optional-props.mdx b/src/content/partials/style-guide/optional-props.mdx
new file mode 100644
index 00000000000000..6c6256808676ea
--- /dev/null
+++ b/src/content/partials/style-guide/optional-props.mdx
@@ -0,0 +1,23 @@
+---
+params:
+ - product
+ - deprecated?
+---
+
+{
+ props.deprecated && (
+
+
+ {props.product} is deprecated, please use alternative products.
+
+
+ )
+}
+
+{
+ props.product === "Thing Three" ? (
+ Welcome to our Thing Three launch countdown!
+ ) : (
+ Welcome to the {props.product} landing page.
+ )
+}
diff --git a/src/content/partials/style-guide/simple-props.mdx b/src/content/partials/style-guide/simple-props.mdx
new file mode 100644
index 00000000000000..89a65604442455
--- /dev/null
+++ b/src/content/partials/style-guide/simple-props.mdx
@@ -0,0 +1,6 @@
+---
+params:
+ - name
+---
+
+Hello, {props.name}!
\ No newline at end of file
diff --git a/src/content/partials/style-guide/strong-in-props.mdx b/src/content/partials/style-guide/strong-in-props.mdx
new file mode 100644
index 00000000000000..70fdd5cc28422c
--- /dev/null
+++ b/src/content/partials/style-guide/strong-in-props.mdx
@@ -0,0 +1,13 @@
+---
+params:
+ - dont
+ - do
+---
+
+**Don't do this!**
+
+{props.dont}
+
+**Do this!**
+
+{props.do}
\ No newline at end of file
diff --git a/src/schemas/partials.ts b/src/schemas/partials.ts
index 0acc176a4fb068..58e1c607553521 100644
--- a/src/schemas/partials.ts
+++ b/src/schemas/partials.ts
@@ -1,5 +1,26 @@
import { z } from "astro:schema";
-export const partialSchema = z.object({
- params: z.string().array().optional(),
-});
+const paramsDocs =
+ "https://developers.cloudflare.com/style-guide/components/render/#defining-expected-properties-in-frontmatter";
+
+export const partialsSchema = z
+ .object({
+ slug: z
+ .string()
+ .optional()
+ .describe(
+ "Used to define custom IDs: https://docs.astro.build/en/guides/content-collections/#defining-custom-ids",
+ ),
+ params: z
+ .string()
+ .array()
+ .optional()
+ .describe(`Used to define expected properties: ${paramsDocs}`),
+ inputParameters: z
+ .string()
+ .optional()
+ .describe(
+ `Deprecated - this field has no functionality, please migrate to ${paramsDocs}`,
+ ),
+ })
+ .strict();