diff --git a/packages/ui/src/components/list.css b/packages/ui/src/components/list.css index 368065e53..f63658d7e 100644 --- a/packages/ui/src/components/list.css +++ b/packages/ui/src/components/list.css @@ -1,14 +1,43 @@ +@property --bottom-fade { + syntax: ""; + inherits: false; + initial-value: 16px; +} + +@property --top-fade-opacity { + syntax: ""; + inherits: true; + initial-value: 0; +} + +@keyframes scroll { + 0% { + --bottom-fade: 16px; + --top-fade-opacity: 0; + } + 10% { + --top-fade-opacity: 1; + } + 90% { + --bottom-fade: 16px; + } + 100% { + --bottom-fade: 0; + --top-fade-opacity: 1; + } +} + [data-component="list"] { display: flex; flex-direction: column; - gap: 20px; + gap: 12px; overflow: hidden; + padding: 0 12px; [data-slot="list-search"] { display: flex; - height: 40px; flex-shrink: 0; - padding: 4px 10px 4px 16px; + padding: 8px 8px 8px 12px; align-items: center; gap: 12px; align-self: stretch; @@ -19,11 +48,17 @@ [data-slot="list-search-container"] { display: flex; align-items: center; - gap: 16px; + gap: 8px; flex: 1 0 0; + max-height: 20px; [data-slot="list-search-input"] { width: 100%; + + &[data-slot="input-input"] { + line-height: 20px; + max-height: 20px; + } } } } @@ -31,88 +66,68 @@ [data-slot="list-scroll"] { display: flex; flex-direction: column; - gap: 20px; + gap: 12px; overflow-y: auto; + overscroll-behavior: contain; + mask: linear-gradient(to bottom, #ffff calc(100% - var(--bottom-fade)), #0000); + animation: scroll; + animation-timeline: --scroll; + scroll-timeline: --scroll y; scrollbar-width: none; -ms-overflow-style: none; &::-webkit-scrollbar { display: none; } - } - [data-slot="list-empty-state"] { - display: flex; - padding: 32px 0px; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 8px; - align-self: stretch; - - [data-slot="list-message"] { + [data-slot="list-empty-state"] { display: flex; + padding: 32px 0px; + flex-direction: column; justify-content: center; align-items: center; - gap: 2px; - color: var(--text-weak); - text-align: center; - - /* text-14-regular */ - font-family: var(--font-family-sans); - font-size: 14px; - font-style: normal; - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); /* 142.857% */ - letter-spacing: var(--letter-spacing-normal); - } - - [data-slot="list-filter"] { - color: var(--text-strong); - } - } - - [data-slot="list-group"] { - position: relative; - display: flex; - flex-direction: column; - - [data-slot="list-header"] { - display: flex; - z-index: 10; - height: 28px; - padding: 0 10px; - justify-content: space-between; - align-items: center; + gap: 8px; align-self: stretch; - background: var(--surface-raised-stronger-non-alpha); - position: sticky; - top: 0; - color: var(--text-base); + [data-slot="list-message"] { + display: flex; + justify-content: center; + align-items: center; + gap: 2px; + color: var(--text-weak); + text-align: center; - /* text-14-medium */ - font-family: var(--font-family-sans); - font-size: 14px; - font-style: normal; - font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); /* 142.857% */ - letter-spacing: var(--letter-spacing-normal); + /* text-14-regular */ + font-family: var(--font-family-sans); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-regular); + line-height: var(--line-height-large); /* 142.857% */ + letter-spacing: var(--letter-spacing-normal); + } + + [data-slot="list-filter"] { + color: var(--text-strong); + } } - [data-slot="list-items"] { + [data-slot="list-group"] { + position: relative; display: flex; flex-direction: column; - align-items: flex-start; - align-self: stretch; - [data-slot="list-item"] { + [data-slot="list-header"] { display: flex; - width: 100%; - height: 28px; - padding: 4px 10px; + z-index: 10; + height: 32px; + padding: 0 12px 8px 12px; + justify-content: space-between; align-items: center; - color: var(--text-strong); - scroll-margin-top: 28px; + align-self: stretch; + background: var(--surface-raised-stronger-non-alpha); + position: sticky; + top: 0; + + color: var(--text-base); /* text-14-medium */ font-family: var(--font-family-sans); @@ -122,30 +137,66 @@ line-height: var(--line-height-large); /* 142.857% */ letter-spacing: var(--letter-spacing-normal); - [data-slot="list-item-selected-icon"] { - color: var(--icon-strong-base); - } - [data-slot="list-item-active-icon"] { - display: none; - color: var(--icon-strong-base); + &::after { + content: ""; + position: absolute; + top: 100%; + left: 0; + right: 0; + height: 16px; + background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent); + pointer-events: none; + opacity: var(--top-fade-opacity); } + } - &[data-active="true"] { - border-radius: var(--radius-md); - background: var(--surface-raised-base-hover); + [data-slot="list-items"] { + display: flex; + flex-direction: column; + align-items: flex-start; + align-self: stretch; + + [data-slot="list-item"] { + display: flex; + width: 100%; + padding: 6px 8px 6px 4px; + align-items: center; + color: var(--text-strong); + scroll-margin-top: 32px; + + /* text-14-medium */ + font-family: var(--font-family-sans); + font-size: 14px; + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 142.857% */ + letter-spacing: var(--letter-spacing-normal); + + [data-slot="list-item-selected-icon"] { + color: var(--icon-strong-base); + } [data-slot="list-item-active-icon"] { - display: block; + display: none; + color: var(--icon-strong-base); } - [data-slot="list-item-extra-icon"] { - display: block !important; - color: var(--icon-strong-base) !important; + + &[data-active="true"] { + border-radius: var(--radius-md); + background: var(--surface-raised-base-hover); + [data-slot="list-item-active-icon"] { + display: block; + } + [data-slot="list-item-extra-icon"] { + display: block !important; + color: var(--icon-strong-base) !important; + } + } + &:active { + background: var(--surface-raised-base-active); + } + &:focus-visible { + outline: none; } - } - &:active { - background: var(--surface-raised-base-active); - } - &:focus-visible { - outline: none; } } } diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx index 0ed745f32..665e2cc6f 100644 --- a/packages/ui/src/components/list.tsx +++ b/packages/ui/src/components/list.tsx @@ -1,7 +1,7 @@ -import { createEffect, on, Show, For, type JSX, createSignal } from "solid-js" +import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks" +import { createEffect, createSignal, For, type JSX, on, Show } from "solid-js" import { createStore } from "solid-js/store" -import { FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks" -import { Icon, IconProps } from "./icon" +import { Icon, type IconProps } from "./icon" import { IconButton } from "./icon-button" import { TextField } from "./text-field" @@ -160,6 +160,7 @@ export function List(props: ListProps & { ref?: (ref: ListRef) => void }) data-active={props.key(item) === active()} data-selected={item === props.current} onClick={() => handleSelect(item, i())} + type="button" onMouseMove={() => { setStore("mouseActive", true) setActive(props.key(item))