Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('SandboxDropdown', () => {

// Click to open
fireEvent.click(button)
expect(screen.getByText('HashiCorp Sandboxes')).toBeInTheDocument()
expect(screen.getByText('Vault Sandboxes')).toBeInTheDocument()

// Click to close
fireEvent.click(button)
Expand All @@ -75,7 +75,7 @@ describe('SandboxDropdown', () => {

// Open dropdown
fireEvent.click(button)
expect(screen.getByText('HashiCorp Sandboxes')).toBeInTheDocument()
expect(screen.getByText('Vault Sandboxes')).toBeInTheDocument()

// Press escape
fireEvent.keyDown(button, { key: 'Escape' })
Expand All @@ -91,7 +91,7 @@ describe('SandboxDropdown', () => {

// Open dropdown
fireEvent.click(button)
expect(screen.getByText('HashiCorp Sandboxes')).toBeInTheDocument()
expect(screen.getByText('Vault Sandboxes')).toBeInTheDocument()

// Click outside
fireEvent.mouseDown(document.body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { KeyboardEvent, useRef, useState } from 'react'
import { useId } from '@react-aria/utils'
import { IconChevronDown16 } from '@hashicorp/flight-icons/svg-react/chevron-down-16'
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
import { useRouter } from 'next/router'
import { useCurrentProduct } from 'contexts'
import { useInstruqtEmbed } from 'contexts/instruqt-lab'
Expand All @@ -23,6 +22,7 @@ import s from './sandbox-dropdown.module.css'
import { SandboxLab } from 'types/sandbox'
import { ProductSlug } from 'types/products'
import { buildLabIdWithConfig } from 'lib/build-instruqt-url'
import { useTheme } from 'next-themes'

interface SandboxDropdownProps {
ariaLabel: string
Expand All @@ -47,6 +47,8 @@ const SandboxDropdown = ({ ariaLabel, label }: SandboxDropdownProps) => {
lab.products.includes(currentProduct.slug)
)

const isOnSandboxPage = router.query?.productSlug === currentProduct?.slug

// Handles closing the menu if there is a click outside of it and it is open.
useOnClickOutside([menuRef], () => setIsOpen(false), isOpen)

Expand Down Expand Up @@ -146,8 +148,23 @@ const SandboxDropdown = ({ ariaLabel, label }: SandboxDropdownProps) => {
setIsOpen(false)
}

const { theme, systemTheme } = useTheme()
const isLightTheme =
theme === 'light' || (theme === 'system' && systemTheme === 'light')
return (
<div className={s.root} onMouseLeave={handleMouseLeave} ref={menuRef}>
<div
className={`${s.root} ${isLightTheme ? 'theme-light' : 'theme-dark'}`}
style={
isLightTheme
? ({
'--intro-bg': '#fafafa',
'--intro-border': '#656a7633',
} as React.CSSProperties)
: undefined
}
onMouseLeave={handleMouseLeave}
ref={menuRef}
>
<div className={s.activatorWrapper}>
<button
aria-controls={menuId}
Expand Down Expand Up @@ -177,42 +194,40 @@ const SandboxDropdown = ({ ariaLabel, label }: SandboxDropdownProps) => {
>
<div className={s.dropdownContainerInner}>
{/* Introduction to Sandboxes */}
<div className={s.introSection}>
<Text
asElement="p"
className={s.sectionTitle}
size={200}
weight="semibold"
>
HashiCorp Sandboxes
</Text>

<button
className={`${s.introSandboxItem} ${s.sandboxItem}`}
onClick={navigateToSandboxPage}
onKeyDown={handleKeyDown}
aria-label={`Go to ${currentProduct.name} Sandboxes`}
aria-current={isOnSandboxPage ? 'page' : undefined}
>
<div className={s.introSandboxRow}>
<ProductIcon
productSlug={currentProduct.slug as ProductSlug}
size={24}
className={s.productIcon}
/>
<Text
asElement="span"
className={`${s.sectionTitle} ${s.introSandboxTitle}`}
size={200}
weight="semibold"
>
{currentProduct.name} Sandboxes
</Text>
</div>
<Text
asElement="p"
className={s.introText}
asElement="span"
className={`${s.introText} ${s.introSandboxText}`}
size={100}
weight="regular"
>
Interactive environments where you can experiment with HashiCorp
products without any installation or setup. Perfect for learning,
testing, and exploring features in a safe sandbox.
</Text>

{/* Learn more link */}
<a
href={`/${currentProduct.slug}/sandbox`}
className={s.learnMoreLink}
onClick={navigateToSandboxPage}
onKeyDown={handleKeyDown}
>
<Text asElement="span" size={100} weight="medium">
Learn more about {currentProduct.name} Sandboxes
</Text>
<IconArrowRight16 className={s.learnMoreIcon} />
</a>
</div>

{/* Divider */}
<hr className={s.divider} />
</button>

{/* Available Product Sandboxes Section */}
<Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
}

.dropdownContainerInner {
padding: 16px;
padding: 12px;
}

.introSection {
Expand All @@ -81,6 +81,8 @@
.sectionTitle {
color: var(--token-color-foreground-strong);
font-size: 14px;
margin-top: 1rem;
margin-left: 1rem;
}

.introText {
Expand Down Expand Up @@ -120,7 +122,6 @@
color: var(--token-color-foreground-primary);
display: flex;
gap: 8px;
padding: 12px;
text-decoration: none;
width: 100%;
background: transparent;
Expand All @@ -136,7 +137,9 @@

.content {
flex: 1;
min-width: 0; /* Allows text truncation */
min-width: 0;
margin-left: 1rem;
padding: 12px 0;
}

.titleRow {
Expand All @@ -148,7 +151,7 @@

.title {
color: var(--token-color-foreground-primary);
font-weight: 500;
font-weight: 600;
transition: color 0.2s;
font-size: 14px;
}
Expand Down Expand Up @@ -236,3 +239,42 @@
.accordionIconOpen {
transform: rotate(90deg);
}

.introSandboxItem {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0 12px;
background: transparent;
text-align: left;
cursor: pointer;
background-color: var(--intro-bg, transparent);
border: 1px solid var(--intro-border, transparent);
transition: background-color 0.2s;
}
.introSandboxItem[aria-disabled='true'] {
cursor: default;
pointer-events: none;
}
.introSandboxRow {
display: flex;
align-items: flex-end;
width: 100%;
}
.introSandboxTitle {
margin-left: 12px;
}
.introSandboxText {
text-align: left;
flex: 1;
margin-left: 2.5rem;
}

.theme-light .introSandboxItem {
background-color: #fafafa;

&:hover {
background-color: #f1f2f3 !important;
}
}
11 changes: 5 additions & 6 deletions src/contexts/instruqt-lab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,19 +220,20 @@ function InstruqtProvider({ children }: InstruqtProviderProps): JSX.Element {

// Extract the base lab ID from the full lab ID (which may contain tokens)
let baseLabId = newLabId.split('?')[0] // Remove query parameters first

if (baseLabId.includes('/')) {
baseLabId = baseLabId.split('/').pop() || baseLabId
}

// Validate that the lab ID exists in current configuration
const labExists = SANDBOX_CONFIG.labs?.some((lab) => {
const trackName = lab.instruqtTrack?.split('/').pop() || lab.labId
const trackName =
lab.instruqtTrack?.split('/').pop()?.split('?')[0] || lab.labId
return (
lab.labId === newLabId ||
lab.labId === baseLabId ||
trackName === baseLabId ||
lab.instruqtTrack === newLabId
lab.instruqtTrack === newLabId ||
lab.instruqtTrack?.split('?')[0] === baseLabId ||
(lab.scenario && newLabId.includes(lab.scenario))
)
})

Expand All @@ -253,8 +254,6 @@ function InstruqtProvider({ children }: InstruqtProviderProps): JSX.Element {
if (newLabId !== labId || !active) {
setLabId(newLabId)
setActive(true)

// Track sandbox open event immediately
trackSandboxEvent(SANDBOX_EVENT.SANDBOX_OPEN, {
labId: newLabId,
page: router.asPath,
Expand Down
30 changes: 28 additions & 2 deletions src/pages/[productSlug]/sandbox/sandbox.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,30 @@
.sandboxCard {
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
width: 80vw;
max-width: 420px;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
&:hover {
transform: translateY(-2px);
box-shadow: var(--token-elevation-high-box-shadow);
}

@media (max-width: 900px) {
max-width: 100%;
width: 100%;
max-width: none;
}
}

.card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}

.otherSandboxCard {
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
Expand Down Expand Up @@ -156,3 +167,18 @@
line-height: var(--token-typography-body-200-line-height);
}
}

.cardsFlexContainer {
display: flex;
flex-wrap: wrap;
gap: 2rem;
justify-content: flex-start;
}

.sandboxGrid li,
.sandboxGrid > div,
.sandboxGrid > div > div {
height: 100%;
width: 100%;
box-sizing: border-box;
}
Loading