Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(website): refactor showcase components #10023

Merged
merged 19 commits into from
Apr 10, 2024
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
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
Loading