feat(app): v2 wsl ui (#34233)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
Aarav Sareen 2026-07-01 13:09:00 +05:30 committed by GitHub
commit 4d91f0cf28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1612 additions and 618 deletions

View file

@ -28,7 +28,7 @@
outline: none;
}
[data-component="button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled) {
[data-component="button-v2"]:is(:focus-visible, [data-state="focus"]):not(:disabled):not([data-variant="loading"]) {
outline: 2px solid var(--v2-border-border-focus);
outline-offset: 2.5px;
}
@ -170,6 +170,15 @@
cursor: not-allowed;
}
/* Loading */
[data-component="button-v2"][data-variant="loading"] {
background: #f2f2f2;
color: var(--v2-text-text-base);
box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.08);
cursor: default;
pointer-events: none;
}
/* Ghost */
[data-component="button-v2"][data-variant="ghost"] {
background-color: transparent;

View file

@ -1,11 +1,11 @@
import { ButtonV2 } from "./button-v2"
const docs = `### Overview
Button v2 with three visual variants and two sizes.
Button v2 with visual variants and three sizes.
### API
- \`variant\`: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted".
- \`size\`: "normal" | "large".
- \`variant\`: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted" | "loading".
- \`size\`: "small" | "normal" | "large".
- \`icon\`: Optional icon name.
- Inherits Kobalte Button props and native button attributes.
@ -39,7 +39,7 @@ export default {
},
variant: {
control: "select",
options: ["neutral", "danger", "contrast", "ghost", "ghost-muted"],
options: ["neutral", "danger", "contrast", "ghost", "ghost-muted", "loading"],
},
size: {
control: "select",
@ -67,6 +67,7 @@ export const Variants = {
<ButtonV2 variant="ghost-muted" icon="edit">
Ghost muted
</ButtonV2>
<ButtonV2 variant="loading">Loading</ButtonV2>
</div>
),
}
@ -116,7 +117,7 @@ export const Icon = {
export const AllStates = {
render: () => {
const variants = ["neutral", "danger", "contrast", "ghost", "ghost-muted"] as const
const variants = ["neutral", "danger", "contrast", "ghost", "ghost-muted", "loading"] as const
const states = ["default", "hover", "pressed", "focus", "disabled"] as const
const toTitleCase = (value: string) => value.charAt(0).toUpperCase() + value.slice(1)
return (

View file

@ -7,7 +7,7 @@ export interface ButtonV2Props
extends ComponentProps<typeof Kobalte>,
Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
size?: "small" | "normal" | "large"
variant?: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted" | "outline"
variant?: "neutral" | "danger" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading"
icon?: IconProps["name"]
}

View file

@ -0,0 +1,32 @@
[data-component="loader-v2"] {
--_duration: 900ms;
display: block;
flex-shrink: 0;
color: var(--v2-icon-icon-muted, #808080);
animation: loader-v2-spin var(--_duration) linear infinite;
transform-origin: center;
}
[data-component="loader-v2"] [data-slot="loader-v2-background"] {
stroke: currentColor;
opacity: 0.2;
}
[data-component="loader-v2"] [data-slot="loader-v2-progress"] {
stroke: currentColor;
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: center;
}
@keyframes loader-v2-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
[data-component="loader-v2"] {
animation: none;
}
}

View file

@ -0,0 +1,42 @@
import { LoaderV2 } from "./loader-v2"
const docs = `### Overview
Circular v2 loader for compact loading states.
### API
- Accepts standard SVG props.
### Behavior
- The foreground ring covers 33% of the circumference and rotates continuously.
### Accessibility
- Sets \`aria-hidden="true"\` by default.
`
export default {
title: "UI V2/Loader",
id: "components-loader-v2",
component: LoaderV2,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: docs,
},
},
},
}
export const Basic = {
render: () => <LoaderV2 />,
}
export const Sizes = {
render: () => (
<div style={{ display: "flex", gap: "16px", "align-items": "center" }}>
<LoaderV2 width={12} height={12} />
<LoaderV2 />
<LoaderV2 width={24} height={24} />
</div>
),
}

View file

@ -0,0 +1,31 @@
import { splitProps, type ComponentProps } from "solid-js"
import "./loader-v2.css"
export function LoaderV2(props: ComponentProps<"svg">) {
const [local, rest] = splitProps(props, ["class", "classList", "width", "height"])
return (
<svg
{...rest}
class={local.class}
classList={local.classList}
width={local.width ?? 16}
height={local.height ?? 16}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-component="loader-v2"
aria-hidden={rest["aria-hidden"] ?? "true"}
>
<circle cx="8" cy="8" r="6" data-slot="loader-v2-background" stroke-width="2" />
<circle
cx="8"
cy="8"
r="6"
data-slot="loader-v2-progress"
pathLength="100"
stroke-width="2"
stroke-dasharray="33 67"
/>
</svg>
)
}

View file

@ -45,6 +45,7 @@
}
[data-slot="radio-v2-item"] {
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
@ -131,10 +132,6 @@
linear-gradient(180deg, var(--v2-alpha-light-20) 0%, var(--v2-alpha-light-0) 100%), var(--v2-background-bg-accent);
}
&:where([data-checked][data-disabled]) [data-slot="radio-v2-item-control"] {
opacity: 0.5;
}
&:where([data-invalid]):where(:not([data-checked])) [data-slot="radio-v2-item-control"] {
background: var(--v2-state-bg-danger);
box-shadow: inset 0 0 0 0.5px var(--v2-state-border-danger);
@ -195,8 +192,11 @@
user-select: none;
}
&:where([data-disabled]) [data-slot="radio-v2-item-label"],
&:where([data-disabled]) [data-slot="radio-v2-item-label-text"] {
color: var(--v2-text-text-muted);
}
&:where([data-disabled]) [data-slot="radio-v2-item-description"] {
opacity: 0.5;
color: var(--v2-text-text-faint);
}
}