mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 10:17:55 +02:00
279 lines
8.3 KiB
TypeScript
279 lines
8.3 KiB
TypeScript
/**
|
||
* 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 clsx from 'clsx';
|
||
import LiteYouTubeEmbed from 'react-lite-youtube-embed';
|
||
import Link from '@docusaurus/Link';
|
||
import Translate, {translate} from '@docusaurus/Translate';
|
||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||
import useBaseUrl, {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
||
|
||
import Image from '@theme/IdealImage';
|
||
import Layout from '@theme/Layout';
|
||
|
||
import Tweet from '@site/src/components/Tweet';
|
||
import Tweets, {type TweetItem} from '@site/src/data/tweets';
|
||
import Quotes from '@site/src/data/quotes';
|
||
import Features, {type FeatureItem} from '@site/src/data/features';
|
||
import Heading from '@theme/Heading';
|
||
|
||
import styles from './styles.module.css';
|
||
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css';
|
||
|
||
function HeroBanner() {
|
||
return (
|
||
<div className={styles.hero} data-theme="dark">
|
||
<div className={styles.heroInner}>
|
||
<Heading as="h1" className={styles.heroProjectTagline}>
|
||
<img
|
||
alt={translate({message: 'Docusaurus with Keytar'})}
|
||
className={styles.heroLogo}
|
||
src={useBaseUrl('/img/docusaurus_keytar.svg')}
|
||
width="200"
|
||
height="200"
|
||
/>
|
||
<span
|
||
className={styles.heroTitleTextHtml}
|
||
// eslint-disable-next-line react/no-danger
|
||
dangerouslySetInnerHTML={{
|
||
__html: translate({
|
||
id: 'homepage.hero.title',
|
||
message:
|
||
'Build <b>optimized</b> websites <b>quickly</b>, focus on your <b>content</b>',
|
||
description:
|
||
'Home page hero title, can contain simple html tags',
|
||
}),
|
||
}}
|
||
/>
|
||
</Heading>
|
||
<div className={styles.indexCtas}>
|
||
<Link className="button button--primary" to="/docs">
|
||
<Translate>Get Started</Translate>
|
||
</Link>
|
||
<Link className="button button--info" to="https://docusaurus.new">
|
||
<Translate>Try a Demo</Translate>
|
||
</Link>
|
||
<span className={styles.indexCtasGitHubButtonWrapper}>
|
||
<iframe
|
||
className={styles.indexCtasGitHubButton}
|
||
src="https://ghbtns.com/github-btn.html?user=facebook&repo=docusaurus&type=star&count=true&size=large"
|
||
width={160}
|
||
height={30}
|
||
title="GitHub Stars"
|
||
/>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function TweetsSection() {
|
||
const tweetColumns: TweetItem[][] = [[], [], []];
|
||
Tweets.filter((tweet) => tweet.showOnHomepage).forEach((tweet, i) =>
|
||
tweetColumns[i % 3]!.push(tweet),
|
||
);
|
||
|
||
return (
|
||
<div className={clsx(styles.section, styles.sectionAlt)}>
|
||
<div className="container">
|
||
<Heading as="h2" className={clsx('margin-bottom--lg', 'text--center')}>
|
||
<Translate>Loved by many engineers</Translate>
|
||
</Heading>
|
||
<div className={clsx('row', styles.tweetsSection)}>
|
||
{tweetColumns.map((tweetItems, i) => (
|
||
<div className="col col--4" key={i}>
|
||
{tweetItems.map((tweet) => (
|
||
<Tweet {...tweet} key={tweet.url} />
|
||
))}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function QuotesSection() {
|
||
return (
|
||
<div className={clsx(styles.section)}>
|
||
<div className="container">
|
||
<div className="row">
|
||
{Quotes.map((quote) => (
|
||
<div className="col" key={quote.name}>
|
||
<div className="avatar avatar--vertical margin-bottom--sm">
|
||
<Image
|
||
alt={quote.name}
|
||
className="avatar__photo avatar__photo--xl"
|
||
img={quote.thumbnail}
|
||
style={{overflow: 'hidden'}}
|
||
/>
|
||
<div className="avatar__intro padding-top--sm">
|
||
<div className="avatar__name">{quote.name}</div>
|
||
<small className="avatar__subtitle">{quote.title}</small>
|
||
</div>
|
||
</div>
|
||
<p className="text--center text--italic padding-horiz--md">
|
||
{quote.text}
|
||
</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function VideoContainer() {
|
||
return (
|
||
<div className="container text--center margin-top--xl">
|
||
<div className="row">
|
||
<div className="col">
|
||
<Heading as="h2">
|
||
<Translate>Check it out in the intro video</Translate>
|
||
</Heading>
|
||
<div className="video-container">
|
||
<LiteYouTubeEmbed
|
||
id="_An9EsKPhp0"
|
||
params="autoplay=1&autohide=1&showinfo=0&rel=0"
|
||
title="Explain Like I'm 5: Docusaurus"
|
||
poster="maxresdefault"
|
||
webp
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Feature({
|
||
feature,
|
||
className,
|
||
}: {
|
||
feature: FeatureItem;
|
||
className?: string;
|
||
}) {
|
||
const {withBaseUrl} = useBaseUrlUtils();
|
||
|
||
return (
|
||
<div className={clsx('col', className)}>
|
||
<img
|
||
className={styles.featureImage}
|
||
alt={feature.title}
|
||
width={Math.floor(feature.image.width)}
|
||
height={Math.floor(feature.image.height)}
|
||
src={withBaseUrl(feature.image.src)}
|
||
loading="lazy"
|
||
/>
|
||
<Heading as="h3" className={clsx(styles.featureHeading)}>
|
||
{feature.title}
|
||
</Heading>
|
||
<p className="padding-horiz--md">{feature.text}</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function FeaturesContainer() {
|
||
const firstRow = Features.slice(0, 3);
|
||
const secondRow = Features.slice(3);
|
||
|
||
return (
|
||
<div className="container text--center">
|
||
<div className="row margin-top--lg margin-bottom--lg">
|
||
{firstRow.map((feature, idx) => (
|
||
<Feature feature={feature} key={idx} />
|
||
))}
|
||
</div>
|
||
<div className="row">
|
||
{secondRow.map((feature, idx) => (
|
||
<Feature
|
||
feature={feature}
|
||
key={idx}
|
||
className={clsx('col--4', idx === 0 && 'col--offset-2')}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function TopBanner() {
|
||
// TODO We should be able to strongly type customFields
|
||
// Refactor to use a CustomFields interface + TS declaration merging
|
||
const announcedVersion = useDocusaurusContext().siteConfig.customFields
|
||
?.announcedVersion as string;
|
||
|
||
return (
|
||
<div className={styles.topBanner}>
|
||
<div className={styles.topBannerTitle}>
|
||
{'🎉\xa0'}
|
||
<Link
|
||
to={`/blog/releases/${announcedVersion}`}
|
||
className={styles.topBannerTitleText}>
|
||
<Translate
|
||
id="homepage.banner.launch.newVersion"
|
||
values={{newVersion: announcedVersion}}>
|
||
{'Docusaurus\xa0{newVersion} is\xa0out!️'}
|
||
</Translate>
|
||
</Link>
|
||
{'\xa0🥳'}
|
||
</div>
|
||
{/*
|
||
<div style={{display: 'flex', alignItems: 'center', flexWrap: 'wrap'}}>
|
||
<div style={{flex: 1, whiteSpace: 'nowrap'}}>
|
||
<div className={styles.topBannerDescription}>
|
||
We are on{' '}
|
||
<b>
|
||
<Link to="https://www.producthunt.com/posts/docusaurus-2-0">
|
||
ProductHunt
|
||
</Link>{' '}
|
||
and{' '}
|
||
<Link to="https://news.ycombinator.com/item?id=32303052">
|
||
Hacker News
|
||
</Link>{' '}
|
||
today!
|
||
</b>
|
||
</div>
|
||
</div>
|
||
<div
|
||
style={{
|
||
flexGrow: 1,
|
||
flexShrink: 0,
|
||
padding: '0.5rem',
|
||
display: 'flex',
|
||
justifyContent: 'center',
|
||
}}>
|
||
<ProductHuntCard />
|
||
<HackerNewsIcon />
|
||
</div>
|
||
</div>
|
||
*/}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function Home(): JSX.Element {
|
||
const {
|
||
siteConfig: {customFields, tagline},
|
||
} = useDocusaurusContext();
|
||
const {description} = customFields as {description: string};
|
||
return (
|
||
<Layout title={tagline} description={description}>
|
||
<main>
|
||
<TopBanner />
|
||
<HeroBanner />
|
||
<div className={styles.section}>
|
||
<FeaturesContainer />
|
||
<VideoContainer />
|
||
</div>
|
||
<TweetsSection />
|
||
<QuotesSection />
|
||
</main>
|
||
</Layout>
|
||
);
|
||
}
|