Skip to content

Commit fb28e41

Browse files
committed
fix(org): name the main region on every page and let loading states be announced
A status region with only a label and hidden children announces nothing, and aria-busy holds it back, so each now has text inside and Home announces once. Settings, Search and chat gain the main landmark.
1 parent 3403deb commit fb28e41

8 files changed

Lines changed: 69 additions & 30 deletions

File tree

‎apps/sim/platform/design-system/components/status/query-boundary.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface LoadingRowsProps {
1313
export function LoadingRows({ label, rows = 3 }: LoadingRowsProps) {
1414
return (
1515
<div className={styles.loadingRows} role='status' aria-label={label}>
16+
<span data-visually-hidden>{label}</span>
1617
{Array.from({ length: rows }, (_, index) => (
1718
<div key={index} className={styles.loadingRow} aria-hidden='true'>
1819
<span className={styles.loadingLabel} />

‎apps/sim/platform/embedded/app/(shell)/_components/conversation/landing.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface ConversationLandingProps {
1111
/** A conversation's first view, on the same lines as the Search view beside it. */
1212
export function ConversationLanding({ heading, children }: ConversationLandingProps) {
1313
return (
14-
<div className={styles.landingScroll}>
14+
<main className={styles.landingScroll}>
1515
<div className={styles.landing}>
1616
<Surface>
1717
<h1 className={styles.landingHeading} data-type='T076'>
@@ -20,6 +20,6 @@ export function ConversationLanding({ heading, children }: ConversationLandingPr
2020
</Surface>
2121
<div className={styles.landingField}>{children}</div>
2222
</div>
23-
</div>
23+
</main>
2424
)
2525
}

‎apps/sim/platform/embedded/app/(shell)/_components/conversation/page-conversation.tsx‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,18 @@ function PageConversationContent({
128128
)}
129129
>
130130
{hasChat ? (
131-
<AssistantActionReviewProvider {...reviewProps}>
132-
<MothershipChat
133-
{...threadProps}
134-
className={cn(hostBridge.palette, styles.thread)}
135-
reserveResourceHeader={!header && threadProps.reserveResourceHeader}
136-
SearchConnectionComponent={SearchIntegrationConnection}
137-
composer={composer}
138-
initialScrollBlocked={panel.initialScrollBlocked}
139-
/>
140-
</AssistantActionReviewProvider>
131+
<main className='contents'>
132+
<AssistantActionReviewProvider {...reviewProps}>
133+
<MothershipChat
134+
{...threadProps}
135+
className={cn(hostBridge.palette, styles.thread)}
136+
reserveResourceHeader={!header && threadProps.reserveResourceHeader}
137+
SearchConnectionComponent={SearchIntegrationConnection}
138+
composer={composer}
139+
initialScrollBlocked={panel.initialScrollBlocked}
140+
/>
141+
</AssistantActionReviewProvider>
142+
</main>
141143
) : (
142144
landing?.({ requestMode, composer })
143145
)}

‎apps/sim/platform/embedded/app/(shell)/home/_components/home-dashboard/home-dashboard-skeleton.tsx‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ export function HomeDashboardSkeleton() {
2121
searchAccess.memberScoped &&
2222
((viewer.canConnectSearchIntegrations && connectedAccountsAvailable) || viewer.canUseSearchMcp)
2323
return (
24-
<div className={styles.scroller} aria-busy='true'>
24+
<div className={styles.scroller}>
2525
<div className={styles.dashboard}>
2626
<Surface>
27+
<span role='status' data-visually-hidden>
28+
Loading Home
29+
</span>
2730
<div className={styles.lead}>
2831
<div className={styles.placeholderGreeting} />
2932
<div className={styles.placeholderWordmark} />
@@ -36,17 +39,17 @@ export function HomeDashboardSkeleton() {
3639
</Surface>
3740
<Surface>
3841
<div className={styles.sections}>
39-
{searchAccess.memberScoped && <SkeletonLane label='Loading recents' />}
42+
{searchAccess.memberScoped && <SkeletonLane label='Loading recents' silent />}
4043
{showsGetStarted && <div className={styles.placeholderGetStarted} />}
4144
<div className={styles.columns}>
4245
<div className={styles.column}>
43-
{canBuild && <SkeletonBlock label='Loading activity' />}
44-
<SkeletonBlock label='Loading what needs your attention' />
45-
{canBuild && <WidgetSkeleton label='Loading workspaces' glyph={false} />}
46+
{canBuild && <SkeletonBlock label='Loading activity' silent />}
47+
<SkeletonBlock label='Loading what needs your attention' silent />
48+
{canBuild && <WidgetSkeleton label='Loading workspaces' glyph={false} silent />}
4649
</div>
4750
<div className={styles.column}>
48-
<SkeletonBlock label='Loading email' />
49-
<SkeletonBlock label='Loading your calendar' />
51+
<SkeletonBlock label='Loading email' silent />
52+
<SkeletonBlock label='Loading your calendar' silent />
5053
</div>
5154
</div>
5255
</div>

‎apps/sim/platform/embedded/app/(shell)/home/_components/widget-skeleton/widget-skeleton.tsx‎

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,36 @@ interface WidgetSkeletonProps {
88
rows?: number
99
/** Whether the list's rows lead with a glyph. */
1010
glyph?: boolean
11+
/** A page's skeleton announces itself once, rather than per block. */
12+
silent?: boolean
13+
}
14+
15+
interface SkeletonPlaceholderProps {
16+
label: string
17+
silent?: boolean
18+
}
19+
20+
/** Text a live region can announce, since these shapes say nothing. */
21+
function announcement(label: string, silent: boolean | undefined) {
22+
return silent
23+
? { props: { 'aria-hidden': true as const }, text: null }
24+
: {
25+
props: { role: 'status', 'aria-label': label },
26+
text: <span data-visually-hidden>{label}</span>,
27+
}
1128
}
1229

1330
/** Holds a widget's list at its final size, so the page does not move when the data arrives. */
14-
export function WidgetSkeleton({ label, rows = ROW_COUNT, glyph = true }: WidgetSkeletonProps) {
31+
export function WidgetSkeleton({
32+
label,
33+
rows = ROW_COUNT,
34+
glyph = true,
35+
silent,
36+
}: WidgetSkeletonProps) {
37+
const { props, text } = announcement(label, silent)
1538
return (
16-
<div className={styles.rows} role='status' aria-label={label} aria-busy='true'>
39+
<div className={styles.rows} {...props}>
40+
{text}
1741
{Array.from({ length: rows }, (_, index) => (
1842
<span key={index} className={styles.row}>
1943
{glyph && <i className={styles.glyph} />}
@@ -28,14 +52,21 @@ export function WidgetSkeleton({ label, rows = ROW_COUNT, glyph = true }: Widget
2852
}
2953

3054
/** The same fill for a chart or schedule, whose shape has no rows. */
31-
export function SkeletonBlock({ label }: { label: string }) {
32-
return <div className={styles.block} role='status' aria-label={label} aria-busy='true' />
55+
export function SkeletonBlock({ label, silent }: SkeletonPlaceholderProps) {
56+
const { props, text } = announcement(label, silent)
57+
return (
58+
<div className={styles.block} {...props}>
59+
{text}
60+
</div>
61+
)
3362
}
3463

3564
/** A recents lane holding its height until the history read settles. */
36-
export function SkeletonLane({ label }: { label: string }) {
65+
export function SkeletonLane({ label, silent }: SkeletonPlaceholderProps) {
66+
const { props, text } = announcement(label, silent)
3767
return (
38-
<div className={styles.lane} role='status' aria-label={label} aria-busy='true'>
68+
<div className={styles.lane} {...props}>
69+
{text}
3970
<i />
4071
<i />
4172
<i />

‎apps/sim/platform/embedded/app/(shell)/search/_components/search-surface/search-surface.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ function OrganizationSearchSurfaceContent({ userId, chatId }: OrganizationSearch
199199
</Suspense>
200200
}
201201
>
202-
<div className={styles.body}>
202+
<main className={styles.body}>
203203
<Surface>
204204
{query ? (
205205
<div className={styles.docked}>
@@ -229,7 +229,7 @@ function OrganizationSearchSurfaceContent({ userId, chatId }: OrganizationSearch
229229
</div>
230230
)}
231231
</Surface>
232-
</div>
232+
</main>
233233
</SearchFrame>
234234
)
235235
}

‎apps/sim/platform/embedded/app/(shell)/settings/_components/sections/usage/usage-heatmap.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ export function UsageCalendar({ organizationId, timezone, workspaceId }: UsageCa
142142
{calendar.failed ? (
143143
<FailureNotice message='Could not load daily usage' onRetry={calendar.retry} />
144144
) : !calendar.days ? (
145-
<span className={styles.listPlaceholder} role='status' aria-label='Loading daily usage' />
145+
<span className={styles.listPlaceholder} role='status' aria-label='Loading daily usage'>
146+
<span data-visually-hidden>Loading daily usage</span>
147+
</span>
146148
) : (
147149
<Heatmap days={calendar.days} />
148150
)}

‎apps/sim/platform/embedded/app/(shell)/settings/_components/settings-screen/settings-screen.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface SettingsScreenProps {
4040
/** A section's scrolling page: heading, description, docs link, then its cards. */
4141
export function SettingsScreen({ title, description, docsHref, children }: SettingsScreenProps) {
4242
return (
43-
<div className={styles.body}>
43+
<main className={styles.body}>
4444
<div className={styles.content}>
4545
<header className={styles.heading}>
4646
<div className={styles.title}>
@@ -65,6 +65,6 @@ export function SettingsScreen({ title, description, docsHref, children }: Setti
6565
</header>
6666
{children}
6767
</div>
68-
</div>
68+
</main>
6969
)
7070
}

0 commit comments

Comments
 (0)