[web] add disconnected state to offline indicator (#35378)

GitOrigin-RevId: 692dc16cee738a8b05353717044a4063c3dd8e8a
This commit is contained in:
Kristina
2026-09-17 11:28:30 +00:00
committed by Copybot
parent e562fc97f1
commit dfd816801d
2 changed files with 87 additions and 11 deletions
@@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next'
import MaterialIcon from '@/shared/components/material-icon'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
export function OfflineIndicatorContent() {
const { t } = useTranslation()
@@ -18,22 +19,49 @@ export function OfflineIndicatorContent() {
)
}
export function DisconnectedIndicatorContent() {
const { t } = useTranslation()
return (
<div className="ide-redesign-toolbar-offline-indicator">
<MaterialIcon type="wifi_off" />
<span>{t('disconnected')}</span>
</div>
)
}
function OfflineIndicatorVisual({ outOfSync }: { outOfSync: boolean }) {
if (outOfSync) {
return <DisconnectedIndicatorContent />
}
return <OfflineIndicatorContent />
}
function OfflineIndicatorStatus({ outOfSync }: { outOfSync: boolean }) {
const { t } = useTranslation()
if (outOfSync) {
return <span>{t('disconnected')}</span>
}
return (
<>
<span>{t('you_re_offline')}</span>
<span>{t('changes_saved_in_browser_sync_when_online')}</span>
</>
)
}
export default function OfflineIndicator({
isOffline,
}: {
isOffline: boolean
}) {
const { t } = useTranslation()
const { outOfSync } = useIdeReactContext()
// The live region div below must stay mounted at all times so screen
// readers announce its contents when the offline state changes.
return (
<>
{isOffline && <OfflineIndicatorContent />}
{isOffline && <OfflineIndicatorVisual outOfSync={outOfSync} />}
<div className="visually-hidden" role="status">
{isOffline && (
<>
<span>{t('you_re_offline')}</span>
<span>{t('changes_saved_in_browser_sync_when_online')}</span>
</>
)}
{isOffline && <OfflineIndicatorStatus outOfSync={outOfSync} />}
</div>
</>
)
@@ -1,19 +1,47 @@
import OfflineIndicator from '@/features/ide-react/components/toolbar/offline-indicator'
import { IdeReactContext } from '@/features/ide-react/context/ide-react-context'
const mount = ({
isOffline = false,
outOfSync = false,
}: {
isOffline?: boolean
outOfSync?: boolean
} = {}) => {
cy.mount(
<IdeReactContext.Provider
value={{
projectId: '',
eventEmitter: {} as any,
startedFreeTrial: false,
setStartedFreeTrial: () => {},
reportError: () => {},
projectJoined: true,
permissionsLevel: 'readAndWrite',
setPermissionsLevel: () => {},
outOfSync,
setOutOfSync: () => {},
}}
>
<OfflineIndicator isOffline={isOffline} />
</IdeReactContext.Provider>
)
}
describe('<OfflineIndicator />', function () {
it('renders an empty live region when online', function () {
cy.mount(<OfflineIndicator isOffline={false} />)
mount({ isOffline: false })
cy.findByText('You’re offline').should('not.exist')
cy.findByRole('status').should('be.empty')
})
it('shows the offline indicator when offline', function () {
cy.mount(<OfflineIndicator isOffline />)
mount({ isOffline: true })
cy.findAllByText('You’re offline').first().should('be.visible')
})
it('announces the offline status to screen readers via a live region', function () {
cy.mount(<OfflineIndicator isOffline />)
mount({ isOffline: true })
cy.findByRole('status').within(() => {
cy.findByText('You’re offline').should('exist')
cy.findByText(
@@ -21,4 +49,24 @@ describe('<OfflineIndicator />', function () {
).should('exist')
})
})
it('shows "Disconnected" without a tooltip when out of sync', function () {
mount({ isOffline: true, outOfSync: true })
cy.findAllByText('Disconnected')
.first()
.should('be.visible')
.trigger('mouseover')
cy.get('#tooltip-offline-indicator-tooltip').should('not.exist')
cy.findByText('You’re offline').should('not.exist')
})
it('announces "Disconnected" to screen readers when out of sync', function () {
mount({ isOffline: true, outOfSync: true })
cy.findByRole('status').within(() => {
cy.findByText('Disconnected').should('exist')
cy.findByText(
'Your changes are saved in the browser. We’ll sync your work when you’re back online.'
).should('not.exist')
})
})
})