summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-10-31 09:45:57 -0500
committerAdam <[email protected]>2025-10-31 09:45:57 -0500
commit0ac943de909fb5a685a608d7894248cdd3a9e129 (patch)
tree6d2e0c0cb1a44de4f53e74a7002622842dab7591 /packages/ui/src
parent485135cf5c4af64d1449aa3cadcdd0aef92201a3 (diff)
downloadopencode-0ac943de909fb5a685a608d7894248cdd3a9e129.tar.gz
opencode-0ac943de909fb5a685a608d7894248cdd3a9e129.zip
wip: desktop work
Diffstat (limited to 'packages/ui/src')
-rw-r--r--packages/ui/src/components/index.ts1
-rw-r--r--packages/ui/src/components/typewriter.css14
-rw-r--r--packages/ui/src/components/typewriter.tsx54
-rw-r--r--packages/ui/src/styles/index.css1
4 files changed, 70 insertions, 0 deletions
diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts
index 115e5f14f..499f2e27f 100644
--- a/packages/ui/src/components/index.ts
+++ b/packages/ui/src/components/index.ts
@@ -20,6 +20,7 @@ export * from "./select-dialog"
export * from "./tabs"
export * from "./basic-tool"
export * from "./tooltip"
+export * from "./typewriter"
export * from "../context/helper"
export * from "../context/shiki"
diff --git a/packages/ui/src/components/typewriter.css b/packages/ui/src/components/typewriter.css
new file mode 100644
index 000000000..e978312a9
--- /dev/null
+++ b/packages/ui/src/components/typewriter.css
@@ -0,0 +1,14 @@
+@keyframes blink {
+ 0%,
+ 50% {
+ opacity: 1;
+ }
+ 51%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+.blinking-cursor {
+ animation: blink 1s step-end infinite;
+}
diff --git a/packages/ui/src/components/typewriter.tsx b/packages/ui/src/components/typewriter.tsx
new file mode 100644
index 000000000..9adb267ad
--- /dev/null
+++ b/packages/ui/src/components/typewriter.tsx
@@ -0,0 +1,54 @@
+import { createEffect, Show, type ValidComponent } from "solid-js"
+import { createStore } from "solid-js/store"
+import { Dynamic } from "solid-js/web"
+
+export const Typewriter = <T extends ValidComponent = "p">(props: {
+ text?: string
+ class?: string
+ as?: T
+}) => {
+ const [store, setStore] = createStore({
+ typing: false,
+ displayed: "",
+ cursor: true,
+ })
+
+ createEffect(() => {
+ const text = props.text
+ if (!text) return
+
+ let i = 0
+ setStore("typing", true)
+ setStore("displayed", "")
+ setStore("cursor", true)
+
+ const getTypingDelay = () => {
+ const random = Math.random()
+ if (random < 0.05) return 150 + Math.random() * 100
+ if (random < 0.15) return 80 + Math.random() * 60
+ return 30 + Math.random() * 50
+ }
+
+ const type = () => {
+ if (i < text.length) {
+ setStore("displayed", text.slice(0, i + 1))
+ i++
+ setTimeout(type, getTypingDelay())
+ } else {
+ setStore("typing", false)
+ setTimeout(() => setStore("cursor", false), 2000)
+ }
+ }
+
+ setTimeout(type, 200)
+ })
+
+ return (
+ <Dynamic component={props.as || "p"} class={props.class}>
+ {store.displayed}
+ <Show when={store.cursor}>
+ <span classList={{ "blinking-cursor": !store.typing }}>│</span>
+ </Show>
+ </Dynamic>
+ )
+}
diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css
index cea5a082d..146d957e2 100644
--- a/packages/ui/src/styles/index.css
+++ b/packages/ui/src/styles/index.css
@@ -25,5 +25,6 @@
@import "../components/select-dialog.css" layer(components);
@import "../components/tabs.css" layer(components);
@import "../components/tooltip.css" layer(components);
+@import "../components/typewriter.css" layer(components);
@import "./utilities.css" layer(utilities);