Skip to content

Commit 41648da

Browse files
Aaron IkerColin4k1024
authored andcommitted
feat(ui): Smooth fading out on scroll, style fixes (anomalyco#11683)
1 parent d218a4d commit 41648da

File tree

10 files changed

+490
-60
lines changed

10 files changed

+490
-60
lines changed

packages/app/src/components/settings-general.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Select } from "@opencode-ai/ui/select"
55
import { Switch } from "@opencode-ai/ui/switch"
66
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
77
import { showToast } from "@opencode-ai/ui/toast"
8+
import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
89
import { useLanguage } from "@/context/language"
910
import { usePlatform } from "@/context/platform"
1011
import { useSettings, monoFontFamily } from "@/context/settings"
@@ -60,24 +61,24 @@ export const SettingsGeneral: Component = () => {
6061
const actions =
6162
platform.update && platform.restart
6263
? [
63-
{
64-
label: language.t("toast.update.action.installRestart"),
65-
onClick: async () => {
66-
await platform.update!()
67-
await platform.restart!()
68-
},
64+
{
65+
label: language.t("toast.update.action.installRestart"),
66+
onClick: async () => {
67+
await platform.update!()
68+
await platform.restart!()
6969
},
70-
{
71-
label: language.t("toast.update.action.notYet"),
72-
onClick: "dismiss" as const,
73-
},
74-
]
70+
},
71+
{
72+
label: language.t("toast.update.action.notYet"),
73+
onClick: "dismiss" as const,
74+
},
75+
]
7576
: [
76-
{
77-
label: language.t("toast.update.action.notYet"),
78-
onClick: "dismiss" as const,
79-
},
80-
]
77+
{
78+
label: language.t("toast.update.action.notYet"),
79+
onClick: "dismiss" as const,
80+
},
81+
]
8182

8283
showToast({
8384
persistent: true,
@@ -130,7 +131,7 @@ export const SettingsGeneral: Component = () => {
130131
const soundOptions = [...SOUND_OPTIONS]
131132

132133
return (
133-
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
134+
<ScrollFade direction="vertical" fadeStartSize={0} fadeEndSize={16} class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
134135
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
135136
<div class="flex flex-col gap-1 pt-6 pb-8">
136137
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
@@ -411,7 +412,7 @@ export const SettingsGeneral: Component = () => {
411412
</div>
412413
</div>
413414
</div>
414-
</div>
415+
</ScrollFade>
415416
)
416417
}
417418

packages/app/src/components/settings-keybinds.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Icon } from "@opencode-ai/ui/icon"
55
import { IconButton } from "@opencode-ai/ui/icon-button"
66
import { TextField } from "@opencode-ai/ui/text-field"
77
import { showToast } from "@opencode-ai/ui/toast"
8+
import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
89
import fuzzysort from "fuzzysort"
910
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
1011
import { useLanguage } from "@/context/language"
@@ -352,7 +353,12 @@ export const SettingsKeybinds: Component = () => {
352353
})
353354

354355
return (
355-
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
356+
<ScrollFade
357+
direction="vertical"
358+
fadeStartSize={0}
359+
fadeEndSize={16}
360+
class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"
361+
>
356362
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
357363
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
358364
<div class="flex items-center justify-between gap-4">
@@ -430,6 +436,6 @@ export const SettingsKeybinds: Component = () => {
430436
</div>
431437
</Show>
432438
</div>
433-
</div>
439+
</ScrollFade>
434440
)
435441
}

packages/app/src/components/settings-models.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { type Component, For, Show } from "solid-js"
99
import { useLanguage } from "@/context/language"
1010
import { useModels } from "@/context/models"
1111
import { popularProviders } from "@/hooks/use-providers"
12+
import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
1213

1314
type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number]
1415

@@ -39,7 +40,7 @@ export const SettingsModels: Component = () => {
3940
})
4041

4142
return (
42-
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
43+
<ScrollFade direction="vertical" fadeStartSize={0} fadeEndSize={16} class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
4344
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
4445
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
4546
<h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
@@ -125,6 +126,6 @@ export const SettingsModels: Component = () => {
125126
</Show>
126127
</Show>
127128
</div>
128-
</div>
129+
</ScrollFade>
129130
)
130131
}

packages/app/src/components/settings-providers.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useGlobalSync } from "@/context/global-sync"
1212
import { DialogConnectProvider } from "./dialog-connect-provider"
1313
import { DialogSelectProvider } from "./dialog-select-provider"
1414
import { DialogCustomProvider } from "./dialog-custom-provider"
15+
import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
1516

1617
type ProviderSource = "env" | "api" | "config" | "custom"
1718
type ProviderMeta = { source?: ProviderSource }
@@ -115,7 +116,7 @@ export const SettingsProviders: Component = () => {
115116
}
116117

117118
return (
118-
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
119+
<ScrollFade direction="vertical" fadeStartSize={0} fadeEndSize={16} class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
119120
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
120121
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
121122
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
@@ -261,6 +262,6 @@ export const SettingsProviders: Component = () => {
261262
</Button>
262263
</div>
263264
</div>
264-
</div>
265+
</ScrollFade>
265266
)
266267
}

packages/ui/src/components/list.css

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,7 @@
1-
@property --bottom-fade {
2-
syntax: "<length>";
3-
inherits: false;
4-
initial-value: 0px;
5-
}
6-
7-
@keyframes scroll {
8-
0% {
9-
--bottom-fade: 20px;
10-
}
11-
90% {
12-
--bottom-fade: 20px;
13-
}
14-
100% {
15-
--bottom-fade: 0;
16-
}
17-
}
18-
191
[data-component="list"] {
202
display: flex;
213
flex-direction: column;
22-
gap: 12px;
4+
gap: 8px;
235
overflow: hidden;
246
padding: 0 12px;
257

@@ -37,7 +19,9 @@
3719
flex-shrink: 0;
3820
background-color: transparent;
3921
opacity: 0.5;
40-
transition: opacity 0.15s ease;
22+
transition-property: opacity;
23+
transition-duration: var(--transition-duration);
24+
transition-timing-function: var(--transition-easing);
4125

4226
&:hover:not(:disabled),
4327
&:focus-visible:not(:disabled),
@@ -88,7 +72,9 @@
8872
height: 20px;
8973
background-color: transparent;
9074
opacity: 0.5;
91-
transition: opacity 0.15s ease;
75+
transition-property: opacity;
76+
transition-duration: var(--transition-duration);
77+
transition-timing-function: var(--transition-easing);
9278

9379
&:hover:not(:disabled),
9480
&:focus-visible:not(:disabled),
@@ -131,15 +117,6 @@
131117
gap: 12px;
132118
overflow-y: auto;
133119
overscroll-behavior: contain;
134-
mask: linear-gradient(to bottom, #ffff calc(100% - var(--bottom-fade)), #0000);
135-
animation: scroll;
136-
animation-timeline: --scroll;
137-
scroll-timeline: --scroll y;
138-
scrollbar-width: none;
139-
-ms-overflow-style: none;
140-
&::-webkit-scrollbar {
141-
display: none;
142-
}
143120

144121
[data-slot="list-empty-state"] {
145122
display: flex;
@@ -215,7 +192,9 @@
215192
background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent);
216193
pointer-events: none;
217194
opacity: 0;
218-
transition: opacity 0.15s ease;
195+
transition-property: opacity;
196+
transition-duration: var(--transition-duration);
197+
transition-timing-function: var(--transition-easing);
219198
}
220199

221200
&[data-stuck="true"]::after {
@@ -251,17 +230,22 @@
251230
align-items: center;
252231
justify-content: center;
253232
flex-shrink: 0;
254-
aspect-ratio: 1/1;
233+
aspect-ratio: 1 / 1;
255234
[data-component="icon"] {
256235
color: var(--icon-strong-base);
257236
}
258237
}
238+
239+
[name="check"] {
240+
color: var(--icon-strong-base);
241+
}
242+
259243
[data-slot="list-item-active-icon"] {
260244
display: none;
261245
align-items: center;
262246
justify-content: center;
263247
flex-shrink: 0;
264-
aspect-ratio: 1/1;
248+
aspect-ratio: 1 / 1;
265249
[data-component="icon"] {
266250
color: var(--icon-strong-base);
267251
}

packages/ui/src/components/list.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useI18n } from "../context/i18n"
55
import { Icon, type IconProps } from "./icon"
66
import { IconButton } from "./icon-button"
77
import { TextField } from "./text-field"
8+
import { ScrollFade } from "./scroll-fade"
89

910
function findByKey(container: HTMLElement, key: string) {
1011
const nodes = container.querySelectorAll<HTMLElement>('[data-slot="list-item"][data-key]')
@@ -267,7 +268,13 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
267268
{searchAction()}
268269
</div>
269270
</Show>
270-
<div ref={setScrollRef} data-slot="list-scroll">
271+
<ScrollFade
272+
ref={setScrollRef}
273+
direction="vertical"
274+
fadeStartSize={0}
275+
fadeEndSize={20}
276+
data-slot="list-scroll"
277+
>
271278
<Show
272279
when={flat().length > 0 || showAdd()}
273280
fallback={
@@ -339,7 +346,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
339346
</div>
340347
</Show>
341348
</Show>
342-
</div>
349+
</ScrollFade>
343350
</div>
344351
)
345-
}
352+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
[data-component="scroll-fade"] {
2+
overflow: auto;
3+
overscroll-behavior: contain;
4+
scrollbar-width: none;
5+
box-sizing: border-box;
6+
color: inherit;
7+
font: inherit;
8+
-ms-overflow-style: none;
9+
10+
&::-webkit-scrollbar {
11+
display: none;
12+
}
13+
14+
&[data-direction="horizontal"] {
15+
overflow-x: auto;
16+
overflow-y: hidden;
17+
18+
/* Both fades */
19+
&[data-fade-start][data-fade-end] {
20+
mask-image: linear-gradient(
21+
to right,
22+
transparent,
23+
black var(--scroll-fade-start),
24+
black calc(100% - var(--scroll-fade-end)),
25+
transparent
26+
);
27+
-webkit-mask-image: linear-gradient(
28+
to right,
29+
transparent,
30+
black var(--scroll-fade-start),
31+
black calc(100% - var(--scroll-fade-end)),
32+
transparent
33+
);
34+
}
35+
36+
/* Only start fade */
37+
&[data-fade-start]:not([data-fade-end]) {
38+
mask-image: linear-gradient(to right, transparent, black var(--scroll-fade-start), black 100%);
39+
-webkit-mask-image: linear-gradient(to right, transparent, black var(--scroll-fade-start), black 100%);
40+
}
41+
42+
/* Only end fade */
43+
&:not([data-fade-start])[data-fade-end] {
44+
mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
45+
-webkit-mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
46+
}
47+
}
48+
49+
&[data-direction="vertical"] {
50+
overflow-y: auto;
51+
overflow-x: hidden;
52+
53+
&[data-fade-start][data-fade-end] {
54+
mask-image: linear-gradient(
55+
to bottom,
56+
transparent,
57+
black var(--scroll-fade-start),
58+
black calc(100% - var(--scroll-fade-end)),
59+
transparent
60+
);
61+
-webkit-mask-image: linear-gradient(
62+
to bottom,
63+
transparent,
64+
black var(--scroll-fade-start),
65+
black calc(100% - var(--scroll-fade-end)),
66+
transparent
67+
);
68+
}
69+
70+
/* Only start fade */
71+
&[data-fade-start]:not([data-fade-end]) {
72+
mask-image: linear-gradient(to bottom, transparent, black var(--scroll-fade-start), black 100%);
73+
-webkit-mask-image: linear-gradient(to bottom, transparent, black var(--scroll-fade-start), black 100%);
74+
}
75+
76+
/* Only end fade */
77+
&:not([data-fade-start])[data-fade-end] {
78+
mask-image: linear-gradient(to bottom, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
79+
-webkit-mask-image: linear-gradient(to bottom, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)