Skip to content

Commit e394df2

Browse files
committed
Replaces tutorial list with sandbox cards
Replaces the tutorial list on the collection view with interactive sandbox cards. This change improves the user experience by allowing users to launch interactive sandboxes directly from the collection page. It also removes the product icons from the sandbox card and replaces it with badges.
1 parent c88fd75 commit e394df2

File tree

6 files changed

+104
-24
lines changed

6 files changed

+104
-24
lines changed

src/components/sandbox-card/index.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
*/
55

66
import React from 'react'
7-
import { ProductSlug } from 'types/products'
7+
import { CardBadgeOption } from 'components/tutorial-collection-cards/components/card-badges/types'
88
import Card from 'components/card'
99
import CardTitle from 'components/card/components/card-title'
1010
import CardDescription from 'components/card/components/card-description'
1111
import CardFooter from 'components/card/components/card-footer'
1212
import ButtonLink from 'components/button-link'
13-
import ProductIcon from 'components/product-icon'
13+
import Badge from 'components/badge'
14+
import { IconTerminalScreen16 } from '@hashicorp/flight-icons/svg-react/terminal-screen-16'
15+
import { CardBadges } from 'components/tutorial-collection-cards'
1416
import s from './sandbox-card.module.css'
1517

1618
export interface SandboxCardProps {
@@ -25,26 +27,26 @@ export interface SandboxCardProps {
2527
const SandboxCard: React.FC<SandboxCardProps> = ({
2628
title,
2729
description,
28-
labId,
2930
products,
3031
onLaunch,
3132
className,
3233
}) => {
34+
const productBadges = products.map(
35+
(productSlug) => productSlug as CardBadgeOption
36+
)
37+
3338
return (
3439
<div className={`${s.sandboxCardWrapper} ${className || ''}`}>
3540
<Card className={s.sandboxCard}>
3641
<div className={s.cardHeader}>
42+
<Badge
43+
icon={<IconTerminalScreen16 />}
44+
ariaLabel="Interactive"
45+
size="small"
46+
type="filled"
47+
className={s.interactiveBadge}
48+
/>
3749
<CardTitle text={title} />
38-
<div className={s.productIcons}>
39-
{products.map((productSlug) => (
40-
<ProductIcon
41-
key={`product-${labId}-${productSlug}`}
42-
productSlug={productSlug as ProductSlug}
43-
size={16}
44-
className={s.productIcon}
45-
/>
46-
))}
47-
</div>
4850
</div>
4951
<CardDescription text={description} className={s.description} />
5052
<CardFooter className={s.footer}>
@@ -61,6 +63,9 @@ const SandboxCard: React.FC<SandboxCardProps> = ({
6163
text="Launch Sandbox"
6264
/>
6365
</CardFooter>
66+
<div style={{ marginTop: '16px' }}>
67+
<CardBadges badges={productBadges} />
68+
</div>
6469
</Card>
6570
</div>
6671
)

src/components/sandbox-card/sandbox-card.module.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
.cardHeader {
1515
display: flex;
1616
align-items: flex-start;
17-
justify-content: space-between;
17+
justify-content: flex-start;
1818
gap: 12px;
1919
}
2020

@@ -43,6 +43,12 @@
4343
justify-content: center;
4444
}
4545

46+
.interactiveBadge {
47+
margin-right: 8px;
48+
display: flex;
49+
align-items: center;
50+
}
51+
4652
@media (max-width: 768px) {
4753
.cardHeader {
4854
flex-direction: column;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
.root {
7+
background: var(--token-color-surface-faint);
8+
box-shadow: var(--token-surface-base-box-shadow);
9+
border-radius: 6px;
10+
padding: 16px;
11+
}
12+
13+
.gridContainer {
14+
display: grid;
15+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
16+
gap: 16px;
17+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
import SandboxCard from 'components/sandbox-card'
7+
import { CollectionSandboxListProps } from './types'
8+
import s from './collection-sandbox-list.module.css'
9+
10+
export const CollectionSandboxList = ({
11+
sandboxes,
12+
}: CollectionSandboxListProps) => {
13+
return (
14+
<div className={s.gridContainer}>
15+
{sandboxes.map((sandbox) => (
16+
<SandboxCard
17+
key={sandbox.labId}
18+
title={sandbox.title}
19+
description={sandbox.description}
20+
labId={sandbox.labId}
21+
products={sandbox.products}
22+
onLaunch={sandbox.onLaunch}
23+
/>
24+
))}
25+
</div>
26+
)
27+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: MPL-2.0
4+
*/
5+
6+
export interface Sandbox {
7+
labId: string
8+
title: string
9+
description: string
10+
products: string[]
11+
onLaunch: () => void
12+
}
13+
14+
export interface CollectionSandboxListProps {
15+
sandboxes: Sandbox[]
16+
isOrdered?: boolean
17+
}

src/views/collection-view/index.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* SPDX-License-Identifier: MPL-2.0
44
*/
55

6-
import { TutorialLite as ClientTutorialLite } from 'lib/learn-client/types'
76
import SidebarSidecarLayout from 'layouts/sidebar-sidecar'
87
import {
98
generateProductLandingSidebarNavData,
@@ -12,25 +11,39 @@ import {
1211
import TutorialsSidebar from 'components/tutorials-sidebar'
1312
import { CollectionPageProps } from './server'
1413
import CollectionMeta from './components/collection-meta'
15-
import CollectionTutorialList from './components/collection-tutorial-list'
16-
import { formatTutorialCard } from 'components/tutorial-card/helpers'
1714
import { generateCollectionSidebarNavData } from './helpers/generate-collection-sidebar-nav-data'
1815
import { SignupFormArea } from 'views/certifications/components'
1916
import s from './style.module.css'
17+
import { CollectionSandboxList } from './components/collection-sandbox-list'
18+
import { useInstruqtEmbed } from 'contexts/instruqt-lab'
2019

2120
function CollectionView({
2221
collection,
2322
layoutProps,
2423
product,
2524
}: CollectionPageProps): React.ReactElement {
26-
const { name, description, tutorials, ordered } = collection
25+
const { openLab, setActive } = useInstruqtEmbed()
26+
27+
const handleLabClick = (labId: string) => {
28+
openLab(labId)
29+
setActive(true)
30+
}
2731

32+
const { name, description, tutorials, ordered } = collection
2833
const sidebarNavDataLevels = [
2934
generateTopLevelSidebarNavData(product.name),
3035
generateProductLandingSidebarNavData(product),
3136
generateCollectionSidebarNavData(product, layoutProps.sidebarSections),
3237
]
3338

39+
const sandboxes = tutorials.map((t) => ({
40+
labId: t.handsOnLab?.id || '',
41+
title: t.name,
42+
description: t.description,
43+
products: t.productsUsed.map((p) => p.product.slug),
44+
onLaunch: () => handleLabClick(t.handsOnLab?.id || ''),
45+
}))
46+
3447
return (
3548
<SidebarSidecarLayout
3649
breadcrumbLinks={layoutProps.breadcrumbLinks}
@@ -51,12 +64,7 @@ function CollectionView({
5164
heading={{ text: name, id: collection.id }}
5265
description={description}
5366
/>
54-
<CollectionTutorialList
55-
isOrdered={ordered}
56-
tutorials={tutorials.map((t: ClientTutorialLite) =>
57-
formatTutorialCard(t, collection)
58-
)}
59-
/>
67+
<CollectionSandboxList isOrdered={ordered} sandboxes={sandboxes} />
6068
{layoutProps.isCertificationPrep && (
6169
<SignupFormArea className={s.newsletterSignupArea} />
6270
)}

0 commit comments

Comments
 (0)