mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(ui): restore pixel-grid spinners across the app
This commit is contained in:
@@ -207,31 +207,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
[data-slot="titlebar-update-loader"] {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
margin: 1px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 9999px;
|
||||
border: 2px solid color-mix(in srgb, var(--v2-icon-icon-accent) 30%, transparent);
|
||||
border-top-color: var(--v2-icon-icon-accent);
|
||||
animation: titlebar-update-loader-spin 0.67s linear infinite;
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
:root[data-motion="reduce"] {
|
||||
[data-slot="titlebar-update-loader"] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes titlebar-update-loader-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes home-projects-fade-top {
|
||||
from {
|
||||
visibility: hidden;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { syncSpinPhase } from "@opencode/ui/hooks"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { Spinner } from "@opencode/ui/spinner"
|
||||
import { TitlebarItem } from "@opencode/gui-extensions/sdk"
|
||||
import { useExtensionHost } from "./host"
|
||||
|
||||
@@ -73,7 +73,7 @@ function TitlebarItemButton(props: { item: TitlebarItem; vertical?: boolean }) {
|
||||
>
|
||||
<Show
|
||||
when={!props.item.busy}
|
||||
fallback={<span ref={syncSpinPhase} data-slot="titlebar-update-loader" aria-hidden="true" />}
|
||||
fallback={<Spinner class="m-px size-3" aria-hidden="true" />}
|
||||
>
|
||||
<Show
|
||||
when={props.item.icon}
|
||||
|
||||
@@ -4,3 +4,7 @@
|
||||
width: 18px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
:root[data-motion="reduce"] [data-slot="spinner-grid"] rect {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
@@ -15,17 +15,14 @@ const squares = Array.from({ length: 16 }, (_, i) => ({
|
||||
corner: cornerIndices.has(i),
|
||||
}))
|
||||
|
||||
export function Spinner(props: {
|
||||
class?: string
|
||||
classList?: ComponentProps<"div">["classList"]
|
||||
style?: ComponentProps<"div">["style"]
|
||||
}) {
|
||||
export function Spinner(props: ComponentProps<"svg"> & { "data-component"?: string }) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
ref={syncSpinPhase}
|
||||
viewBox="0 0 15 15"
|
||||
data-component="spinner"
|
||||
data-component={props["data-component"] ?? "spinner"}
|
||||
data-slot="spinner-grid"
|
||||
classList={{
|
||||
...props.classList,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
|
||||
@@ -2,31 +2,4 @@
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
color: var(--v2-icon-icon-muted);
|
||||
/* The tail fades in to a solid head; the head's round cap still gets full colour just past the arc. */
|
||||
mask-image: conic-gradient(from 90deg, transparent 0turn, black 0.72turn, black 0.78turn, transparent 0.9turn);
|
||||
animation: activity-ring-spin 800ms linear infinite;
|
||||
}
|
||||
|
||||
[data-component="activity-ring"] circle {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
@keyframes activity-ring-spin {
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
[data-component="activity-ring"] {
|
||||
mask-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-motion="reduce"] {
|
||||
[data-component="activity-ring"] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import { splitProps, type ComponentProps } from "solid-js"
|
||||
import { syncSpinPhase } from "../../hooks/spin-phase"
|
||||
import { type ComponentProps } from "solid-js"
|
||||
import { Spinner } from "../../components/spinner"
|
||||
import "./activity-ring.css"
|
||||
|
||||
// An open arc in constant rotation, matching the iOS drawer's busy ring.
|
||||
export function ActivityRing(props: ComponentProps<"svg">) {
|
||||
const [local, rest] = splitProps(props, ["class", "classList", "width", "height"])
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...rest}
|
||||
ref={syncSpinPhase}
|
||||
class={local.class}
|
||||
classList={local.classList}
|
||||
width={local.width ?? 14}
|
||||
height={local.height ?? 14}
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<Spinner
|
||||
{...props}
|
||||
width={props.width ?? 14}
|
||||
height={props.height ?? 14}
|
||||
data-component="activity-ring"
|
||||
aria-hidden={rest["aria-hidden"] ?? "true"}
|
||||
>
|
||||
<circle cx="7" cy="7" r="6.1" pathLength="100" stroke-dasharray="72 28" />
|
||||
</svg>
|
||||
aria-hidden={props["aria-hidden"] ?? "true"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-motion="reduce"] {
|
||||
[data-component="loader-v2"] {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Loader } from "./loader"
|
||||
|
||||
const docs = `### Overview
|
||||
Circular v2 loader for compact loading states.
|
||||
Pixel-grid loader for compact loading states.
|
||||
|
||||
### API
|
||||
- Accepts standard SVG props.
|
||||
|
||||
### Behavior
|
||||
- The foreground ring covers 33% of the circumference and rotates continuously.
|
||||
- Grid pixels pulse in opacity using the shared spinner animation.
|
||||
|
||||
### Accessibility
|
||||
- Sets \`aria-hidden="true"\` by default.
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
import { splitProps, type ComponentProps } from "solid-js"
|
||||
import { syncSpinPhase } from "../../hooks/spin-phase"
|
||||
import { type ComponentProps } from "solid-js"
|
||||
import { Spinner } from "../../components/spinner"
|
||||
import "./loader.css"
|
||||
|
||||
export function Loader(props: ComponentProps<"svg">) {
|
||||
const [local, rest] = splitProps(props, ["class", "classList", "width", "height"])
|
||||
|
||||
return (
|
||||
<svg
|
||||
{...rest}
|
||||
ref={syncSpinPhase}
|
||||
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"
|
||||
<Spinner
|
||||
{...props}
|
||||
width={props.width ?? 16}
|
||||
height={props.height ?? 16}
|
||||
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>
|
||||
aria-hidden={props["aria-hidden"] ?? "true"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user