Skip to content

Commit 2b0b662

Browse files
KianNHkodster28
andauthored
[Docs Site] Refactor ResourcesBySelector (#23423)
* [Docs Site] Refactor ResourcesBySelector * move to React component * read filter from url on load * update props docs * rename prop * Apply suggestions from code review * add meta descriptions : --------- Co-authored-by: Kody Jackson <[email protected]>
1 parent b54039d commit 2b0b662

File tree

36 files changed

+308
-111
lines changed

36 files changed

+308
-111
lines changed

src/components/ReactSelect.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Select, { type Props } from "react-select";
2+
import type { ActionMeta, StylesConfig } from "react-select";
3+
import { setSearchParams } from "~/util/url";
4+
5+
export type Option = {
6+
label: string;
7+
value: string;
8+
};
9+
10+
export default function ReactSelect(props: Props & { urlParam?: string }) {
11+
const selectStyles: StylesConfig = {
12+
control: (base, state) => ({
13+
...base,
14+
backgroundColor: "var(--sl-color-gray-6)",
15+
borderColor: state.isFocused
16+
? "var(--sl-color-gray-3)"
17+
: "var(--sl-color-gray-4)",
18+
"&:hover": {
19+
borderColor: "var(--sl-color-gray-3)",
20+
},
21+
boxShadow: state.isFocused ? "0 0 0 1px var(--sl-color-gray-3)" : "none",
22+
}),
23+
menu: (base) => ({
24+
...base,
25+
backgroundColor: "var(--sl-color-gray-6)",
26+
borderColor: "var(--sl-color-gray-4)",
27+
}),
28+
option: (base, state) => ({
29+
...base,
30+
backgroundColor: state.isFocused
31+
? "var(--sl-color-gray-5)"
32+
: "var(--sl-color-gray-6)",
33+
color: "var(--sl-color-gray-1)",
34+
"&:active": {
35+
backgroundColor: "var(--sl-color-gray-4)",
36+
},
37+
}),
38+
singleValue: (base) => ({
39+
...base,
40+
color: "var(--sl-color-gray-1)",
41+
}),
42+
input: (base) => ({
43+
...base,
44+
color: "var(--sl-color-gray-1)",
45+
}),
46+
groupHeading: (base) => ({
47+
...base,
48+
color: "var(--sl-color-gray-3)",
49+
}),
50+
};
51+
52+
const onChangeHandler = (
53+
option: Option | null,
54+
actionMeta: ActionMeta<Option>,
55+
) => {
56+
props.onChange?.(option, actionMeta);
57+
58+
const params = new URLSearchParams(window.location.search);
59+
60+
if (option) {
61+
params.set(props.urlParam || "filters", option.value);
62+
} else {
63+
params.delete(props.urlParam || "filters");
64+
}
65+
66+
setSearchParams(params);
67+
};
68+
69+
return (
70+
<Select
71+
{...props}
72+
styles={selectStyles}
73+
onChange={(val: unknown, meta: ActionMeta<unknown>) =>
74+
onChangeHandler(val as Option | null, meta as ActionMeta<Option>)
75+
}
76+
/>
77+
);
78+
}
Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,56 @@
11
---
22
import { z } from "astro:schema";
3-
import { getCollection } from "astro:content";
3+
import { getCollection, type CollectionEntry } from "astro:content";
4+
import ResourcesBySelectorReact from "./ResourcesBySelector";
45
56
type Props = z.infer<typeof props>;
7+
type Frontmatter = keyof CollectionEntry<"docs">["data"];
68
79
const props = z.object({
810
tags: z.string().array().optional(),
911
types: z.string().array(),
1012
products: z.string().array().optional(),
13+
directory: z.string().optional(),
14+
filterables: z.custom<Frontmatter>().array().optional(),
1115
});
1216
13-
const { tags, types, products } = props.parse(Astro.props);
17+
const { tags, types, products, directory, filterables } = props.parse(
18+
Astro.props,
19+
);
1420
15-
const resources = await getCollection("docs", ({ data }) => {
21+
const resources = await getCollection("docs", ({ id, data }) => {
1622
return (
1723
types.includes(data.pcx_content_type ?? "") &&
24+
(directory ? id.startsWith(directory) : true) &&
1825
(tags ? data.tags?.some((v: string) => tags.includes(v)) : true) &&
1926
(products ? data.products?.some((v: string) => products.includes(v)) : true)
2027
);
2128
});
29+
30+
const facets = resources.reduce(
31+
(acc, page) => {
32+
if (!filterables) return acc;
33+
34+
for (const filter of filterables) {
35+
const val = page.data[filter];
36+
if (val) {
37+
if (Array.isArray(val) && val.every((v) => typeof v === "string")) {
38+
acc[filter] = [...new Set([...(acc[filter] || []), ...val])];
39+
} else if (typeof val === "string") {
40+
acc[filter] = [...new Set([...(acc[filter] || []), val])];
41+
}
42+
}
43+
}
44+
45+
return acc;
46+
},
47+
{} as Record<string, string[]>,
48+
);
2249
---
2350

24-
<ul>
25-
{
26-
resources.map((page) => {
27-
const description = page.data.description;
28-
return (
29-
<li>
30-
<!-- prettier-ignore -->
31-
<a href={`/${page.id}/`}>{page.data.title}</a>{description && `: ${description}`}
32-
</li>
33-
);
34-
})
35-
}
36-
</ul>
51+
<ResourcesBySelectorReact
52+
resources={resources}
53+
facets={facets}
54+
filters={filterables}
55+
client:load
56+
/>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useEffect, useState } from "react";
2+
import ReactSelect from "./ReactSelect";
3+
import type { CollectionEntry } from "astro:content";
4+
5+
type Frontmatter = keyof CollectionEntry<"docs">["data"];
6+
7+
interface Props {
8+
resources: CollectionEntry<"docs">[];
9+
facets: Record<string, string[]>;
10+
filters?: Frontmatter[];
11+
}
12+
13+
export default function ResourcesBySelector({
14+
resources,
15+
facets,
16+
filters,
17+
}: Props) {
18+
const [selectedFilter, setSelectedFilter] = useState<string | null>(null);
19+
20+
const handleFilterChange = (option: any) => {
21+
setSelectedFilter(option?.value || null);
22+
};
23+
24+
const options = Object.entries(facets).map(([key, values]) => ({
25+
label: key,
26+
options: values.map((v) => ({
27+
value: v,
28+
label: v,
29+
})),
30+
}));
31+
32+
const visibleResources = resources.filter((resource) => {
33+
if (!selectedFilter || !filters) return true;
34+
35+
const filterableValues: string[] = [];
36+
for (const filter of filters) {
37+
const val = resource.data[filter];
38+
if (val) {
39+
if (Array.isArray(val) && val.every((v) => typeof v === "string")) {
40+
filterableValues.push(...val);
41+
} else if (typeof val === "string") {
42+
filterableValues.push(val);
43+
}
44+
}
45+
}
46+
47+
return filterableValues.includes(selectedFilter);
48+
});
49+
50+
useEffect(() => {
51+
const params = new URLSearchParams(window.location.search);
52+
const value = params.get("filters");
53+
54+
if (value) {
55+
setSelectedFilter(value);
56+
}
57+
}, []);
58+
59+
return (
60+
<div>
61+
{filters && (
62+
<div className="not-content">
63+
<ReactSelect
64+
className="mt-2"
65+
value={{ value: selectedFilter, label: selectedFilter }}
66+
options={options}
67+
onChange={handleFilterChange}
68+
isClearable
69+
placeholder="Filter resources..."
70+
/>
71+
</div>
72+
)}
73+
74+
<div className="grid grid-cols-2 gap-4">
75+
{visibleResources.map((page) => (
76+
<a
77+
key={page.id}
78+
href={`/${page.id}/`}
79+
className="flex flex-col gap-2 rounded-sm border border-solid border-gray-200 p-6 text-black no-underline hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800"
80+
>
81+
<p className="decoration-accent underline decoration-2 underline-offset-4">
82+
{page.data.title}
83+
</p>
84+
<span className="line-clamp-3" title={page.data.description}>
85+
{page.data.description}
86+
</span>
87+
</a>
88+
))}
89+
</div>
90+
</div>
91+
);
92+
}
Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import type { CollectionEntry } from "astro:content";
2-
import type { StylesConfig } from "react-select";
3-
import Select from "react-select";
2+
import ReactSelect, { type Option } from "../ReactSelect";
43
import { useEffect, useState } from "react";
54

65
interface Props {
76
products: CollectionEntry<"products">[];
87
groups: string[];
98
}
109

11-
interface Option {
12-
label?: string;
13-
value: string;
14-
}
15-
1610
export default function ProductSelect({ products, groups }: Props) {
1711
const [selectedOption, setSelectedOption] = useState<Option>();
1812

@@ -42,47 +36,6 @@ export default function ProductSelect({ products, groups }: Props) {
4236
},
4337
];
4438

45-
const selectStyles: StylesConfig<Option, false> = {
46-
control: (base, state) => ({
47-
...base,
48-
backgroundColor: "var(--sl-color-gray-6)",
49-
borderColor: state.isFocused
50-
? "var(--sl-color-gray-3)"
51-
: "var(--sl-color-gray-4)",
52-
"&:hover": {
53-
borderColor: "var(--sl-color-gray-3)",
54-
},
55-
boxShadow: state.isFocused ? "0 0 0 1px var(--sl-color-gray-3)" : "none",
56-
}),
57-
menu: (base) => ({
58-
...base,
59-
backgroundColor: "var(--sl-color-gray-6)",
60-
borderColor: "var(--sl-color-gray-4)",
61-
}),
62-
option: (base, state) => ({
63-
...base,
64-
backgroundColor: state.isFocused
65-
? "var(--sl-color-gray-5)"
66-
: "var(--sl-color-gray-6)",
67-
color: "var(--sl-color-gray-1)",
68-
"&:active": {
69-
backgroundColor: "var(--sl-color-gray-4)",
70-
},
71-
}),
72-
singleValue: (base) => ({
73-
...base,
74-
color: "var(--sl-color-gray-1)",
75-
}),
76-
input: (base) => ({
77-
...base,
78-
color: "var(--sl-color-gray-1)",
79-
}),
80-
groupHeading: (base) => ({
81-
...base,
82-
color: "var(--sl-color-gray-3)",
83-
}),
84-
};
85-
8639
useEffect(() => {
8740
const url = new URL(window.location.href);
8841
const param = url.searchParams.get("product");
@@ -100,6 +53,7 @@ export default function ProductSelect({ products, groups }: Props) {
10053

10154
const handleChange = (option: Option | null) => {
10255
if (!option) return;
56+
10357
setSelectedOption(option);
10458

10559
const event = new Event("change");
@@ -114,13 +68,13 @@ export default function ProductSelect({ products, groups }: Props) {
11468
};
11569

11670
return (
117-
<Select
71+
<ReactSelect
11872
id="changelogs-next-filter"
11973
className="mt-2"
12074
options={options}
12175
value={selectedOption}
122-
onChange={handleChange}
123-
styles={selectStyles}
76+
onChange={(e) => handleChange(e as Option | null)}
77+
urlParam="product"
12478
/>
12579
);
12680
}

src/content/docs/ai-gateway/tutorials/deploy-aig-worker.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ products:
99
- Workers
1010
languages:
1111
- JavaScript
12+
description: Learn how to deploy a Worker that makes calls to OpenAI through AI Gateway
1213
---
1314

1415
import { Render, PackageManagers } from "~/components";

src/content/docs/cloudflare-for-platforms/workers-for-platforms/demos.mdx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ pcx_content_type: navigation
33
title: Demos and architectures
44
sidebar:
55
order: 8
6-
76
---
87

9-
import { ExternalResources, GlossaryTooltip, ResourcesBySelector } from "~/components"
8+
import {
9+
ExternalResources,
10+
GlossaryTooltip,
11+
ResourcesBySelector,
12+
} from "~/components";
1013

1114
Learn how you can use Workers for Platforms within your existing architecture.
1215

@@ -20,4 +23,11 @@ Explore the following <GlossaryTooltip term="demo application">demo applications
2023

2124
Explore the following <GlossaryTooltip term="reference architecture">reference architectures</GlossaryTooltip> that use Workers:
2225

23-
<ResourcesBySelector types={["reference-architecture","design-guide","reference-architecture-diagram"]} products={["WorkersForPlatforms"]} />
26+
<ResourcesBySelector
27+
types={[
28+
"reference-architecture",
29+
"design-guide",
30+
"reference-architecture-diagram",
31+
]}
32+
products={["workers-for-platforms"]}
33+
/>

src/content/docs/d1/tutorials/build-a-staff-directory-app/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ tags:
1010
languages:
1111
- TypeScript
1212
- SQL
13+
description: Build a staff directory using D1. Users access employee info; admins add new employees within the app.
1314
---
1415

1516
import { WranglerConfig } from "~/components";

src/content/docs/developer-spotlight/tutorials/creating-a-recommendation-api.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ tags:
1919
- Stripe
2020
sidebar:
2121
order: 2
22+
description: Create APIs for related product searches and recommendations using Workers AI and Stripe.
2223
---
2324

2425
import {

src/content/docs/pages/tutorials/build-a-blog-using-nuxt-and-sanity/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ tags:
88
- Vue
99
languages:
1010
- JavaScript
11+
description: Build a blog application using Nuxt.js and Sanity.io and deploy it on Cloudflare Pages.
1112
---
1213

1314
import { Stream, PackageManagers } from "~/components";

0 commit comments

Comments
 (0)