diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 2135b1edf..4f0dcc3ee 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -90,10 +90,9 @@ const ModelList: Component<{ export function ModelSelectorPopover(props: { provider?: string - children?: JSX.Element | ((open: boolean) => JSX.Element) + children?: JSX.Element triggerAs?: T triggerProps?: ComponentProps - gutter?: number }) { const [store, setStore] = createStore<{ open: boolean @@ -176,14 +175,14 @@ export function ModelSelectorPopover(props: { }} modal={false} placement="top-start" - gutter={props.gutter ?? 8} + gutter={8} > setStore("trigger", el)} as={props.triggerAs ?? "div"} {...(props.triggerProps as any)} > - {typeof props.children === "function" ? props.children(store.open) : props.children} + {props.children} = (props) => { clearInput() client.session .shell({ - sessionID: session?.id || "", + sessionID: session.id, agent, model, command: text, @@ -1280,7 +1277,7 @@ export const PromptInput: Component = (props) => { clearInput() client.session .command({ - sessionID: session?.id || "", + sessionID: session.id, command: commandName, arguments: args.join(" "), agent, @@ -1436,13 +1433,13 @@ export const PromptInput: Component = (props) => { const optimisticParts = requestParts.map((part) => ({ ...part, - sessionID: session?.id || "", + sessionID: session.id, messageID, })) as unknown as Part[] const optimisticMessage: Message = { id: messageID, - sessionID: session?.id || "", + sessionID: session.id, role: "user", time: { created: Date.now() }, agent, @@ -1453,9 +1450,9 @@ export const PromptInput: Component = (props) => { if (sessionDirectory === projectDirectory) { sync.set( produce((draft) => { - const messages = draft.message[session?.id || ""] + const messages = draft.message[session.id] if (!messages) { - draft.message[session?.id || ""] = [optimisticMessage] + draft.message[session.id] = [optimisticMessage] } else { const result = Binary.search(messages, messageID, (m) => m.id) messages.splice(result.index, 0, optimisticMessage) @@ -1471,9 +1468,9 @@ export const PromptInput: Component = (props) => { globalSync.child(sessionDirectory)[1]( produce((draft) => { - const messages = draft.message[session?.id || ""] + const messages = draft.message[session.id] if (!messages) { - draft.message[session?.id || ""] = [optimisticMessage] + draft.message[session.id] = [optimisticMessage] } else { const result = Binary.search(messages, messageID, (m) => m.id) messages.splice(result.index, 0, optimisticMessage) @@ -1490,7 +1487,7 @@ export const PromptInput: Component = (props) => { if (sessionDirectory === projectDirectory) { sync.set( produce((draft) => { - const messages = draft.message[session?.id || ""] + const messages = draft.message[session.id] if (messages) { const result = Binary.search(messages, messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) @@ -1503,7 +1500,7 @@ export const PromptInput: Component = (props) => { globalSync.child(sessionDirectory)[1]( produce((draft) => { - const messages = draft.message[session?.id || ""] + const messages = draft.message[session.id] if (messages) { const result = Binary.search(messages, messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) @@ -1524,15 +1521,15 @@ export const PromptInput: Component = (props) => { const worktree = WorktreeState.get(sessionDirectory) if (!worktree || worktree.status !== "pending") return true - if (sessionDirectory === projectDirectory && session?.id) { - sync.set("session_status", session?.id, { type: "busy" }) + if (sessionDirectory === projectDirectory) { + sync.set("session_status", session.id, { type: "busy" }) } const controller = new AbortController() const cleanup = () => { - if (sessionDirectory === projectDirectory && session?.id) { - sync.set("session_status", session?.id, { type: "idle" }) + if (sessionDirectory === projectDirectory) { + sync.set("session_status", session.id, { type: "idle" }) } removeOptimisticMessage() for (const item of commentItems) { @@ -1549,7 +1546,7 @@ export const PromptInput: Component = (props) => { restoreInput() } - pending.set(session?.id || "", { abort: controller, cleanup }) + pending.set(session.id, { abort: controller, cleanup }) const abort = new Promise>>((resolve) => { if (controller.signal.aborted) { @@ -1577,7 +1574,7 @@ export const PromptInput: Component = (props) => { if (timer.id === undefined) return clearTimeout(timer.id) }) - pending.delete(session?.id || "") + pending.delete(session.id) if (controller.signal.aborted) return false if (result.status === "failed") throw new Error(result.message) return true @@ -1587,7 +1584,7 @@ export const PromptInput: Component = (props) => { const ok = await waitForWorktree() if (!ok) return await client.session.prompt({ - sessionID: session?.id || "", + sessionID: session.id, agent, model, messageID, @@ -1597,9 +1594,9 @@ export const PromptInput: Component = (props) => { } void send().catch((err) => { - pending.delete(session?.id || "") - if (sessionDirectory === projectDirectory && session?.id) { - sync.set("session_status", session?.id, { type: "idle" }) + pending.delete(session.id) + if (sessionDirectory === projectDirectory) { + sync.set("session_status", session.id, { type: "idle" }) } showToast({ title: language.t("prompt.toast.promptSendFailed.title"), @@ -1621,28 +1618,6 @@ export const PromptInput: Component = (props) => { }) } - const currrentModelVariant = createMemo(() => { - const modelVariant = local.model.variant.current() ?? "" - return modelVariant === "xhigh" - ? "xHigh" - : modelVariant.length > 0 - ? modelVariant[0].toUpperCase() + modelVariant.slice(1) - : "Default" - }) - - const reasoningPercentage = createMemo(() => { - const variants = local.model.variant.list() - const current = local.model.variant.current() - const totalEntries = variants.length + 1 - - if (totalEntries <= 2 || current === "Default") { - return 0 - } - - const currentIndex = current ? variants.indexOf(current) + 1 : 0 - return ((currentIndex + 1) / totalEntries) * 100 - }, [local.model.variant]) - return (
@@ -1695,7 +1670,7 @@ export const PromptInput: Component = (props) => { } > - + @{(item as { type: "agent"; name: string }).name} @@ -1760,9 +1735,9 @@ export const PromptInput: Component = (props) => { }} > -
+
- + {language.t("prompt.dropzone.label")}
@@ -1848,7 +1823,7 @@ export const PromptInput: Component = (props) => { when={attachment.mime.startsWith("image/")} fallback={
- +
} > @@ -1922,7 +1897,7 @@ export const PromptInput: Component = (props) => {
-
+
@@ -1943,7 +1918,6 @@ export const PromptInput: Component = (props) => { onSelect={local.agent.set} class="capitalize" variant="ghost" - gutter={12} /> = (props) => { title={language.t("command.model.choose")} keybind={command.keybind("model.choose")} > - } @@ -1976,16 +1943,12 @@ export const PromptInput: Component = (props) => { title={language.t("command.model.choose")} keybind={command.keybind("model.choose")} > - - {(open) => ( - <> - - - - {local.model.current()?.name ?? language.t("dialog.model.select.title")} - - - )} + + + + + {local.model.current()?.name ?? language.t("dialog.model.select.title")} + @@ -1998,13 +1961,10 @@ export const PromptInput: Component = (props) => { @@ -2018,7 +1978,7 @@ export const PromptInput: Component = (props) => { variant="ghost" onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)} classList={{ - "_hidden group-hover/prompt-input:flex items-center justify-center": true, + "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true, "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory), "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory), }} @@ -2040,7 +2000,7 @@ export const PromptInput: Component = (props) => {
-
+
= (props) => { e.currentTarget.value = "" }} /> -
+
@@ -2083,7 +2042,7 @@ export const PromptInput: Component = (props) => {
{language.t("prompt.action.send")} - +
@@ -2094,7 +2053,7 @@ export const PromptInput: Component = (props) => { disabled={!prompt.dirty() && !working()} icon={working() ? "stop" : "arrow-up"} variant="primary" - class="h-6 w-5.5" + class="h-6 w-4.5" aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")} /> diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 94813871e..b31cfb6cc 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -226,7 +226,7 @@ export const SettingsGeneral: Component = () => { variant="secondary" size="small" triggerVariant="settings" - triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "field-sizing": "content" }} + triggerStyle={{ "font-family": monoFontFamily(settings.appearance.font()), "min-width": "180px" }} > {(option) => ( diff --git a/packages/ui/src/components/button.css b/packages/ui/src/components/button.css index afff0c476..d9b345923 100644 --- a/packages/ui/src/components/button.css +++ b/packages/ui/src/components/button.css @@ -9,13 +9,7 @@ user-select: none; cursor: default; outline: none; - padding: 4px 8px; white-space: nowrap; - transition-property: background-color, border-color, color, box-shadow, opacity; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); - outline: none; - line-height: 20px; &[data-variant="primary"] { background-color: var(--button-primary-base); @@ -100,6 +94,7 @@ &:active:not(:disabled) { background-color: var(--button-secondary-base); scale: 0.99; + transition: all 150ms ease-out; } &:disabled { border-color: var(--border-disabled); @@ -115,32 +110,33 @@ &[data-size="small"] { height: 22px; - padding: 4px 8px; + padding: 0 8px; &[data-icon] { - padding: 4px 12px 4px 4px; + padding: 0 12px 0 4px; } + font-size: var(--font-size-small); + line-height: var(--line-height-large); gap: 4px; /* text-12-medium */ font-family: var(--font-family-sans); - font-size: var(--font-size-base); + font-size: var(--font-size-small); font-style: normal; font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); /* 166.667% */ letter-spacing: var(--letter-spacing-normal); } &[data-size="normal"] { height: 24px; - padding: 4px 6px; + line-height: 24px; + padding: 0 6px; &[data-icon] { - padding: 4px 12px 4px 4px; - } - - &[aria-haspopup] { - padding: 4px 6px 4px 8px; + padding: 0 12px 0 4px; } + font-size: var(--font-size-small); gap: 6px; /* text-12-medium */ diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index b2d2004d3..7f974b2f7 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon" export interface ButtonProps extends ComponentProps, - Pick, "class" | "classList" | "children" | "style"> { + Pick, "class" | "classList" | "children"> { size?: "small" | "normal" | "large" variant?: "primary" | "secondary" | "ghost" icon?: IconProps["name"] diff --git a/packages/ui/src/components/cycle-label.css b/packages/ui/src/components/cycle-label.css deleted file mode 100644 index 3c98fcd26..000000000 --- a/packages/ui/src/components/cycle-label.css +++ /dev/null @@ -1,49 +0,0 @@ -.cycle-label { - --c-duration: 200ms; - --c-stagger: 30ms; - --c-opacity-start: 0; - --c-opacity-end: 1; - --c-blur-start: 0px; - --c-blur-end: 0px; - --c-skew: 10deg; - - display: inline-flex; - position: relative; - - transform-style: preserve-3d; - perspective: 500px; - transition: width var(--transition-duration) var(--transition-easing); - will-change: width; - overflow: hidden; - - .cycle-char { - display: inline-block; - transform-style: preserve-3d; - min-width: 0.25em; - backface-visibility: hidden; - - transition-property: transform, opacity, filter; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); - transition-delay: calc(var(--i, 0) * var(--c-stagger)); - - &.enter { - opacity: var(--c-opacity-end); - filter: blur(var(--c-blur-end)); - transform: translateY(0) rotateX(0) skewX(0); - } - - &.exit { - opacity: var(--c-opacity-start); - filter: blur(var(--c-blur-start)); - transform: translateY(50%) rotateX(90deg) skewX(var(--c-skew)); - } - - &.pre { - opacity: var(--c-opacity-start); - filter: blur(var(--c-blur-start)); - transition: none; - transform: translateY(-50%) rotateX(-90deg) skewX(calc(var(--c-skew) * -1)); - } - } -} diff --git a/packages/ui/src/components/cycle-label.tsx b/packages/ui/src/components/cycle-label.tsx deleted file mode 100644 index dc12bd75c..000000000 --- a/packages/ui/src/components/cycle-label.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import "./cycle-label.css" -import { createEffect, createSignal, JSX, on } from "solid-js" - -export interface CycleLabelProps extends JSX.HTMLAttributes { - value: string - onValueChange?: (value: string) => void - duration?: number | ((value: string) => number) - stagger?: number - opacity?: [number, number] - blur?: [number, number] - skewX?: number - onAnimationStart?: () => void - onAnimationEnd?: () => void -} - -const segmenter = - typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter("en", { granularity: "grapheme" }) : null - -const getChars = (text: string): string[] => - segmenter ? Array.from(segmenter.segment(text), (s) => s.segment) : text.split("") - -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - -export function CycleLabel(props: CycleLabelProps) { - const getDuration = (text: string) => { - const d = - props.duration ?? - Number(getComputedStyle(document.documentElement).getPropertyValue("--transition-duration")) ?? - 200 - return typeof d === "function" ? d(text) : d - } - const stagger = () => props?.stagger ?? 30 - const opacity = () => props?.opacity ?? [0, 1] - const blur = () => props?.blur ?? [0, 0] - const skewX = () => props?.skewX ?? 10 - - let containerRef: HTMLSpanElement | undefined - let isAnimating = false - const [currentText, setCurrentText] = createSignal(props.value) - - const setChars = (el: HTMLElement, text: string, state: "enter" | "exit" | "pre" = "enter") => { - el.innerHTML = "" - const chars = getChars(text) - chars.forEach((char, i) => { - const span = document.createElement("span") - span.textContent = char === " " ? "\u00A0" : char - span.className = `cycle-char ${state}` - span.style.setProperty("--i", String(i)) - el.appendChild(span) - }) - } - - const animateToText = async (newText: string) => { - if (!containerRef || isAnimating) return - if (newText === currentText()) return - - isAnimating = true - props.onAnimationStart?.() - - const dur = getDuration(newText) - const stag = stagger() - - containerRef.style.width = containerRef.offsetWidth + "px" - - const oldChars = containerRef.querySelectorAll(".cycle-char") - oldChars.forEach((c) => c.classList.replace("enter", "exit")) - - const clone = containerRef.cloneNode(false) as HTMLElement - Object.assign(clone.style, { - position: "absolute", - visibility: "hidden", - width: "auto", - transition: "none", - }) - setChars(clone, newText) - document.body.appendChild(clone) - const nextWidth = clone.offsetWidth - clone.remove() - - const exitTime = oldChars.length * stag + dur - await wait(exitTime * 0.3) - - containerRef.style.width = nextWidth + "px" - - const widthDur = 200 - await wait(widthDur * 0.3) - - setChars(containerRef, newText, "pre") - containerRef.offsetWidth - - Array.from(containerRef.children).forEach((c) => (c.className = "cycle-char enter")) - setCurrentText(newText) - props.onValueChange?.(newText) - - const enterTime = getChars(newText).length * stag + dur - await wait(enterTime) - - containerRef.style.width = "" - isAnimating = false - props.onAnimationEnd?.() - } - - createEffect( - on( - () => props.value, - (newValue) => { - if (newValue !== currentText()) { - animateToText(newValue) - } - }, - ), - ) - - const initRef = (el: HTMLSpanElement) => { - containerRef = el - setChars(el, props.value) - } - - return ( - - ) -} diff --git a/packages/ui/src/components/dropdown-menu.css b/packages/ui/src/components/dropdown-menu.css index 18266ac1a..cba041613 100644 --- a/packages/ui/src/components/dropdown-menu.css +++ b/packages/ui/src/components/dropdown-menu.css @@ -2,29 +2,26 @@ [data-component="dropdown-menu-sub-content"] { min-width: 8rem; overflow: hidden; - border: none; border-radius: var(--radius-md); - box-shadow: var(--shadow-xs-border); + border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent); background-clip: padding-box; background-color: var(--surface-raised-stronger-non-alpha); padding: 4px; - z-index: 100; + box-shadow: var(--shadow-md); + z-index: 50; transform-origin: var(--kb-menu-content-transform-origin); - &:focus-within, - &:focus { + &:focus, + &:focus-visible { outline: none; } - animation: dropdownMenuContentHide var(--transition-duration) var(--transition-easing) forwards; - - @starting-style { - animation: none; + &[data-closed] { + animation: dropdown-menu-close 0.15s ease-out; } &[data-expanded] { - pointer-events: auto; - animation: dropdownMenuContentShow var(--transition-duration) var(--transition-easing) forwards; + animation: dropdown-menu-open 0.15s ease-out; } } @@ -41,22 +38,18 @@ padding: 4px 8px; border-radius: var(--radius-sm); cursor: default; + user-select: none; outline: none; font-family: var(--font-family-sans); - font-size: var(--font-size-base); + font-size: var(--font-size-small); font-weight: var(--font-weight-medium); line-height: var(--line-height-large); letter-spacing: var(--letter-spacing-normal); color: var(--text-strong); - transition-property: background-color, color; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); - user-select: none; - - &:hover { - background-color: var(--surface-raised-base-hover); + &[data-highlighted] { + background: var(--surface-raised-base-hover); } &[data-disabled] { @@ -68,8 +61,6 @@ [data-slot="dropdown-menu-sub-trigger"] { &[data-expanded] { background: var(--surface-raised-base-hover); - outline: none; - border: none; } } @@ -111,24 +102,24 @@ } } -@keyframes dropdownMenuContentShow { +@keyframes dropdown-menu-open { from { opacity: 0; - transform: scaleY(0.95); + transform: scale(0.96); } to { opacity: 1; - transform: scaleY(1); + transform: scale(1); } } -@keyframes dropdownMenuContentHide { +@keyframes dropdown-menu-close { from { opacity: 1; - transform: scaleY(1); + transform: scale(1); } to { opacity: 0; - transform: scaleY(0.95); + transform: scale(0.96); } } diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 97488a42f..544c6abdd 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -80,16 +80,13 @@ const icons = { export interface IconProps extends ComponentProps<"svg"> { name: keyof typeof icons - size?: "small" | "normal" | "medium" | "large" | number + size?: "small" | "normal" | "medium" | "large" } export function Icon(props: IconProps) { const [local, others] = splitProps(props, ["name", "size", "class", "classList"]) return ( -
+
- +
m.id === perm.tool!.messageID) + const message = findLast(messages, (m) => m.id === perm.tool!.messageID) if (!message) return undefined const parts = data.store.part[message.id] ?? [] for (const part of parts) { diff --git a/packages/ui/src/components/morph-chevron.css b/packages/ui/src/components/morph-chevron.css deleted file mode 100644 index f6edb3f64..000000000 --- a/packages/ui/src/components/morph-chevron.css +++ /dev/null @@ -1,10 +0,0 @@ -[data-slot="morph-chevron-svg"] { - width: 16px; - height: 16px; - display: block; - fill: none; - stroke-width: 1.5; - stroke: currentcolor; - stroke-linecap: round; - stroke-linejoin: round; -} diff --git a/packages/ui/src/components/morph-chevron.tsx b/packages/ui/src/components/morph-chevron.tsx deleted file mode 100644 index 280aeb7e3..000000000 --- a/packages/ui/src/components/morph-chevron.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { createEffect, createUniqueId, on } from "solid-js" - -export interface MorphChevronProps { - expanded: boolean - class?: string -} - -const COLLAPSED = "M4 6L8 10L12 6" -const EXPANDED = "M4 10L8 6L12 10" - -export function MorphChevron(props: MorphChevronProps) { - const id = createUniqueId() - let path: SVGPathElement | undefined - let expandAnim: SVGAnimateElement | undefined - let collapseAnim: SVGAnimateElement | undefined - - createEffect( - on( - () => props.expanded, - (expanded, prev) => { - if (prev === undefined) { - // Set initial state without animation - path?.setAttribute("d", expanded ? EXPANDED : COLLAPSED) - return - } - if (expanded) { - expandAnim?.beginElement() - } else { - collapseAnim?.beginElement() - } - }, - ), - ) - - return ( - - ) -} diff --git a/packages/ui/src/components/popover.css b/packages/ui/src/components/popover.css index d200fe8b2..b49542afd 100644 --- a/packages/ui/src/components/popover.css +++ b/packages/ui/src/components/popover.css @@ -15,35 +15,16 @@ transform-origin: var(--kb-popover-content-transform-origin); - animation: popoverContentHide var(--transition-duration) var(--transition-easing) forwards; + &:focus-within { + outline: none; + } - @starting-style { - animation: none; + &[data-closed] { + animation: popover-close 0.15s ease-out; } &[data-expanded] { - pointer-events: auto; - animation: popoverContentShow var(--transition-duration) var(--transition-easing) forwards; - } - - [data-origin-top-right] { - transform-origin: top right; - } - - [data-origin-top-left] { - transform-origin: top left; - } - - [data-origin-bottom-right] { - transform-origin: bottom right; - } - - [data-origin-bottom-left] { - transform-origin: bottom left; - } - - &:focus-within { - outline: none; + animation: popover-open 0.15s ease-out; } [data-slot="popover-header"] { @@ -94,39 +75,24 @@ } } -@keyframes popoverContentShow { +@keyframes popover-open { from { opacity: 0; - transform: scaleY(0.95); + transform: scale(0.96); } to { opacity: 1; - transform: scaleY(1); + transform: scale(1); } } -@keyframes popoverContentHide { +@keyframes popover-close { from { opacity: 1; - transform: scaleY(1); + transform: scale(1); } to { opacity: 0; - transform: scaleY(0.95); - } -} - -[data-component="model-popover-content"] { - transform-origin: var(--kb-popper-content-transform-origin); - pointer-events: none; - animation: popoverContentHide var(--transition-duration) var(--transition-easing) forwards; - - @starting-style { - animation: none; - } - - &[data-expanded] { - pointer-events: auto; - animation: popoverContentShow var(--transition-duration) var(--transition-easing) forwards; + transform: scale(0.96); } } diff --git a/packages/ui/src/components/reasoning-icon.css b/packages/ui/src/components/reasoning-icon.css deleted file mode 100644 index 26fbc0144..000000000 --- a/packages/ui/src/components/reasoning-icon.css +++ /dev/null @@ -1,9 +0,0 @@ -[data-component="reasoning-icon"] { - color: var(--icon-strong-base); - - [data-slot="reasoning-icon-percentage"] { - transition: clip-path 200ms cubic-bezier(0.25, 0, 0.5, 1); - clip-path: inset(calc(100% - var(--reasoning-icon-percentage) * 100%) 0 0 0); - opacity: calc(var(--reasoning-icon-percentage) * 0.75); - } -} diff --git a/packages/ui/src/components/reasoning-icon.tsx b/packages/ui/src/components/reasoning-icon.tsx deleted file mode 100644 index 7bac49ffd..000000000 --- a/packages/ui/src/components/reasoning-icon.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { type ComponentProps, splitProps } from "solid-js" - -export interface ReasoningIconProps extends Pick, "class" | "classList"> { - percentage: number - size?: number - strokeWidth?: number -} - -export function ReasoningIcon(props: ReasoningIconProps) { - const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"]) - - const size = () => split.size || 16 - const strokeWidth = () => split.strokeWidth || 1.25 - - return ( - - - - - ) -} diff --git a/packages/ui/src/components/select.css b/packages/ui/src/components/select.css index eaba6fd6d..25dd2eb40 100644 --- a/packages/ui/src/components/select.css +++ b/packages/ui/src/components/select.css @@ -1,13 +1,7 @@ [data-component="select"] { [data-slot="select-select-trigger"] { - display: flex; - padding: 4px 8px !important; - align-items: center; - justify-content: space-between; + padding: 0 4px 0 8px; box-shadow: none; - transition-property: background-color; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); [data-slot="select-select-trigger-value"] { overflow: hidden; @@ -21,10 +15,10 @@ align-items: center; justify-content: center; flex-shrink: 0; - color: var(--icon-base); + color: var(--text-weak); + transition: transform 0.1s ease-in-out; } - &:hover, &[data-expanded] { &[data-variant="secondary"] { background-color: var(--button-secondary-hover); @@ -36,13 +30,13 @@ background-color: var(--icon-strong-active); } } - &:not([data-expanded]):focus, + &:not([data-expanded]):focus-visible { &[data-variant="secondary"] { background-color: var(--button-secondary-base); } &[data-variant="ghost"] { - background-color: transparent; + background-color: var(--surface-raised-base-hover); } &[data-variant="primary"] { background-color: var(--icon-strong-base); @@ -52,10 +46,10 @@ &[data-trigger-style="settings"] { [data-slot="select-select-trigger"] { - padding: 6px 6px 6px 10px; + padding: 6px 6px 6px 12px; box-shadow: none; border-radius: 6px; - field-sizing: content; + min-width: 160px; height: 32px; justify-content: flex-end; gap: 12px; @@ -67,7 +61,6 @@ white-space: nowrap; font-size: var(--font-size-base); font-weight: var(--font-weight-regular); - padding: 4px 8px 4px 4px; } [data-slot="select-select-trigger-icon"] { width: 16px; @@ -98,26 +91,17 @@ } [data-component="select-content"] { - min-width: 8rem; + min-width: 104px; max-width: 23rem; overflow: hidden; border-radius: var(--radius-md); background-color: var(--surface-raised-stronger-non-alpha); padding: 4px; box-shadow: var(--shadow-xs-border); - z-index: 50; - transform-origin: var(--kb-popper-content-transform-origin); - pointer-events: none; - - animation: selectContentHide var(--transition-duration) var(--transition-easing) forwards; - - @starting-style { - animation: none; - } + z-index: 60; &[data-expanded] { - pointer-events: auto; - animation: selectContentShow var(--transition-duration) var(--transition-easing) forwards; + animation: select-open 0.15s ease-out; } [data-slot="select-select-content-list"] { @@ -127,38 +111,43 @@ overflow-x: hidden; display: flex; flex-direction: column; + &:focus { outline: none; } + > *:not([role="presentation"]) + *:not([role="presentation"]) { margin-top: 2px; } } + [data-slot="select-select-item"] { position: relative; display: flex; align-items: center; - padding: 4px 8px; + padding: 2px 8px; gap: 12px; - border-radius: var(--radius-sm); + border-radius: 4px; + cursor: default; /* text-12-medium */ font-family: var(--font-family-sans); - font-size: var(--font-size-base); + font-size: var(--font-size-small); font-style: normal; font-weight: var(--font-weight-medium); line-height: var(--line-height-large); /* 166.667% */ letter-spacing: var(--letter-spacing-normal); + color: var(--text-strong); - transition-property: background-color, color; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); + transition: + background-color 0.2s ease-in-out, + color 0.2s ease-in-out; outline: none; user-select: none; - &:hover { - background-color: var(--surface-raised-base-hover); + &[data-highlighted] { + background: var(--surface-raised-base-hover); } &[data-disabled] { background-color: var(--surface-raised-base); @@ -171,11 +160,6 @@ margin-left: auto; width: 16px; height: 16px; - color: var(--icon-strong-base); - - svg { - color: var(--icon-strong-base); - } } &:focus { outline: none; @@ -187,9 +171,13 @@ } [data-component="select-content"][data-trigger-style="settings"] { - field-sizing: content; + min-width: 160px; border-radius: 8px; - padding: 0 0 0 4px; + padding: 0; + + [data-slot="select-select-content-list"] { + padding: 4px; + } [data-slot="select-select-item"] { /* text-14-regular */ @@ -202,24 +190,13 @@ } } -@keyframes selectContentShow { +@keyframes select-open { from { opacity: 0; - transform: scaleY(0.95); + transform: scale(0.95); } to { opacity: 1; - transform: scaleY(1); - } -} - -@keyframes selectContentHide { - from { - opacity: 1; - transform: scaleY(1); - } - to { - opacity: 0; - transform: scaleY(0.95); + transform: scale(1); } } diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx index fef00500a..0386c329e 100644 --- a/packages/ui/src/components/select.tsx +++ b/packages/ui/src/components/select.tsx @@ -1,10 +1,8 @@ import { Select as Kobalte } from "@kobalte/core/select" -import { createMemo, createSignal, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js" +import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js" import { pipe, groupBy, entries, map } from "remeda" -import { Show } from "solid-js" import { Button, ButtonProps } from "./button" import { Icon } from "./icon" -import { MorphChevron } from "./morph-chevron" export type SelectProps = Omit>, "value" | "onSelect" | "children"> & { placeholder?: string @@ -40,8 +38,6 @@ export function Select(props: SelectProps & Omit) "triggerVariant", ]) - const [isOpen, setIsOpen] = createSignal(false) - const state = { key: undefined as string | undefined, cleanup: undefined as (() => void) | void, @@ -89,7 +85,7 @@ export function Select(props: SelectProps & Omit) data-component="select" data-trigger-style={local.triggerVariant} placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"} - gutter={8} + gutter={4} value={local.current} options={grouped()} optionValue={(x) => (local.value ? local.value(x) : (x as string))} @@ -119,7 +115,7 @@ export function Select(props: SelectProps & Omit) : (itemProps.item.rawValue as string)} - + )} @@ -128,7 +124,6 @@ export function Select(props: SelectProps & Omit) stop() }} onOpenChange={(open) => { - setIsOpen(open) local.onOpenChange?.(open) if (!open) stop() }} @@ -154,12 +149,7 @@ export function Select(props: SelectProps & Omit) }} - - - - - - + diff --git a/packages/ui/src/styles/index.css b/packages/ui/src/styles/index.css index 55e1a16d1..c038f69f6 100644 --- a/packages/ui/src/styles/index.css +++ b/packages/ui/src/styles/index.css @@ -49,8 +49,6 @@ @import "../components/toast.css" layer(components); @import "../components/tooltip.css" layer(components); @import "../components/typewriter.css" layer(components); -@import "../components/morph-chevron.css" layer(components); -@import "../components/reasoning-icon.css" layer(components); @import "./utilities.css" layer(utilities); @import "./animations.css" layer(utilities); diff --git a/packages/ui/src/styles/utilities.css b/packages/ui/src/styles/utilities.css index 82a913c88..8c954f1fe 100644 --- a/packages/ui/src/styles/utilities.css +++ b/packages/ui/src/styles/utilities.css @@ -1,17 +1,6 @@ :root { interpolate-size: allow-keywords; - /* Transition tokens */ - --transition-duration: 200ms; - --transition-easing: cubic-bezier(0.25, 0, 0.5, 1); - --transition-fast: 150ms; - --transition-slow: 300ms; - - /* Allow height transitions from 0 to auto */ - @supports (interpolate-size: allow-keywords) { - interpolate-size: allow-keywords; - } - [data-popper-positioner] { pointer-events: none; } @@ -140,34 +129,3 @@ line-height: var(--line-height-x-large); /* 120% */ letter-spacing: var(--letter-spacing-tightest); } - -/* Transition utility classes */ -.transition-colors { - transition-property: background-color, border-color, color, fill, stroke; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); -} - -.transition-opacity { - transition-property: opacity; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); -} - -.transition-transform { - transition-property: transform; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); -} - -.transition-shadow { - transition-property: box-shadow; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); -} - -.transition-interactive { - transition-property: background-color, border-color, color, box-shadow, opacity; - transition-duration: var(--transition-duration); - transition-timing-function: var(--transition-easing); -}