Skip to content

Commit

Permalink
refactor(website): refactor showcase components (#10023)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored Apr 10, 2024
1 parent 73016d4 commit 964a4e4
Show file tree
Hide file tree
Showing 28 changed files with 723 additions and 822 deletions.
7 changes: 7 additions & 0 deletions packages/docusaurus-theme-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ export {useDocsPreferredVersion} from './contexts/docsPreferredVersion';

export {processAdmonitionProps} from './utils/admonitionUtils';

export {
useHistorySelector,
useQueryString,
useQueryStringList,
useClearQueryString,
} from './utils/historyUtils';

export {
SkipToContentFallbackId,
SkipToContentLink,
Expand Down
85 changes: 65 additions & 20 deletions packages/docusaurus-theme-common/src/utils/historyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {useCallback, useEffect, useSyncExternalStore} from 'react';
import {useCallback, useEffect, useMemo, useSyncExternalStore} from 'react';
import {useHistory} from '@docusaurus/router';
import {useEvent} from './reactUtils';

Expand Down Expand Up @@ -74,41 +74,86 @@ export function useQueryStringValue(key: string | null): string | null {
});
}

export function useQueryStringKeySetter(): (
function useQueryStringUpdater(
key: string,
newValue: string | null,
options?: {push: boolean},
) => void {
): (newValue: string | null, options?: {push: boolean}) => void {
const history = useHistory();
return useCallback(
(key, newValue, options) => {
(newValue, options) => {
const searchParams = new URLSearchParams(history.location.search);
if (newValue) {
searchParams.set(key, newValue);
} else {
searchParams.delete(key);
}
const updaterFn = options?.push ? history.push : history.replace;
updaterFn({
const updateHistory = options?.push ? history.push : history.replace;
updateHistory({
search: searchParams.toString(),
});
},
[history],
[key, history],
);
}

export function useQueryString(
key: string,
): [string, (newValue: string, options?: {push: boolean}) => void] {
): [string, (newValue: string | null, options?: {push: boolean}) => void] {
const value = useQueryStringValue(key) ?? '';
const setQueryString = useQueryStringKeySetter();
return [
value,
useCallback(
(newValue: string, options) => {
setQueryString(key, newValue, options);
},
[setQueryString, key],
),
];
const update = useQueryStringUpdater(key);
return [value, update];
}

function useQueryStringListValues(key: string): string[] {
// Unfortunately we can't just use searchParams.getAll(key) in the selector
// It would create a new array every time and lead to an infinite loop...
// The selector has to return a primitive/string value to avoid that...
const arrayJsonString = useHistorySelector((history) => {
const values = new URLSearchParams(history.location.search).getAll(key);
return JSON.stringify(values);
});
return useMemo(() => JSON.parse(arrayJsonString), [arrayJsonString]);
}

type ListUpdate = string[] | ((oldValues: string[]) => string[]);
type ListUpdateFunction = (
update: ListUpdate,
options?: {push: boolean},
) => void;

function useQueryStringListUpdater(key: string): ListUpdateFunction {
const history = useHistory();
const setValues: ListUpdateFunction = useCallback(
(update, options) => {
const searchParams = new URLSearchParams(history.location.search);
const newValues = Array.isArray(update)
? update
: update(searchParams.getAll(key));
searchParams.delete(key);
newValues.forEach((v) => searchParams.append(key, v));

const updateHistory = options?.push ? history.push : history.replace;
updateHistory({
search: searchParams.toString(),
});
},
[history, key],
);
return setValues;
}

export function useQueryStringList(
key: string,
): [string[], ListUpdateFunction] {
const values = useQueryStringListValues(key);
const setValues = useQueryStringListUpdater(key);
return [values, setValues];
}

export function useClearQueryString(): () => void {
const history = useHistory();
return useCallback(() => {
history.replace({
search: undefined,
});
}, [history]);
}
1 change: 0 additions & 1 deletion project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ rtcts
rtlcss
saurus
Scaleway
searchbar
Sebastien
sebastien
sebastienlorber
Expand Down
2 changes: 0 additions & 2 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"@docusaurus/theme-mermaid": "3.2.1",
"@docusaurus/utils": "3.2.1",
"@docusaurus/utils-common": "3.2.1",
"@popperjs/core": "^2.11.8",
"@swc/core": "1.2.197",
"clsx": "^2.0.0",
"color": "^4.2.3",
Expand All @@ -61,7 +60,6 @@
"react-dom": "^18.0.0",
"react-lite-youtube-embed": "^2.3.52",
"react-medium-image-zoom": "^5.1.6",
"react-popper": "^2.3.0",
"rehype-katex": "^7.0.0",
"remark-math": "^6.0.0",
"swc-loader": "^0.2.3",
Expand Down
19 changes: 0 additions & 19 deletions website/src/components/svgIcons/FavoriteIcon/index.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions website/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
*/
--site-primary-hue-saturation: 167 68%;
--site-primary-hue-saturation-light: 167 56%; /* do we really need this extra one? */
--site-color-favorite-background: #f6fdfd;
--site-color-tooltip: #fff;
--site-color-tooltip-background: #353738;
--site-color-svg-icon-favorite: #e9669e;
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 25%);
--site-color-feedback-background: #f0f8ff;
--docusaurus-highlighted-code-line-bg: rgb(0 0 0 / 10%);
/* Use a darker color to ensure contrast, ideally we don't need important */
Expand All @@ -28,8 +23,6 @@

html[data-theme='dark'] {
--site-color-feedback-background: #2a2929;
--site-color-favorite-background: #1d1e1e;
--site-color-checkbox-checked-bg: hsl(167deg 56% 73% / 10%);
--docusaurus-highlighted-code-line-bg: rgb(66 66 66 / 35%);
}

Expand Down
22 changes: 22 additions & 0 deletions website/src/pages/showcase/_components/ClearAllButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its 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 ReactNode} from 'react';
import {useClearQueryString} from '@docusaurus/theme-common';

export default function ClearAllButton(): ReactNode {
const clearQueryString = useClearQueryString();
// TODO translate
return (
<button
className="button button--outline button--primary"
type="button"
onClick={() => clearQueryString()}>
Clear All
</button>
);
}
32 changes: 32 additions & 0 deletions website/src/pages/showcase/_components/FavoriteIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) Facebook, Inc. and its 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} from 'react';
import clsx from 'clsx';

import styles from './styles.module.css';

interface Props {
className?: string;
style?: ComponentProps<'svg'>['style'];
size: 'small' | 'medium' | 'large';
}

export default function FavoriteIcon({
size,
className,
style,
}: Props): React.ReactNode {
return (
<svg
viewBox="0 0 24 24"
className={clsx(styles.svg, styles[size], className)}
style={style}>
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
</svg>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

.svg {
user-select: none;
color: #e9669e;
width: 1em;
height: 1em;
display: inline-block;
fill: currentColor;
}

.small {
font-size: 1rem;
}

.medium {
font-size: 1.25rem;
}

.large {
font-size: 1.8rem;
}
41 changes: 41 additions & 0 deletions website/src/pages/showcase/_components/OperatorButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its 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, {useId} from 'react';
import clsx from 'clsx';
import {useOperator} from '../../_utils';

import styles from './styles.module.css';

export default function OperatorButton() {
const id = useId();
const [operator, toggleOperator] = useOperator();
// TODO add translations
return (
<>
<input
id={id}
type="checkbox"
className="screen-reader-only"
aria-label="Toggle between or and and for the tags you selected"
checked={operator === 'AND'}
onChange={toggleOperator}
onKeyDown={(e) => {
if (e.key === 'Enter') {
toggleOperator();
}
}}
/>
<label htmlFor={id} className={clsx(styles.checkboxLabel, 'shadow--md')}>
{/* eslint-disable @docusaurus/no-untranslated-text */}
<span className={styles.checkboxLabelOr}>OR</span>
<span className={styles.checkboxLabelAnd}>AND</span>
{/* eslint-enable @docusaurus/no-untranslated-text */}
</label>
</>
);
}
44 changes: 18 additions & 26 deletions website/src/pages/showcase/_components/ShowcaseCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,28 @@ import clsx from 'clsx';
import Link from '@docusaurus/Link';
import Translate from '@docusaurus/Translate';
import Image from '@theme/IdealImage';
import FavoriteIcon from '@site/src/components/svgIcons/FavoriteIcon';
import {
Tags,
TagList,
type TagType,
type User,
type Tag,
} from '@site/src/data/users';
import {Tags, TagList, type TagType, type User} from '@site/src/data/users';
import {sortBy} from '@site/src/utils/jsUtils';
import Heading from '@theme/Heading';
import Tooltip from '../ShowcaseTooltip';
import FavoriteIcon from '../FavoriteIcon';
import styles from './styles.module.css';

const TagComp = React.forwardRef<HTMLLIElement, Tag>(
({label, color, description}, ref) => (
<li ref={ref} className={styles.tag} title={description}>
function TagItem({
label,
description,
color,
}: {
label: string;
description: string;
color: string;
}) {
return (
<li className={styles.tag} title={description}>
<span className={styles.textLabel}>{label.toLowerCase()}</span>
<span className={styles.colorLabel} style={{backgroundColor: color}} />
</li>
),
);
);
}

function ShowcaseCardTag({tags}: {tags: TagType[]}) {
const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]}));
Expand All @@ -43,17 +44,7 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) {
return (
<>
{tagObjectsSorted.map((tagObject, index) => {
const id = `showcase_card_tag_${tagObject.tag}`;

return (
<Tooltip
key={index}
text={tagObject.description}
anchorEl="#__docusaurus"
id={id}>
<TagComp key={index} {...tagObject} />
</Tooltip>
);
return <TagItem key={index} {...tagObject} />;
})}
</>
);
Expand All @@ -62,6 +53,7 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) {
function getCardImage(user: User): string {
return (
user.preview ??
// TODO make it configurable
`https://slorber-api-screenshot.netlify.app/${encodeURIComponent(
user.website,
)}/showcase`
Expand All @@ -83,7 +75,7 @@ function ShowcaseCard({user}: {user: User}) {
</Link>
</Heading>
{user.tags.includes('favorite') && (
<FavoriteIcon svgClass={styles.svgIconFavorite} size="small" />
<FavoriteIcon size="medium" style={{marginRight: '0.25rem'}} />
)}
{user.source && (
<Link
Expand Down
Loading

0 comments on commit 964a4e4

Please sign in to comment.