Co-authored-by: adamelmore <2363879+adamdottv@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user