This commit is contained in:
ozakione 2024-04-17 17:55:49 +02:00
parent 5b6626bcf4
commit 8c12b1c619
16 changed files with 126 additions and 193 deletions

View file

@ -19,27 +19,27 @@ import type {
ShowcaseItem, ShowcaseItem,
} from '@docusaurus/plugin-content-showcase'; } from '@docusaurus/plugin-content-showcase';
export function filterUsers({ export function filterItems({
users, items,
tags, tags,
operator, operator,
searchName, searchName,
}: { }: {
users: ShowcaseItem[]; items: ShowcaseItem[];
tags: TagType[]; tags: TagType[];
operator: Operator; operator: Operator;
searchName: string | undefined | null; searchName: string | undefined | null;
}): ShowcaseItem[] { }): ShowcaseItem[] {
if (searchName) { if (searchName) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
users = users.filter((user) => items = items.filter((user) =>
user.title.toLowerCase().includes(searchName.toLowerCase()), user.title.toLowerCase().includes(searchName.toLowerCase()),
); );
} }
if (tags.length === 0) { if (tags.length === 0) {
return users; return items;
} }
return users.filter((user) => { return items.filter((user) => {
if (user.tags.length === 0) { if (user.tags.length === 0) {
return false; return false;
} }
@ -71,19 +71,19 @@ export function useOperator(): [Operator, () => void] {
return [operator, toggleOperator]; return [operator, toggleOperator];
} }
export function useFilteredUsers(users: ShowcaseItem[]): ShowcaseItem[] { export function useFilteredItems(items: ShowcaseItem[]): ShowcaseItem[] {
const [tags] = useTags(); const [tags] = useTags();
const [searchName] = useSearchName() ?? ['']; const [searchName] = useSearchName() ?? [''];
const [operator] = useOperator(); const [operator] = useOperator();
return useMemo( return useMemo(
() => () =>
filterUsers({ filterItems({
users, items,
tags: tags as TagType[], tags: tags as TagType[],
operator, operator,
searchName, searchName,
}), }),
[users, tags, operator, searchName], [items, tags, operator, searchName],
); );
} }
@ -122,108 +122,7 @@ export type Tag = {
color: string; color: string;
}; };
export const Tags: {[type in TagType]: Tag} = { export function sortItems(params: ShowcaseItem[]): ShowcaseItem[] {
favorite: {
label: translate({message: 'Favorite'}),
description: translate({
message:
'Our favorite Docusaurus sites that you must absolutely check out!',
id: 'showcase.tag.favorite.description',
}),
color: '#e9669e',
},
opensource: {
label: translate({message: 'Open-Source'}),
description: translate({
message: 'Open-Source Docusaurus sites can be useful for inspiration!',
id: 'showcase.tag.opensource.description',
}),
color: '#39ca30',
},
product: {
label: translate({message: 'Product'}),
description: translate({
message: 'Docusaurus sites associated to a commercial product!',
id: 'showcase.tag.product.description',
}),
color: '#dfd545',
},
design: {
label: translate({message: 'Design'}),
description: translate({
message:
'Beautiful Docusaurus sites, polished and standing out from the initial template!',
id: 'showcase.tag.design.description',
}),
color: '#a44fb7',
},
i18n: {
label: translate({message: 'I18n'}),
description: translate({
message:
'Translated Docusaurus sites using the internationalization support with more than 1 locale.',
id: 'showcase.tag.i18n.description',
}),
color: '#127f82',
},
versioning: {
label: translate({message: 'Versioning'}),
description: translate({
message:
'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.',
id: 'showcase.tag.versioning.description',
}),
color: '#fe6829',
},
large: {
label: translate({message: 'Large'}),
description: translate({
message:
'Very large Docusaurus sites, including many more pages than the average!',
id: 'showcase.tag.large.description',
}),
color: '#8c2f00',
},
meta: {
label: translate({message: 'Meta'}),
description: translate({
message: 'Docusaurus sites of Meta (formerly Facebook) projects',
id: 'showcase.tag.meta.description',
}),
color: '#4267b2', // Facebook blue
},
personal: {
label: translate({message: 'Personal'}),
description: translate({
message:
'Personal websites, blogs and digital gardens built with Docusaurus',
id: 'showcase.tag.personal.description',
}),
color: '#14cfc3',
},
rtl: {
label: translate({message: 'RTL Direction'}),
description: translate({
message:
'Docusaurus sites using the right-to-left reading direction support.',
id: 'showcase.tag.rtl.description',
}),
color: '#ffcfc3',
},
};
export const TagList = Object.keys(Tags) as TagType[];
export function sortUsers(params: ShowcaseItem[]): ShowcaseItem[] {
let result = params; let result = params;
// Sort by site name // Sort by site name
result = sortBy(result, (user) => user.title.toLowerCase()); result = sortBy(result, (user) => user.title.toLowerCase());
@ -231,5 +130,3 @@ export function sortUsers(params: ShowcaseItem[]): ShowcaseItem[] {
result = sortBy(result, (user) => !user.tags.includes('favorite')); result = sortBy(result, (user) => !user.tags.includes('favorite'));
return result; return result;
} }
// export const sortedUsers = sortUsers();

View file

@ -37,17 +37,17 @@ export default async function pluginContentShowcase(
contentPath: path.resolve(siteDir, sitePath), contentPath: path.resolve(siteDir, sitePath),
contentPathLocalized: getPluginI18nPath({ contentPathLocalized: getPluginI18nPath({
localizationDir, localizationDir,
pluginName: 'docusaurus-plugin-content-pages', pluginName: 'docusaurus-plugin-content-showcase',
pluginId: id, pluginId: id,
}), }),
}; };
const tagList = await getTagsList({ const {tags: validatedTags, tagkeys} = await getTagsList({
configTags: tags, configTags: tags,
configPath: contentPaths.contentPath, configPath: contentPaths.contentPath,
}); });
const showcaseItemSchema = createShowcaseItemSchema(tagList); const showcaseItemSchema = createShowcaseItemSchema(tagkeys);
return { return {
name: 'docusaurus-plugin-content-showcase', name: 'docusaurus-plugin-content-showcase',
@ -80,6 +80,7 @@ export default async function pluginContentShowcase(
await processContentLoaded({ await processContentLoaded({
content, content,
tags: validatedTags,
routeBasePath, routeBasePath,
addRoute, addRoute,
}); });

View file

@ -5,16 +5,21 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import type {ShowcaseItems} from '@docusaurus/plugin-content-showcase'; import type {
ShowcaseItems,
TagsOption,
} from '@docusaurus/plugin-content-showcase';
import type {PluginContentLoadedActions} from '@docusaurus/types'; import type {PluginContentLoadedActions} from '@docusaurus/types';
export async function processContentLoaded({ export async function processContentLoaded({
content, content,
tags,
routeBasePath, routeBasePath,
addRoute, addRoute,
}: { }: {
content: ShowcaseItems; content: ShowcaseItems;
routeBasePath: string; routeBasePath: string;
tags: TagsOption;
addRoute: PluginContentLoadedActions['addRoute']; addRoute: PluginContentLoadedActions['addRoute'];
}): Promise<void> { }): Promise<void> {
addRoute({ addRoute({
@ -22,6 +27,7 @@ export async function processContentLoaded({
component: '@theme/Showcase', component: '@theme/Showcase',
props: { props: {
items: content.items, items: content.items,
tags,
}, },
exact: true, exact: true,
}); });

View file

@ -17,6 +17,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
include: ['**/*.{yml,yaml}'], include: ['**/*.{yml,yaml}'],
// TODO exclude won't work if user pass a custom file name // TODO exclude won't work if user pass a custom file name
exclude: [...GlobExcludeDefault, 'tags.*'], exclude: [...GlobExcludeDefault, 'tags.*'],
screenshotApi: 'https://slorber-api-screenshot.netlify.app',
tags: 'tags.yml', tags: 'tags.yml',
}; };
@ -28,6 +29,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
tags: Joi.alternatives() tags: Joi.alternatives()
.try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema) .try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema)
.default(DEFAULT_OPTIONS.tags), .default(DEFAULT_OPTIONS.tags),
screenshotApi: Joi.string().default(DEFAULT_OPTIONS.screenshotApi),
}); });
export function validateOptions({ export function validateOptions({

View file

@ -32,7 +32,7 @@ declare module '@docusaurus/plugin-content-showcase' {
| 'rtl'; | 'rtl';
export type TagsOption = { export type TagsOption = {
[tagName: string]: Tag; [type in TagType]: Tag;
}; };
export type PluginOptions = { export type PluginOptions = {
@ -42,6 +42,7 @@ declare module '@docusaurus/plugin-content-showcase' {
include: string[]; include: string[];
exclude: string[]; exclude: string[];
tags: string | TagsOption; tags: string | TagsOption;
screenshotApi: string;
}; };
export type ShowcaseItem = { export type ShowcaseItem = {

View file

@ -9,7 +9,10 @@ import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import Yaml from 'js-yaml'; import Yaml from 'js-yaml';
import {Joi} from '@docusaurus/utils-validation'; import {Joi} from '@docusaurus/utils-validation';
import type {PluginOptions} from '@docusaurus/plugin-content-showcase'; import type {
PluginOptions,
TagsOption,
} from '@docusaurus/plugin-content-showcase';
export const tagSchema = Joi.object().pattern( export const tagSchema = Joi.object().pattern(
Joi.string(), Joi.string(),
@ -35,7 +38,7 @@ export async function getTagsList({
}: { }: {
configTags: PluginOptions['tags']; configTags: PluginOptions['tags'];
configPath: PluginOptions['path']; configPath: PluginOptions['path'];
}): Promise<string[]> { }): Promise<{tagkeys: string[]; tags: TagsOption}> {
if (typeof configTags === 'object') { if (typeof configTags === 'object') {
const tags = tagSchema.validate(configTags); const tags = tagSchema.validate(configTags);
if (tags.error) { if (tags.error) {
@ -44,7 +47,10 @@ export async function getTagsList({
{cause: tags}, {cause: tags},
); );
} }
return Object.keys(tags.value); return {
tagkeys: Object.keys(tags.value),
tags: tags.value,
};
} }
const tagsPath = path.resolve(configPath, configTags); const tagsPath = path.resolve(configPath, configTags);
@ -61,8 +67,10 @@ export async function getTagsList({
); );
} }
const tagLabels = Object.keys(tags.value); return {
return tagLabels; tagkeys: Object.keys(tags.value),
tags: tags.value,
};
} catch (error) { } catch (error) {
throw new Error(`Failed to read tags file for showcase`, {cause: error}); throw new Error(`Failed to read tags file for showcase`, {cause: error});
} }

View file

@ -248,10 +248,14 @@ declare module '@theme/BlogPostItems' {
} }
declare module '@theme/Showcase' { declare module '@theme/Showcase' {
import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; import type {
ShowcaseItem,
TagsOption,
} from '@docusaurus/plugin-content-showcase';
export type Props = { export type Props = {
items: ShowcaseItem[]; items: ShowcaseItem[];
tags: TagsOption;
}; };
export default function Showcase(props: Props): JSX.Element; export default function Showcase(props: Props): JSX.Element;

View file

@ -7,16 +7,20 @@
import React, {type ReactNode} from 'react'; import React, {type ReactNode} from 'react';
import {useClearQueryString} from '@docusaurus/theme-common'; import {useClearQueryString} from '@docusaurus/theme-common';
import Translate from '@docusaurus/Translate';
export default function ClearAllButton(): ReactNode { export default function ClearAllButton(): ReactNode {
const clearQueryString = useClearQueryString(); const clearQueryString = useClearQueryString();
// TODO translate
return ( return (
<button <button
className="button button--outline button--primary" className="button button--outline button--primary"
type="button" type="button"
onClick={() => clearQueryString()}> onClick={() => clearQueryString()}>
Clear All <Translate
id="theme.Showcase.ClearAllButton.label"
description="The label for the Clear All button">
Clear All
</Translate>
</button> </button>
); );
} }

View file

@ -7,7 +7,6 @@
import React from 'react'; import React from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import type {Props} from '@theme/Showcase/FavoriteIcon'; import type {Props} from '@theme/Showcase/FavoriteIcon';
import styles from './styles.module.css'; import styles from './styles.module.css';

View file

@ -8,13 +8,12 @@
import React, {useId} from 'react'; import React, {useId} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {useOperator} from '@docusaurus/plugin-content-showcase/client'; import {useOperator} from '@docusaurus/plugin-content-showcase/client';
import Translate from '@docusaurus/Translate';
import styles from './styles.module.css'; import styles from './styles.module.css';
export default function OperatorButton(): JSX.Element { export default function OperatorButton(): JSX.Element {
const id = useId(); const id = useId();
const [operator, toggleOperator] = useOperator(); const [operator, toggleOperator] = useOperator();
// TODO add translations
return ( return (
<> <>
<input <input
@ -30,11 +29,22 @@ export default function OperatorButton(): JSX.Element {
} }
}} }}
/> />
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}> <label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
{/* eslint-disable @docusaurus/no-untranslated-text */} <span className={styles.checkboxLabelOr}>
<span className={styles.checkboxLabelOr}>OR</span> <Translate
<span className={styles.checkboxLabelAnd}>AND</span> id="theme.Showcase.OrOperatorButton.label"
{/* eslint-enable @docusaurus/no-untranslated-text */} description="The label for the OR operator button">
OR
</Translate>
</span>
<span className={styles.checkboxLabelAnd}>
<Translate
id="theme.Showcase.AndOperatorButton.label"
description="The label for the AND operator button">
AND
</Translate>
</span>
</label> </label>
</> </>
); );

View file

@ -9,12 +9,8 @@ import React from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import Translate from '@docusaurus/Translate'; import Translate from '@docusaurus/Translate';
// import Image from '@theme/IdealImage'; import {sortBy} from '@docusaurus/plugin-content-showcase/client';
import { import {useShowcase} from '@docusaurus/theme-common/internal';
sortBy,
Tags,
TagList,
} from '@docusaurus/plugin-content-showcase/client';
import Heading from '@theme/Heading'; import Heading from '@theme/Heading';
import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon';
import type {ShowcaseItem, TagType} from '@docusaurus/plugin-content-showcase'; import type {ShowcaseItem, TagType} from '@docusaurus/plugin-content-showcase';
@ -26,11 +22,14 @@ function TagItem({
color, color,
}: { }: {
label: string; label: string;
description: string; description: {
message: string;
id: string;
};
color: string; color: string;
}) { }) {
return ( return (
<li className={styles.tag} title={description}> <li className={styles.tag} title={description.message}>
<span className={styles.textLabel}>{label.toLowerCase()}</span> <span className={styles.textLabel}>{label.toLowerCase()}</span>
<span className={styles.colorLabel} style={{backgroundColor: color}} /> <span className={styles.colorLabel} style={{backgroundColor: color}} />
</li> </li>
@ -38,6 +37,9 @@ function TagItem({
} }
function ShowcaseCardTag({tags}: {tags: TagType[]}) { function ShowcaseCardTag({tags}: {tags: TagType[]}) {
const {tags: Tags} = useShowcase();
const TagList = Object.keys(Tags) as TagType[];
const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]})); const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]}));
// Keep same order for all tags // Keep same order for all tags
@ -54,23 +56,21 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) {
); );
} }
function getCardImage(user: ShowcaseItem): string { function getCardImage(item: ShowcaseItem): string {
return ( return (
user.preview ?? item.preview ??
// TODO make it configurable // TODO make it configurable
`https://slorber-api-screenshot.netlify.app/${encodeURIComponent( `https://slorber-api-screenshot.netlify.app/${encodeURIComponent(
user.website, item.website,
)}/showcase` )}/showcase`
); );
} }
function ShowcaseCard({item}: {item: ShowcaseItem}) { function ShowcaseCard({item}: {item: ShowcaseItem}) {
console.log('ShowcaseCard user:', item);
const image = getCardImage(item); const image = getCardImage(item);
return ( return (
<li key={item.title} className="card shadow--md"> <li key={item.title} className="card shadow--md">
<div className={clsx('card__image', styles.showcaseCardImage)}> <div className={clsx('card__image', styles.showcaseCardImage)}>
{/* TODO change back to ideal image */}
<img src={image} alt={item.title} /> <img src={image} alt={item.title} />
</div> </div>
<div className="card__body"> <div className="card__body">

View file

@ -9,21 +9,20 @@ import type {ReactNode} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Translate from '@docusaurus/Translate'; import Translate from '@docusaurus/Translate';
import { import {
useFilteredUsers, useFilteredItems,
sortUsers, sortItems,
} from '@docusaurus/plugin-content-showcase/client'; } from '@docusaurus/plugin-content-showcase/client';
import {useShowcase} from '@docusaurus/theme-common/internal'; import {useShowcase} from '@docusaurus/theme-common/internal';
import Heading from '@theme/Heading'; import Heading from '@theme/Heading';
import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon';
import ShowcaseCard from '@theme/Showcase/ShowcaseCard'; import ShowcaseCard from '@theme/Showcase/ShowcaseCard';
import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase';
import styles from './styles.module.css'; import styles from './styles.module.css';
function HeadingNoResult() { function HeadingNoResult() {
return ( return (
<Heading as="h2"> <Heading as="h2">
<Translate id="showcase.usersList.noResult">No result</Translate> <Translate id="showcase.itemsList.noResult">No result</Translate>
</Heading> </Heading>
); );
} }
@ -40,7 +39,7 @@ function HeadingFavorites() {
function HeadingAllSites() { function HeadingAllSites() {
return ( return (
<Heading as="h2"> <Heading as="h2">
<Translate id="showcase.usersList.allUsers">All sites</Translate> <Translate id="showcase.itemsList.allItems">All sites</Translate>
</Heading> </Heading>
); );
} }
@ -52,7 +51,6 @@ function CardList({
heading?: ReactNode; heading?: ReactNode;
items: ShowcaseItem[]; items: ShowcaseItem[];
}) { }) {
console.log('CardList items:', items);
return ( return (
<div className="container"> <div className="container">
{heading} {heading}
@ -76,40 +74,37 @@ function NoResultSection() {
} }
export default function ShowcaseCards(): JSX.Element { export default function ShowcaseCards(): JSX.Element {
const users = useShowcase().items; const {showcaseItems: items} = useShowcase();
console.log('ShowcaseCards users:', users);
const filteredUsers = useFilteredUsers(users); const filteredItems = useFilteredItems(items);
if (filteredUsers.length === 0) { if (filteredItems.length === 0) {
return <NoResultSection />; return <NoResultSection />;
} }
const sortedUsers = sortUsers(users); const sortedItems = sortItems(items);
const favoriteUsers = sortedUsers.filter((user: ShowcaseItem) => const favoriteItems = sortedItems.filter((item: ShowcaseItem) =>
user.tags.includes('favorite'), item.tags.includes('favorite'),
); );
console.log('favoriteUsers:', favoriteUsers);
const otherUsers = sortedUsers.filter( const otherItems = sortedItems.filter(
(user: ShowcaseItem) => !user.tags.includes('favorite'), (item: ShowcaseItem) => !item.tags.includes('favorite'),
); );
console.log('otherUsers:', otherUsers);
return ( return (
<section className="margin-top--lg margin-bottom--xl"> <section className="margin-top--lg margin-bottom--xl">
{filteredUsers.length === sortedUsers.length ? ( {filteredItems.length === sortedItems.length ? (
<> <>
<div className={styles.showcaseFavorite}> <div className={styles.showcaseFavorite}>
<CardList heading={<HeadingFavorites />} items={favoriteUsers} /> <CardList heading={<HeadingFavorites />} items={favoriteItems} />
</div> </div>
<div className="margin-top--lg"> <div className="margin-top--lg">
<CardList heading={<HeadingAllSites />} items={otherUsers} /> <CardList heading={<HeadingAllSites />} items={otherItems} />
</div> </div>
</> </>
) : ( ) : (
<CardList items={filteredUsers} /> <CardList items={filteredItems} />
)} )}
</section> </section>
); );

View file

@ -9,10 +9,8 @@ import type {ReactNode, CSSProperties} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Translate from '@docusaurus/Translate'; import Translate from '@docusaurus/Translate';
import { import {
useFilteredUsers, useFilteredItems,
useSiteCountPlural, useSiteCountPlural,
Tags,
TagList,
} from '@docusaurus/plugin-content-showcase/client'; } from '@docusaurus/plugin-content-showcase/client';
import {useShowcase} from '@docusaurus/theme-common/internal'; import {useShowcase} from '@docusaurus/theme-common/internal';
import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon';
@ -21,7 +19,6 @@ import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect';
import OperatorButton from '@theme/Showcase/OperatorButton'; import OperatorButton from '@theme/Showcase/OperatorButton';
import ClearAllButton from '@theme/Showcase/ClearAllButton'; import ClearAllButton from '@theme/Showcase/ClearAllButton';
import type {TagType} from '@docusaurus/plugin-content-showcase'; import type {TagType} from '@docusaurus/plugin-content-showcase';
import styles from './styles.module.css'; import styles from './styles.module.css';
function TagCircleIcon({color, style}: {color: string; style?: CSSProperties}) { function TagCircleIcon({color, style}: {color: string; style?: CSSProperties}) {
@ -39,13 +36,14 @@ function TagCircleIcon({color, style}: {color: string; style?: CSSProperties}) {
} }
function ShowcaseTagListItem({tag}: {tag: TagType}) { function ShowcaseTagListItem({tag}: {tag: TagType}) {
const {label, description, color} = Tags[tag]; const {tags} = useShowcase();
const {label, description, color} = tags[tag];
return ( return (
<li className={styles.tagListItem}> <li className={styles.tagListItem}>
<ShowcaseTagSelect <ShowcaseTagSelect
tag={tag} tag={tag}
label={label} label={label}
description={description} description={description.message}
icon={ icon={
tag === 'favorite' ? ( tag === 'favorite' ? (
<FavoriteIcon size="small" style={{marginLeft: 8}} /> <FavoriteIcon size="small" style={{marginLeft: 8}} />
@ -65,6 +63,8 @@ function ShowcaseTagListItem({tag}: {tag: TagType}) {
} }
function ShowcaseTagList() { function ShowcaseTagList() {
const {tags} = useShowcase();
const TagList = Object.keys(tags) as TagType[];
return ( return (
<ul className={clsx('clean-list', styles.tagList)}> <ul className={clsx('clean-list', styles.tagList)}>
{TagList.map((tag) => { {TagList.map((tag) => {
@ -75,15 +75,15 @@ function ShowcaseTagList() {
} }
function HeadingText() { function HeadingText() {
const users = useShowcase().items; const {showcaseItems: items} = useShowcase();
const filteredUsers = useFilteredUsers(users); const filteredItems = useFilteredItems(items);
const siteCountPlural = useSiteCountPlural(); const siteCountPlural = useSiteCountPlural();
return ( return (
<div className={styles.headingText}> <div className={styles.headingText}>
<Heading as="h2"> <Heading as="h2">
<Translate id="showcase.filters.title">Filters</Translate> <Translate id="showcase.filters.title">Filters</Translate>
</Heading> </Heading>
<span>{siteCountPlural(filteredUsers.length)}</span> <span>{siteCountPlural(filteredItems.length)}</span>
</div> </div>
); );
} }

View file

@ -8,7 +8,6 @@
import React, {useCallback, type ReactNode, useId} from 'react'; import React, {useCallback, type ReactNode, useId} from 'react';
import {useTags} from '@docusaurus/plugin-content-showcase/client'; import {useTags} from '@docusaurus/plugin-content-showcase/client';
import type {Props} from '@theme/Showcase/ShowcaseTagSelect'; import type {Props} from '@theme/Showcase/ShowcaseTagSelect';
import styles from './styles.module.css'; import styles from './styles.module.css';
function useTagState(tag: string) { function useTagState(tag: string) {

View file

@ -6,12 +6,10 @@
*/ */
import Translate, {translate} from '@docusaurus/Translate'; import Translate, {translate} from '@docusaurus/Translate';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import {ShowcaseProvider} from '@docusaurus/theme-common/internal'; import {ShowcaseProvider} from '@docusaurus/theme-common/internal';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import Heading from '@theme/Heading'; import Heading from '@theme/Heading';
import ShowcaseSearchBar from '@theme/Showcase/ShowcaseSearchBar'; import ShowcaseSearchBar from '@theme/Showcase/ShowcaseSearchBar';
import ShowcaseCards from '@theme/Showcase/ShowcaseCards'; import ShowcaseCards from '@theme/Showcase/ShowcaseCards';
import ShowcaseFilters from '@theme/Showcase/ShowcaseFilters'; import ShowcaseFilters from '@theme/Showcase/ShowcaseFilters';
@ -39,7 +37,7 @@ function ShowcaseHeader() {
export default function Showcase(props: Props): JSX.Element { export default function Showcase(props: Props): JSX.Element {
return ( return (
<ShowcaseProvider content={{items: props.items}}> <ShowcaseProvider content={props.items} tags={props.tags}>
<Layout title={TITLE} description={DESCRIPTION}> <Layout title={TITLE} description={DESCRIPTION}>
<main className="margin-vert--lg"> <main className="margin-vert--lg">
<ShowcaseHeader /> <ShowcaseHeader />

View file

@ -7,31 +7,40 @@
import React, {useMemo, type ReactNode, useContext} from 'react'; import React, {useMemo, type ReactNode, useContext} from 'react';
import {ReactContextError} from '../utils/reactUtils'; import {ReactContextError} from '../utils/reactUtils';
import type {ShowcaseItems} from '@docusaurus/plugin-content-showcase'; import type {
ShowcaseItem,
TagsOption,
} from '@docusaurus/plugin-content-showcase';
const Context = React.createContext<ShowcaseItems | null>(null); const Context = React.createContext<{
showcaseItems: ShowcaseItem[];
tags: TagsOption;
} | null>(null);
function useContextValue(content: ShowcaseItems): ShowcaseItems { function useContextValue(
return useMemo( content: ShowcaseItem[],
() => ({ tags: TagsOption,
items: content.items, ): {showcaseItems: ShowcaseItem[]; tags: TagsOption} {
}), return useMemo(() => ({showcaseItems: content, tags}), [content, tags]);
[content],
);
} }
export function ShowcaseProvider({ export function ShowcaseProvider({
children, children,
content, content,
tags,
}: { }: {
children: ReactNode; children: ReactNode;
content: ShowcaseItems; content: ShowcaseItem[];
tags: TagsOption;
}): JSX.Element { }): JSX.Element {
const contextValue = useContextValue(content); const contextValue = useContextValue(content, tags);
return <Context.Provider value={contextValue}>{children}</Context.Provider>; return <Context.Provider value={contextValue}>{children}</Context.Provider>;
} }
export function useShowcase(): ShowcaseItems { export function useShowcase(): {
showcaseItems: ShowcaseItem[];
tags: TagsOption;
} {
const showcase = useContext(Context); const showcase = useContext(Context);
if (showcase === null) { if (showcase === null) {
throw new ReactContextError('ShowcaseProvider'); throw new ReactContextError('ShowcaseProvider');