diff --git a/packages/core/package.json b/packages/core/package.json index 77b0f60..50ffc17 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,6 +44,7 @@ "chalk": "^5.0.1", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", + "cli-spinners": "^2.7.0", "cli-truncate": "^3.1.0", "indent-string": "^5.0.0", "slice-ansi": "^5.0.0", diff --git a/packages/core/src/components/Loading.ts b/packages/core/src/components/Loading.ts new file mode 100644 index 0000000..b8466d7 --- /dev/null +++ b/packages/core/src/components/Loading.ts @@ -0,0 +1,45 @@ +import { + computed, + defineComponent, + h, + inject, + onMounted, + onUnmounted, + onUpdated, + ref, +} from '@vue/runtime-core' +import spinners from 'cli-spinners' +import type { PropType } from '@vue/runtime-core' +import { scheduleUpdateSymbol } from '../injectionSymbols' +import type { SpinnerName } from 'cli-spinners' + +export const TuiLoading = defineComponent({ + props: { + /** + * Type of a loading. + * See [cli-spinners](https://github.com/sindresorhus/cli-spinners) for available spinners. + * + * @default dots + */ + type: String as PropType, + }, + setup(props) { + const spinner = computed(() => spinners[props.type ?? 'dots']) + const frame = ref(0) + let timer: NodeJS.Timer | null = null + const scheduleUpdate = inject(scheduleUpdateSymbol)! + onUpdated(scheduleUpdate) + onMounted(() => { + timer = setInterval(() => { + frame.value = (frame.value + 1) % spinner.value?.frames?.length + }, spinner.value.interval) + }) + onUnmounted(() => { + clearInterval(timer!) + }) + + return () => { + return h('tui:text', spinner.value?.frames[frame.value]) + } + }, +}) diff --git a/packages/core/src/components/TextTransform.ts b/packages/core/src/components/TextTransform.ts index 796828c..485a043 100644 --- a/packages/core/src/components/TextTransform.ts +++ b/packages/core/src/components/TextTransform.ts @@ -1,9 +1,9 @@ import chalk, { ForegroundColor } from 'chalk' import { h, inject, FunctionalComponent } from '@vue/runtime-core' -import { scheduleUpdateSymbol } from '../injectionSymbols' import type { OutputTransformer } from '../renderer/Output' import { defaultStyle, TuiTextProps } from './Text' import { colorize } from '../renderer/textColor' +import { scheduleUpdateSymbol } from '../injectionSymbols' /** * A Text Transforms allows modifying the text before it is written to the stdout while accounting for line breaks and diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 392921c..13664b2 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -5,4 +5,5 @@ export { TuiApp } from './App' export { TuiBox } from './Box' export { TuiLink } from './Link' +export { TuiLoading } from './Loading' // export { default as TuiInput } from './Input.vue' diff --git a/packages/playground/components.d.ts b/packages/playground/components.d.ts index c44c4ad..bfb1e24 100644 --- a/packages/playground/components.d.ts +++ b/packages/playground/components.d.ts @@ -7,8 +7,10 @@ export {} declare module '@vue/runtime-core' { export interface GlobalComponents { + Box: typeof import('vue-termui')['TuiBox'] Div: typeof import('vue-termui')['TuiBox'] Link: typeof import('vue-termui')['TuiLink'] + Loading: typeof import('vue-termui')['TuiLoading'] Text: typeof import('vue-termui')['TuiText'] } } diff --git a/packages/playground/src/LoadingDemo.vue b/packages/playground/src/LoadingDemo.vue new file mode 100644 index 0000000..71bdec7 --- /dev/null +++ b/packages/playground/src/LoadingDemo.vue @@ -0,0 +1,8 @@ + diff --git a/packages/playground/src/main.ts b/packages/playground/src/main.ts index bbaa6b2..8520259 100644 --- a/packages/playground/src/main.ts +++ b/packages/playground/src/main.ts @@ -1,7 +1,8 @@ // import devtools from '@vue/devtools' // import devtools from '@vue/devtools/node' import { createApp } from 'vue-termui' -import App from './Focusables.vue' +// import App from './Focusables.vue' +import App from './LoadingDemo.vue' // import App from './Fragments.vue' // import App from './CenteredDemo.vue' // import App from './App.vue' diff --git a/packages/playground/vite.config.cjs b/packages/playground/vite.config.cjs index 46f9e7c..4b6434d 100644 --- a/packages/playground/vite.config.cjs +++ b/packages/playground/vite.config.cjs @@ -27,6 +27,7 @@ export default defineConfig({ 'yoga-layout-prebuilt', 'is-fullwidth-code-point', 'terminal-link', + 'cli-spinners', 'restore-cursor', ], }, diff --git a/packages/vite-plugin-vue-termui/src/index.ts b/packages/vite-plugin-vue-termui/src/index.ts index 35dce1b..d0c5a2d 100644 --- a/packages/vite-plugin-vue-termui/src/index.ts +++ b/packages/vite-plugin-vue-termui/src/index.ts @@ -116,6 +116,8 @@ export default function VueTermui({ 'wrap-ansi', 'yoga-layout', 'yoga-layout-prebuilt', + 'terminal-link', + 'cli-spinners', ], output: { @@ -169,6 +171,8 @@ export const VueTuiComponents = new Map([ ['a', 'TuiLink'], ['link', 'TuiLink'], + ['loading', 'TuiLoading'], + ['div', 'TuiBox'], ['box', 'TuiBox'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a181d6..775a62c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,7 @@ importers: chalk: ^5.0.1 cli-boxes: ^3.0.0 cli-cursor: ^4.0.0 + cli-spinners: ^2.7.0 cli-truncate: ^3.1.0 indent-string: ^5.0.0 slice-ansi: ^5.0.0 @@ -123,6 +124,7 @@ importers: chalk: 5.0.1 cli-boxes: 3.0.0 cli-cursor: 4.0.0 + cli-spinners: 2.7.0 cli-truncate: 3.1.0 indent-string: 5.0.0 slice-ansi: 5.0.0 @@ -1249,6 +1251,11 @@ packages: restore-cursor: 4.0.0 dev: false + /cli-spinners/2.7.0: + resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} + engines: {node: '>=6'} + dev: false + /cli-truncate/3.1.0: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}