mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-02 21:59:00 +02:00
Update loading spinner status (#28177)
* Update test for the loading spinner component * Create a story for the loading spinner component * Move role and use CSS for spacing instead * Add a classname prop * Reuse LoadingSpinner * Use OLSpinner instead of Spinner * Use data-testid since status role was moved * Wait for journals to load * Use `isLoading` prop instead and fix the button's height * Use `isLoading` prop instead * Use LoadingSpinner instead and remove spacing * Update test for the loading spinner component * Use `isLoading` prop instead * Add aria-describedby to layout button for processing state * Replace `spinner` to `ol-spinner` * Scope status * Remove redundant `div.loading` --------- Co-authored-by: Antoine Clausse <antoine.clausse@overleaf.com> GitOrigin-RevId: 8f43b991f8f458b2abd5a4661913ac9b972d892a
This commit is contained in:
@@ -210,7 +210,7 @@ export function openFile(fileName: string, waitFor: string) {
|
||||
.click({ force: true })
|
||||
|
||||
// wait until we've switched to the selected file
|
||||
cy.findByText('Loading…').should('not.exist')
|
||||
cy.findByRole('status').should('not.exist')
|
||||
cy.findByText(waitFor)
|
||||
}
|
||||
|
||||
@@ -230,7 +230,10 @@ export function createNewFile() {
|
||||
.click({ force: true })
|
||||
|
||||
// wait until we've switched to the newly created empty file
|
||||
cy.findByText('Loading…').should('not.exist')
|
||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
||||
cy.findByRole('status').should('not.exist')
|
||||
cy.get('.cm-line').should('have.length', 1)
|
||||
})
|
||||
cy.get('.cm-line').should('have.length', 1)
|
||||
|
||||
return fileName
|
||||
|
||||
+2
-9
@@ -2,8 +2,8 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLFormSelect from '@/shared/components/ol/ol-form-select'
|
||||
import { ChangeEventHandler, useCallback, useEffect, useRef } from 'react'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { useEditorLeftMenuContext } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
type PossibleValue = string | number | boolean
|
||||
|
||||
@@ -82,14 +82,7 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
|
||||
>
|
||||
<OLFormLabel>{label}</OLFormLabel>
|
||||
{loading ? (
|
||||
<p className="mb-0">
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
</p>
|
||||
<OLSpinner size="sm" />
|
||||
) : (
|
||||
<OLFormSelect
|
||||
size="sm"
|
||||
|
||||
+7
-21
@@ -1,5 +1,4 @@
|
||||
import { memo, useCallback, forwardRef } from 'react'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
@@ -18,6 +17,7 @@ import useEventListener from '../../../shared/hooks/use-event-listener'
|
||||
import { DetachRole } from '@/shared/context/detach-context'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
const isActiveDropdownItem = ({
|
||||
iconFor,
|
||||
@@ -171,24 +171,18 @@ export const LayoutDropdownButtonUi = ({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
{processing && (
|
||||
<div aria-live="assertive" className="visually-hidden">
|
||||
{t('layout_processing')}
|
||||
</div>
|
||||
)}
|
||||
<div aria-live="assertive" className="visually-hidden" id="layout-status">
|
||||
{processing ? t('layout_processing') : ''}
|
||||
</div>
|
||||
<Dropdown className="toolbar-item layout-dropdown" align="end">
|
||||
<DropdownToggle
|
||||
aria-describedby={processing ? 'layout-status' : undefined}
|
||||
id="layout-dropdown-btn"
|
||||
className="btn-full-height"
|
||||
as={LayoutDropdownToggleButton}
|
||||
>
|
||||
{processing ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
<OLSpinner size="sm" />
|
||||
) : (
|
||||
<MaterialIcon type="dock_to_right" className="align-middle" />
|
||||
)}
|
||||
@@ -257,15 +251,7 @@ export const LayoutDropdownButtonUi = ({
|
||||
detachIsLinked ? (
|
||||
'check'
|
||||
) : (
|
||||
<span className="spinner-container">
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
<span className="visually-hidden">{t('loading')}</span>
|
||||
</span>
|
||||
<OLSpinner size="sm" />
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
+2
-11
@@ -4,7 +4,6 @@ import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { canUseNewEditorViaPrimaryFeatureFlag } from '../ide-redesign/utils/new-editor-utils'
|
||||
import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
|
||||
const TryNewEditorButton = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -27,17 +26,9 @@ const TryNewEditorButton = () => {
|
||||
onClick={onClick}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
isLoading={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
) : (
|
||||
t('try_the_new_editor')
|
||||
)}
|
||||
{t('try_the_new_editor')}
|
||||
</OLButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
+7
-43
@@ -24,7 +24,7 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLForm from '@/shared/components/ol/ol-form'
|
||||
import OLFormSelect from '@/shared/components/ol/ol-form-select'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
export default function FileTreeImportFromProject() {
|
||||
const { t } = useTranslation()
|
||||
@@ -209,20 +209,8 @@ function SelectProject({
|
||||
|
||||
return (
|
||||
<OLFormGroup controlId="project-select">
|
||||
<OLFormLabel>{t('select_a_project')}</OLFormLabel>
|
||||
|
||||
{loading && (
|
||||
<span>
|
||||
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<OLFormLabel className="me-1">{t('select_a_project')}</OLFormLabel>
|
||||
{loading && <OLSpinner size="sm" />}
|
||||
<OLFormSelect
|
||||
disabled={!data}
|
||||
value={selectedProject ? selectedProject._id : ''}
|
||||
@@ -275,20 +263,8 @@ function SelectProjectOutputFile({
|
||||
className="row-spaced-small"
|
||||
controlId="project-output-file-select"
|
||||
>
|
||||
<OLFormLabel>{t('select_an_output_file')}</OLFormLabel>
|
||||
|
||||
{loading && (
|
||||
<span>
|
||||
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<OLFormLabel className="me-1">{t('select_an_output_file')}</OLFormLabel>
|
||||
{loading && <OLSpinner size="sm" />}
|
||||
<OLFormSelect
|
||||
disabled={!data}
|
||||
value={selectedProjectOutputFile?.path || ''}
|
||||
@@ -334,20 +310,8 @@ function SelectProjectEntity({
|
||||
|
||||
return (
|
||||
<OLFormGroup className="row-spaced-small" controlId="project-entity-select">
|
||||
<OLFormLabel>{t('select_a_file')}</OLFormLabel>
|
||||
|
||||
{loading && (
|
||||
<span>
|
||||
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<OLFormLabel className="me-1">{t('select_a_file')}</OLFormLabel>
|
||||
{loading && <OLSpinner size="sm" />}
|
||||
<OLFormSelect
|
||||
disabled={!data}
|
||||
value={selectedProjectEntity?.path || ''}
|
||||
|
||||
+2
-11
@@ -19,8 +19,8 @@ import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import DropdownListItem from '@/shared/components/dropdown/dropdown-list-item'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
type resendInviteResponse = {
|
||||
success: boolean
|
||||
@@ -322,16 +322,7 @@ function MenuItemButton({
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={onClick}
|
||||
leadingIcon={
|
||||
isLoading ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
leadingIcon={isLoading ? <OLSpinner size="sm" /> : null}
|
||||
data-testid={dataTestId}
|
||||
variant={variant}
|
||||
>
|
||||
|
||||
+2
-7
@@ -2,7 +2,7 @@ import OLFormSelect from '@/shared/components/ol/ol-form-select'
|
||||
import { ChangeEventHandler, useCallback } from 'react'
|
||||
import Setting from './setting'
|
||||
import classNames from 'classnames'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
type PossibleValue = string | number | boolean
|
||||
|
||||
@@ -64,12 +64,7 @@ export default function DropdownSetting<T extends PossibleValue = string>({
|
||||
return (
|
||||
<Setting controlId={id} label={label} description={description}>
|
||||
{loading ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
<OLSpinner size="sm" />
|
||||
) : (
|
||||
<OLFormSelect
|
||||
id={id}
|
||||
|
||||
+2
-4
@@ -11,8 +11,8 @@ import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||
import { DetachRole } from '@/shared/context/detach-context'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf'
|
||||
|
||||
@@ -65,9 +65,7 @@ const LayoutDropdownItem = ({
|
||||
}) => {
|
||||
let trailingIcon: string | React.ReactNode | null = null
|
||||
if (processing) {
|
||||
trailingIcon = (
|
||||
<Spinner animation="border" aria-hidden="true" size="sm" role="status" />
|
||||
)
|
||||
trailingIcon = <OLSpinner size="sm" />
|
||||
} else if (active) {
|
||||
trailingIcon = 'check'
|
||||
}
|
||||
|
||||
+2
-9
@@ -9,7 +9,7 @@ import PdfHybridDownloadButton from './pdf-hybrid-download-button'
|
||||
import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button'
|
||||
import PdfOrphanRefreshButton from './pdf-orphan-refresh-button'
|
||||
import { DetachedSynctexControl } from './detach-synctex-control'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
|
||||
const ORPHAN_UI_TIMEOUT_MS = 5000
|
||||
|
||||
@@ -91,14 +91,7 @@ function PdfPreviewHybridToolbarConnectingInner() {
|
||||
return (
|
||||
<>
|
||||
<div className="toolbar-pdf-orphan">
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
|
||||
{t('tab_connecting')}…
|
||||
<LoadingSpinner size="sm" loadingText={`${t('tab_connecting')}…`} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { Placement } from 'react-bootstrap/types'
|
||||
import useSynctex from '../hooks/use-synctex'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
const GoToCodeButton = memo(function GoToCodeButton({
|
||||
syncToCode,
|
||||
@@ -27,9 +27,7 @@ const GoToCodeButton = memo(function GoToCodeButton({
|
||||
|
||||
let buttonIcon = null
|
||||
if (syncToCodeInFlight) {
|
||||
buttonIcon = (
|
||||
<Spinner animation="border" aria-hidden="true" size="sm" role="status" />
|
||||
)
|
||||
buttonIcon = <OLSpinner size="sm" />
|
||||
} else if (!isDetachLayout) {
|
||||
buttonIcon = (
|
||||
<MaterialIcon type="arrow_left_alt" className="synctex-control-icon" />
|
||||
@@ -89,9 +87,7 @@ const GoToPdfButton = memo(function GoToPdfButton({
|
||||
|
||||
let buttonIcon = null
|
||||
if (syncToPdfInFlight) {
|
||||
buttonIcon = (
|
||||
<Spinner animation="border" aria-hidden="true" size="sm" role="status" />
|
||||
)
|
||||
buttonIcon = <OLSpinner size="sm" />
|
||||
} else if (!isDetachLayout) {
|
||||
buttonIcon = (
|
||||
<MaterialIcon type="arrow_right_alt" className="synctex-control-icon" />
|
||||
|
||||
+3
-7
@@ -1,5 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
@@ -18,6 +17,7 @@ import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
|
||||
import RenameProjectButton from '../table/cells/action-buttons/rename-project-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
type ActionDropdownProps = {
|
||||
project: Project
|
||||
@@ -89,13 +89,9 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
|
||||
}}
|
||||
leadingIcon={
|
||||
pendingCompile ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
as="span"
|
||||
className="dropdown-item-leading-icon spinner"
|
||||
<OLSpinner
|
||||
size="sm"
|
||||
role="status"
|
||||
className="dropdown-item-leading-icon spinner"
|
||||
/>
|
||||
) : (
|
||||
'picture_as_pdf'
|
||||
|
||||
@@ -9,7 +9,7 @@ import EmailsHeader from './emails/header'
|
||||
import EmailsRow from './emails/row'
|
||||
import AddEmail from './emails/add-email'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
|
||||
function EmailsSectionContent() {
|
||||
const { t } = useTranslation()
|
||||
@@ -62,7 +62,7 @@ function EmailsSectionContent() {
|
||||
{isInitializing ? (
|
||||
<div className="affiliations-table-row-highlighted">
|
||||
<div className="affiliations-table-cell text-center">
|
||||
<OLSpinner size="sm" /> {t('loading')}...
|
||||
<LoadingSpinner size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
+2
-10
@@ -7,9 +7,9 @@ import classnames from 'classnames'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import Tag from '@/shared/components/tag'
|
||||
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { Contact } from '../utils/types'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
export type ContactItem = {
|
||||
email: string
|
||||
@@ -167,15 +167,7 @@ export default function SelectCollaborators({
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
|
||||
<OLFormLabel className="small" {...getLabelProps()}>
|
||||
{t('add_email_address')}
|
||||
{loading && (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
className="ms-2"
|
||||
/>
|
||||
)}
|
||||
{loading && <OLSpinner size="sm" className="ms-2" />}
|
||||
</OLFormLabel>
|
||||
|
||||
<div className="host">
|
||||
|
||||
+2
-11
@@ -11,7 +11,7 @@ import OLModal, {
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
const ReadOnlyTokenLink = lazy(() =>
|
||||
import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
|
||||
@@ -67,16 +67,7 @@ export default function ShareProjectModalContent({
|
||||
</OLModalBody>
|
||||
|
||||
<OLModalFooter>
|
||||
<div className="me-auto">
|
||||
{inFlight && (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="me-auto">{inFlight && <OLSpinner size="sm" />}</div>
|
||||
|
||||
<ClickableElementEnhancer
|
||||
onClick={cancel}
|
||||
|
||||
+2
-11
@@ -11,8 +11,8 @@ import OLModal, {
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { ProjectMember } from '@/shared/context/types/project-metadata'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
export default function TransferOwnershipModal({
|
||||
member,
|
||||
@@ -68,16 +68,7 @@ export default function TransferOwnershipModal({
|
||||
)}
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<div className="me-auto">
|
||||
{inflight && (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="me-auto">{inflight && <OLSpinner size="sm" />}</div>
|
||||
<OLButton variant="secondary" onClick={cancel} disabled={inflight}>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { WordCountData } from '@/features/word-count-modal/components/word-count-data'
|
||||
import { WordCountLoading } from '@/features/word-count-modal/components/word-count-loading'
|
||||
import { WordCountError } from '@/features/word-count-modal/components/word-count-error'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import useAbortController from '@/shared/hooks/use-abort-controller'
|
||||
@@ -14,6 +13,7 @@ import { isMainFile } from '@/features/pdf-preview/util/editor-files'
|
||||
import { countWordsInFile } from '@/features/word-count-modal/utils/count-words-in-file'
|
||||
import { createSegmenters } from '@/features/word-count-modal/utils/segmenters'
|
||||
import { WordCountsClient } from './word-counts-client'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
|
||||
export const WordCountClient: FC = () => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -107,7 +107,7 @@ export const WordCountClient: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && !error && <WordCountLoading />}
|
||||
{loading && !error && <LoadingSpinner />}
|
||||
{error && <WordCountError />}
|
||||
{data && <WordCountsClient data={data} />}
|
||||
</>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Spinner } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const WordCountLoading = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="loading">
|
||||
<Spinner animation="border" aria-hidden="true" size="sm" role="status" />
|
||||
|
||||
{t('loading')}…
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { ServerWordCountData } from '@/features/word-count-modal/components/word-count-data'
|
||||
import { WordCountLoading } from '@/features/word-count-modal/components/word-count-loading'
|
||||
import { WordCountError } from '@/features/word-count-modal/components/word-count-error'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import { useLocalCompileContext } from '@/shared/context/local-compile-context'
|
||||
@@ -8,6 +7,7 @@ import useAbortController from '@/shared/hooks/use-abort-controller'
|
||||
import { getJSON } from '@/infrastructure/fetch-json'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { WordCounts } from '@/features/word-count-modal/components/word-counts'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
|
||||
export const WordCountServer: FC = () => {
|
||||
const { projectId } = useProjectContext()
|
||||
@@ -40,7 +40,7 @@ export const WordCountServer: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && !error && <WordCountLoading />}
|
||||
{loading && !error && <LoadingSpinner />}
|
||||
{error && <WordCountError />}
|
||||
{data && <WordCounts data={data} />}
|
||||
</>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { forwardRef } from 'react'
|
||||
import { Button as BS5Button, Spinner } from 'react-bootstrap'
|
||||
import { Button as BS5Button } from 'react-bootstrap'
|
||||
import type { ButtonProps } from '@/shared/components/types/button-props'
|
||||
import classNames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLSpinner from '../ol/ol-spinner'
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
@@ -56,13 +57,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
>
|
||||
{isLoading && (
|
||||
<span className="spinner-container">
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
as="span"
|
||||
className={loadingSpinnerClassName}
|
||||
role="status"
|
||||
/>
|
||||
<OLSpinner size="sm" className={loadingSpinnerClassName} />
|
||||
<span className="visually-hidden">
|
||||
{loadingLabel ?? t('loading')}
|
||||
</span>
|
||||
|
||||
@@ -42,6 +42,7 @@ function LoadingSpinner({
|
||||
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
className={classNames(
|
||||
'loading',
|
||||
className,
|
||||
@@ -49,7 +50,6 @@ function LoadingSpinner({
|
||||
)}
|
||||
>
|
||||
<OLSpinner size={size} />
|
||||
|
||||
{loadingText || `${t('loading')}…`}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,13 +2,20 @@ import { Spinner } from 'react-bootstrap'
|
||||
|
||||
export type OLSpinnerSize = 'sm' | 'lg'
|
||||
|
||||
function OLSpinner({ size = 'sm' }: { size: OLSpinnerSize }) {
|
||||
function OLSpinner({
|
||||
size = 'sm',
|
||||
className,
|
||||
}: {
|
||||
size?: OLSpinnerSize
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<Spinner
|
||||
size={size === 'sm' ? 'sm' : undefined}
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
role="status"
|
||||
className={className}
|
||||
data-testid="ol-spinner"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import {
|
||||
import classNames from 'classnames'
|
||||
import { useSelect } from 'downshift'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Form, Spinner } from 'react-bootstrap'
|
||||
import { Form } from 'react-bootstrap'
|
||||
import FormControl from '@/shared/components/form/form-control'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { DropdownItem } from '@/shared/components/dropdown/dropdown-menu'
|
||||
import OLSpinner from './ol/ol-spinner'
|
||||
|
||||
export type SelectProps<T> = {
|
||||
// The items rendered as dropdown options.
|
||||
@@ -152,17 +153,7 @@ export const Select = <T,>({
|
||||
{optionalLabel && (
|
||||
<span className="fw-normal">({t('optional')})</span>
|
||||
)}{' '}
|
||||
{loading && (
|
||||
<span data-testid="spinner">
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
as="span"
|
||||
role="status"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{loading && <OLSpinner size="sm" />}
|
||||
</Form.Label>
|
||||
) : null}
|
||||
<FormControl
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import LoadingSpinner, {
|
||||
FullSizeLoadingSpinner,
|
||||
} from '@/shared/components/loading-spinner'
|
||||
|
||||
type Story = StoryObj<typeof LoadingSpinner>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
loadingText: 'Loading content...',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithDelay: Story = {
|
||||
args: {
|
||||
delay: 500,
|
||||
loadingText: 'This will appear after a 500ms delay...',
|
||||
},
|
||||
}
|
||||
|
||||
export const FullSize: StoryObj<typeof FullSizeLoadingSpinner> = {
|
||||
render: args => <FullSizeLoadingSpinner {...args} />,
|
||||
args: {
|
||||
loadingText: 'Loading entire section...',
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
|
||||
const meta: Meta<typeof LoadingSpinner> = {
|
||||
title: 'Shared / Components / Loading Spinner',
|
||||
component: LoadingSpinner,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
argTypes: {
|
||||
delay: {
|
||||
control: 'select',
|
||||
options: [0, 500],
|
||||
},
|
||||
size: {
|
||||
control: 'radio',
|
||||
options: ['lg', 'sm'],
|
||||
},
|
||||
},
|
||||
args: {
|
||||
size: 'sm',
|
||||
delay: 0,
|
||||
},
|
||||
render: args => <LoadingSpinner {...args} />,
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -5,6 +5,7 @@
|
||||
.spinner-border {
|
||||
// Ensure the thickness of the spinner is independent of the font size of its container
|
||||
font-size: var(--font-size-03);
|
||||
margin-right: var(--spacing-02);
|
||||
}
|
||||
|
||||
// Adjust the small spinner to be 25% larger than Bootstrap's default in each dimension
|
||||
|
||||
@@ -538,6 +538,5 @@
|
||||
|
||||
max-height: 39px;
|
||||
font-size: var(--font-size-01);
|
||||
line-height: var(--line-height-01);
|
||||
margin-right: var(--spacing-04);
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ describe('<PdfSynctexControls/>', function () {
|
||||
'not.be.disabled'
|
||||
)
|
||||
|
||||
cy.findByRole('status', { hidden: true }).should('not.exist')
|
||||
cy.findByTestId('ol-spinner').should('not.exist')
|
||||
|
||||
cy.wrap(null).then(() => {
|
||||
testDetachChannel.postMessage({
|
||||
@@ -448,7 +448,7 @@ describe('<PdfSynctexControls/>', function () {
|
||||
'be.disabled'
|
||||
)
|
||||
|
||||
cy.findByRole('status', { hidden: true }).should('have.length', 1)
|
||||
cy.findByTestId('ol-spinner').should('have.length', 1)
|
||||
|
||||
cy.wrap(null).then(() => {
|
||||
testDetachChannel.postMessage({
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('<Select />', function () {
|
||||
describe('initial rendering', function () {
|
||||
it('renders default text', function () {
|
||||
render({ defaultText: 'Choose an item' })
|
||||
cy.findByTestId('spinner').should('not.exist')
|
||||
cy.findByTestId('ol-spinner').should('not.exist')
|
||||
cy.findByRole('combobox').should('have.value', 'Choose an item')
|
||||
})
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('<Select />', function () {
|
||||
label: 'test label',
|
||||
loading: true,
|
||||
})
|
||||
cy.findByTestId('spinner')
|
||||
cy.findByTestId('ol-spinner')
|
||||
})
|
||||
|
||||
it('does not render a spinner while loading if there is no label', function () {
|
||||
@@ -110,7 +110,7 @@ describe('<Select />', function () {
|
||||
defaultText: 'Choose an item',
|
||||
loading: true,
|
||||
})
|
||||
cy.findByTestId('spinner').should('not.exist')
|
||||
cy.findByTestId('ol-spinner').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user