journey learning tracks UI (#57538)
This commit is contained in:
@@ -1,16 +1,105 @@
|
|||||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||||
import { useLandingContext } from '@/landings/context/LandingContext'
|
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||||
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
import { LandingHero } from '@/landings/components/shared/LandingHero'
|
||||||
|
import { JourneyLearningTracks } from './JourneyLearningTracks'
|
||||||
|
|
||||||
|
export type JourneyLearningTrack = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
trackName: string
|
||||||
|
trackProduct: string
|
||||||
|
guides?: Array<{
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
export const JourneyLanding = () => {
|
export const JourneyLanding = () => {
|
||||||
const { title, intro, heroImage, introLinks } = useLandingContext()
|
const { title, intro, heroImage, introLinks } = useLandingContext()
|
||||||
|
|
||||||
|
// Temp until we hookup real data
|
||||||
|
const stubLearningTracks: JourneyLearningTrack[] = [
|
||||||
|
{
|
||||||
|
id: 'admin:get_started_with_your_enterprise_account',
|
||||||
|
title: 'Get started with your enterprise account',
|
||||||
|
description:
|
||||||
|
'Set up your enterprise account and configure initial settings for your organization.',
|
||||||
|
trackName: 'get_started_with_your_enterprise_account',
|
||||||
|
trackProduct: 'admin',
|
||||||
|
guides: [
|
||||||
|
{
|
||||||
|
href: '/admin/overview/about-enterprise-accounts?learn=get_started_with_your_enterprise_account&learnProduct=admin',
|
||||||
|
title: 'About enterprise accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/inviting-people-to-manage-your-enterprise?learn=get_started_with_your_enterprise_account&learnProduct=admin',
|
||||||
|
title: 'Inviting people to manage your enterprise',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/admin/policies/enforcing-policies-for-your-enterprise/about-enterprise-policies?learn=get_started_with_your_enterprise_account&learnProduct=admin',
|
||||||
|
title: 'About enterprise policies',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin:adopting_github_actions_for_your_enterprise_ghec',
|
||||||
|
title: 'Adopt GitHub Actions for your enterprise',
|
||||||
|
description:
|
||||||
|
'Learn how to plan and implement a rollout of GitHub Actions in your enterprise.',
|
||||||
|
trackName: 'adopting_github_actions_for_your_enterprise_ghec',
|
||||||
|
trackProduct: 'admin',
|
||||||
|
guides: [
|
||||||
|
{
|
||||||
|
href: '/admin/managing-github-actions-for-your-enterprise/getting-started-with-github-actions-for-your-enterprise/about-github-actions-for-enterprises?learn=adopting_github_actions_for_your_enterprise_ghec&learnProduct=admin',
|
||||||
|
title: 'About GitHub Actions for enterprises',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/actions/get-started/understand-github-actions?learn=adopting_github_actions_for_your_enterprise_ghec&learnProduct=admin',
|
||||||
|
title: 'Understanding GitHub Actions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/admin/managing-github-actions-for-your-enterprise/getting-started-with-github-actions-for-your-enterprise/introducing-github-actions-to-your-enterprise?learn=adopting_github_actions_for_your_enterprise_ghec&learnProduct=admin',
|
||||||
|
title: 'Introducing GitHub Actions to your enterprise',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/admin/managing-github-actions-for-your-enterprise/getting-started-with-github-actions-for-your-enterprise/migrating-your-enterprise-to-github-actions?learn=adopting_github_actions_for_your_enterprise_ghec&learnProduct=admin',
|
||||||
|
title: 'Migrating your enterprise to GitHub Actions',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions:continuous-integration',
|
||||||
|
title: 'Continuous integration with GitHub Actions',
|
||||||
|
description:
|
||||||
|
'Set up automated testing and building for your projects using GitHub Actions workflows.',
|
||||||
|
trackName: 'continuous-integration',
|
||||||
|
trackProduct: 'actions',
|
||||||
|
guides: [
|
||||||
|
{
|
||||||
|
href: '/actions/automating-builds-and-tests/about-continuous-integration?learn=continuous-integration&learnProduct=actions',
|
||||||
|
title: 'About continuous integration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/actions/automating-builds-and-tests/building-and-testing-nodejs?learn=continuous-integration&learnProduct=actions',
|
||||||
|
title: 'Building and testing Node.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/actions/automating-builds-and-tests/building-and-testing-python?learn=continuous-integration&learnProduct=actions',
|
||||||
|
title: 'Building and testing Python',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<div>
|
<div>
|
||||||
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
|
||||||
|
|
||||||
<div>TODO</div>
|
<div className="container-xl px-3 px-md-6 mt-6 mb-4">
|
||||||
|
<JourneyLearningTracks tracks={stubLearningTracks} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
213
src/landings/components/journey/JourneyLearningTracks.module.css
Normal file
213
src/landings/components/journey/JourneyLearningTracks.module.css
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
.learningTracks {
|
||||||
|
border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
padding-bottom: .75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
box-shadow:
|
||||||
|
0px 1px 3px 0px rgba(31, 35, 40, 0.08),
|
||||||
|
0px 1px 0px 0px rgba(31, 35, 40, 0.06);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: var(--bgColor-default, var(--color-canvas-default, #ffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackHeader {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
padding-right: 3rem;
|
||||||
|
color: var(--fgColor-default, var(--color-fg-default, #1f2328));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anchorLink {
|
||||||
|
color: var(--fgColor-default, var(--color-fg-default, #1f2328));
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackDescription {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandButton {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackGuides {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
counter-reset: guide-counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackGuides li {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
counter-increment: guide-counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackGuides li::before {
|
||||||
|
content: counter(guide-counter);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: -0.125rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--borderColor-default, var(--color-border-default, #d0d7de));
|
||||||
|
color: var(--fgColor-muted, var(--color-fg-muted, #656d76));
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.70rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guideLink {
|
||||||
|
color: var(--fgColor-accent, var(--color-accent-fg, #0969da));
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide only the timeline line that extends below the last badge, preserve everything else */
|
||||||
|
.timelineContainer :global(.Timeline-Item:last-child::before) {
|
||||||
|
background: linear-gradient(to bottom, var(--borderColor-default, #d1d9e0) 0%, var(--borderColor-default, #d1d9e0) 30%, transparent 30%, transparent 100%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timelineBadge {
|
||||||
|
background-color: var(--color-canvas-subtle, #f6f8fa) !important;
|
||||||
|
border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix entire timeline component overlapping header */
|
||||||
|
.timelineContainer {
|
||||||
|
z-index: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timelineContainer :global(.Timeline) {
|
||||||
|
z-index: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timelineThinLine :global(.Timeline-Item::before) {
|
||||||
|
width: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-first: custom stacked layout */
|
||||||
|
.mobileLayout {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileItem {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center; /* Only for centering the badge */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileBadge {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
background-color: var(--color-canvas-subtle, #f6f8fa);
|
||||||
|
color: var(--fgColor-muted, var(--color-fg-muted));
|
||||||
|
border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add connecting line from badge downward */
|
||||||
|
.mobileBadge::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
width: 1px;
|
||||||
|
height: 2.5rem;
|
||||||
|
background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add connecting line above badge (except first item) */
|
||||||
|
.mobileItem:not(:first-child) .mobileBadge::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 100%;
|
||||||
|
width: 1px;
|
||||||
|
height: 2.5rem;
|
||||||
|
background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileConnector {
|
||||||
|
width: 1px;
|
||||||
|
height: 1rem;
|
||||||
|
background-color: var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileConnector:last-child {
|
||||||
|
display: none; /* Hide connector after last item */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileItem .mobileTile {
|
||||||
|
border: 1px solid var(--borderColor-default, var(--color-border-default, #d1d9e0));
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
box-shadow:
|
||||||
|
0px 1px 3px 0px rgba(31, 35, 40, 0.08),
|
||||||
|
0px 1px 0px 0px rgba(31, 35, 40, 0.06);
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
background-color: var(--bgColor-default, var(--color-canvas-default, #ffffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: show Timeline component */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.mobileLayout {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timelineContainer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: hide Timeline component */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.timelineContainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: stack h3 and Token vertically when mobile layout is active */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.trackHeader {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trackHeader h3 {
|
||||||
|
margin-bottom: .50rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/landings/components/journey/JourneyLearningTracks.tsx
Normal file
85
src/landings/components/journey/JourneyLearningTracks.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/* filepath: /workspaces/docs-internal/src/landings/components/journey/JourneyLearningTracks.tsx */
|
||||||
|
import { ChevronDownIcon, ChevronUpIcon } from '@primer/octicons-react'
|
||||||
|
import { Button, Details, Timeline, Token, useDetails } from '@primer/react'
|
||||||
|
import type { JourneyLearningTrack } from './JourneyLanding'
|
||||||
|
import styles from './JourneyLearningTracks.module.css'
|
||||||
|
|
||||||
|
type JourneyLearningTracksProps = {
|
||||||
|
tracks: JourneyLearningTrack[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JourneyLearningTracks = ({ tracks }: JourneyLearningTracksProps) => {
|
||||||
|
if (!tracks || tracks.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderTrackContent = (track: JourneyLearningTrack, trackIndex: number) => {
|
||||||
|
const { getDetailsProps, open } = useDetails({})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.trackHeader}>
|
||||||
|
<h3 className="h4 text-bold">{track.title}</h3>
|
||||||
|
<Token text={`${track.guides?.length || 0} articles`} />
|
||||||
|
</div>
|
||||||
|
<p className={styles.trackDescription}>{track.description}</p>
|
||||||
|
<Details {...getDetailsProps()}>
|
||||||
|
<Button
|
||||||
|
as="summary"
|
||||||
|
variant="invisible"
|
||||||
|
className={`position-absolute ${styles.expandButton}`}
|
||||||
|
aria-label={
|
||||||
|
open
|
||||||
|
? `Collapse article list ${trackIndex + 1}`
|
||||||
|
: `Expand article list ${trackIndex + 1}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||||
|
</Button>
|
||||||
|
<ol className={styles.trackGuides}>
|
||||||
|
{(track.guides || []).map((guide) => (
|
||||||
|
<li key={guide.title}>
|
||||||
|
<a href={guide.href} className={`text-semibold ${styles.guideLink}`}>
|
||||||
|
{guide.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</Details>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Desktop: Timeline component */}
|
||||||
|
<div className={styles.timelineContainer}>
|
||||||
|
<Timeline clipSidebar className={styles.timelineThinLine}>
|
||||||
|
{tracks.map((track, trackIndex) => {
|
||||||
|
return (
|
||||||
|
<Timeline.Item key={track.id}>
|
||||||
|
<Timeline.Badge className={styles.timelineBadge}>{trackIndex + 1}</Timeline.Badge>
|
||||||
|
<Timeline.Body className={styles.learningTracks}>
|
||||||
|
<div className="position-relative">{renderTrackContent(track, trackIndex)}</div>
|
||||||
|
</Timeline.Body>
|
||||||
|
</Timeline.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Timeline>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile: Custom stacked layout */}
|
||||||
|
<div className={styles.mobileLayout}>
|
||||||
|
{tracks.map((track, trackIndex) => (
|
||||||
|
<div key={track.id} className={styles.mobileItem}>
|
||||||
|
<div className={styles.mobileBadge}>{trackIndex + 1}</div>
|
||||||
|
<div className={styles.mobileTile}>
|
||||||
|
<div className="position-relative">{renderTrackContent(track, trackIndex)}</div>
|
||||||
|
</div>
|
||||||
|
{trackIndex < tracks.length - 1 && <div className={styles.mobileConnector} />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user