feat(nuxt): allow displaying error state in loading indicator (#27176)

This commit is contained in:
Deth 2024-05-16 11:23:18 -03:00 committed by GitHub
parent c545c1da5b
commit 58423772a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 7 deletions

View File

@ -30,6 +30,7 @@ You can pass custom HTML or components through the loading indicator's default s
## Props ## Props
- `color`: The color of the loading bar. It can be set to `false` to turn off explicit color styling. - `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`). - `height`: Height of the loading bar, in pixels (default `3`).
- `duration`: Duration of the loading bar, in milliseconds (default `2000`). - `duration`: Duration of the loading bar, in milliseconds (default `2000`).
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`). - `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).

View File

@ -26,6 +26,11 @@ It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime)
- **type**: `Ref<boolean>` - **type**: `Ref<boolean>`
- **description**: The loading state - **description**: The loading state
### `error`
- **type**: `Ref<boolean>`
- **description**: The error state
### `progress` ### `progress`
- **type**: `Ref<number>` - **type**: `Ref<number>`
@ -39,7 +44,7 @@ Set `isLoading` to true and start to increase the `progress` value.
### `finish()` ### `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()` ### `clear()`

View File

@ -20,20 +20,24 @@ export default defineComponent({
type: [String, Boolean], type: [String, Boolean],
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)', 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: { estimatedProgress: {
type: Function as unknown as () => (duration: number, elapsed: number) => number, type: Function as unknown as () => (duration: number, elapsed: number) => number,
required: false, required: false,
}, },
}, },
setup (props, { slots, expose }) { setup (props, { slots, expose }) {
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({ const { progress, isLoading, error, start, finish, clear } = useLoadingIndicator({
duration: props.duration, duration: props.duration,
throttle: props.throttle, throttle: props.throttle,
estimatedProgress: props.estimatedProgress, estimatedProgress: props.estimatedProgress,
}) })
expose({ expose({
progress, isLoading, start, finish, clear, progress, isLoading, error, start, finish, clear,
}) })
return () => h('div', { return () => h('div', {
@ -47,7 +51,7 @@ export default defineComponent({
width: 'auto', width: 'auto',
height: `${props.height}px`, height: `${props.height}px`,
opacity: isLoading.value ? 1 : 0, opacity: isLoading.value ? 1 : 0,
background: props.color || undefined, background: error.value ? props.errorColor : props.color || undefined,
backgroundSize: `${(100 / progress.value) * 100}% auto`, backgroundSize: `${(100 / progress.value) * 100}% auto`,
transform: `scaleX(${progress.value}%)`, transform: `scaleX(${progress.value}%)`,
transformOrigin: 'left', transformOrigin: 'left',

View File

@ -23,9 +23,10 @@ export type LoadingIndicator = {
_cleanup: () => void _cleanup: () => void
progress: Ref<number> progress: Ref<number>
isLoading: Ref<boolean> isLoading: Ref<boolean>
error: Ref<boolean>
start: () => void start: () => void
set: (value: number) => void set: (value: number) => void
finish: (opts?: { force?: boolean }) => void finish: (opts?: { force?: boolean, error?: boolean }) => void
clear: () => void clear: () => void
} }
@ -40,6 +41,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
const progress = ref(0) const progress = ref(0)
const isLoading = ref(false) const isLoading = ref(false)
const error = ref(false)
let done = false let done = false
let rafId: number let rafId: number
@ -47,7 +49,10 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
let hideTimeout: number | NodeJS.Timeout let hideTimeout: number | NodeJS.Timeout
let resetTimeout: number | NodeJS.Timeout let resetTimeout: number | NodeJS.Timeout
const start = () => set(0) const start = () => {
error.value = false
set(0)
}
function set (at = 0) { function set (at = 0) {
if (nuxtApp.isHydrating) { if (nuxtApp.isHydrating) {
@ -76,11 +81,14 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
} }
} }
function finish (opts: { force?: boolean } = {}) { function finish (opts: { force?: boolean, error?: boolean } = {}) {
progress.value = 100 progress.value = 100
done = true done = true
clear() clear()
_clearTimeouts() _clearTimeouts()
if (opts.error) {
error.value = true
}
if (opts.force) { if (opts.force) {
progress.value = 0 progress.value = 0
isLoading.value = false isLoading.value = false
@ -145,6 +153,7 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
_cleanup, _cleanup,
progress: computed(() => progress.value), progress: computed(() => progress.value),
isLoading: computed(() => isLoading.value), isLoading: computed(() => isLoading.value),
error: computed(() => error.value),
start, start,
set, set,
finish, finish,

View File

@ -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', () => { describe.skipIf(process.env.TEST_MANIFEST === 'manifest-off')('app manifests', () => {
it('getAppManifest', async () => { it('getAppManifest', async () => {
const manifest = await getAppManifest() const manifest = await getAppManifest()