diff --git a/docs/3.api/1.components/5.nuxt-loading-indicator.md b/docs/3.api/1.components/5.nuxt-loading-indicator.md index e22b582422..ad2f6936cb 100644 --- a/docs/3.api/1.components/5.nuxt-loading-indicator.md +++ b/docs/3.api/1.components/5.nuxt-loading-indicator.md @@ -30,6 +30,7 @@ You can pass custom HTML or components through the loading indicator's default s ## Props - `color`: The color of the loading bar. It can be set to `false` to turn off explicit color styling. +- `errorColor`: The color of the loading bar when `error` is set to `true`. - `height`: Height of the loading bar, in pixels (default `3`). - `duration`: Duration of the loading bar, in milliseconds (default `2000`). - `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`). diff --git a/docs/3.api/2.composables/use-loading-indicator.md b/docs/3.api/2.composables/use-loading-indicator.md index 2e4a5bb97b..357c260bbd 100644 --- a/docs/3.api/2.composables/use-loading-indicator.md +++ b/docs/3.api/2.composables/use-loading-indicator.md @@ -26,6 +26,11 @@ It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime) - **type**: `Ref` - **description**: The loading state +### `error` + +- **type**: `Ref` +- **description**: The error state + ### `progress` - **type**: `Ref` @@ -39,7 +44,7 @@ Set `isLoading` to true and start to increase the `progress` value. ### `finish()` -Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset. +Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset, and `{ error: true }` to change the loading bar color and set the error property to true. ### `clear()` diff --git a/packages/nuxt/src/app/components/nuxt-loading-indicator.ts b/packages/nuxt/src/app/components/nuxt-loading-indicator.ts index 2b51dd6584..a0273a8428 100644 --- a/packages/nuxt/src/app/components/nuxt-loading-indicator.ts +++ b/packages/nuxt/src/app/components/nuxt-loading-indicator.ts @@ -20,20 +20,24 @@ export default defineComponent({ type: [String, Boolean], default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)', }, + errorColor: { + type: String, + default: 'repeating-linear-gradient(to right,#f87171 0%,#ef4444 100%)', + }, estimatedProgress: { type: Function as unknown as () => (duration: number, elapsed: number) => number, required: false, }, }, setup (props, { slots, expose }) { - const { progress, isLoading, start, finish, clear } = useLoadingIndicator({ + const { progress, isLoading, error, start, finish, clear } = useLoadingIndicator({ duration: props.duration, throttle: props.throttle, estimatedProgress: props.estimatedProgress, }) expose({ - progress, isLoading, start, finish, clear, + progress, isLoading, error, start, finish, clear, }) return () => h('div', { @@ -47,7 +51,7 @@ export default defineComponent({ width: 'auto', height: `${props.height}px`, opacity: isLoading.value ? 1 : 0, - background: props.color || undefined, + background: error.value ? props.errorColor : props.color || undefined, backgroundSize: `${(100 / progress.value) * 100}% auto`, transform: `scaleX(${progress.value}%)`, transformOrigin: 'left', diff --git a/packages/nuxt/src/app/composables/loading-indicator.ts b/packages/nuxt/src/app/composables/loading-indicator.ts index 5bae3c98b5..b3c0a86455 100644 --- a/packages/nuxt/src/app/composables/loading-indicator.ts +++ b/packages/nuxt/src/app/composables/loading-indicator.ts @@ -23,9 +23,10 @@ export type LoadingIndicator = { _cleanup: () => void progress: Ref isLoading: Ref + error: Ref start: () => void set: (value: number) => void - finish: (opts?: { force?: boolean }) => void + finish: (opts?: { force?: boolean, error?: boolean }) => void clear: () => void } @@ -40,6 +41,7 @@ function createLoadingIndicator (opts: Partial = {}) { const nuxtApp = useNuxtApp() const progress = ref(0) const isLoading = ref(false) + const error = ref(false) let done = false let rafId: number @@ -47,7 +49,10 @@ function createLoadingIndicator (opts: Partial = {}) { let hideTimeout: number | NodeJS.Timeout let resetTimeout: number | NodeJS.Timeout - const start = () => set(0) + const start = () => { + error.value = false + set(0) + } function set (at = 0) { if (nuxtApp.isHydrating) { @@ -76,11 +81,14 @@ function createLoadingIndicator (opts: Partial = {}) { } } - function finish (opts: { force?: boolean } = {}) { + function finish (opts: { force?: boolean, error?: boolean } = {}) { progress.value = 100 done = true clear() _clearTimeouts() + if (opts.error) { + error.value = true + } if (opts.force) { progress.value = 0 isLoading.value = false @@ -145,6 +153,7 @@ function createLoadingIndicator (opts: Partial = {}) { _cleanup, progress: computed(() => progress.value), isLoading: computed(() => isLoading.value), + error: computed(() => error.value), start, set, finish, diff --git a/test/nuxt/composables.test.ts b/test/nuxt/composables.test.ts index 5c20e9a038..9ee12c8638 100644 --- a/test/nuxt/composables.test.ts +++ b/test/nuxt/composables.test.ts @@ -515,6 +515,22 @@ describe('loading state', () => { }) }) +describe('loading state', () => { + it('expect error from loading state to be changed by finish({ error: true })', async () => { + vi.stubGlobal('setTimeout', vi.fn((cb: Function) => cb())) + const nuxtApp = useNuxtApp() + const { error, start, finish } = useLoadingIndicator() + expect(error.value).toBeFalsy() + await nuxtApp.callHook('page:loading:start') + start() + finish({ error: true }) + expect(error.value).toBeTruthy() + start() + expect(error.value).toBeFalsy() + finish() + }) +}) + describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => { it('getAppManifest', async () => { const manifest = await getAppManifest()