Co-authored-by: adamelmore <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
parent
511c7abaca
commit
a552652fcc
49 changed files with 380 additions and 1321 deletions
|
|
@ -36,9 +36,7 @@
|
|||
border-radius: var(--radius-md);
|
||||
overflow: clip;
|
||||
color: var(--text-strong);
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
/* text-12-regular */
|
||||
font-family: var(--font-family-sans);
|
||||
|
|
@ -60,48 +58,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
[data-slot="accordion-arrow"] {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
&[data-expanded] {
|
||||
[data-slot="accordion-trigger"] {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
[data-slot="accordion-content"] {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition-property: grid-template-rows, opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
overflow: hidden;
|
||||
[data-slot="accordion-content"] {
|
||||
border: 1px solid var(--border-weak-base);
|
||||
border-top: none;
|
||||
border-bottom-left-radius: var(--radius-md);
|
||||
border-bottom-right-radius: var(--radius-md);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="accordion-content"][data-expanded] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
[data-slot="accordion-content"][data-closed] {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
|
||||
&[data-expanded] [data-slot="accordion-trigger"] {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&[data-expanded] [data-slot="accordion-content"] {
|
||||
border: 1px solid var(--border-weak-base);
|
||||
border-top: none;
|
||||
border-bottom-left-radius: var(--radius-md);
|
||||
border-bottom-right-radius: var(--radius-md);
|
||||
height: auto;
|
||||
[data-slot="accordion-content"] {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--kb-accordion-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
height: var(--kb-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Accordion as Kobalte } from "@kobalte/core/accordion"
|
||||
import { Accessor, createContext, splitProps, useContext } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
import type { ComponentProps, ParentProps } from "solid-js"
|
||||
import { MorphChevron } from "./morph-chevron"
|
||||
|
||||
export interface AccordionProps extends ComponentProps<typeof Kobalte> {}
|
||||
export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {}
|
||||
|
|
@ -9,8 +8,6 @@ export interface AccordionHeaderProps extends ComponentProps<typeof Kobalte.Head
|
|||
export interface AccordionTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
|
||||
export interface AccordionContentProps extends ComponentProps<typeof Kobalte.Content> {}
|
||||
|
||||
const AccordionItemContext = createContext<Accessor<boolean>>()
|
||||
|
||||
function AccordionRoot(props: AccordionProps) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList"])
|
||||
return (
|
||||
|
|
@ -25,19 +22,17 @@ function AccordionRoot(props: AccordionProps) {
|
|||
)
|
||||
}
|
||||
|
||||
function AccordionItem(props: AccordionItemProps & { expanded?: boolean }) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList", "expanded"])
|
||||
function AccordionItem(props: AccordionItemProps) {
|
||||
const [split, rest] = splitProps(props, ["class", "classList"])
|
||||
return (
|
||||
<AccordionItemContext.Provider value={() => split.expanded ?? false}>
|
||||
<Kobalte.Item
|
||||
{...rest}
|
||||
data-slot="accordion-item"
|
||||
classList={{
|
||||
...(split.classList ?? {}),
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
/>
|
||||
</AccordionItemContext.Provider>
|
||||
<Kobalte.Item
|
||||
{...rest}
|
||||
data-slot="accordion-item"
|
||||
classList={{
|
||||
...(split.classList ?? {}),
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -89,25 +84,9 @@ function AccordionContent(props: ParentProps<AccordionContentProps>) {
|
|||
)
|
||||
}
|
||||
|
||||
export interface AccordionArrowProps extends ComponentProps<"div"> {
|
||||
expanded?: boolean
|
||||
}
|
||||
|
||||
function AccordionArrow(props: AccordionArrowProps = {}) {
|
||||
const [local, rest] = splitProps(props, ["expanded"])
|
||||
const contextExpanded = useContext(AccordionItemContext)
|
||||
const isExpanded = () => local.expanded ?? contextExpanded?.() ?? false
|
||||
return (
|
||||
<div data-slot="accordion-arrow" {...rest}>
|
||||
<MorphChevron expanded={isExpanded()} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Accordion = Object.assign(AccordionRoot, {
|
||||
Item: AccordionItem,
|
||||
Header: AccordionHeader,
|
||||
Trigger: AccordionTrigger,
|
||||
Content: AccordionContent,
|
||||
Arrow: AccordionArrow,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,13 +8,8 @@
|
|||
text-decoration: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
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;
|
||||
white-space: nowrap;
|
||||
|
||||
&[data-variant="primary"] {
|
||||
background-color: var(--button-primary-base);
|
||||
|
|
@ -99,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);
|
||||
|
|
@ -113,27 +109,34 @@
|
|||
}
|
||||
|
||||
&[data-size="small"] {
|
||||
padding: 2px 8px;
|
||||
height: 22px;
|
||||
padding: 0 8px;
|
||||
&[data-icon] {
|
||||
padding: 2px 12px 2px 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"] {
|
||||
padding: 4px 6px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
padding: 0 6px;
|
||||
&[data-icon] {
|
||||
padding: 4px 12px 4px 4px;
|
||||
padding: 0 12px 0 4px;
|
||||
}
|
||||
|
||||
font-size: var(--font-size-small);
|
||||
gap: 6px;
|
||||
|
||||
/* text-12-medium */
|
||||
|
|
@ -145,10 +148,11 @@
|
|||
}
|
||||
|
||||
&[data-size="large"] {
|
||||
height: 32px;
|
||||
padding: 6px 12px;
|
||||
|
||||
&[data-icon] {
|
||||
padding: 6px 12px 6px 8px;
|
||||
padding: 0 12px 0 8px;
|
||||
}
|
||||
|
||||
gap: 4px;
|
||||
|
|
@ -158,6 +162,7 @@
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon"
|
|||
|
||||
export interface ButtonProps
|
||||
extends ComponentProps<typeof Kobalte>,
|
||||
Pick<ComponentProps<"button">, "class" | "classList" | "children" | "style"> {
|
||||
Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
|
||||
size?: "small" | "normal" | "large"
|
||||
variant?: "primary" | "secondary" | "ghost"
|
||||
icon?: IconProps["name"]
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@
|
|||
flex-direction: column;
|
||||
background-color: var(--surface-inset-base);
|
||||
border: 1px solid var(--border-weaker-base);
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: background-color 0.15s ease;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 6px 12px;
|
||||
overflow: clip;
|
||||
|
|
|
|||
|
|
@ -4,18 +4,6 @@
|
|||
gap: 12px;
|
||||
cursor: default;
|
||||
|
||||
[data-slot="checkbox-checkbox-control"] {
|
||||
transition-property: border-color, background-color, box-shadow;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
}
|
||||
|
||||
[data-slot="checkbox-checkbox-indicator"] {
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
}
|
||||
|
||||
[data-slot="checkbox-checkbox-input"] {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@
|
|||
flex-direction: column;
|
||||
background-color: var(--surface-inset-base);
|
||||
border: 1px solid var(--border-weaker-base);
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: background-color 0.15s ease;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: clip;
|
||||
|
||||
|
|
@ -46,28 +44,16 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="collapsible-content"] {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition-property: grid-template-rows, opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
overflow: hidden;
|
||||
/* animation: slideUp 250ms ease-out; */
|
||||
|
||||
> * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&[data-expanded] {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
&[data-closed] {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
/* &[data-expanded] { */
|
||||
/* animation: slideDown 250ms ease-out; */
|
||||
/* } */
|
||||
}
|
||||
|
||||
&[data-variant="ghost"] {
|
||||
|
|
@ -97,3 +83,21 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--kb-collapsible-content-height);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
height: var(--kb-collapsible-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { Collapsible as Kobalte, CollapsibleRootProps } from "@kobalte/core/collapsible"
|
||||
import { Accessor, ComponentProps, createContext, createSignal, ParentProps, splitProps, useContext } from "solid-js"
|
||||
import { MorphChevron } from "./morph-chevron"
|
||||
|
||||
const CollapsibleContext = createContext<Accessor<boolean>>()
|
||||
import { ComponentProps, ParentProps, splitProps } from "solid-js"
|
||||
import { Icon } from "./icon"
|
||||
|
||||
export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
|
||||
class?: string
|
||||
|
|
@ -11,30 +9,17 @@ export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
|
|||
}
|
||||
|
||||
function CollapsibleRoot(props: CollapsibleProps) {
|
||||
const [local, others] = splitProps(props, ["class", "classList", "variant", "open", "onOpenChange", "children"])
|
||||
const [internalOpen, setInternalOpen] = createSignal(local.open ?? false)
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
setInternalOpen(open)
|
||||
local.onOpenChange?.(open)
|
||||
}
|
||||
|
||||
const [local, others] = splitProps(props, ["class", "classList", "variant"])
|
||||
return (
|
||||
<CollapsibleContext.Provider value={internalOpen}>
|
||||
<Kobalte
|
||||
data-component="collapsible"
|
||||
data-variant={local.variant || "normal"}
|
||||
open={local.open}
|
||||
onOpenChange={handleOpenChange}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</Kobalte>
|
||||
</CollapsibleContext.Provider>
|
||||
<Kobalte
|
||||
data-component="collapsible"
|
||||
data-variant={local.variant || "normal"}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
{...others}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -47,10 +32,9 @@ function CollapsibleContent(props: ComponentProps<typeof Kobalte.Content>) {
|
|||
}
|
||||
|
||||
function CollapsibleArrow(props?: ComponentProps<"div">) {
|
||||
const isOpen = useContext(CollapsibleContext)
|
||||
return (
|
||||
<div data-slot="collapsible-arrow" {...(props || {})}>
|
||||
<MorphChevron expanded={isOpen?.() ?? false} />
|
||||
<Icon name="chevron-grabber-vertical" size="small" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
.cycle-label {
|
||||
--c-dur: 200ms;
|
||||
--c-stag: 30ms;
|
||||
--c-ease: cubic-bezier(0.25, 0, 0.5, 1);
|
||||
--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 200ms var(--c-ease);
|
||||
will-change: width;
|
||||
overflow: hidden;
|
||||
|
||||
.cycle-char {
|
||||
display: inline-block;
|
||||
transform-style: preserve-3d;
|
||||
min-width: 0.25em;
|
||||
backface-visibility: hidden;
|
||||
|
||||
transition:
|
||||
transform var(--c-dur) var(--c-ease),
|
||||
opacity var(--c-dur) var(--c-ease),
|
||||
filter var(--c-dur) var(--c-ease);
|
||||
transition-delay: calc(var(--i, 0) * var(--c-stag));
|
||||
|
||||
&.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
import "./cycle-label.css"
|
||||
import { createEffect, createSignal, JSX, on } from "solid-js"
|
||||
|
||||
export interface CycleLabelProps extends JSX.HTMLAttributes<HTMLSpanElement> {
|
||||
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 ?? 200
|
||||
return typeof d === "function" ? d(text) : d
|
||||
}
|
||||
const stagger = () => props?.stagger ?? 20
|
||||
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 (
|
||||
<span
|
||||
ref={initRef}
|
||||
class={`cycle-label ${props.class ?? ""}`}
|
||||
style={{
|
||||
"--c-dur": `${getDuration(currentText())}ms`,
|
||||
"--c-stag": `${stagger()}ms`,
|
||||
"--c-opacity-start": opacity()[0],
|
||||
"--c-opacity-end": opacity()[1],
|
||||
"--c-blur-start": `${blur()[0]}px`,
|
||||
"--c-blur-end": `${blur()[1]}px`,
|
||||
"--c-skew": `${skewX()}deg`,
|
||||
...(typeof props.style === "object" ? props.style : {}),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -5,16 +5,6 @@
|
|||
inset: 0;
|
||||
z-index: 50;
|
||||
background-color: hsl(from var(--background-base) h s l / 0.2);
|
||||
|
||||
animation: overlayHide var(--transition-duration) var(--transition-easing) forwards;
|
||||
|
||||
&[data-expanded] {
|
||||
animation: overlayShow var(--transition-duration) var(--transition-easing) forwards;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="dialog"] {
|
||||
|
|
@ -35,6 +25,7 @@
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-items: start;
|
||||
overflow: visible;
|
||||
|
||||
[data-slot="dialog-content"] {
|
||||
display: flex;
|
||||
|
|
@ -44,8 +35,16 @@
|
|||
width: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 280px;
|
||||
overflow: auto;
|
||||
pointer-events: auto;
|
||||
|
||||
/* Hide scrollbar */
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* padding: 8px; */
|
||||
/* padding: 8px 8px 0 8px; */
|
||||
border-radius: var(--radius-xl);
|
||||
|
|
@ -53,16 +52,6 @@
|
|||
background-clip: padding-box;
|
||||
box-shadow: var(--shadow-lg-border-base);
|
||||
|
||||
animation: contentHide var(--transition-duration) var(--transition-easing) forwards;
|
||||
|
||||
&[data-expanded] {
|
||||
animation: contentShow var(--transition-duration) var(--transition-easing) forwards;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
[data-slot="dialog-header"] {
|
||||
display: flex;
|
||||
padding: 20px;
|
||||
|
|
@ -173,7 +162,7 @@
|
|||
@keyframes contentShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(2.5%) scale(0.975);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
|
|
@ -187,6 +176,6 @@
|
|||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-2.5%) scale(0.975);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@
|
|||
}
|
||||
|
||||
&[data-closed] {
|
||||
animation: hover-card-close var(--transition-duration) var(--transition-easing);
|
||||
animation: hover-card-close 0.15s ease-out;
|
||||
}
|
||||
|
||||
&[data-expanded] {
|
||||
animation: hover-card-open var(--transition-duration) var(--transition-easing);
|
||||
animation: hover-card-open 0.15s ease-out;
|
||||
}
|
||||
|
||||
[data-slot="hover-card-body"] {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@
|
|||
user-select: none;
|
||||
aspect-ratio: 1;
|
||||
flex-shrink: 0;
|
||||
transition-property: background-color, color, opacity, box-shadow;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
|
||||
&[data-variant="primary"] {
|
||||
background-color: var(--icon-strong-base);
|
||||
|
|
@ -102,7 +99,7 @@
|
|||
/* color: var(--icon-active); */
|
||||
/* } */
|
||||
}
|
||||
&[data-selected]:not(:disabled) {
|
||||
&:selected:not(:disabled) {
|
||||
background-color: var(--surface-raised-base-active);
|
||||
/* [data-slot="icon-svg"] { */
|
||||
/* color: var(--icon-selected); */
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
/* resize: both; */
|
||||
aspect-ratio: 1 / 1;
|
||||
aspect-ratio: 1/1;
|
||||
color: var(--icon-base);
|
||||
|
||||
&[data-size="small"] {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
data-component="icon"
|
||||
data-size={typeof local.size !== "number" ? local.size || "normal" : `size-[${local.size}px]`}
|
||||
>
|
||||
<div data-component="icon" data-size={local.size || "normal"}>
|
||||
<svg
|
||||
data-slot="icon-svg"
|
||||
classList={{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
[data-component="list"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
padding: 0 12px;
|
||||
|
||||
|
|
@ -37,9 +37,7 @@
|
|||
flex-shrink: 0;
|
||||
background-color: transparent;
|
||||
opacity: 0.5;
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled),
|
||||
&:focus-visible:not(:disabled),
|
||||
|
|
@ -90,9 +88,7 @@
|
|||
height: 20px;
|
||||
background-color: transparent;
|
||||
opacity: 0.5;
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled),
|
||||
&:focus-visible:not(:disabled),
|
||||
|
|
@ -135,6 +131,15 @@
|
|||
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;
|
||||
|
|
@ -210,9 +215,7 @@
|
|||
background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
&[data-stuck="true"]::after {
|
||||
|
|
@ -248,22 +251,17 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
aspect-ratio: 1/1;
|
||||
[data-component="icon"] {
|
||||
color: var(--icon-strong-base);
|
||||
}
|
||||
}
|
||||
|
||||
[name="check"] {
|
||||
color: var(--icon-strong-base);
|
||||
}
|
||||
|
||||
[data-slot="list-item-active-icon"] {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
aspect-ratio: 1/1;
|
||||
[data-component="icon"] {
|
||||
color: var(--icon-strong-base);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { useI18n } from "../context/i18n"
|
|||
import { Icon, type IconProps } from "./icon"
|
||||
import { IconButton } from "./icon-button"
|
||||
import { TextField } from "./text-field"
|
||||
import { ScrollFade } from "./scroll-fade"
|
||||
|
||||
function findByKey(container: HTMLElement, key: string) {
|
||||
const nodes = container.querySelectorAll<HTMLElement>('[data-slot="list-item"][data-key]')
|
||||
|
|
@ -268,7 +267,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
{searchAction()}
|
||||
</div>
|
||||
</Show>
|
||||
<ScrollFade ref={setScrollRef} direction="vertical" fadeStartSize={0} fadeEndSize={20} data-slot="list-scroll">
|
||||
<div ref={setScrollRef} data-slot="list-scroll">
|
||||
<Show
|
||||
when={flat().length > 0 || showAdd()}
|
||||
fallback={
|
||||
|
|
@ -340,7 +339,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
|||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</ScrollFade>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[data-component="logo-mark"] {
|
||||
width: 16px;
|
||||
aspect-ratio: 4 / 5;
|
||||
aspect-ratio: 4/5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ import { Tooltip } from "./tooltip"
|
|||
import { IconButton } from "./icon-button"
|
||||
import { createAutoScroll } from "../hooks"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { MorphChevron } from "./morph-chevron"
|
||||
|
||||
interface Diagnostic {
|
||||
range: {
|
||||
|
|
@ -416,7 +415,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
|||
toggleExpanded()
|
||||
}}
|
||||
>
|
||||
<MorphChevron expanded={expanded()} />
|
||||
<Icon name="chevron-down" size="small" />
|
||||
</button>
|
||||
<div data-slot="user-message-copy-wrapper">
|
||||
<Tooltip
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
data-slot="morph-chevron-svg"
|
||||
class={props.class}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path ref={path} d={COLLAPSED} id={`morph-chevron-path-${id}`}>
|
||||
<animate
|
||||
ref={(el) => {
|
||||
expandAnim = el
|
||||
}}
|
||||
id={`morph-expand-${id}`}
|
||||
attributeName="d"
|
||||
dur="200ms"
|
||||
fill="freeze"
|
||||
calcMode="spline"
|
||||
keySplines="0.25 0 0.5 1"
|
||||
values="M4 6L8 10L12 6;M4 10L8 6L12 10"
|
||||
begin="indefinite"
|
||||
/>
|
||||
<animate
|
||||
ref={(el) => {
|
||||
collapseAnim = el
|
||||
}}
|
||||
id={`morph-collapse-${id}`}
|
||||
attributeName="d"
|
||||
dur="200ms"
|
||||
fill="freeze"
|
||||
calcMode="spline"
|
||||
keySplines="0.25 0 0.5 1"
|
||||
values="M4 10L8 6L12 10;M4 6L8 10L12 6"
|
||||
begin="indefinite"
|
||||
/>
|
||||
</path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
[data-component="progress-circle"] {
|
||||
color: inherit;
|
||||
transform: rotate(-90deg);
|
||||
|
||||
[data-slot="progress-circle-background"] {
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(270deg);
|
||||
stroke-opacity: 0.5;
|
||||
stroke: var(--border-weak-base);
|
||||
}
|
||||
|
||||
[data-slot="progress-circle-progress"] {
|
||||
stroke: var(--border-active);
|
||||
transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type ComponentProps, splitProps } from "solid-js"
|
||||
import { type ComponentProps, createMemo, splitProps } from "solid-js"
|
||||
|
||||
export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
|
||||
percentage: number
|
||||
|
|
@ -9,15 +9,26 @@ export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class"
|
|||
export function ProgressCircle(props: ProgressCircleProps) {
|
||||
const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
|
||||
|
||||
const size = () => split.size || 18
|
||||
const r = 7
|
||||
const size = () => split.size || 16
|
||||
const strokeWidth = () => split.strokeWidth || 3
|
||||
|
||||
const viewBoxSize = 16
|
||||
const center = viewBoxSize / 2
|
||||
const radius = () => center - strokeWidth() / 2
|
||||
const circumference = createMemo(() => 2 * Math.PI * radius())
|
||||
|
||||
const offset = createMemo(() => {
|
||||
const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0))
|
||||
const progress = clampedPercentage / 100
|
||||
return circumference() * (1 - progress)
|
||||
})
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...rest}
|
||||
width={size()}
|
||||
height={size()}
|
||||
viewBox="0 0 18 18"
|
||||
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
||||
fill="none"
|
||||
data-component="progress-circle"
|
||||
classList={{
|
||||
|
|
@ -25,18 +36,21 @@ export function ProgressCircle(props: ProgressCircleProps) {
|
|||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
>
|
||||
<circle cx="9" cy="9" r="7.75" stroke="currentColor" stroke-width="1.5" />
|
||||
<path
|
||||
opacity="0.5"
|
||||
d={(() => {
|
||||
const pct = Math.min(100, Math.max(0, split.percentage))
|
||||
const angle = (pct / 100) * 2 * Math.PI - Math.PI / 2
|
||||
const x = 9 + r * Math.cos(angle)
|
||||
const y = 9 + r * Math.sin(angle)
|
||||
const largeArc = pct > 50 ? 1 : 0
|
||||
return `M9 2A${r} ${r} 0 ${largeArc} 1 ${x} ${y}L9 9Z`
|
||||
})()}
|
||||
fill="currentColor"
|
||||
<circle
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius()}
|
||||
data-slot="progress-circle-background"
|
||||
stroke-width={strokeWidth()}
|
||||
/>
|
||||
<circle
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius()}
|
||||
data-slot="progress-circle-progress"
|
||||
stroke-width={strokeWidth()}
|
||||
stroke-dasharray={circumference().toString()}
|
||||
stroke-dashoffset={offset()}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@
|
|||
content: "";
|
||||
opacity: var(--indicator-opacity, 1);
|
||||
position: absolute;
|
||||
transition-property: opacity, box-shadow, width, height, transform;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition:
|
||||
opacity 300ms ease-in-out,
|
||||
box-shadow 100ms ease-in-out,
|
||||
width 150ms ease,
|
||||
height 150ms ease,
|
||||
transform 150ms ease;
|
||||
}
|
||||
|
||||
[data-slot="radio-group-item"] {
|
||||
|
|
@ -43,9 +46,7 @@
|
|||
content: "";
|
||||
inset: 6px 0;
|
||||
position: absolute;
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 150ms ease;
|
||||
width: 1px;
|
||||
transform: translateX(-0.5px);
|
||||
}
|
||||
|
|
@ -71,9 +72,9 @@
|
|||
padding: 6px 12px;
|
||||
place-content: center;
|
||||
position: relative;
|
||||
transition-duration: 150ms;
|
||||
transition-property: color, opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition-timing-function: ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { type ComponentProps, splitProps } from "solid-js"
|
||||
|
||||
export interface ReasoningIconProps extends Pick<ComponentProps<"svg">, "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 (
|
||||
<svg
|
||||
{...rest}
|
||||
width={size()}
|
||||
height={size()}
|
||||
viewBox={`0 0 16 16`}
|
||||
fill="none"
|
||||
data-component="reasoning-icon"
|
||||
classList={{
|
||||
...(split.classList ?? {}),
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M5.83196 10.3225V11.1666C5.83196 11.7189 6.27967 12.1666 6.83196 12.1666H9.16687C9.71915 12.1666 10.1669 11.7189 10.1669 11.1666V10.3225M5.83196 10.3225C5.55695 10.1843 5.29695 10.0206 5.05505 9.83459C3.90601 8.95086 3.16549 7.56219 3.16549 6.00055C3.16549 3.33085 5.32971 1.16663 7.99941 1.16663C10.6691 1.16663 12.8333 3.33085 12.8333 6.00055C12.8333 7.56219 12.0928 8.95086 10.9438 9.83459C10.7019 10.0206 10.4419 10.1843 10.1669 10.3225M5.83196 10.3225H10.1669M6.5 14.1666H9.5"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="5.83325"
|
||||
r="2.86364"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
stroke-width={strokeWidth()}
|
||||
style={{ "--reasoning-icon-percentage": split.percentage / 100 }}
|
||||
data-slot="reasoning-icon-percentage"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
@ -6,9 +6,7 @@
|
|||
content: "";
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover::after,
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
[data-component="scroll-fade"] {
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: none;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[data-direction="horizontal"] {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
/* Both fades */
|
||||
&[data-fade-start][data-fade-end] {
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black var(--scroll-fade-start),
|
||||
black calc(100% - var(--scroll-fade-end)),
|
||||
transparent
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black var(--scroll-fade-start),
|
||||
black calc(100% - var(--scroll-fade-end)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
/* Only start fade */
|
||||
&[data-fade-start]:not([data-fade-end]) {
|
||||
mask-image: linear-gradient(to right, transparent, black var(--scroll-fade-start), black 100%);
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black var(--scroll-fade-start), black 100%);
|
||||
}
|
||||
|
||||
/* Only end fade */
|
||||
&:not([data-fade-start])[data-fade-end] {
|
||||
mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
|
||||
-webkit-mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-direction="vertical"] {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&[data-fade-start][data-fade-end] {
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
black var(--scroll-fade-start),
|
||||
black calc(100% - var(--scroll-fade-end)),
|
||||
transparent
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
black var(--scroll-fade-start),
|
||||
black calc(100% - var(--scroll-fade-end)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
/* Only start fade */
|
||||
&[data-fade-start]:not([data-fade-end]) {
|
||||
mask-image: linear-gradient(to bottom, transparent, black var(--scroll-fade-start), black 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent, black var(--scroll-fade-start), black 100%);
|
||||
}
|
||||
|
||||
/* Only end fade */
|
||||
&:not([data-fade-start])[data-fade-end] {
|
||||
mask-image: linear-gradient(to bottom, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
|
||||
-webkit-mask-image: linear-gradient(to bottom, black 0%, black calc(100% - var(--scroll-fade-end)), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
import { type JSX, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
|
||||
import "./scroll-fade.css"
|
||||
|
||||
export interface ScrollFadeProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
direction?: "horizontal" | "vertical"
|
||||
fadeStartSize?: number
|
||||
fadeEndSize?: number
|
||||
trackTransformSelector?: string
|
||||
ref?: (el: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
export function ScrollFade(props: ScrollFadeProps) {
|
||||
const [local, others] = splitProps(props, [
|
||||
"children",
|
||||
"direction",
|
||||
"fadeStartSize",
|
||||
"fadeEndSize",
|
||||
"trackTransformSelector",
|
||||
"class",
|
||||
"style",
|
||||
"ref",
|
||||
])
|
||||
|
||||
const direction = () => local.direction ?? "vertical"
|
||||
const fadeStartSize = () => local.fadeStartSize ?? 20
|
||||
const fadeEndSize = () => local.fadeEndSize ?? 20
|
||||
|
||||
const getTransformOffset = (element: Element): number => {
|
||||
const style = getComputedStyle(element)
|
||||
const transform = style.transform
|
||||
if (!transform || transform === "none") return 0
|
||||
|
||||
const match = transform.match(/matrix(?:3d)?\(([^)]+)\)/)
|
||||
if (!match) return 0
|
||||
|
||||
const values = match[1].split(",").map((v) => parseFloat(v.trim()))
|
||||
const isHorizontal = direction() === "horizontal"
|
||||
|
||||
if (transform.startsWith("matrix3d")) {
|
||||
return isHorizontal ? -(values[12] || 0) : -(values[13] || 0)
|
||||
} else {
|
||||
return isHorizontal ? -(values[4] || 0) : -(values[5] || 0)
|
||||
}
|
||||
}
|
||||
|
||||
let containerRef: HTMLDivElement | undefined
|
||||
|
||||
const [fadeStart, setFadeStart] = createSignal(0)
|
||||
const [fadeEnd, setFadeEnd] = createSignal(0)
|
||||
const [isScrollable, setIsScrollable] = createSignal(false)
|
||||
|
||||
let lastScrollPos = 0
|
||||
let lastTransformPos = 0
|
||||
let lastScrollSize = 0
|
||||
let lastClientSize = 0
|
||||
|
||||
const updateFade = () => {
|
||||
if (!containerRef) return
|
||||
|
||||
const isHorizontal = direction() === "horizontal"
|
||||
const scrollPos = isHorizontal ? containerRef.scrollLeft : containerRef.scrollTop
|
||||
const scrollSize = isHorizontal ? containerRef.scrollWidth : containerRef.scrollHeight
|
||||
const clientSize = isHorizontal ? containerRef.clientWidth : containerRef.clientHeight
|
||||
|
||||
let transformPos = 0
|
||||
if (local.trackTransformSelector) {
|
||||
const transformElement = containerRef.querySelector(local.trackTransformSelector)
|
||||
if (transformElement) {
|
||||
transformPos = getTransformOffset(transformElement)
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveScrollPos = Math.max(scrollPos, transformPos)
|
||||
|
||||
if (
|
||||
effectiveScrollPos === lastScrollPos &&
|
||||
transformPos === lastTransformPos &&
|
||||
scrollSize === lastScrollSize &&
|
||||
clientSize === lastClientSize
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
lastScrollPos = effectiveScrollPos
|
||||
lastTransformPos = transformPos
|
||||
lastScrollSize = scrollSize
|
||||
lastClientSize = clientSize
|
||||
|
||||
const maxScroll = scrollSize - clientSize
|
||||
const canScroll = maxScroll > 1
|
||||
|
||||
setIsScrollable(canScroll)
|
||||
|
||||
if (!canScroll) {
|
||||
setFadeStart(0)
|
||||
setFadeEnd(0)
|
||||
return
|
||||
}
|
||||
|
||||
const progress = maxScroll > 0 ? effectiveScrollPos / maxScroll : 0
|
||||
|
||||
const startProgress = Math.min(progress / 0.1, 1)
|
||||
setFadeStart(startProgress * fadeStartSize())
|
||||
|
||||
const endProgress = progress > 0.9 ? (1 - progress) / 0.1 : 1
|
||||
setFadeEnd(Math.max(0, endProgress) * fadeEndSize())
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!containerRef) return
|
||||
|
||||
updateFade()
|
||||
|
||||
let rafId: number | undefined
|
||||
let isPolling = false
|
||||
let pollTimeout: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
const startPolling = () => {
|
||||
if (isPolling) return
|
||||
isPolling = true
|
||||
|
||||
const pollScroll = () => {
|
||||
updateFade()
|
||||
rafId = requestAnimationFrame(pollScroll)
|
||||
}
|
||||
rafId = requestAnimationFrame(pollScroll)
|
||||
}
|
||||
|
||||
const stopPolling = () => {
|
||||
if (!isPolling) return
|
||||
isPolling = false
|
||||
if (rafId !== undefined) {
|
||||
cancelAnimationFrame(rafId)
|
||||
rafId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const schedulePollingStop = () => {
|
||||
if (pollTimeout !== undefined) clearTimeout(pollTimeout)
|
||||
pollTimeout = setTimeout(stopPolling, 1000)
|
||||
}
|
||||
|
||||
const onActivity = () => {
|
||||
updateFade()
|
||||
if (local.trackTransformSelector) {
|
||||
startPolling()
|
||||
schedulePollingStop()
|
||||
}
|
||||
}
|
||||
|
||||
containerRef.addEventListener("scroll", onActivity, { passive: true })
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
lastScrollSize = 0
|
||||
lastClientSize = 0
|
||||
onActivity()
|
||||
})
|
||||
resizeObserver.observe(containerRef)
|
||||
|
||||
const mutationObserver = new MutationObserver(() => {
|
||||
lastScrollSize = 0
|
||||
lastClientSize = 0
|
||||
requestAnimationFrame(onActivity)
|
||||
})
|
||||
mutationObserver.observe(containerRef, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
containerRef?.removeEventListener("scroll", onActivity)
|
||||
resizeObserver.disconnect()
|
||||
mutationObserver.disconnect()
|
||||
stopPolling()
|
||||
if (pollTimeout !== undefined) clearTimeout(pollTimeout)
|
||||
})
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
local.children
|
||||
requestAnimationFrame(updateFade)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(el) => {
|
||||
containerRef = el
|
||||
local.ref?.(el)
|
||||
}}
|
||||
data-component="scroll-fade"
|
||||
data-direction={direction()}
|
||||
data-scrollable={isScrollable() || undefined}
|
||||
data-fade-start={fadeStart() > 0 || undefined}
|
||||
data-fade-end={fadeEnd() > 0 || undefined}
|
||||
class={local.class}
|
||||
style={{
|
||||
...(typeof local.style === "object" ? local.style : {}),
|
||||
"--scroll-fade-start": `${fadeStart()}px`,
|
||||
"--scroll-fade-end": `${fadeEnd()}px`,
|
||||
}}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import { type JSX, onCleanup, splitProps } from "solid-js"
|
||||
import { ScrollFade, type ScrollFadeProps } from "./scroll-fade"
|
||||
|
||||
const SCROLL_SPEED = 60
|
||||
const PAUSE_DURATION = 800
|
||||
|
||||
type ScrollAnimationState = {
|
||||
rafId: number | null
|
||||
startTime: number
|
||||
running: boolean
|
||||
}
|
||||
|
||||
const startScrollAnimation = (containerEl: HTMLElement): ScrollAnimationState | null => {
|
||||
containerEl.offsetHeight
|
||||
|
||||
const extraWidth = containerEl.scrollWidth - containerEl.clientWidth
|
||||
|
||||
if (extraWidth <= 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const scrollDuration = (extraWidth / SCROLL_SPEED) * 1000
|
||||
const totalDuration = PAUSE_DURATION + scrollDuration + PAUSE_DURATION + scrollDuration + PAUSE_DURATION
|
||||
|
||||
const state: ScrollAnimationState = {
|
||||
rafId: null,
|
||||
startTime: performance.now(),
|
||||
running: true,
|
||||
}
|
||||
|
||||
const animate = (currentTime: number) => {
|
||||
if (!state.running) return
|
||||
|
||||
const elapsed = currentTime - state.startTime
|
||||
const progress = (elapsed % totalDuration) / totalDuration
|
||||
|
||||
const pausePercent = PAUSE_DURATION / totalDuration
|
||||
const scrollPercent = scrollDuration / totalDuration
|
||||
|
||||
const pauseEnd1 = pausePercent
|
||||
const scrollEnd1 = pauseEnd1 + scrollPercent
|
||||
const pauseEnd2 = scrollEnd1 + pausePercent
|
||||
const scrollEnd2 = pauseEnd2 + scrollPercent
|
||||
|
||||
let scrollPos = 0
|
||||
|
||||
if (progress < pauseEnd1) {
|
||||
scrollPos = 0
|
||||
} else if (progress < scrollEnd1) {
|
||||
const scrollProgress = (progress - pauseEnd1) / scrollPercent
|
||||
scrollPos = scrollProgress * extraWidth
|
||||
} else if (progress < pauseEnd2) {
|
||||
scrollPos = extraWidth
|
||||
} else if (progress < scrollEnd2) {
|
||||
const scrollProgress = (progress - pauseEnd2) / scrollPercent
|
||||
scrollPos = extraWidth * (1 - scrollProgress)
|
||||
} else {
|
||||
scrollPos = 0
|
||||
}
|
||||
|
||||
containerEl.scrollLeft = scrollPos
|
||||
state.rafId = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
state.rafId = requestAnimationFrame(animate)
|
||||
return state
|
||||
}
|
||||
|
||||
const stopScrollAnimation = (state: ScrollAnimationState | null, containerEl?: HTMLElement) => {
|
||||
if (state) {
|
||||
state.running = false
|
||||
if (state.rafId !== null) {
|
||||
cancelAnimationFrame(state.rafId)
|
||||
}
|
||||
}
|
||||
if (containerEl) {
|
||||
containerEl.scrollLeft = 0
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScrollRevealProps extends Omit<ScrollFadeProps, "direction"> {
|
||||
hoverDelay?: number
|
||||
}
|
||||
|
||||
export function ScrollReveal(props: ScrollRevealProps) {
|
||||
const [local, others] = splitProps(props, ["children", "hoverDelay", "ref"])
|
||||
|
||||
const hoverDelay = () => local.hoverDelay ?? 300
|
||||
|
||||
let containerRef: HTMLDivElement | undefined
|
||||
let hoverTimeout: ReturnType<typeof setTimeout> | undefined
|
||||
let scrollAnimationState: ScrollAnimationState | null = null
|
||||
|
||||
const handleMouseEnter: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
if (!containerRef) return
|
||||
|
||||
containerRef.offsetHeight
|
||||
|
||||
const isScrollable = containerRef.scrollWidth > containerRef.clientWidth + 1
|
||||
|
||||
if (isScrollable) {
|
||||
stopScrollAnimation(scrollAnimationState, containerRef)
|
||||
scrollAnimationState = startScrollAnimation(containerRef)
|
||||
}
|
||||
}, hoverDelay())
|
||||
}
|
||||
|
||||
const handleMouseLeave: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
|
||||
if (hoverTimeout) {
|
||||
clearTimeout(hoverTimeout)
|
||||
hoverTimeout = undefined
|
||||
}
|
||||
stopScrollAnimation(scrollAnimationState, containerRef)
|
||||
scrollAnimationState = null
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
if (hoverTimeout) {
|
||||
clearTimeout(hoverTimeout)
|
||||
}
|
||||
stopScrollAnimation(scrollAnimationState, containerRef)
|
||||
})
|
||||
|
||||
return (
|
||||
<ScrollFade
|
||||
ref={(el) => {
|
||||
containerRef = el
|
||||
local.ref?.(el)
|
||||
}}
|
||||
fadeStartSize={8}
|
||||
fadeEndSize={8}
|
||||
direction="horizontal"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</ScrollFade>
|
||||
)
|
||||
}
|
||||
|
|
@ -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,42 +30,78 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[data-trigger-style="settings"] {
|
||||
[data-slot="select-select-trigger"] {
|
||||
padding: 6px 6px 6px 12px;
|
||||
box-shadow: none;
|
||||
border-radius: 6px;
|
||||
min-width: 160px;
|
||||
height: 32px;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
background-color: transparent;
|
||||
|
||||
[data-slot="select-select-trigger-value"] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
[data-slot="select-select-trigger-icon"] {
|
||||
width: 16px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-weak);
|
||||
background-color: var(--surface-raised-base);
|
||||
border-radius: 4px;
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
&[data-slot="select-select-trigger"]:hover:not(:disabled),
|
||||
&[data-slot="select-select-trigger"][data-expanded],
|
||||
&[data-slot="select-select-trigger"][data-expanded]:hover:not(:disabled) {
|
||||
background-color: var(--input-base);
|
||||
box-shadow: var(--shadow-xs-border-base);
|
||||
}
|
||||
|
||||
&:not([data-expanded]):focus {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[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"] {
|
||||
|
|
@ -81,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);
|
||||
|
|
@ -125,11 +160,6 @@
|
|||
margin-left: auto;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--icon-strong-base);
|
||||
|
||||
svg {
|
||||
color: var(--icon-strong-base);
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
|
@ -140,24 +170,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes selectContentShow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scaleY(0.95);
|
||||
[data-component="select-content"][data-trigger-style="settings"] {
|
||||
min-width: 160px;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
|
||||
[data-slot="select-select-content-list"] {
|
||||
padding: 4px;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
|
||||
[data-slot="select-select-item"] {
|
||||
/* text-14-regular */
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-base);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--line-height-large);
|
||||
letter-spacing: var(--letter-spacing-normal);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes selectContentHide {
|
||||
@keyframes select-open {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scaleY(0.95);
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect" | "children"> & {
|
||||
placeholder?: string
|
||||
|
|
@ -40,8 +38,6 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
|||
"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<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
|||
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<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
|||
: (itemProps.item.rawValue as string)}
|
||||
</Kobalte.ItemLabel>
|
||||
<Kobalte.ItemIndicator data-slot="select-select-item-indicator">
|
||||
<Icon name="check" size="small" />
|
||||
<Icon name="check-small" size="small" />
|
||||
</Kobalte.ItemIndicator>
|
||||
</Kobalte.Item>
|
||||
)}
|
||||
|
|
@ -128,7 +124,6 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
|||
stop()
|
||||
}}
|
||||
onOpenChange={(open) => {
|
||||
setIsOpen(open)
|
||||
local.onOpenChange?.(open)
|
||||
if (!open) stop()
|
||||
}}
|
||||
|
|
@ -154,12 +149,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
|
|||
}}
|
||||
</Kobalte.Value>
|
||||
<Kobalte.Icon data-slot="select-select-trigger-icon">
|
||||
<Show when={local.triggerVariant === "settings"}>
|
||||
<Icon name="selector" size="small" />
|
||||
</Show>
|
||||
<Show when={local.triggerVariant !== "settings"}>
|
||||
<MorphChevron expanded={isOpen()} />
|
||||
</Show>
|
||||
<Icon name={local.triggerVariant === "settings" ? "selector" : "chevron-down"} size="small" />
|
||||
</Kobalte.Icon>
|
||||
</Kobalte.Trigger>
|
||||
<Kobalte.Portal>
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@
|
|||
|
||||
[data-slot="accordion-item"] {
|
||||
[data-slot="accordion-content"] {
|
||||
/* Use grid-template-rows for smooth height transition */
|
||||
display: grid;
|
||||
display: none;
|
||||
}
|
||||
&[data-expanded] {
|
||||
[data-slot="accordion-content"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,9 +130,7 @@
|
|||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition-property: opacity, background-color, color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-strong);
|
||||
|
|
|
|||
|
|
@ -290,8 +290,8 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
<div data-slot="session-review-title">{i18n.t("ui.sessionReview.title")}</div>
|
||||
<div data-slot="session-review-actions">
|
||||
<Show when={props.onDiffStyleChange}>
|
||||
<RadioGroup<SessionReviewDiffStyle>
|
||||
options={["unified", "split"]}
|
||||
<RadioGroup
|
||||
options={["unified", "split"] as const}
|
||||
current={diffStyle()}
|
||||
value={(style) => style}
|
||||
label={(style) =>
|
||||
|
|
@ -501,7 +501,6 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
value={diff.file}
|
||||
id={diffId(diff.file)}
|
||||
data-file={diff.file}
|
||||
expanded={open().includes(diff.file)}
|
||||
data-slot="session-review-accordion-item"
|
||||
data-selected={props.focusedFile === diff.file ? "" : undefined}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -102,11 +102,10 @@
|
|||
|
||||
[data-component="user-message"] [data-slot="user-message-text"] {
|
||||
max-height: var(--user-message-collapsed-height, 64px);
|
||||
transition: max-height 200ms cubic-bezier(0.25, 0, 0.5, 1);
|
||||
}
|
||||
|
||||
[data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] {
|
||||
max-height: 2000px;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
[data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] {
|
||||
|
|
@ -152,6 +151,17 @@
|
|||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--text-weak);
|
||||
|
||||
[data-slot="icon-svg"] {
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="user-message"][data-expanded="true"]
|
||||
[data-slot="user-message-text"]
|
||||
[data-slot="user-message-expand"]
|
||||
[data-slot="icon-svg"] {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover {
|
||||
|
|
@ -457,7 +467,6 @@
|
|||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: var(--icon-base);
|
||||
}
|
||||
|
||||
[data-slot="session-turn-accordion-content"] {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@
|
|||
border-radius: 3px;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background: var(--surface-base);
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition:
|
||||
background-color 150ms,
|
||||
border-color 150ms;
|
||||
}
|
||||
|
||||
[data-slot="switch-thumb"] {
|
||||
|
|
@ -47,9 +47,9 @@
|
|||
0 1px 3px 0 rgba(19, 16, 16, 0.08);
|
||||
|
||||
transform: translateX(-1px);
|
||||
transition-property: transform, background-color, border-color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
transition:
|
||||
transform 150ms,
|
||||
background-color 150ms;
|
||||
}
|
||||
|
||||
[data-slot="switch-label"] {
|
||||
|
|
|
|||
|
|
@ -58,9 +58,6 @@
|
|||
border-bottom: 1px solid var(--border-weak-base);
|
||||
border-right: 1px solid var(--border-weak-base);
|
||||
background-color: var(--background-base);
|
||||
transition-property: background-color, border-color, color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
|
||||
[data-slot="tabs-trigger"] {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
border: 0.5px solid var(--border-weak-base);
|
||||
background: var(--surface-raised-base);
|
||||
color: var(--text-base);
|
||||
transition-property: background-color, border-color, color;
|
||||
transition-duration: var(--transition-duration);
|
||||
transition-timing-function: var(--transition-easing);
|
||||
|
||||
&[data-size="normal"] {
|
||||
height: 18px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue