Terminal
An implementation of the MacOS terminal. Useful for showcasing a command line interface.
Credits
Shout out to Magic UI for the inspiration. I actually discovered this component while browsing their website.
Getting Started
Add Components
This consists of three components
Terminal
<template>
<Primitive :as :as-child :class="styles({ class: props.class })">
<div class="flex flex-col gap-y-2 border-b border-border p-4">
<div class="flex flex-row gap-x-2">
<div
v-for="(item, i) in buttonColors"
:key="i"
class="size-2 rounded-full"
:class="[item]"
/>
</div>
</div>
<pre class="overflow-auto p-4"><code class="grid gap-y-1 overflow-auto"><slot /></code></pre>
</Primitive>
</template>
<script lang="ts">
import type { PrimitiveProps } from "reka-ui";
import type { HTMLAttributes } from "vue";
const styles = tv({
base: "z-0 h-full max-h-[400px] w-full max-w-lg rounded-lg border border-border bg-background",
});
</script>
<script lang="ts" setup>
const props = withDefaults(
defineProps<
PrimitiveProps & {
class?: HTMLAttributes["class"];
buttonColors?: string[];
}
>(),
{
buttonColors: () => ["bg-red-500", "bg-yellow-500", "bg-green-500"],
}
);
</script>
AnimatedSpan
<template>
<motion.div
:initial="{ opacity: 0, y: -5 }"
:animate="{ opacity: 1, y: 0 }"
:transition="{ duration: 0.3, delay: delay / 1000 }"
:class="styles({ class: props.class })"
>
<slot />
</motion.div>
</template>
<script lang="ts">
import { motion } from "motion-v";
import type { MotionProps } from "motion-v";
import type { PrimitiveProps } from "reka-ui";
import type { HTMLAttributes } from "vue";
export interface AnimatedSpanProps extends Omit<MotionProps, "as" | "asChild">, PrimitiveProps {
class?: HTMLAttributes["class"];
delay?: number;
}
const styles = tv({ base: "grid text-sm font-normal tracking-tight" });
</script>
<script lang="ts" setup>
const props = withDefaults(defineProps<AnimatedSpanProps>(), {
delay: 0,
as: "span",
});
</script>
TypingAnimation
<template>
<Motion ref="elementRef" :class="styles({ class: props.class })">{{ displayedText }}</Motion>
</template>
<script lang="ts">
import type { MotionProps } from "motion-v";
import type { PrimitiveProps } from "reka-ui";
import type { HTMLAttributes } from "vue";
export interface TypingAnimationProps
extends Omit<MotionProps, "as" | "asChild">,
PrimitiveProps {
text?: string;
class?: HTMLAttributes["class"];
duration?: number;
delay?: number;
}
const styles = tv({
base: "text-sm font-normal tracking-tight",
});
</script>
<script lang="ts" setup>
const props = withDefaults(defineProps<TypingAnimationProps>(), {
duration: 60,
delay: 0,
as: "span",
});
if (!props.text) {
createError({
message: "[Terminal - TypingAnimation]: Text prop is required",
fatal: false,
statusCode: 400,
});
}
const displayedText = ref("");
const started = ref(false);
let typingInterval: ReturnType<typeof setInterval> | null = null;
let startTimeout: ReturnType<typeof setTimeout> | null = null;
onMounted(() => {
startTimeout = setTimeout(() => {
started.value = true;
}, props.delay);
});
onUnmounted(() => {
if (startTimeout) clearTimeout(startTimeout);
if (typingInterval) clearInterval(typingInterval);
});
watch(
() => started.value,
(value) => {
if (!value) return;
let i = 0;
typingInterval = setInterval(() => {
const text = props.text ?? "";
if (i < text.length) {
displayedText.value = text.substring(0, i + 1);
i++;
} else {
if (typingInterval) clearInterval(typingInterval);
}
}, props.duration);
}
);
</script>
Anatomy
<UiTerminal>
<UiTerminalTypingAnimation>
<UiTerminalAnimatedSpan>Hello, world!</UiTerminalAnimatedSpan>
<UiTerminalTypingAnimation>UI Thing is awesome!</UiTerminalTypingAnimation>
</UiTerminalTypingAnimation>
</UiTerminal>
Usage
✔ Which Nuxt version are you using? › Nuxt 4✔ Which theme do you want to start with? › Zinc✔ Where is your tailwind.css file located? … app/assets/css/tailwind.css✔ Where is your tailwind.config file located? … tailwind.config.js✔ Where should your components be stored? … app/components/Ui✔ Where should your composables be stored? … app/composables✔ Where should your plugins be stored? … app/plugins✔ Where should your utils be stored? … app/utils✔ Should we just replace component files if they already exist? … yes✔ Would you like to use the default filename when adding components? … yes✔ Which package manager do you use? › NPM