Skip to content

Commit f386137

Browse files
committed
chore: refactoring ui hooks
1 parent c797b60 commit f386137

16 files changed

Lines changed: 123 additions & 129 deletions

bun.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app/src/pages/session/session-timeline-header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { createEffect, createMemo, on, onCleanup, Show } from "solid-js"
22
import { createStore, produce } from "solid-js/store"
33
import { useNavigate, useParams } from "@solidjs/router"
44
import { Button } from "@opencode-ai/ui/button"
5+
import { useReducedMotion } from "@opencode-ai/ui/hooks"
56
import { IconButton } from "@opencode-ai/ui/icon-button"
67
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
78
import { Dialog } from "@opencode-ai/ui/dialog"
8-
import { prefersReducedMotion } from "@opencode-ai/ui/hooks"
99
import { InlineInput } from "@opencode-ai/ui/inline-input"
1010
import { animate, type AnimationPlaybackControls, clearFadeStyles, FAST_SPRING } from "@opencode-ai/ui/motion"
1111
import { showToast } from "@opencode-ai/ui/toast"
@@ -32,7 +32,7 @@ export function SessionTimelineHeader(props: {
3232
const sync = useSync()
3333
const dialog = useDialog()
3434
const language = useLanguage()
35-
const reduce = prefersReducedMotion
35+
const reduce = useReducedMotion()
3636

3737
const [title, setTitle] = createStore({
3838
draft: "",

packages/ui/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@
4848
"@pierre/diffs": "catalog:",
4949
"@shikijs/transformers": "3.9.2",
5050
"@solid-primitives/bounds": "0.1.3",
51+
"@solid-primitives/lifecycle": "0.1.2",
5152
"@solid-primitives/media": "2.3.3",
53+
"@solid-primitives/page-visibility": "2.1.1",
5254
"@solid-primitives/resize-observer": "2.1.3",
55+
"@solid-primitives/rootless": "1.5.2",
5356
"@solidjs/meta": "catalog:",
5457
"@solidjs/router": "catalog:",
5558
"dompurify": "3.3.1",

packages/ui/src/components/context-tool-results.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createMemo, createSignal, For, onMount } from "solid-js"
22
import type { ToolPart } from "@opencode-ai/sdk/v2"
33
import { getFilename } from "@opencode-ai/util/path"
4+
import { useReducedMotion } from "../hooks/use-reduced-motion"
45
import { useI18n } from "../context/i18n"
5-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
66
import { ToolCall } from "./basic-tool"
77
import { ToolStatusTitle } from "./tool-status-title"
88
import { AnimatedCountList } from "./tool-count-summary"
@@ -149,10 +149,10 @@ export function ContextToolExpandedList(props: { parts: ToolPart[]; expanded: bo
149149
}
150150

151151
export function ContextToolRollingResults(props: { parts: ToolPart[]; pending: boolean }) {
152+
const reduce = useReducedMotion()
152153
const wiped = new Set<string>()
153154
const [mounted, setMounted] = createSignal(false)
154155
onMount(() => setMounted(true))
155-
const reduce = prefersReducedMotion
156156
const show = () => mounted() && props.pending
157157
const opacity = useSpring(() => (show() ? 1 : 0), GROW_SPRING)
158158
const blur = useSpring(() => (show() ? 0 : 2), GROW_SPRING)

packages/ui/src/components/grow-box.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createEffect, on, type JSX, onMount, onCleanup } from "solid-js"
2+
import { useReducedMotion } from "../hooks/use-reduced-motion"
23
import { animate, tunableSpringValue, type AnimationPlaybackControls, GROW_SPRING, type SpringConfig } from "./motion"
3-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
44

55
export interface GrowBoxProps {
66
children: JSX.Element
@@ -49,7 +49,7 @@ export interface GrowBoxProps {
4949
* Used for timeline turns, assistant part groups, and user messages.
5050
*/
5151
export function GrowBox(props: GrowBoxProps) {
52-
const reduce = prefersReducedMotion
52+
const reduce = useReducedMotion()
5353
const spring = () => props.spring ?? GROW_SPRING
5454
const toggleSpring = () => props.toggleSpring ?? spring()
5555
let mode: "mount" | "toggle" = "mount"
@@ -293,6 +293,18 @@ export function GrowBox(props: GrowBoxProps) {
293293
offChange()
294294
})
295295

296+
if (watch()) {
297+
observer = new ResizeObserver(() => {
298+
if (!open()) return
299+
if (resizeFrame !== undefined) return
300+
resizeFrame = requestAnimationFrame(() => {
301+
resizeFrame = undefined
302+
setHeight("mount")
303+
})
304+
})
305+
observer.observe(body)
306+
}
307+
296308
if (!animated()) {
297309
setInstant(open())
298310
return
@@ -318,17 +330,6 @@ export function GrowBox(props: GrowBoxProps) {
318330
if (grow()) setHeight("mount")
319331
})
320332
}
321-
if (watch()) {
322-
observer = new ResizeObserver(() => {
323-
if (!open()) return
324-
if (resizeFrame !== undefined) return
325-
resizeFrame = requestAnimationFrame(() => {
326-
resizeFrame = undefined
327-
setHeight("mount")
328-
})
329-
})
330-
observer.observe(body)
331-
}
332333
})
333334

334335
createEffect(
@@ -402,7 +403,12 @@ export function GrowBox(props: GrowBoxProps) {
402403
ref={root}
403404
data-slot={props.slot}
404405
class={props.class}
405-
style={{ transform: "translateZ(0)", position: "relative" }}
406+
style={{
407+
transform: "translateZ(0)",
408+
position: "relative",
409+
height: open() ? undefined : "0px",
410+
overflow: open() ? undefined : "clip",
411+
}}
406412
>
407413
<div ref={body} style={{ "padding-top": gap() > 0 ? `${gap()}px` : undefined }}>
408414
{props.children}

packages/ui/src/components/message-part.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { usePageVisibility } from "@solid-primitives/page-visibility"
12
import { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js"
23
import stripAnsi from "strip-ansi"
34
import { createStore } from "solid-js/store"
@@ -254,8 +255,6 @@ function urls(text: string | undefined) {
254255
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
255256
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
256257

257-
import { pageVisible } from "../hooks/use-page-visible"
258-
259258
function createGroupOpenState() {
260259
const [state, setState] = createStore<Record<string, boolean>>({})
261260
const read = (key?: string, collapse?: boolean) => {
@@ -277,6 +276,7 @@ function createGroupOpenState() {
277276
function shouldCollapseGroup(
278277
statuses: (string | undefined)[],
279278
opts: { afterTool?: boolean; groupTail?: boolean; working?: boolean },
279+
pageVisible: () => boolean,
280280
) {
281281
if (opts.afterTool) return true
282282
if (opts.groupTail === false) return true
@@ -363,6 +363,7 @@ export function AssistantParts(props: {
363363
}) {
364364
const data = useData()
365365
const emptyParts: PartType[] = []
366+
const pageVisible = usePageVisibility()
366367
const groupState = createGroupOpenState()
367368
const grouped = createMemo(() => {
368369
const keys: string[] = []
@@ -485,11 +486,15 @@ export function AssistantParts(props: {
485486
groupTail?: boolean,
486487
group?: { part: ToolPart; message: AssistantMessage }[],
487488
) =>
488-
shouldCollapseGroup(group?.map((item) => item.part.state.status) ?? [], {
489-
afterTool,
490-
groupTail,
491-
working: props.working,
492-
})
489+
shouldCollapseGroup(
490+
group?.map((item) => item.part.state.status) ?? [],
491+
{
492+
afterTool,
493+
groupTail,
494+
working: props.working,
495+
},
496+
pageVisible,
497+
)
493498
const value = ctx()
494499
if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
495500
const entry = part()

packages/ui/src/components/motion-spring.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { attachSpring, motionValue } from "motion"
22
import type { SpringOptions } from "motion"
33
import { createEffect, createSignal, onCleanup } from "solid-js"
4-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
4+
import { useReducedMotion } from "../hooks/use-reduced-motion"
55

66
type Opt = Pick<SpringOptions, "visualDuration" | "bounce" | "stiffness" | "damping" | "mass" | "velocity">
77
const eq = (a: Opt | undefined, b: Opt | undefined) =>
@@ -14,7 +14,7 @@ const eq = (a: Opt | undefined, b: Opt | undefined) =>
1414

1515
export function useSpring(target: () => number, options?: Opt | (() => Opt)) {
1616
const read = () => (typeof options === "function" ? options() : options)
17-
const reduce = prefersReducedMotion
17+
const reduce = useReducedMotion()
1818
const [value, setValue] = createSignal(target())
1919
const source = motionValue(value())
2020
const spring = motionValue(value())

packages/ui/src/components/rolling-results.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { For, Show, batch, createEffect, createMemo, createSignal, on, onCleanup, onMount, type JSX } from "solid-js"
2+
import { useReducedMotion } from "../hooks/use-reduced-motion"
23
import { animate, clearMaskStyles, GROW_SPRING, type AnimationPlaybackControls, type SpringConfig } from "./motion"
3-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
44

55
export type RollingResultsProps<T> = {
66
items: T[]
@@ -27,8 +27,7 @@ export function RollingResults<T>(props: RollingResultsProps<T>) {
2727
let shift: AnimationPlaybackControls | undefined
2828
let resize: AnimationPlaybackControls | undefined
2929
let edgeFade: AnimationPlaybackControls | undefined
30-
31-
const reducedMotion = prefersReducedMotion
30+
const reduce = useReducedMotion()
3231

3332
const rows = createMemo(() => Math.max(1, Math.round(props.rows ?? 3)))
3433
const rowHeight = createMemo(() => Math.max(16, Math.round(props.rowHeight ?? 22)))
@@ -54,7 +53,7 @@ export function RollingResults<T>(props: RollingResultsProps<T>) {
5453
return count() - rendered().length
5554
})
5655
const open = createMemo(() => props.open !== false)
57-
const active = createMemo(() => (props.animate !== false || props.spring !== undefined) && !reducedMotion())
56+
const active = createMemo(() => (props.animate !== false || props.spring !== undefined) && !reduce())
5857
const noFade = () => props.noFadeOnCollapse === true
5958
const overflowing = createMemo(() => count() > rows())
6059
const shown = createMemo(() => Math.min(rows(), count()))

packages/ui/src/components/shell-rolling-results.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
22
import stripAnsi from "strip-ansi"
33
import type { ToolPart } from "@opencode-ai/sdk/v2"
4-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
4+
import { useReducedMotion } from "../hooks/use-reduced-motion"
55
import { useI18n } from "../context/i18n"
66
import { RollingResults } from "./rolling-results"
77
import { Icon } from "./icon"
@@ -178,6 +178,7 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
178178

179179
export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }) {
180180
const i18n = useI18n()
181+
const reduce = useReducedMotion()
181182
const wiped = new Set<string>()
182183
const [mounted, setMounted] = createSignal(false)
183184
const [userToggled, setUserToggled] = createSignal(false)
@@ -208,7 +209,6 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
208209
if (typeof value === "string") return value
209210
return ""
210211
})
211-
const reduce = prefersReducedMotion
212212
const skip = () => reduce() || props.animate === false
213213
const opacity = useSpring(() => (mounted() ? 1 : 0), GROW_SPRING)
214214
const blur = useSpring(() => (mounted() ? 0 : 2), GROW_SPRING)

packages/ui/src/components/text-reveal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
2+
import { useReducedMotion } from "../hooks/use-reduced-motion"
23
import {
34
animate,
45
type AnimationPlaybackControls,
@@ -7,7 +8,6 @@ import {
78
GROW_SPRING,
89
WIPE_MASK,
910
} from "./motion"
10-
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
1111

1212
const px = (value: number | string | undefined, fallback: number) => {
1313
if (typeof value === "number") return `${value}px`
@@ -143,12 +143,13 @@ export function TextWipe(props: { text?: string; class?: string; delay?: number;
143143
let ref: HTMLSpanElement | undefined
144144
let frame: number | undefined
145145
let anim: AnimationPlaybackControls | undefined
146+
const reduce = useReducedMotion()
146147

147148
const run = () => {
148149
if (props.animate === false) return
149150
const el = ref
150151
if (!el || !props.text || typeof window === "undefined") return
151-
if (prefersReducedMotion()) return
152+
if (reduce()) return
152153

153154
const mask =
154155
typeof CSS !== "undefined" &&

0 commit comments

Comments
 (0)