feat: add new spinner component

This commit is contained in:
reya 2024-04-13 15:01:27 +07:00
parent 89f577fbef
commit ed6aca41ea
29 changed files with 177 additions and 102 deletions

View File

@ -60,3 +60,63 @@ input::-ms-clear {
::-webkit-input-placeholder { ::-webkit-input-placeholder {
line-height: normal; line-height: normal;
} }
.spinner-leaf {
position: absolute;
top: 0;
left: calc(50% - 12.5% / 2);
width: 12.5%;
height: 100%;
animation: spinner-leaf-fade 800ms linear infinite;
&::before {
content: "";
display: block;
width: 100%;
height: 30%;
background-color: currentColor;
@apply rounded;
}
&:where(:nth-child(1)) {
transform: rotate(0deg);
animation-delay: -800ms;
}
&:where(:nth-child(2)) {
transform: rotate(45deg);
animation-delay: -700ms;
}
&:where(:nth-child(3)) {
transform: rotate(90deg);
animation-delay: -600ms;
}
&:where(:nth-child(4)) {
transform: rotate(135deg);
animation-delay: -500ms;
}
&:where(:nth-child(5)) {
transform: rotate(180deg);
animation-delay: -400ms;
}
&:where(:nth-child(6)) {
transform: rotate(225deg);
animation-delay: -300ms;
}
&:where(:nth-child(7)) {
transform: rotate(270deg);
animation-delay: -200ms;
}
&:where(:nth-child(8)) {
transform: rotate(315deg);
animation-delay: -100ms;
}
}
@keyframes spinner-leaf-fade {
from {
opacity: 1;
}
to {
opacity: 0.25;
}
}

View File

@ -1,4 +1,4 @@
import { LoaderIcon } from "@lume/icons"; import { Spinner } from "@lume/ui";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Dispatch, ReactNode, SetStateAction, useState } from "react"; import { Dispatch, ReactNode, SetStateAction, useState } from "react";
@ -36,7 +36,7 @@ export function AvatarUploader({
onClick={() => uploadAvatar()} onClick={() => uploadAvatar()}
className={cn("size-4", className)} className={cn("size-4", className)}
> >
{loading ? <LoaderIcon className="size-4 animate-spin" /> : children} {loading ? <Spinner className="size-4" /> : children}
</button> </button>
); );
} }

View File

@ -1,7 +1,7 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { LumeColumn } from "@lume/types"; import { LumeColumn } from "@lume/types";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { LoaderIcon } from "@lume/icons"; import { Spinner } from "@lume/ui";
export function Col({ export function Col({
column, column,
@ -68,7 +68,7 @@ export function Col({
{column.label !== "open" ? ( {column.label !== "open" ? (
<div className="w-full h-full flex items-center justify-center rounded-xl flex-col bg-black/5 dark:bg-white/5 backdrop-blur-lg"> <div className="w-full h-full flex items-center justify-center rounded-xl flex-col bg-black/5 dark:bg-white/5 backdrop-blur-lg">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
) : null} ) : null}

View File

@ -1,7 +1,8 @@
import { Col } from "@/components/col"; import { Col } from "@/components/col";
import { Toolbar } from "@/components/toolbar"; import { Toolbar } from "@/components/toolbar";
import { ArrowLeftIcon, ArrowRightIcon, LoaderIcon } from "@lume/icons"; import { ArrowLeftIcon, ArrowRightIcon } from "@lume/icons";
import { EventColumns, LumeColumn } from "@lume/types"; import { EventColumns, LumeColumn } from "@lume/types";
import { Spinner } from "@lume/ui";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { resolveResource } from "@tauri-apps/api/path"; import { resolveResource } from "@tauri-apps/api/path";
@ -148,7 +149,7 @@ function Pending() {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
); );

View File

@ -1,9 +1,9 @@
import { LoaderIcon } from "@lume/icons";
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import { type Ark } from "@lume/ark"; import { type Ark } from "@lume/ark";
import { type QueryClient } from "@tanstack/react-query"; import { type QueryClient } from "@tanstack/react-query";
import { type Platform } from "@tauri-apps/plugin-os"; import { type Platform } from "@tauri-apps/plugin-os";
import { Account, Interests, Settings } from "@lume/types"; import { Account, Interests, Settings } from "@lume/types";
import { Spinner } from "@lume/ui";
interface RouterContext { interface RouterContext {
ark: Ark; ark: Ark;
@ -25,7 +25,7 @@ function Pending() {
return ( return (
<div className="flex h-screen w-screen flex-col items-center justify-center"> <div className="flex h-screen w-screen flex-col items-center justify-center">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
); );

View File

@ -1,8 +1,8 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon, ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons"; import { ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons";
import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { ColumnRouteSearch, Event, Kind } from "@lume/types";
import { Column } from "@lume/ui"; import { Column, Spinner } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router"; import { Link, createFileRoute } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
@ -53,7 +53,7 @@ export function Screen() {
<Column.Content> <Column.Content>
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</div> </div>
) : !data.length ? ( ) : !data.length ? (
<Empty /> <Empty />
@ -71,7 +71,7 @@ export function Screen() {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -1,6 +1,7 @@
import { AvatarUploader } from "@/components/avatarUploader"; import { AvatarUploader } from "@/components/avatarUploader";
import { LoaderIcon, PlusIcon } from "@lume/icons"; import { PlusIcon } from "@lume/icons";
import { Metadata } from "@lume/types"; import { Metadata } from "@lume/types";
import { Spinner } from "@lume/ui";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -134,11 +135,7 @@ function Screen() {
type="submit" type="submit"
className="mt-3 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" className="mt-3 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
> >
{loading ? ( {loading ? <Spinner /> : t("global.continue")}
<LoaderIcon className="size-4 animate-spin" />
) : (
t("global.continue")
)}
</button> </button>
</form> </form>
</div> </div>

View File

@ -1,4 +1,4 @@
import { LoaderIcon } from "@lume/icons"; import { Spinner } from "@lume/ui";
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router"; import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@ -82,7 +82,7 @@ function Screen() {
disabled={loading} disabled={loading}
className="mt-3 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" className="mt-3 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50"
> >
{loading ? <LoaderIcon className="size-4 animate-spin" /> : "Login"} {loading ? <Spinner /> : "Login"}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { LaurelIcon, LoaderIcon } from "@lume/icons"; import { LaurelIcon } from "@lume/icons";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import * as Switch from "@radix-ui/react-switch"; import * as Switch from "@radix-ui/react-switch";
@ -9,6 +9,7 @@ import {
requestPermission, requestPermission,
} from "@tauri-apps/plugin-notification"; } from "@tauri-apps/plugin-notification";
import { toast } from "sonner"; import { toast } from "sonner";
import { Spinner } from "@lume/ui";
export const Route = createFileRoute("/auth/settings")({ export const Route = createFileRoute("/auth/settings")({
validateSearch: (search: Record<string, string>): AppRouteSearch => { validateSearch: (search: Record<string, string>): AppRouteSearch => {
@ -181,7 +182,7 @@ function Pending() {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
); );

View File

@ -1,4 +1,4 @@
import { AddMediaIcon, LoaderIcon } from "@lume/icons"; import { AddMediaIcon } from "@lume/icons";
import { cn, insertImage, isImagePath } from "@lume/utils"; import { cn, insertImage, isImagePath } from "@lume/utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSlateStatic } from "slate-react"; import { useSlateStatic } from "slate-react";
@ -6,6 +6,7 @@ import { toast } from "sonner";
import { getCurrent } from "@tauri-apps/api/window"; import { getCurrent } from "@tauri-apps/api/window";
import { UnlistenFn } from "@tauri-apps/api/event"; import { UnlistenFn } from "@tauri-apps/api/event";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "@lume/ui";
export function MediaButton({ className }: { className?: string }) { export function MediaButton({ className }: { className?: string }) {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -69,7 +70,7 @@ export function MediaButton({ className }: { className?: string }) {
className={cn("inline-flex items-center justify-center", className)} className={cn("inline-flex items-center justify-center", className)}
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<AddMediaIcon className="size-5" /> <AddMediaIcon className="size-5" />
)} )}

View File

@ -31,7 +31,7 @@ import {
Editable, Editable,
} from "slate-react"; } from "slate-react";
import { Contact } from "@lume/types"; import { Contact } from "@lume/types";
import { User } from "@lume/ui"; import { Spinner, User } from "@lume/ui";
import { nip19 } from "nostr-tools"; import { nip19 } from "nostr-tools";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@ -212,11 +212,7 @@ function Screen() {
onClick={publish} onClick={publish}
className="inline-flex h-9 w-24 items-center justify-center rounded-full bg-blue-500 px-3 font-medium text-white hover:bg-blue-600" className="inline-flex h-9 w-24 items-center justify-center rounded-full bg-blue-500 px-3 font-medium text-white hover:bg-blue-600"
> >
{loading ? ( {loading ? <Spinner className="size-5" /> : t("global.post")}
<LoaderIcon className="size-5 animate-spin" />
) : (
t("global.post")
)}
</button> </button>
</div> </div>
<div className="flex h-full min-h-0 w-full"> <div className="flex h-full min-h-0 w-full">
@ -280,7 +276,7 @@ function Pending() {
className="flex h-full w-full items-center justify-center gap-2.5" className="flex h-full w-full items-center justify-center gap-2.5"
> >
<button type="button" disabled> <button type="button" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
<p>Loading cache...</p> <p>Loading cache...</p>
</div> </div>

View File

@ -1,6 +1,5 @@
import { useEvent } from "@lume/ark"; import { useEvent } from "@lume/ark";
import { LoaderIcon } from "@lume/icons"; import { Box, Container, Note, Spinner, User } from "@lume/ui";
import { Box, Container, Note, User } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { ReplyList } from "./-components/replyList"; import { ReplyList } from "./-components/replyList";
import { WindowVirtualizer } from "virtua"; import { WindowVirtualizer } from "virtua";
@ -17,7 +16,7 @@ function Event() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5 animate-spin" />
</div> </div>
); );
} }

View File

@ -1,10 +1,10 @@
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { EventWithReplies } from "@lume/types"; import { EventWithReplies } from "@lume/types";
import { Reply } from "./reply"; import { Reply } from "./reply";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "@lume/ui";
export function ReplyList({ export function ReplyList({
eventId, eventId,
@ -29,7 +29,7 @@ export function ReplyList({
<div className={cn("flex flex-col gap-3", className)}> <div className={cn("flex flex-col gap-3", className)}>
{!data ? ( {!data ? (
<div className="mt-4 flex h-16 items-center justify-center p-3"> <div className="mt-4 flex h-16 items-center justify-center p-3">
<LoaderIcon className="h-5 w-5 animate-spin" /> <Spinner className="size-5" />
</div> </div>
) : data.length === 0 ? ( ) : data.length === 0 ? (
<div className="mt-4 flex w-full items-center justify-center"> <div className="mt-4 flex w-full items-center justify-center">

View File

@ -1,8 +1,8 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon, ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons"; import { ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons";
import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { ColumnRouteSearch, Event, Kind } from "@lume/types";
import { Column } from "@lume/ui"; import { Column, Spinner } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Link, createFileRoute, redirect } from "@tanstack/react-router"; import { Link, createFileRoute, redirect } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
@ -77,7 +77,7 @@ export function Screen() {
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
) : !data.length ? ( ) : !data.length ? (
@ -97,7 +97,7 @@ export function Screen() {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -1,8 +1,8 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon, ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons"; import { ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons";
import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { ColumnRouteSearch, Event, Kind } from "@lume/types";
import { Column } from "@lume/ui"; import { Column, Spinner } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router"; import { Link, createFileRoute } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
@ -60,7 +60,7 @@ export function Screen() {
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<button type="button" className="size-5" disabled> <button type="button" className="size-5" disabled>
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</button> </button>
</div> </div>
) : !data.length ? ( ) : !data.length ? (
@ -79,7 +79,7 @@ export function Screen() {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -1,8 +1,8 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon, ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons"; import { ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons";
import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { ColumnRouteSearch, Event, Kind } from "@lume/types";
import { Column } from "@lume/ui"; import { Column, Spinner } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Link, createFileRoute, redirect } from "@tanstack/react-router"; import { Link, createFileRoute, redirect } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
@ -71,7 +71,7 @@ export function Screen() {
<Column.Content> <Column.Content>
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</div> </div>
) : !data.length ? ( ) : !data.length ? (
<Empty /> <Empty />
@ -89,7 +89,7 @@ export function Screen() {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -1,5 +1,5 @@
import { LoaderIcon, PlusIcon } from "@lume/icons"; import { PlusIcon } from "@lume/icons";
import { User } from "@lume/ui"; import { Spinner, User } from "@lume/ui";
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
@ -73,7 +73,7 @@ function Screen() {
<div className="flex items-center justify-center gap-6"> <div className="flex items-center justify-center gap-6">
{loading ? ( {loading ? (
<div className="inline-flex size-6 items-center justify-center"> <div className="inline-flex size-6 items-center justify-center">
<LoaderIcon className="size-6 animate-spin text-white" /> <Spinner className="size-6" />
</div> </div>
) : ( ) : (
<> <>

View File

@ -1,8 +1,8 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon, ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons"; import { ArrowRightCircleIcon, ArrowRightIcon } from "@lume/icons";
import { ColumnRouteSearch, Event, Kind } from "@lume/types"; import { ColumnRouteSearch, Event, Kind } from "@lume/types";
import { Column } from "@lume/ui"; import { Column, Spinner } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Link, createFileRoute } from "@tanstack/react-router"; import { Link, createFileRoute } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
@ -59,9 +59,7 @@ export function Screen() {
<Column.Content> <Column.Content>
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<button type="button" className="size-5" disabled> <Spinner className="size-5" />
<LoaderIcon className="size-5 animate-spin" />
</button>
</div> </div>
) : !data.length ? ( ) : !data.length ? (
<Empty /> <Empty />
@ -79,7 +77,7 @@ export function Screen() {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -1,11 +1,11 @@
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { LoaderIcon } from "@lume/icons";
import { Event, Kind } from "@lume/types"; import { Event, Kind } from "@lume/types";
import { Await, createFileRoute } from "@tanstack/react-router"; import { Await, createFileRoute } from "@tanstack/react-router";
import { Virtualizer } from "virtua"; import { Virtualizer } from "virtua";
import { defer } from "@tanstack/react-router"; import { defer } from "@tanstack/react-router";
import { Suspense } from "react"; import { Suspense } from "react";
import { Spinner } from "@lume/ui";
export const Route = createFileRoute("/trending/notes")({ export const Route = createFileRoute("/trending/notes")({
loader: async ({ abortController }) => { loader: async ({ abortController }) => {
@ -50,7 +50,7 @@ export function Screen() {
className="inline-flex items-center gap-2 text-sm font-medium" className="inline-flex items-center gap-2 text-sm font-medium"
disabled disabled
> >
<LoaderIcon className="animate-spin size-5" /> <Spinner className="size-5" />
Loading... Loading...
</button> </button>
</div> </div>

View File

@ -1,5 +1,4 @@
import { LoaderIcon } from "@lume/icons"; import { Spinner, User } from "@lume/ui";
import { User } from "@lume/ui";
import { Await, defer } from "@tanstack/react-router"; import { Await, defer } from "@tanstack/react-router";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { Suspense } from "react"; import { Suspense } from "react";
@ -34,7 +33,7 @@ export function Screen() {
className="inline-flex items-center gap-2 text-sm font-medium" className="inline-flex items-center gap-2 text-sm font-medium"
disabled disabled
> >
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
Loading... Loading...
</button> </button>
</div> </div>

View File

@ -1,10 +1,11 @@
import { TextNote } from "@/components/text"; import { TextNote } from "@/components/text";
import { RepostNote } from "@/components/repost"; import { RepostNote } from "@/components/repost";
import { ArrowRightCircleIcon, InfoIcon, LoaderIcon } from "@lume/icons"; import { ArrowRightCircleIcon, InfoIcon } from "@lume/icons";
import { Event, Kind } from "@lume/types"; import { Event, Kind } from "@lume/types";
import { FETCH_LIMIT } from "@lume/utils"; import { FETCH_LIMIT } from "@lume/utils";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "@lume/ui";
export function EventList({ id }: { id: string }) { export function EventList({ id }: { id: string }) {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -39,7 +40,7 @@ export function EventList({ id }: { id: string }) {
<div> <div>
{isLoading ? ( {isLoading ? (
<div className="flex h-20 w-full flex-col items-center justify-center gap-1"> <div className="flex h-20 w-full flex-col items-center justify-center gap-1">
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
</div> </div>
) : !data.length ? ( ) : !data.length ? (
<div className="flex items-center gap-2 rounded-xl bg-neutral-50 p-5 dark:bg-neutral-950"> <div className="flex items-center gap-2 rounded-xl bg-neutral-50 p-5 dark:bg-neutral-950">
@ -58,7 +59,7 @@ export function EventList({ id }: { id: string }) {
className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800" className="inline-flex h-12 w-36 items-center justify-center gap-2 rounded-full bg-neutral-100 px-3 font-medium hover:bg-neutral-200 focus:outline-none dark:bg-neutral-900 dark:hover:bg-neutral-800"
> >
{isFetchingNextPage ? ( {isFetchingNextPage ? (
<LoaderIcon className="size-5 animate-spin" /> <Spinner className="size-5" />
) : ( ) : (
<> <>
<ArrowRightCircleIcon className="size-5" /> <ArrowRightCircleIcon className="size-5" />

View File

@ -33,7 +33,6 @@ export * from "./src/threads";
export * from "./src/trash"; export * from "./src/trash";
export * from "./src/world"; export * from "./src/world";
export * from "./src/zap"; export * from "./src/zap";
export * from "./src/loader";
export * from "./src/trending"; export * from "./src/trending";
export * from "./src/empty"; export * from "./src/empty";
export * from "./src/cmd"; export * from "./src/cmd";

View File

@ -1,28 +0,0 @@
import { SVGProps } from "react";
export function LoaderIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
);
}

View File

@ -5,3 +5,4 @@ export * from "./column";
// UI // UI
export * from "./container"; export * from "./container";
export * from "./box"; export * from "./box";
export * from "./spinner";

View File

@ -1,10 +1,11 @@
import { ArrowDownIcon, LoaderIcon } from "@lume/icons"; import { ArrowDownIcon } from "@lume/icons";
import { useState } from "react"; import { useState } from "react";
import { useNoteContext } from "../provider"; import { useNoteContext } from "../provider";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "../../spinner";
export function NoteDownvote() { export function NoteDownvote() {
const ark = useRouteContext({ strict: false }); const ark = useRouteContext({ strict: false });
@ -41,7 +42,7 @@ export function NoteDownvote() {
)} )}
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-4 animate-spin" /> <Spinner className="size-4" />
) : ( ) : (
<ArrowDownIcon className="size-4" /> <ArrowDownIcon className="size-4" />
)} )}

View File

@ -1,4 +1,4 @@
import { LoaderIcon, QuoteIcon, RepostIcon } from "@lume/icons"; import { QuoteIcon, RepostIcon } from "@lume/icons";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { useNoteContext } from "../provider"; import { useNoteContext } from "../provider";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "../../spinner";
export function NoteRepost() { export function NoteRepost() {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -46,7 +47,7 @@ export function NoteRepost() {
className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200" className="group inline-flex size-7 items-center justify-center text-neutral-800 dark:text-neutral-200"
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-4 animate-spin" /> <Spinner className="size-4" />
) : ( ) : (
<RepostIcon <RepostIcon
className={cn( className={cn(

View File

@ -1,10 +1,11 @@
import { ArrowUpIcon, LoaderIcon } from "@lume/icons"; import { ArrowUpIcon } from "@lume/icons";
import { useState } from "react"; import { useState } from "react";
import { useNoteContext } from "../provider"; import { useNoteContext } from "../provider";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "../../spinner";
export function NoteUpvote() { export function NoteUpvote() {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -41,7 +42,7 @@ export function NoteUpvote() {
)} )}
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-4 animate-spin" /> <Spinner className="size-4" />
) : ( ) : (
<ArrowUpIcon className="size-4" /> <ArrowUpIcon className="size-4" />
)} )}

View File

@ -0,0 +1,47 @@
import { cn } from "@lume/utils";
import { ReactNode } from "react";
export function Spinner({
children,
className,
}: {
children?: ReactNode;
className?: string;
}) {
const spinner = (
<span className={cn("block relative opacity-65 size-4", className)}>
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
<span className="spinner-leaf" />
</span>
);
if (children === undefined) return spinner;
return (
<div className="relative flex items-center justify-center">
<span>
{/**
* `display: contents` removes the content from the accessibility tree in some browsers,
* so we force remove it with `aria-hidden`
*/}
<span
aria-hidden
style={{ display: "contents", visibility: "hidden" }}
// Workaround to use `inert` until https://github.com/facebook/react/pull/24730 is merged.
{...{ inert: true ? "" : undefined }}
>
{children}
</span>
<div className="absolute flex items-center justify-center">
<span>{spinner}</span>
</div>
</span>
</div>
);
}

View File

@ -1,9 +1,9 @@
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useUserContext } from "./provider"; import { useUserContext } from "./provider";
import { useRouteContext } from "@tanstack/react-router"; import { useRouteContext } from "@tanstack/react-router";
import { Spinner } from "../spinner";
export function UserFollowButton({ className }: { className?: string }) { export function UserFollowButton({ className }: { className?: string }) {
const { ark } = useRouteContext({ strict: false }); const { ark } = useRouteContext({ strict: false });
@ -47,7 +47,7 @@ export function UserFollowButton({ className }: { className?: string }) {
className={cn("w-max", className)} className={cn("w-max", className)}
> >
{loading ? ( {loading ? (
<LoaderIcon className="size-4 animate-spin" /> <Spinner className="size-4" />
) : followed ? ( ) : followed ? (
t("user.unfollow") t("user.unfollow")
) : ( ) : (