journey learning tracks UI (#57538)
This commit is contained in:
@@ -1,16 +1,105 @@
|
||||
import { DefaultLayout } from '@/frame/components/DefaultLayout'
|
||||
import { useLandingContext } from '@/landings/context/LandingContext'
|
||||
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 = () => {
|
||||
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 (
|
||||
<DefaultLayout>
|
||||
<div>
|
||||
<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>
|
||||
</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