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
78 changes: 78 additions & 0 deletions src/components/ReactSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Select, { type Props } from "react-select";
import type { ActionMeta, StylesConfig } from "react-select";
import { setSearchParams } from "~/util/url";

export type Option = {
label: string;
value: string;
};

export default function ReactSelect(props: Props & { urlParam?: string }) {
const selectStyles: StylesConfig = {
control: (base, state) => ({
...base,
backgroundColor: "var(--sl-color-gray-6)",
borderColor: state.isFocused
? "var(--sl-color-gray-3)"
: "var(--sl-color-gray-4)",
"&:hover": {
borderColor: "var(--sl-color-gray-3)",
},
boxShadow: state.isFocused ? "0 0 0 1px var(--sl-color-gray-3)" : "none",
}),
menu: (base) => ({
...base,
backgroundColor: "var(--sl-color-gray-6)",
borderColor: "var(--sl-color-gray-4)",
}),
option: (base, state) => ({
...base,
backgroundColor: state.isFocused
? "var(--sl-color-gray-5)"
: "var(--sl-color-gray-6)",
color: "var(--sl-color-gray-1)",
"&:active": {
backgroundColor: "var(--sl-color-gray-4)",
},
}),
singleValue: (base) => ({
...base,
color: "var(--sl-color-gray-1)",
}),
input: (base) => ({
...base,
color: "var(--sl-color-gray-1)",
}),
groupHeading: (base) => ({
...base,
color: "var(--sl-color-gray-3)",
}),
};

const onChangeHandler = (
option: Option | null,
actionMeta: ActionMeta<Option>,
) => {
props.onChange?.(option, actionMeta);

const params = new URLSearchParams(window.location.search);

if (option) {
params.set(props.urlParam || "filters", option.value);
} else {
params.delete(props.urlParam || "filters");
}

setSearchParams(params);
};

return (
<Select
{...props}
styles={selectStyles}
onChange={(val: unknown, meta: ActionMeta<unknown>) =>
onChangeHandler(val as Option | null, meta as ActionMeta<Option>)
}
/>
);
}
52 changes: 36 additions & 16 deletions src/components/ResourcesBySelector.astro
Original file line number Diff line number Diff line change
@@ -1,36 +1,56 @@
---
import { z } from "astro:schema";
import { getCollection } from "astro:content";
import { getCollection, type CollectionEntry } from "astro:content";
import ResourcesBySelectorReact from "./ResourcesBySelector";

type Props = z.infer<typeof props>;
type Frontmatter = keyof CollectionEntry<"docs">["data"];

const props = z.object({
tags: z.string().array().optional(),
types: z.string().array(),
products: z.string().array().optional(),
directory: z.string().optional(),
filterables: z.custom<Frontmatter>().array().optional(),
});

const { tags, types, products } = props.parse(Astro.props);
const { tags, types, products, directory, filterables } = props.parse(
Astro.props,
);

const resources = await getCollection("docs", ({ data }) => {
const resources = await getCollection("docs", ({ id, data }) => {
return (
types.includes(data.pcx_content_type ?? "") &&
(directory ? id.startsWith(directory) : true) &&
(tags ? data.tags?.some((v: string) => tags.includes(v)) : true) &&
(products ? data.products?.some((v: string) => products.includes(v)) : true)
);
});

const facets = resources.reduce(
(acc, page) => {
if (!filterables) return acc;

for (const filter of filterables) {
const val = page.data[filter];
if (val) {
if (Array.isArray(val) && val.every((v) => typeof v === "string")) {
acc[filter] = [...new Set([...(acc[filter] || []), ...val])];
} else if (typeof val === "string") {
acc[filter] = [...new Set([...(acc[filter] || []), val])];
}
}
}

return acc;
},
{} as Record<string, string[]>,
);
---

<ul>
{
resources.map((page) => {
const description = page.data.description;
return (
<li>
<!-- prettier-ignore -->
<a href={`/${page.id}/`}>{page.data.title}</a>{description && `: ${description}`}
</li>
);
})
}
</ul>
<ResourcesBySelectorReact
resources={resources}
facets={facets}
filters={filterables}
client:load
/>
92 changes: 92 additions & 0 deletions src/components/ResourcesBySelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useEffect, useState } from "react";
import ReactSelect from "./ReactSelect";
import type { CollectionEntry } from "astro:content";

type Frontmatter = keyof CollectionEntry<"docs">["data"];

interface Props {
resources: CollectionEntry<"docs">[];
facets: Record<string, string[]>;
filters?: Frontmatter[];
}

export default function ResourcesBySelector({
resources,
facets,
filters,
}: Props) {
const [selectedFilter, setSelectedFilter] = useState<string | null>(null);

const handleFilterChange = (option: any) => {
setSelectedFilter(option?.value || null);
};

const options = Object.entries(facets).map(([key, values]) => ({
label: key,
options: values.map((v) => ({
value: v,
label: v,
})),
}));

const visibleResources = resources.filter((resource) => {
if (!selectedFilter || !filters) return true;

const filterableValues: string[] = [];
for (const filter of filters) {
const val = resource.data[filter];
if (val) {
if (Array.isArray(val) && val.every((v) => typeof v === "string")) {
filterableValues.push(...val);
} else if (typeof val === "string") {
filterableValues.push(val);
}
}
}

return filterableValues.includes(selectedFilter);
});

useEffect(() => {
const params = new URLSearchParams(window.location.search);
const value = params.get("filters");

if (value) {
setSelectedFilter(value);
}
}, []);

return (
<div>
{filters && (
<div className="not-content">
<ReactSelect
className="mt-2"
value={{ value: selectedFilter, label: selectedFilter }}
options={options}
onChange={handleFilterChange}
isClearable
placeholder="Filter resources..."
/>
</div>
)}

<div className="grid grid-cols-2 gap-4">
{visibleResources.map((page) => (
<a
key={page.id}
href={`/${page.id}/`}
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"
>
<p className="decoration-accent underline decoration-2 underline-offset-4">
{page.data.title}
</p>
<span className="line-clamp-3" title={page.data.description}>
{page.data.description}
</span>
</a>
))}
</div>
</div>
);
}
56 changes: 5 additions & 51 deletions src/components/changelog/ProductSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import type { CollectionEntry } from "astro:content";
import type { StylesConfig } from "react-select";
import Select from "react-select";
import ReactSelect, { type Option } from "../ReactSelect";
import { useEffect, useState } from "react";

interface Props {
products: CollectionEntry<"products">[];
groups: string[];
}

interface Option {
label?: string;
value: string;
}

export default function ProductSelect({ products, groups }: Props) {
const [selectedOption, setSelectedOption] = useState<Option>();

Expand Down Expand Up @@ -42,47 +36,6 @@ export default function ProductSelect({ products, groups }: Props) {
},
];

const selectStyles: StylesConfig<Option, false> = {
control: (base, state) => ({
...base,
backgroundColor: "var(--sl-color-gray-6)",
borderColor: state.isFocused
? "var(--sl-color-gray-3)"
: "var(--sl-color-gray-4)",
"&:hover": {
borderColor: "var(--sl-color-gray-3)",
},
boxShadow: state.isFocused ? "0 0 0 1px var(--sl-color-gray-3)" : "none",
}),
menu: (base) => ({
...base,
backgroundColor: "var(--sl-color-gray-6)",
borderColor: "var(--sl-color-gray-4)",
}),
option: (base, state) => ({
...base,
backgroundColor: state.isFocused
? "var(--sl-color-gray-5)"
: "var(--sl-color-gray-6)",
color: "var(--sl-color-gray-1)",
"&:active": {
backgroundColor: "var(--sl-color-gray-4)",
},
}),
singleValue: (base) => ({
...base,
color: "var(--sl-color-gray-1)",
}),
input: (base) => ({
...base,
color: "var(--sl-color-gray-1)",
}),
groupHeading: (base) => ({
...base,
color: "var(--sl-color-gray-3)",
}),
};

useEffect(() => {
const url = new URL(window.location.href);
const param = url.searchParams.get("product");
Expand All @@ -100,6 +53,7 @@ export default function ProductSelect({ products, groups }: Props) {

const handleChange = (option: Option | null) => {
if (!option) return;

setSelectedOption(option);

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

return (
<Select
<ReactSelect
id="changelogs-next-filter"
className="mt-2"
options={options}
value={selectedOption}
onChange={handleChange}
styles={selectStyles}
onChange={(e) => handleChange(e as Option | null)}
urlParam="product"
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ products:
- Workers
languages:
- JavaScript
description: Learn how to deploy a Worker that makes calls to OpenAI through AI Gateway
---

import { Render, PackageManagers } from "~/components";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ pcx_content_type: navigation
title: Demos and architectures
sidebar:
order: 8

---

import { ExternalResources, GlossaryTooltip, ResourcesBySelector } from "~/components"
import {
ExternalResources,
GlossaryTooltip,
ResourcesBySelector,
} from "~/components";

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

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

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

<ResourcesBySelector types={["reference-architecture","design-guide","reference-architecture-diagram"]} products={["WorkersForPlatforms"]} />
<ResourcesBySelector
types={[
"reference-architecture",
"design-guide",
"reference-architecture-diagram",
]}
products={["workers-for-platforms"]}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ tags:
languages:
- TypeScript
- SQL
description: Build a staff directory using D1. Users access employee info; admins add new employees within the app.
---

import { WranglerConfig } from "~/components";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tags:
- Stripe
sidebar:
order: 2
description: Create APIs for related product searches and recommendations using Workers AI and Stripe.
---

import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ tags:
- Vue
languages:
- JavaScript
description: Build a blog application using Nuxt.js and Sanity.io and deploy it on Cloudflare Pages.
---

import { Stream, PackageManagers } from "~/components";
Expand Down
Loading
Loading