From 5217e4f1b2c14012ff275816282da5050f7c0f67 Mon Sep 17 00:00:00 2001 From: Sahejkm <163521239+Sahejkm@users.noreply.github.com> Date: Thu, 11 Jul 2024 12:54:15 +0800 Subject: [PATCH] [Gallery] Add option to filter plugins based on tags (#6391) --- .../src/components/Gallery/GalleryCards.tsx | 25 ++++- .../components/Gallery/components/Filters.tsx | 100 ++++++++++++++++++ .../Gallery/components/TagSelect.tsx | 71 +++++++++++++ .../Gallery/components/styles.module.css | 92 ++++++++++++++++ .../src/components/Gallery/pluginList.tsx | 3 + .../src/components/Gallery/tagList.tsx | 27 +++++ .../src/components/Gallery/utils.tsx | 17 ++- 7 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 packages/lexical-website/src/components/Gallery/components/Filters.tsx create mode 100644 packages/lexical-website/src/components/Gallery/components/TagSelect.tsx create mode 100644 packages/lexical-website/src/components/Gallery/tagList.tsx diff --git a/packages/lexical-website/src/components/Gallery/GalleryCards.tsx b/packages/lexical-website/src/components/Gallery/GalleryCards.tsx index 37d959adae4b..71c5ec3d75cd 100644 --- a/packages/lexical-website/src/components/Gallery/GalleryCards.tsx +++ b/packages/lexical-website/src/components/Gallery/GalleryCards.tsx @@ -12,9 +12,11 @@ import clsx from 'clsx'; import React, {useEffect, useState} from 'react'; import Card from './Card'; +import Filters from './components/Filters'; import SearchBar from './components/SearchBar'; import {Example, plugins} from './pluginList'; import styles from './styles.module.css'; +import {Tag, TagList} from './tagList'; import {useFilteredExamples} from './utils'; function CardList({cards}: {cards: Array}) { @@ -41,26 +43,41 @@ function GalleryCardsImpl() { const [internGalleryCards, setInternGalleryCards] = useState<{ InternGalleryCards: () => Array; } | null>(null); + const [internGalleryTags, setInternGalleryTags] = useState<{ + InternGalleryTags: () => {[type in string]: Tag}; + } | null>(null); const pluginsCombined = plugins(customFields ?? {}).concat( internGalleryCards != null ? internGalleryCards.InternGalleryCards() : [], ); + const tagList = { + ...TagList, + ...(internGalleryTags != null ? internGalleryTags.InternGalleryTags() : {}), + }; + const filteredPlugins = useFilteredExamples(pluginsCombined); useEffect(() => { if (process.env.FB_INTERNAL) { // @ts-ignore runtime dependency for intern builds import('../../../../InternGalleryCards').then(setInternGalleryCards); + // @ts-ignore runtime dependency for intern builds + import('../../../../InternGalleryTags').then(setInternGalleryTags); } }, []); return (
-
- -
- +
+ +
+ +
+ +
); } diff --git a/packages/lexical-website/src/components/Gallery/components/Filters.tsx b/packages/lexical-website/src/components/Gallery/components/Filters.tsx new file mode 100644 index 000000000000..1ba011e21c98 --- /dev/null +++ b/packages/lexical-website/src/components/Gallery/components/Filters.tsx @@ -0,0 +1,100 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import type {CSSProperties, ReactNode} from 'react'; + +import Heading from '@theme/Heading'; +import clsx from 'clsx'; +import React from 'react'; + +import {Example} from '../pluginList'; +import {Tag} from '../tagList'; +import styles from './styles.module.css'; +import TagSelect from './TagSelect'; + +function TagCircleIcon({color, style}: {color: string; style?: CSSProperties}) { + return ( + + ); +} + +function TagListItem({tag, tagKey}: {tag: Tag; tagKey: string}) { + const {title, description, color} = tag; + return ( +
  • + + } + /> +
  • + ); +} + +function TagList({allTags}: {allTags: {[type in string]: Tag}}) { + return ( +
      + {Object.keys(allTags).map((tag) => { + return ; + })} +
    + ); +} + +function HeadingText({filteredPlugins}: {filteredPlugins: Array}) { + return ( +
    + Filters + + {filteredPlugins.length === 1 + ? '1 exampe' + : `${filteredPlugins.length} examples`} + +
    + ); +} + +function HeadingRow({filteredPlugins}: {filteredPlugins: Array}) { + return ( +
    + +
    + ); +} + +export default function Filters({ + filteredPlugins, + tagList, +}: { + filteredPlugins: Array; + tagList: {[type in string]: Tag}; +}): ReactNode { + return ( +
    + + +
    + ); +} diff --git a/packages/lexical-website/src/components/Gallery/components/TagSelect.tsx b/packages/lexical-website/src/components/Gallery/components/TagSelect.tsx new file mode 100644 index 000000000000..5d5798b0b303 --- /dev/null +++ b/packages/lexical-website/src/components/Gallery/components/TagSelect.tsx @@ -0,0 +1,71 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import React, { + type ComponentProps, + type ReactElement, + type ReactNode, + useCallback, + useId, +} from 'react'; + +import {useTags} from '../utils'; +import styles from './styles.module.css'; + +function useTagState(tag: string) { + const [tags, setTags] = useTags(); + const isSelected = tags.includes(tag); + const toggle = useCallback(() => { + setTags((list) => { + return list.includes(tag) + ? list.filter((t) => t !== tag) + : [...list, tag]; + }); + }, [tag, setTags]); + + return [isSelected, toggle] as const; +} + +interface Props extends ComponentProps<'input'> { + tag: string; + label: string; + description: string; + icon: ReactElement>; +} + +export default function TagSelect({ + icon, + label, + description, + tag, + ...rest +}: Props): ReactNode { + const id = useId(); + const [isSelected, toggle] = useTagState(tag); + return ( + <> + { + if (e.key === 'Enter') { + toggle(); + } + }} + {...rest} + /> + + + ); +} diff --git a/packages/lexical-website/src/components/Gallery/components/styles.module.css b/packages/lexical-website/src/components/Gallery/components/styles.module.css index e30db94e5309..00c669c18354 100644 --- a/packages/lexical-website/src/components/Gallery/components/styles.module.css +++ b/packages/lexical-website/src/components/Gallery/components/styles.module.css @@ -16,3 +16,95 @@ padding: 10px; border: 1px solid gray; } + +.headingRow { + display: flex; + align-items: center; + justify-content: space-between; +} + +.headingText { + display: flex; + align-items: baseline; +} + +.headingText > h2 { + margin-bottom: 0; +} + +.headingText > span { + margin-left: 8px; +} + +.headingButtons { + display: flex; + align-items: center; +} + +.tagList { + display: flex; + align-items: center; + flex-wrap: wrap; +} + +.tagListItem { + user-select: none; + white-space: nowrap; + height: 32px; + font-size: 0.8rem; + margin-top: 0.5rem; + margin-right: 0.5rem; +} + +.tagListItem:last-child { + margin-right: 0; +} + +.checkboxLabel:hover { + opacity: 1; + box-shadow: 0 0 2px 1px var(--ifm-color-secondary-darkest); +} + +input[type='checkbox'] + .checkboxLabel { + display: flex; + align-items: center; + cursor: pointer; + line-height: 1.5; + border-radius: 4px; + padding: 0.275rem 0.8rem; + opacity: 0.85; + transition: opacity 200ms ease-out; + border: 2px solid var(--ifm-color-secondary-darkest); +} + +input:focus-visible + .checkboxLabel { + outline: 2px solid currentColor; +} + +input:checked + .checkboxLabel { + opacity: 0.9; + background-color: hsl(167deg 56% 73% / 25%); + border: 2px solid var(--ifm-color-primary-darkest); +} + +input:checked + .checkboxLabel:hover { + opacity: 0.75; + box-shadow: 0 0 2px 1px var(--ifm-color-primary-dark); +} + +html[data-theme='dark'] input:checked + .checkboxLabel { + background-color: hsl(167deg 56% 73% / 10%); +} + +.screenReaderOnly { + border: 0; + clip: rect(0 0 0 0); + clip-path: polygon(0 0, 0 0, 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; +} diff --git a/packages/lexical-website/src/components/Gallery/pluginList.tsx b/packages/lexical-website/src/components/Gallery/pluginList.tsx index 6364759d8628..97deca02e34e 100644 --- a/packages/lexical-website/src/components/Gallery/pluginList.tsx +++ b/packages/lexical-website/src/components/Gallery/pluginList.tsx @@ -14,6 +14,7 @@ export type Example = { uri?: string; preview?: string; renderPreview?: () => ReactNode; + tags: Array; }; export const plugins = (customFields: { @@ -21,11 +22,13 @@ export const plugins = (customFields: { }): Array => [ { description: 'Learn how to create an editor with Emojis', + tags: ['opensource'], title: 'EmojiPlugin', uri: `${customFields.STACKBLITZ_PREFIX}examples/vanilla-js-plugin?embed=1&file=src%2Femoji-plugin%2FEmojiPlugin.ts&terminalHeight=0&ctl=0`, }, { description: 'Learn how to create an editor with Real Time Collaboration', + tags: ['opensource', 'favorite'], title: 'Collab RichText', uri: 'https://stackblitz.com/github/facebook/lexical/tree/fix/collab_example/examples/react-rich-collab?ctl=0&file=src%2Fmain.tsx&terminalHeight=0&embed=1', }, diff --git a/packages/lexical-website/src/components/Gallery/tagList.tsx b/packages/lexical-website/src/components/Gallery/tagList.tsx new file mode 100644 index 000000000000..daf38b9aae45 --- /dev/null +++ b/packages/lexical-website/src/components/Gallery/tagList.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export type Tag = { + color: string; + description: string; + title: string; +}; + +export const TagList: {[type in string]: Tag} = { + favorite: { + color: '#e9669e', + description: + 'Our favorite Docusaurus sites that you must absolutely check out!', + title: 'Favorite', + }, + opensource: { + color: '#39ca30', + description: 'Open-Source Lexical plugins for inspiration', + title: 'Open-Source', + }, +}; diff --git a/packages/lexical-website/src/components/Gallery/utils.tsx b/packages/lexical-website/src/components/Gallery/utils.tsx index 436c53d4ee79..4a908e835e85 100644 --- a/packages/lexical-website/src/components/Gallery/utils.tsx +++ b/packages/lexical-website/src/components/Gallery/utils.tsx @@ -6,7 +6,7 @@ * */ -import {useQueryString} from '@docusaurus/theme-common'; +import {useQueryString, useQueryStringList} from '@docusaurus/theme-common'; import {useMemo} from 'react'; import {Example} from './pluginList'; @@ -15,28 +15,41 @@ export function useSearchName() { return useQueryString('title'); } +export function useTags() { + return useQueryStringList('tags'); +} + function filterExamples({ examples, searchName, + tags, }: { examples: Array; searchName: string; + tags: Array; }) { if (searchName) { - return examples.filter((example) => + examples = examples.filter((example) => example.title.toLowerCase().includes(searchName.toLowerCase()), ); } + if (tags.length !== 0) { + examples = examples.filter((example) => + example.tags.some((tag) => tags.includes(tag)), + ); + } return examples; } export function useFilteredExamples(examples: Array) { const [searchName] = useSearchName(); + const [tags] = useTags(); return useMemo( () => filterExamples({ examples, searchName, + tags, }), [examples, searchName], );