Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/component-adapter/component-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@
| **textAlign** | `"start" \| "center" \| "end"` | No | Text alignment within the heading |
| **children** | `React.ReactNode` | No | Content to be displayed inside the heading |
| **className** | `string` | No | - |
| **id** | `string` | No | - |

## LinkProps

Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/company-onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ test.describe('CompanyOnboardingFlow', () => {
const buttonText = await taxpayerButton.textContent()
if (buttonText?.includes('Select')) {
await taxpayerButton.click()
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()
}
}

Expand Down
8 changes: 4 additions & 4 deletions e2e/tests/dismissal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test.describe('DismissalFlow', () => {

const payPeriodSelect = page.getByRole('button', { name: /pay period/i })
await payPeriodSelect.click()
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()

const continueButton = page.getByRole('button', { name: /continue/i })
await expect(continueButton).toBeEnabled()
Expand All @@ -71,7 +71,7 @@ test.describe('DismissalFlow', () => {
const payPeriodSelect = page.getByRole('button', { name: /pay period/i })
await payPeriodSelect.click()

const options = page.getByRole('option')
const options = page.getByRole('listbox').getByRole('option')
const optionCount = await options.count()
expect(optionCount).toBeGreaterThan(0)

Expand Down Expand Up @@ -150,7 +150,7 @@ test.describe('DismissalFlow', () => {

const payPeriodSelect = page.getByRole('button', { name: /pay period/i })
await payPeriodSelect.click()
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()

await page.getByRole('button', { name: /continue/i }).click()
await waitForLoadingComplete(page)
Expand Down Expand Up @@ -211,7 +211,7 @@ test.describe('DismissalFlow', () => {

const payPeriodSelect = page.getByRole('button', { name: /pay period/i })
await payPeriodSelect.click()
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()

await page.getByRole('button', { name: /continue/i }).click()
await waitForLoadingComplete(page)
Expand Down
20 changes: 10 additions & 10 deletions e2e/tests/employee-onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test.describe('EmployeeOnboardingFlow', () => {
let hasWorkAddress = false
if (await workAddressButton.isVisible().catch(() => false)) {
await workAddressButton.click()
const firstOption = page.getByRole('option').first()
const firstOption = page.getByRole('listbox').getByRole('option').first()
hasWorkAddress = await firstOption
.waitFor({ timeout: 10000 })
.then(() => true)
Expand All @@ -59,7 +59,7 @@ test.describe('EmployeeOnboardingFlow', () => {
await page.getByLabel('Street 1').fill('123 Test St')
await page.getByLabel(/city/i).fill('San Francisco')
await page.getByLabel('State').click()
await page.getByRole('option', { name: 'California' }).click()
await page.getByRole('listbox').getByRole('option', { name: 'California' }).click()
const zipField = page.getByLabel(/zip/i)
await zipField.clear()
await zipField.fill('94105')
Expand Down Expand Up @@ -98,8 +98,8 @@ test.describe('EmployeeOnboardingFlow', () => {
const buttonText = await employeeTypeButton.textContent()
if (buttonText?.includes('Select')) {
await employeeTypeButton.click()
await page.getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('listbox').getByRole('option').first().click()
}
}

Expand All @@ -117,11 +117,11 @@ test.describe('EmployeeOnboardingFlow', () => {
const buttonText = await perButton.textContent()
if (!buttonText?.includes('Year')) {
await perButton.click()
const yearOption = page.getByRole('option', { name: /year/i })
const yearOption = page.getByRole('listbox').getByRole('option', { name: /year/i })
if (await yearOption.isVisible().catch(() => false)) {
await yearOption.click()
} else {
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()
}
}
}
Expand All @@ -136,8 +136,8 @@ test.describe('EmployeeOnboardingFlow', () => {
const buttonText = await filingStatusButton.textContent()
if (buttonText?.includes('Select')) {
await filingStatusButton.click()
await page.getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('listbox').getByRole('option').first().click()
}
}

Expand All @@ -152,8 +152,8 @@ test.describe('EmployeeOnboardingFlow', () => {
const buttonText = await stateFilingStatus.textContent()
if (buttonText?.includes('Select')) {
await stateFilingStatus.click()
await page.getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().waitFor({ timeout: 5000 })
await page.getByRole('listbox').getByRole('option').first().click()
}
}

Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/employee-self-onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ test.describe('EmployeeSelfOnboardingFlow', () => {
await streetField.fill('123 Test Street')
await page.getByLabel(/city/i).fill('San Francisco')
await page.getByRole('button', { name: /state/i }).click()
await page.getByRole('option').first().click()
await page.getByRole('listbox').getByRole('option').first().click()
await page.getByLabel(/zip/i).fill('94105')
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.root {
display: flex;
flex-direction: column;
gap: toRem(32);
width: 100%;
}

.backButton {
align-self: flex-start;
}

.pageHeader {
display: flex;
flex-wrap: wrap;
gap: toRem(16) toRem(20);
align-items: flex-start;
width: 100%;
}

.titleGroup {
display: flex;
flex-direction: column;
gap: toRem(4);
flex: 1 0 0;
min-width: toRem(320);
}
59 changes: 59 additions & 0 deletions src/components/Common/DetailViewLayout/DetailViewLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useId } from 'react'
import classNames from 'classnames'
import type { DetailViewLayoutProps } from './DetailViewLayoutTypes'
import styles from './DetailViewLayout.module.scss'
import CaretLeftIcon from '@/assets/icons/caret-left.svg?react'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
import { ActionsLayout } from '@/components/Common/ActionsLayout'

export function DetailViewLayout({
title,
subtitle,
onBack,
backLabel,
actions,
tabs,
selectedTabId,
onTabChange,
children,
className,
}: DetailViewLayoutProps) {
const { Button, Heading, Text, Tabs } = useComponentContext()
const headingId = useId()

return (
<section className={classNames(styles.root, className)} aria-labelledby={headingId}>
{onBack && (
<Button
variant="tertiary"
className={styles.backButton}
icon={<CaretLeftIcon aria-hidden="true" />}
onClick={onBack}
>
{backLabel}
</Button>
)}

<div className={styles.pageHeader}>
<div className={styles.titleGroup}>
<Heading as="h2" styledAs="h2" id={headingId}>
{title}
</Heading>
{subtitle && <Text variant="supporting">{subtitle}</Text>}
</div>
{actions && <ActionsLayout>{actions}</ActionsLayout>}
</div>

{tabs !== undefined ? (
<Tabs
tabs={tabs}
selectedId={selectedTabId}
onSelectionChange={onTabChange}
aria-label={title}
/>
) : (
children
)}
</section>
)
}
41 changes: 41 additions & 0 deletions src/components/Common/DetailViewLayout/DetailViewLayoutTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ReactNode } from 'react'
import type { TabProps } from '@/components/Common/UI/Tabs/TabsTypes'

type BackNavigation =
| { onBack: () => void; backLabel: string }
| { onBack?: never; backLabel?: never }

type TabbedContent =
| {
tabs: TabProps[]
selectedTabId: string
onTabChange: (id: string) => void
children?: never
}
| {
tabs?: never
selectedTabId?: never
onTabChange?: never
children: ReactNode
}

type BaseDetailViewLayoutProps = {
/**
* Page title displayed as a heading
*/
title: string
/**
* Optional subtitle displayed below the title
*/
subtitle?: string
/**
* Action buttons rendered in the page header (right-aligned on desktop)
*/
actions?: ReactNode
/**
* Optional CSS class name applied to the root element
*/
className?: string
}

export type DetailViewLayoutProps = BaseDetailViewLayoutProps & BackNavigation & TabbedContent
2 changes: 2 additions & 0 deletions src/components/Common/DetailViewLayout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DetailViewLayout } from './DetailViewLayout'
export type { DetailViewLayoutProps } from './DetailViewLayoutTypes'
2 changes: 2 additions & 0 deletions src/components/Common/UI/Heading/Heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ export const Heading = ({
textAlign,
className,
children,
id,
}: HeadingProps) => {
const levelStyles = styledAs ?? Component

return (
<Component
id={id}
className={classNames(
className,
styles.root,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Common/UI/Heading/HeadingTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { HTMLAttributes, ReactNode } from 'react'

export interface HeadingProps extends Pick<HTMLAttributes<HTMLHeadingElement>, 'className'> {
export interface HeadingProps extends Pick<HTMLAttributes<HTMLHeadingElement>, 'className' | 'id'> {
/**
* The HTML heading element to render (h1-h6)
*/
Expand Down
1 change: 1 addition & 0 deletions src/components/Common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export { SwitchField } from './Fields/SwitchField'
export { MultiSelectComboBoxField } from './Fields/MultiSelectComboBoxField'
export { VisuallyHidden } from './VisuallyHidden'
export { HamburgerMenu } from './HamburgerMenu'
export { DetailViewLayout } from './DetailViewLayout'
Loading