Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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>)
}
/>
);
}
77 changes: 63 additions & 14 deletions src/components/ResourcesBySelector.astro
Original file line number Diff line number Diff line change
@@ -1,36 +1,85 @@
---
import { z } from "astro:schema";
import { getCollection } from "astro:content";
import { getCollection, type CollectionEntry } from "astro:content";
import ReactSelect from "./ReactSelect";

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(),
filters: z.custom<Frontmatter>().array().optional(),
});

const { tags, types, products } = props.parse(Astro.props);
const { tags, types, products, directory, filters } = 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 (!filters) return acc;

for (const filter of filters) {
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>
{
filters && (
<div class="not-content">
<ReactSelect
id="resources-filters"
className="mt-2"
options={Object.entries(facets).map(([key, values]) => ({
label: key,
options: values.map((v) => ({
value: v,
label: v,
})),
}))}
onChange={(opt) => console.log(opt)}
client:idle
/>
</div>
)
}

<div class="grid grid-cols-2 gap-4">
{
resources.map((page) => {
const description = page.data.description;
return (
<li>
<!-- prettier-ignore -->
<a href={`/${page.id}/`}>{page.data.title}</a>{description && `: ${description}`}
</li>
);
})
resources.map((page) => (
<a
href={`/${page.id}/`}
class="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 class="decoration-accent underline decoration-2 underline-offset-4">
{page.data.title}
</p>
<span class="line-clamp-3" title={page.data.description}>
{page.data.description}
</span>
</a>
))
}
</ul>
</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 @@ -14,23 +14,24 @@ The `ResourcesBySelector` component allows you to pull in documentation resource
import { ResourcesBySelector } from "~/components";

<ResourcesBySelector
tags={["AI"]}
types={["reference-architecture","design-guide","reference-architecture-diagram"]}
directory="workers/examples/"
types={["example"]}
filters={["languages", "tags"]}
/>
```

### Inputs

- `types` <Type text="string[]" />

An array of `pcx_content_type` values to filter by.
An array of `pcx_content_type` values to filter by.

- `tags` <Type text="string[]" /> <MetaInfo text="optional" />

An array of `tags` values to filter by.
An array of `tags` values to filter by.

To see a list of the available tags, and which pages are associated with them, refer to [this list](/style-guide/frontmatter/tags/).
To see a list of the available tags, and which pages are associated with them, refer to [this list](/style-guide/frontmatter/tags/).

- `products` <Type text="string[]" /> <MetaInfo text="optional" />

An array of `products` values to filter by.
An array of `products` values to filter by.