Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/branded-loading-indicator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/app': patch
'@shopify/cli-kit': patch
---

Replace task and app dev spinners with a branded Shopify loading indicator.
9 changes: 4 additions & 5 deletions docs/cli-kit/ui-kit/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- [`Text` vs `Box`](#text-vs-box)
- [Utility components](#utility-components)
- [`TokenizedText`](#tokenizedtext)
- [`TextAnimation`](#textanimation)
- [`LoadingIndicator`](#loadingindicator)
- [Helpful tips](#helpful-tips)
- [Handling user input](#handling-user-input)
- [Components that deal with async functions](#components-that-deal-with-async-functions)
Expand Down Expand Up @@ -166,11 +166,10 @@ add a new interface named `ItalicToken` in the `TokenizedText` and decide how it
In this example we would use `inline`. But before you go ahead and add a new token, consider if all the users of UI kit
might need this new token or not. If the answer is no, then a simple regular component will suffice.

#### `TextAnimation`
#### `LoadingIndicator`

At the moment this component simply animates text with a rainbow effect, however it can be extended to support more animations.
If you wish to do so you can take a look at how [chalk-animation](https://github.com/bokub/chalk-animation/blob/master/index.js)
implemented animations and take inspiration from there.
This component renders the branded loading indicator used by task-based UI components. `LoadingBar` renders it only in TTY
environments so captured and redirected output remains static.

## Helpful tips

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ describe('DevSessionUI', () => {
devSessionStatusManager.updateStatus(initialStatus)
})

test('renders the branded loading indicator for loading status messages', async () => {
devSessionStatusManager.updateStatus({
statusMessage: {message: 'Preparing dev preview', type: 'loading'},
})

const renderInstance = render(
<DevSessionUI
processes={[]}
abortController={new AbortController()}
devSessionStatusManager={devSessionStatusManager}
shopFqdn="mystore.myshopify.com"
onAbort={onAbort}
/>,
)

await waitForContent(renderInstance, 'Preparing dev preview')

expect(unstyled(renderInstance.lastFrame()!)).toMatch(/S[> ] Preparing dev preview \.\.\./)

renderInstance.unmount()
})

test('renders a stream of concurrent outputs from sub-processes, shortcuts and URLs', async () => {
// Given
let backendPromiseResolve: () => void
Expand Down
34 changes: 13 additions & 21 deletions packages/app/src/cli/services/dev/ui/components/DevSessionUI.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {Spinner} from './Spinner.js'
import {TabPanel, Tab, TabShortcut} from './TabPanel.js'
import metadata from '../../../../metadata.js'
import {
DevSessionStatus,
DevSessionStatusManager,
DevSessionStatusMessageType,
} from '../../processes/dev-session/dev-session-status-manager.js'
import {DevSessionStatus, DevSessionStatusManager} from '../../processes/dev-session/dev-session-status-manager.js'
import {MAX_EXTENSION_HANDLE_LENGTH} from '../../../../models/extensions/schemas.js'
import {buildDevConsoleURL} from '../../../../utilities/app/app-url.js'
import {OutputProcess} from '@shopify/cli-kit/node/output'
import {Alert, ConcurrentOutput, Link, TabularData} from '@shopify/cli-kit/node/ui/components'
import {Alert, ConcurrentOutput, Link, LoadingIndicator, TabularData} from '@shopify/cli-kit/node/ui/components'
import {useAbortSignal} from '@shopify/cli-kit/node/ui/hooks'
import React, {FunctionComponent, useEffect, useMemo, useState} from 'react'
import {AbortController, AbortSignal} from '@shopify/cli-kit/node/abort'
Expand All @@ -26,6 +21,16 @@ interface DevStatusShortcut extends TabShortcut {
url?: string
}

const StatusMessage = ({message, type}: NonNullable<DevSessionStatus['statusMessage']>) => {
if (type === 'loading') return <LoadingIndicator title={message} />

return (
<Text>
{type === 'success' ? '✅' : '❌'} {message}
</Text>
)
}

interface DevSesionUIProps {
processes: OutputProcess[]
abortController: AbortController
Expand Down Expand Up @@ -103,17 +108,6 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
{isActive: Boolean(canUseShortcuts)},
)

const getStatusIndicator = (type: DevSessionStatusMessageType) => {
switch (type) {
case 'loading':
return <Spinner />
case 'success':
return '✅'
case 'error':
return '❌'
}
}

const devStatusShortcuts: DevStatusShortcut[] = [
{
key: 'p',
Expand Down Expand Up @@ -170,9 +164,7 @@ const DevSessionUI: FunctionComponent<DevSesionUIProps> = ({
content: (
<>
{status.statusMessage && (
<Text>
{getStatusIndicator(status.statusMessage.type)} {status.statusMessage.message}
</Text>
<StatusMessage message={status.statusMessage.message} type={status.statusMessage.type} />
)}
{canUseShortcuts && activeShortcuts.length > 0 && (
<Box marginTop={1} flexDirection="column">
Expand Down
18 changes: 0 additions & 18 deletions packages/app/src/cli/services/dev/ui/components/Spinner.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions packages/cli-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@
"find-up": "6.3.0",
"form-data": "4.0.6",
"fs-extra": "11.1.0",
"gradient-string": "2.0.2",
"graphql": "16.14.2",
"graphql-request": "6.1.0",
"h3": "1.15.11",
Expand Down Expand Up @@ -161,7 +160,6 @@
"@types/archiver": "5.3.2",
"@types/diff": "^5.2.3",
"@types/fs-extra": "9.0.13",
"@types/gradient-string": "^1.1.2",
"@types/lodash": "4.17.24",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/cli-kit/src/private/node/output.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ describe('logging during an Ink task', () => {

try {
await vi.waitFor(() => expect(writes.join('')).toContain('Preparing the result'))
expect(writes.join('')).toContain('▀')
expect(unstyled(writes.join(''))).toContain('S>')
const beforeLog = writes.length

log('Prepared an item')

// Clear both UI lines and their trailing newline before writing the log.
// Clear the UI line and its trailing newline before writing the log.
const logWrites = writes.slice(beforeLog)
const messageIndex = logWrites.findIndex((write) => write.includes('Prepared an item'))
expect(messageIndex).toBeGreaterThan(0)
expect(logWrites[messageIndex - 1]).toBe(ansiEscapes.eraseLines(3))
expect(logWrites[messageIndex - 1]).toBe(ansiEscapes.eraseLines(2))
expect(unstyled(logWrites[messageIndex]!)).toBe('Prepared an item\n')
expect(logWrites[messageIndex + 1]).toContain('Preparing the result')

Expand All @@ -68,7 +68,7 @@ describe('logging during an Ink task', () => {
await instance.waitUntilExit()

const cleanup = writes.slice(afterLog).join('')
expect(cleanup).toContain(ansiEscapes.eraseLines(3))
expect(cleanup).toContain(ansiEscapes.eraseLines(2))
expect(unstyled(cleanup).trim()).toBe('')
} finally {
finishTask()
Expand Down
Loading
Loading