-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
1,220 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
website/src/pages/showcase/_components/FavouriteIcon/favourite-icon.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/** | ||
* 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, type ComponentProps } from "react"; | ||
import clsx from "clsx"; | ||
import styles from "./styles.module.css"; | ||
|
||
export type SvgIconProps = ComponentProps<"svg"> & { | ||
viewBox?: string; | ||
size?: "inherit" | "small" | "medium" | "large"; | ||
color?: "inherit" | "primary" | "secondary" | "success" | "error" | "warning"; | ||
svgClass?: string; // Class attribute on the child | ||
colorAttr?: string; // Applies a color attribute to the SVG element. | ||
}; | ||
|
||
export default function FavouriteIcon(props: SvgIconProps): JSX.Element { | ||
const { svgClass, colorAttr, children, color = "inherit", size = "medium", viewBox = "0 0 24 24", ...rest } = props; | ||
|
||
return ( | ||
<svg | ||
viewBox={viewBox} | ||
color={colorAttr} | ||
aria-hidden | ||
className={clsx(styles.svgIcon, styles[color], styles[size], svgClass)} | ||
{...rest} | ||
> | ||
<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> | ||
); | ||
} |
54 changes: 54 additions & 0 deletions
54
website/src/pages/showcase/_components/FavouriteIcon/styles.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
.svgIcon { | ||
user-select: none; | ||
width: 1em; | ||
height: 1em; | ||
display: inline-block; | ||
fill: currentColor; | ||
flex-shrink: 0; | ||
color: inherit; | ||
} | ||
|
||
/* font-size */ | ||
.small { | ||
font-size: 1.25rem; | ||
} | ||
|
||
.medium { | ||
font-size: 1.5rem; | ||
} | ||
|
||
.large { | ||
font-size: 2.185rem; | ||
} | ||
|
||
/* colours */ | ||
.primary { | ||
color: var(--ifm-color-primary); | ||
} | ||
|
||
.secondary { | ||
color: var(--ifm-color-secondary); | ||
} | ||
|
||
.success { | ||
color: var(--ifm-color-success); | ||
} | ||
|
||
.error { | ||
color: var(--ifm-color-error); | ||
} | ||
|
||
.warning { | ||
color: var(--ifm-color-warning); | ||
} | ||
|
||
.inherit { | ||
color: inherit; | ||
} |
81 changes: 81 additions & 0 deletions
81
website/src/pages/showcase/_components/ShowcaseCard/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* 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 from "react"; | ||
import clsx from "clsx"; | ||
import Link from "@docusaurus/Link"; | ||
import Translate from "@docusaurus/Translate"; | ||
// import Image from '@theme/IdealImage'; | ||
import FavouriteIcon from "../FavouriteIcon/favourite-icon"; | ||
import { Tags, TagList, type TagType, type Website, type Tag } from "../../data"; | ||
import Heading from "@theme/Heading"; | ||
import Tooltip from "../ShowcaseTooltip"; | ||
import styles from "./styles.module.css"; | ||
|
||
const TagComp = React.forwardRef<HTMLLIElement, Tag>(({ label, color, description }, ref) => ( | ||
<li ref={ref} 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] })); | ||
|
||
// Keep same order for all tags | ||
const tagObjectsSorted = tagObjects.sort((a, b) => TagList.indexOf(a.tag) - TagList.indexOf(b.tag)); | ||
|
||
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> | ||
); | ||
})} | ||
</> | ||
); | ||
} | ||
|
||
function getCardImage(user: Website): string { | ||
return user.preview ?? `https://slorber-api-screenshot.netlify.app/${encodeURIComponent(user.website)}/showcase`; | ||
} | ||
|
||
function ShowcaseCard({ user }: { user: Website }) { | ||
const image = getCardImage(user); | ||
return ( | ||
<li key={user.title} className="card shadow--md"> | ||
<div className={clsx("card__image", styles.showcaseCardImage)}> | ||
<img src={image} alt={user.title} /> | ||
</div> | ||
<div className="card__body"> | ||
<div className={clsx(styles.showcaseCardHeader)}> | ||
<Heading as="h4" className={styles.showcaseCardTitle}> | ||
<Link href={user.website} className={styles.showcaseCardLink}> | ||
{user.title} | ||
</Link> | ||
</Heading> | ||
{user.tags.includes("favourite") && <FavouriteIcon svgClass={styles.svgIconFavorite} size="small" />} | ||
{user.source && ( | ||
<Link href={user.source} className={clsx("button button--secondary button--sm", styles.showcaseCardSrcBtn)}> | ||
<Translate id="showcase.card.sourceLink">source</Translate> | ||
</Link> | ||
)} | ||
</div> | ||
<p className={styles.showcaseCardBody}>{user.description}</p> | ||
</div> | ||
<ul className={clsx("card__footer", styles.cardFooter)}> | ||
<ShowcaseCardTag tags={user.tags} /> | ||
</ul> | ||
</li> | ||
); | ||
} | ||
|
||
export default React.memo(ShowcaseCard); |
99 changes: 99 additions & 0 deletions
99
website/src/pages/showcase/_components/ShowcaseCard/styles.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
.showcaseCardImage { | ||
overflow: hidden; | ||
height: 150px; | ||
border-bottom: 2px solid var(--ifm-color-emphasis-200); | ||
} | ||
|
||
.showcaseCardHeader { | ||
display: flex; | ||
align-items: center; | ||
margin-bottom: 12px; | ||
} | ||
|
||
.showcaseCardTitle { | ||
margin-bottom: 0; | ||
flex: 1 1 auto; | ||
} | ||
|
||
.showcaseCardTitle a { | ||
text-decoration: none; | ||
background: linear-gradient( | ||
var(--ifm-color-primary), | ||
var(--ifm-color-primary) | ||
) | ||
0% 100% / 0% 1px no-repeat; | ||
transition: background-size ease-out 200ms; | ||
} | ||
|
||
.showcaseCardTitle a:not(:focus):hover { | ||
background-size: 100% 1px; | ||
} | ||
|
||
.showcaseCardTitle, | ||
.showcaseCardHeader .svgIconFavorite { | ||
margin-right: 0.25rem; | ||
} | ||
|
||
.showcaseCardHeader .svgIconFavorite { | ||
color: var(--site-color-svg-icon-favorite); | ||
} | ||
|
||
.showcaseCardSrcBtn { | ||
margin-left: 6px; | ||
padding-left: 12px; | ||
padding-right: 12px; | ||
border: none; | ||
} | ||
|
||
.showcaseCardSrcBtn:focus-visible { | ||
background-color: var(--ifm-color-secondary-dark); | ||
} | ||
|
||
[data-theme='dark'] .showcaseCardSrcBtn { | ||
background-color: var(--ifm-color-emphasis-200) !important; | ||
color: inherit; | ||
} | ||
|
||
[data-theme='dark'] .showcaseCardSrcBtn:hover { | ||
background-color: var(--ifm-color-emphasis-300) !important; | ||
} | ||
|
||
.showcaseCardBody { | ||
font-size: smaller; | ||
line-height: 1.66; | ||
} | ||
|
||
.cardFooter { | ||
display: flex; | ||
flex-wrap: wrap; | ||
} | ||
|
||
.tag { | ||
font-size: 0.675rem; | ||
border: 1px solid var(--ifm-color-secondary-darkest); | ||
cursor: default; | ||
margin-right: 6px; | ||
margin-bottom: 6px !important; | ||
border-radius: 12px; | ||
display: inline-flex; | ||
align-items: center; | ||
} | ||
|
||
.tag .textLabel { | ||
margin-left: 8px; | ||
} | ||
|
||
.tag .colorLabel { | ||
width: 7px; | ||
height: 7px; | ||
border-radius: 50%; | ||
margin-left: 6px; | ||
margin-right: 6px; | ||
} |
85 changes: 85 additions & 0 deletions
85
website/src/pages/showcase/_components/ShowcaseFilterToggle/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* 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, {useState, useEffect, useCallback} from 'react'; | ||
import clsx from 'clsx'; | ||
import {useHistory, useLocation} from '@docusaurus/router'; | ||
|
||
import {prepareUserState} from '../../index'; | ||
|
||
import styles from './styles.module.css'; | ||
|
||
export type Operator = 'OR' | 'AND'; | ||
|
||
export const OperatorQueryKey = 'operator'; | ||
|
||
export function readOperator(search: string): Operator { | ||
return (new URLSearchParams(search).get(OperatorQueryKey) ?? | ||
'OR') as Operator; | ||
} | ||
|
||
export default function ShowcaseFilterToggle(): JSX.Element { | ||
const id = 'showcase_filter_toggle'; | ||
const location = useLocation(); | ||
const history = useHistory(); | ||
const [operator, setOperator] = useState(false); | ||
useEffect(() => { | ||
setOperator(readOperator(location.search) === 'AND'); | ||
}, [location]); | ||
const toggleOperator = useCallback(() => { | ||
setOperator((o) => !o); | ||
const searchParams = new URLSearchParams(location.search); | ||
searchParams.delete(OperatorQueryKey); | ||
if (!operator) { | ||
searchParams.append(OperatorQueryKey, 'AND'); | ||
} | ||
history.push({ | ||
...location, | ||
search: searchParams.toString(), | ||
state: prepareUserState(), | ||
}); | ||
}, [operator, location, history]); | ||
|
||
const ClearTag = () => { | ||
history.push({ | ||
...location, | ||
search: '', | ||
state: prepareUserState(), | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="row" style={{alignItems: 'center'}}> | ||
<input | ||
type="checkbox" | ||
id={id} | ||
className="screen-reader-only" | ||
aria-label="Toggle between or and and for the tags you selected" | ||
onChange={toggleOperator} | ||
onKeyDown={(e) => { | ||
if (e.key === 'Enter') { | ||
toggleOperator(); | ||
} | ||
}} | ||
checked={operator} | ||
/> | ||
<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> | ||
|
||
<button | ||
className="button button--outline button--primary" | ||
type="button" | ||
onClick={() => ClearTag()}> | ||
Clear All | ||
</button> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.