1
0
mirror of synced 2026-01-29 03:03:52 -05:00

Merge pull request #7293 from github/repo-sync

repo sync
This commit is contained in:
Octomerger Bot
2021-06-12 03:47:41 +10:00
committed by GitHub
11 changed files with 434 additions and 4 deletions

View File

@@ -26,6 +26,20 @@ export const Breadcrumbs = () => {
<span key={title} title={title}>
{breadcrumb.title}
</span>
) : pathWithLocale.includes('/guides') ? (
<span className="text-mono color-text-secondary text-uppercase">
<Link
key={title}
href={breadcrumb.href}
title={title}
className={cx(
'd-inline-block',
pathWithLocale === breadcrumb.href && 'color-text-tertiary'
)}
>
{breadcrumb.title}
</Link>
</span>
) : (
<Link
key={title}

View File

@@ -0,0 +1,64 @@
import { createContext, useContext } from 'react'
import pick from 'lodash/pick'
export type FeaturedTrack = {
trackName: string
title: string
description: string
guides?: Array<{ href: string; page: { type: string }; title: string; intro: string }>
}
export type ArticleGuide = {
href: string
title: string
intro: string
type: string
topics: Array<string>
}
export type ProductSubLandingContextT = {
title: string
intro: string
featuredTrack?: FeaturedTrack
learningTracks?: Array<FeaturedTrack>
includeGuides?: Array<ArticleGuide>
allTopics?: Array<string>
}
export const ProductSubLandingContext = createContext<ProductSubLandingContextT | null>(null)
export const useProductSubLandingContext = (): ProductSubLandingContextT => {
const context = useContext(ProductSubLandingContext)
if (!context) {
throw new Error(
'"useProductSubLandingContext" may only be used inside "ProductSubLandingContext.Provider"'
)
}
return context
}
export const getProductSubLandingContextFromRequest = (req: any): ProductSubLandingContextT => {
const page = req.context.page
return {
...pick(page, ['intro', 'allTopics']),
title: req.context.productMap[req.context.currentProduct].name,
featuredTrack: {
...pick(page.featuredTrack, ['title', 'description', 'trackName', 'guides']),
guides: (page.featuredTrack?.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
},
learningTracks: (page.learningTracks || []).map((track: any) => ({
...pick(track, ['title', 'description', 'trackName', 'guides']),
guides: (track.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
})),
includeGuides: (page.includeGuides || []).map((guide: any) => {
return pick(guide, ['href', 'title', 'intro', 'type', 'topics'])
}),
}
}

View File

@@ -29,7 +29,11 @@ export const CodeExampleCard = ({ example }: Props) => {
</div>
<footer className="border-top p-4 color-text-secondary d-flex flex-items-center">
<RepoIcon className="flex-shrink-0" />
<TruncateLines as="span" maxLines={1} className="ml-2 text-mono text-small color-text-link line-break-anywhere">
<TruncateLines
as="span"
maxLines={1}
className="ml-2 text-mono text-small color-text-link line-break-anywhere"
>
{example.href}
</TruncateLines>
</footer>

View File

@@ -1,19 +1,29 @@
import cx from 'classnames'
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
title?: React.ReactNode
sectionLink?: string
children?: React.ReactNode
className?: string
description?: string
}
export const LandingSection = ({ title, children, className, sectionLink }: Props) => {
export const LandingSection = ({ title, children, className, sectionLink, description }: Props) => {
const { t } = useTranslation('product_sublanding')
return (
<div className={cx('container-xl px-3 px-md-6', className)} id={sectionLink}>
{title && (
<h2 className="font-mktg h1 mb-4">
<h2 className={cx('font-mktg', !description ? 'mb-3' : 'mb-4')}>
{sectionLink ? <a href={`#${sectionLink}`}>{title}</a> : title}
</h2>
)}
{description && (
<div
className="lead-mktg color-text-secondary f4 description-text"
dangerouslySetInnerHTML={{ __html: t(description) }}
/>
)}
{children}
</div>
)

View File

@@ -0,0 +1,34 @@
import { ArticleGuide } from 'components/context/ProductSubLandingContext'
type Props = {
card: ArticleGuide
type: string
display?: string
}
export const ArticleCard = ({ card, type, display }: Props) => {
return (
<div
className={`d-flex col-12 col-md-4 pr-0 pr-md-6 pr-lg-8 ${display} js-filter-card`}
data-type={card.type}
data-topics={card.topics.join(',')}
>
<a className="no-underline d-flex flex-column py-3 border-bottom" href={card.href}>
<h4 className="h4 color-text-primary mb-1">{card.title}</h4>
<div className="h6 text-uppercase">{type}</div>
<p className="color-text-secondary my-3">{card.intro}</p>
{card.topics.length > 0 && (
<div>
{card.topics.map((topic) => {
return (
<span key={topic} className="IssueLabel bg-gradient--pink-blue color-text-inverse mr-1">
{topic}
</span>
)
})}
</div>
)}
</a>
</div>
)
}

View File

@@ -0,0 +1,67 @@
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import { useTranslation } from 'components/hooks/useTranslation'
import { ArticleCard } from './ArticleCard'
const MAX_ARTICLES = 9
export const ArticleCards = () => {
const { t } = useTranslation('product_sublanding')
const guideTypes: Record<string, string> = t('guide_types')
const { allTopics, includeGuides } = useProductSubLandingContext()
return (
<div>
<form className="mt-2 mb-5 d-flex d-flex">
<div>
<label htmlFor="type" className="text-uppercase f6 color-text-secondary d-block">
{t('filters.type')}
</label>
<select
className="form-select js-filter-card-filter-dropdown f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0 js-filter-card-filter-dropdown"
name="type"
aria-label="guide types"
>
<option value="">{t('filters.all')}</option>
{Object.entries(guideTypes).map(([key, val]) => {
return <option key={key} value={key}>{val}</option>
})}
</select>
</div>
<div className="mx-4">
<label htmlFor="topic" className="text-uppercase f6 color-text-secondary d-block">
{t('filters.topic')}
</label>
<select
className="form-select js-filter-card-filter-dropdown f4 text-bold border-0 rounded-0 border-top box-shadow-none pl-0 js-filter-card-filter-dropdown"
name="topics"
aria-label="guide topics"
>
<option value="">{t('filters.all')}</option>
{allTopics?.map((topic) => {
return <option key={topic} value={topic}>{topic}</option>
})}
</select>
</div>
</form>
<div className="d-flex flex-wrap mr-0 mr-md-n6 mr-lg-n8">
{(includeGuides || []).map((card, index) => {
return index + 1 > MAX_ARTICLES ? (
<ArticleCard key={card.title} card={card} type={guideTypes[card.type]} display={'d-none'} />
) : (
<ArticleCard key={card.title} card={card} type={guideTypes[card.type]} />
)
})}
</div>
{includeGuides && includeGuides.length > MAX_ARTICLES && (
<button
className="col-12 mt-5 text-center text-bold color-text-link btn-link js-filter-card-show-more"
data-js-filter-card-max={MAX_ARTICLES}
>
{t('load_more')}
</button>
)}
<div className="js-filter-card-no-results d-none py-4 text-center color-text-secondary">
<h4 className="text-normal">{t('no_result')}</h4>
</div>
</div>
)
}

View File

@@ -0,0 +1,80 @@
import { useTranslation } from 'components/hooks/useTranslation'
import { ArrowRightIcon } from '@primer/octicons-react'
import { useState } from 'react'
import { FeaturedTrack } from 'components/context/ProductSubLandingContext'
type Props = {
track: FeaturedTrack
}
const MAX_VISIBLE_GUIDES = 4
export const LearningTrack = ({ track }: Props) => {
const [visibleGuides, setVisibleGuides] = useState(track.guides?.slice(0, 4))
const showAll = () => {
setVisibleGuides(track.guides)
}
const { t } = useTranslation('product_sublanding')
return (
<div className="my-3 px-4 col-12 col-md-6 learning-track">
<div className="Box js-show-more-container d-flex flex-column">
<div className="Box-header bg-gradient--blue-pink p-4 d-flex flex-1 flex-items-start flex-wrap">
<div className="d-flex flex-auto flex-items-start col-8 col-md-12 col-xl-8">
<div className="my-xl-0 mr-xl-3">
<h5 className="mb-3 color-text-inverse font-mktg h3-mktg ">{track.title}</h5>
<p className="color-text-inverse truncate-overflow-3 learning-track--description">
{track.description}
</p>
</div>
</div>
<a
className="d-inline-block border color-border-inverse color-text-inverse px-3 py-2 f5 no-underline text-bold no-wrap mt-3 mt-md-0"
role="button"
href={`${track.guides && track.guides[0].href}?learn=${track.trackName}`}
>
{t('start')}
<span className="mr-2">
<ArrowRightIcon size={20} />
</span>
</a>
</div>
{visibleGuides?.map((guide) => (
<div>
<a
className="Box-row d-flex flex-items-center color-text-primary no-underline js-show-more-item"
href={`${guide.href}?learn=${track.trackName}`}
>
<div className="circle color-bg-tertiary d-inline-flex mr-4">
{track.guides && (
<span className="m-2 f3 lh-condensed-ultra text-center text-bold step-circle-text">
{track.guides?.indexOf(guide) + 1}
</span>
)}
</div>
<h5 className="flex-auto pr-2">{guide.title}</h5>
<div className="color-text-tertiary h6 text-uppercase">
{t('guide_types')[guide.page.type]}
</div>
</a>
{track.guides && track.guides?.indexOf(guide) + 1 === MAX_VISIBLE_GUIDES ? (
<button
className="Box-footer btn-link border-top-0 position-relative text-center text-bold color-text-link pt-1 pb-3 col-12 js-show-more-button"
onClick={showAll}
>
<div
className="position-absolute left-0 right-0 py-5 fade-background-bottom"
style={{ bottom: '50px' }}
></div>
<span>
Show {track.guides?.length - MAX_VISIBLE_GUIDES} {t(`more_guides`)}
</span>
</button>
) : (
<div />
)}
</div>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,16 @@
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import { LearningTrack } from 'components/sublanding/LearningTrack'
export const LearningTracks = () => {
const { learningTracks } = useProductSubLandingContext()
return (
<div>
<div className="d-flex flex-wrap flex-items-start my-5 gutter">
{(learningTracks || []).map((track) => {
return <LearningTrack key={track.title} track={track} />
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,40 @@
import { DefaultLayout } from 'components/DefaultLayout'
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import React from 'react'
import { LandingSection } from 'components/landing/LandingSection'
import { SubLandingHero } from 'components/sublanding/SubLandingHero'
import { LearningTracks } from 'components/sublanding/LearningTracks'
import { ArticleCards } from 'components/sublanding/ArticleCards'
export const ProductSubLanding = () => {
const { title, learningTracks, includeGuides } = useProductSubLandingContext()
return (
<DefaultLayout>
<LandingSection className="pt-3">
<SubLandingHero />
</LandingSection>
{learningTracks && learningTracks.length > 0 && (
<LandingSection
title={`${title} learning paths`}
className="border-top py-6"
sectionLink="learning-paths"
description="learning_paths_desc"
>
<LearningTracks />
</LandingSection>
)}
{includeGuides && (
<LandingSection
title={`All ${title} guides`}
className="border-top py-6 color-border-primary"
sectionLink="all-guides"
>
<ArticleCards />
</LandingSection>
)}
</DefaultLayout>
)
}

View File

@@ -0,0 +1,88 @@
import { Breadcrumbs } from '../Breadcrumbs'
import { useProductSubLandingContext } from 'components/context/ProductSubLandingContext'
import { ArrowRightIcon, StarFillIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
export const SubLandingHero = () => {
const { title, intro, featuredTrack } = useProductSubLandingContext()
const { t } = useTranslation('product_sublanding')
const guideItems = featuredTrack?.guides?.map((guide) => (
<li className="px-2 d-flex flex-shrink-0">
<a
href={`${guide.href}?learn=${featuredTrack.trackName}`}
className="d-inline-block Box p-5 color-bg-primary color-border-primary no-underline"
>
<div className="d-flex flex-justify-between flex-items-center">
<div className="circle color-bg-primary color-text-link border-gradient--pink-blue-dark d-inline-flex">
{featuredTrack.guides && (
<span
className="m-2 f2 lh-condensed-ultra text-center text-bold step-circle-text"
style={{ width: '24px', height: '24px' }}
>
{featuredTrack.guides?.indexOf(guide) + 1}
</span>
)}
</div>
<div className="color-text-tertiary h6 text-uppercase">
{t('guide_types')[guide.page.type]}
</div>
</div>
<h3 className="font-mktg h3-mktg my-4 color-text-primary">{guide.title}</h3>
<div className="lead-mktg color-text-secondary f5 my-4 truncate-overflow-8">
{guide.intro}
</div>
</a>
</li>
))
return (
<div>
<header className="d-flex gutter mb-6">
<div className="col-12">
<Breadcrumbs />
<h1 className="my-3 font-mktg">{title} guides</h1>
<div
className="lead-mktg color-text-secondary f4 description-text"
dangerouslySetInnerHTML={{ __html: intro }}
/>
</div>
</header>
{featuredTrack && (
<div className="mb-6 position-relative overflow-hidden mr-n3 ml-n3 px-3">
<ul className="list-style-none d-flex flex-nowrap overflow-x-scroll px-2 feature-track">
<li className="px-2 d-flex flex-shrink-0">
<div className="d-inline-block Box p-5 bg-gradient--blue-pink color-text-inverse">
<div
className="circle color-text-inverse d-inline-flex"
style={{ border: '2px white solid' }}
>
<StarFillIcon className="v-align-middle m-2" size={24} />
</div>
<h3 className="font-mktg h3-mktg my-4">{featuredTrack.title}</h3>
<div className="lead-mktg color-text-inverse f5 my-4">
{featuredTrack.description}
</div>
{featuredTrack.guides && (
<a
className="d-inline-block border color-border-inverse color-text-inverse px-4 py-2 f5 no-underline text-bold"
role="button"
href={`${featuredTrack.guides[0].href}?learn=${featuredTrack.trackName}`}
>
<span className="mr-2">
<ArrowRightIcon size={20} />
</span>
{t(`start_path`)}
</a>
)}
</div>
</li>
{guideItems}
</ul>
<div className="position-absolute top-0 bottom-0 left-0 ml-3 pl-3 fade-background-left"></div>
<div className="position-absolute top-0 bottom-0 right-0 mr-3 pr-3 fade-background-right"></div>
</div>
)}
</div>
)
}

View File

@@ -11,6 +11,11 @@ import {
ProductLandingContextT,
ProductLandingContext,
} from 'components/context/ProductLandingContext'
import {
getProductSubLandingContextFromRequest,
ProductSubLandingContextT,
ProductSubLandingContext,
} from 'components/context/ProductSubLandingContext'
import {
getArticleContextFromRequest,
@@ -20,6 +25,7 @@ import {
import { ArticlePage } from 'components/article/ArticlePage'
import { ProductLanding } from 'components/landing/ProductLanding'
import { ProductSubLanding } from 'components/sublanding/ProductSubLanding'
import { TocLanding } from 'components/landing/TocLanding'
import {
getTocLandingContextFromRequest,
@@ -30,12 +36,14 @@ import {
type Props = {
mainContext: MainContextT
productLandingContext: ProductLandingContextT
productSubLandingContext: ProductSubLandingContextT
tocLandingContext: TocLandingContextT
articleContext: ArticleContextT
}
const GlobalPage = ({
mainContext,
productLandingContext,
productSubLandingContext,
tocLandingContext,
articleContext,
}: Props) => {
@@ -49,7 +57,11 @@ const GlobalPage = ({
</ProductLandingContext.Provider>
)
} else if (currentLayoutName === 'product-sublanding') {
content = <p>todo: product sub-landing</p>
content = (
<ProductSubLandingContext.Provider value={productSubLandingContext}>
<ProductSubLanding />
</ProductSubLandingContext.Provider>
)
} else if (relativePath?.endsWith('index.md')) {
content = (
<TocLandingContext.Provider value={tocLandingContext}>
@@ -76,6 +88,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
props: {
mainContext: getMainContextFromRequest(req),
productLandingContext: getProductLandingContextFromRequest(req),
productSubLandingContext: getProductSubLandingContextFromRequest(req),
tocLandingContext: getTocLandingContextFromRequest(req),
articleContext: getArticleContextFromRequest(req),
},