diff --git a/apps/desktop2/src/app.css b/apps/desktop2/src/app.css index a9df0c78..2a29365f 100644 --- a/apps/desktop2/src/app.css +++ b/apps/desktop2/src/app.css @@ -60,3 +60,63 @@ input::-ms-clear { ::-webkit-input-placeholder { 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; + } +} diff --git a/apps/desktop2/src/components/avatarUploader.tsx b/apps/desktop2/src/components/avatarUploader.tsx index cffb22e3..0ad39d7c 100644 --- a/apps/desktop2/src/components/avatarUploader.tsx +++ b/apps/desktop2/src/components/avatarUploader.tsx @@ -1,4 +1,4 @@ -import { LoaderIcon } from "@lume/icons"; +import { Spinner } from "@lume/ui"; import { cn } from "@lume/utils"; import { useRouteContext } from "@tanstack/react-router"; import { Dispatch, ReactNode, SetStateAction, useState } from "react"; @@ -36,7 +36,7 @@ export function AvatarUploader({ onClick={() => uploadAvatar()} className={cn("size-4", className)} > - {loading ? : children} + {loading ? : children} ); } diff --git a/apps/desktop2/src/components/col.tsx b/apps/desktop2/src/components/col.tsx index a6c474a4..466c8a2c 100644 --- a/apps/desktop2/src/components/col.tsx +++ b/apps/desktop2/src/components/col.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; import { LumeColumn } from "@lume/types"; import { invoke } from "@tauri-apps/api/core"; -import { LoaderIcon } from "@lume/icons"; +import { Spinner } from "@lume/ui"; export function Col({ column, @@ -68,7 +68,7 @@ export function Col({ {column.label !== "open" ? (
) : null} diff --git a/apps/desktop2/src/routes/$account.home.tsx b/apps/desktop2/src/routes/$account.home.tsx index 37d9bb59..97330d14 100644 --- a/apps/desktop2/src/routes/$account.home.tsx +++ b/apps/desktop2/src/routes/$account.home.tsx @@ -1,7 +1,8 @@ import { Col } from "@/components/col"; 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 { Spinner } from "@lume/ui"; import { createFileRoute } from "@tanstack/react-router"; import { listen } from "@tauri-apps/api/event"; import { resolveResource } from "@tauri-apps/api/path"; @@ -148,7 +149,7 @@ function Pending() { return (
); diff --git a/apps/desktop2/src/routes/__root.tsx b/apps/desktop2/src/routes/__root.tsx index e9e739b6..bc3f3b4b 100644 --- a/apps/desktop2/src/routes/__root.tsx +++ b/apps/desktop2/src/routes/__root.tsx @@ -1,9 +1,9 @@ -import { LoaderIcon } from "@lume/icons"; import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import { type Ark } from "@lume/ark"; import { type QueryClient } from "@tanstack/react-query"; import { type Platform } from "@tauri-apps/plugin-os"; import { Account, Interests, Settings } from "@lume/types"; +import { Spinner } from "@lume/ui"; interface RouterContext { ark: Ark; @@ -25,7 +25,7 @@ function Pending() { return (
); diff --git a/apps/desktop2/src/routes/antenas.tsx b/apps/desktop2/src/routes/antenas.tsx index d00c9c33..c6318c43 100644 --- a/apps/desktop2/src/routes/antenas.tsx +++ b/apps/desktop2/src/routes/antenas.tsx @@ -1,8 +1,8 @@ import { RepostNote } from "@/components/repost"; 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 { Column } from "@lume/ui"; +import { Column, Spinner } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Link, createFileRoute } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; @@ -53,7 +53,7 @@ export function Screen() { {isLoading ? (
- +
) : !data.length ? ( @@ -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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/apps/desktop2/src/routes/auth/new/profile.tsx b/apps/desktop2/src/routes/auth/new/profile.tsx index 57170983..553b0f42 100644 --- a/apps/desktop2/src/routes/auth/new/profile.tsx +++ b/apps/desktop2/src/routes/auth/new/profile.tsx @@ -1,6 +1,7 @@ import { AvatarUploader } from "@/components/avatarUploader"; -import { LoaderIcon, PlusIcon } from "@lume/icons"; +import { PlusIcon } from "@lume/icons"; import { Metadata } from "@lume/types"; +import { Spinner } from "@lume/ui"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -134,11 +135,7 @@ function Screen() { 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" > - {loading ? ( - - ) : ( - t("global.continue") - )} + {loading ? : t("global.continue")} diff --git a/apps/desktop2/src/routes/auth/privkey.lazy.tsx b/apps/desktop2/src/routes/auth/privkey.lazy.tsx index 254971c5..afca9f25 100644 --- a/apps/desktop2/src/routes/auth/privkey.lazy.tsx +++ b/apps/desktop2/src/routes/auth/privkey.lazy.tsx @@ -1,4 +1,4 @@ -import { LoaderIcon } from "@lume/icons"; +import { Spinner } from "@lume/ui"; import { createLazyFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { toast } from "sonner"; @@ -82,7 +82,7 @@ function Screen() { 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" > - {loading ? : "Login"} + {loading ? : "Login"} diff --git a/apps/desktop2/src/routes/auth/settings.tsx b/apps/desktop2/src/routes/auth/settings.tsx index da5f23f1..a104e95b 100644 --- a/apps/desktop2/src/routes/auth/settings.tsx +++ b/apps/desktop2/src/routes/auth/settings.tsx @@ -1,4 +1,4 @@ -import { LaurelIcon, LoaderIcon } from "@lume/icons"; +import { LaurelIcon } from "@lume/icons"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useTranslation } from "react-i18next"; import * as Switch from "@radix-ui/react-switch"; @@ -9,6 +9,7 @@ import { requestPermission, } from "@tauri-apps/plugin-notification"; import { toast } from "sonner"; +import { Spinner } from "@lume/ui"; export const Route = createFileRoute("/auth/settings")({ validateSearch: (search: Record): AppRouteSearch => { @@ -181,7 +182,7 @@ function Pending() { return (
); diff --git a/apps/desktop2/src/routes/editor/-components/media.tsx b/apps/desktop2/src/routes/editor/-components/media.tsx index 247f3800..bfcbddaf 100644 --- a/apps/desktop2/src/routes/editor/-components/media.tsx +++ b/apps/desktop2/src/routes/editor/-components/media.tsx @@ -1,4 +1,4 @@ -import { AddMediaIcon, LoaderIcon } from "@lume/icons"; +import { AddMediaIcon } from "@lume/icons"; import { cn, insertImage, isImagePath } from "@lume/utils"; import { useEffect, useState } from "react"; import { useSlateStatic } from "slate-react"; @@ -6,6 +6,7 @@ import { toast } from "sonner"; import { getCurrent } from "@tauri-apps/api/window"; import { UnlistenFn } from "@tauri-apps/api/event"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "@lume/ui"; export function MediaButton({ className }: { className?: string }) { const { ark } = useRouteContext({ strict: false }); @@ -69,7 +70,7 @@ export function MediaButton({ className }: { className?: string }) { className={cn("inline-flex items-center justify-center", className)} > {loading ? ( - + ) : ( )} diff --git a/apps/desktop2/src/routes/editor/index.tsx b/apps/desktop2/src/routes/editor/index.tsx index 97d6105d..c8576602 100644 --- a/apps/desktop2/src/routes/editor/index.tsx +++ b/apps/desktop2/src/routes/editor/index.tsx @@ -31,7 +31,7 @@ import { Editable, } from "slate-react"; import { Contact } from "@lume/types"; -import { User } from "@lume/ui"; +import { Spinner, User } from "@lume/ui"; import { nip19 } from "nostr-tools"; import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; import { invoke } from "@tauri-apps/api/core"; @@ -212,11 +212,7 @@ function Screen() { 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" > - {loading ? ( - - ) : ( - t("global.post") - )} + {loading ? : t("global.post")}
@@ -280,7 +276,7 @@ function Pending() { className="flex h-full w-full items-center justify-center gap-2.5" >

Loading cache...

diff --git a/apps/desktop2/src/routes/events/$eventId.lazy.tsx b/apps/desktop2/src/routes/events/$eventId.lazy.tsx index 55b72e58..d5740c7e 100644 --- a/apps/desktop2/src/routes/events/$eventId.lazy.tsx +++ b/apps/desktop2/src/routes/events/$eventId.lazy.tsx @@ -1,6 +1,5 @@ import { useEvent } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { Box, Container, Note, User } from "@lume/ui"; +import { Box, Container, Note, Spinner, User } from "@lume/ui"; import { createLazyFileRoute } from "@tanstack/react-router"; import { ReplyList } from "./-components/replyList"; import { WindowVirtualizer } from "virtua"; @@ -17,7 +16,7 @@ function Event() { if (isLoading) { return (
- +
); } diff --git a/apps/desktop2/src/routes/events/-components/replyList.tsx b/apps/desktop2/src/routes/events/-components/replyList.tsx index a3f5c1e4..e40fcdbc 100644 --- a/apps/desktop2/src/routes/events/-components/replyList.tsx +++ b/apps/desktop2/src/routes/events/-components/replyList.tsx @@ -1,10 +1,10 @@ -import { LoaderIcon } from "@lume/icons"; import { cn } from "@lume/utils"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { EventWithReplies } from "@lume/types"; import { Reply } from "./reply"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "@lume/ui"; export function ReplyList({ eventId, @@ -29,7 +29,7 @@ export function ReplyList({
{!data ? (
- +
) : data.length === 0 ? (
diff --git a/apps/desktop2/src/routes/foryou.tsx b/apps/desktop2/src/routes/foryou.tsx index fcbde498..42e6538c 100644 --- a/apps/desktop2/src/routes/foryou.tsx +++ b/apps/desktop2/src/routes/foryou.tsx @@ -1,8 +1,8 @@ import { RepostNote } from "@/components/repost"; 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 { Column } from "@lume/ui"; +import { Column, Spinner } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Link, createFileRoute, redirect } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; @@ -77,7 +77,7 @@ export function Screen() { {isLoading ? (
) : !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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/apps/desktop2/src/routes/global.tsx b/apps/desktop2/src/routes/global.tsx index e096052b..ffa3fe01 100644 --- a/apps/desktop2/src/routes/global.tsx +++ b/apps/desktop2/src/routes/global.tsx @@ -1,8 +1,8 @@ import { RepostNote } from "@/components/repost"; 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 { Column } from "@lume/ui"; +import { Column, Spinner } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Link, createFileRoute } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; @@ -60,7 +60,7 @@ export function Screen() { {isLoading ? (
) : !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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/apps/desktop2/src/routes/group.tsx b/apps/desktop2/src/routes/group.tsx index 79d72600..47c22d16 100644 --- a/apps/desktop2/src/routes/group.tsx +++ b/apps/desktop2/src/routes/group.tsx @@ -1,8 +1,8 @@ import { RepostNote } from "@/components/repost"; 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 { Column } from "@lume/ui"; +import { Column, Spinner } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Link, createFileRoute, redirect } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; @@ -71,7 +71,7 @@ export function Screen() { {isLoading ? (
- +
) : !data.length ? ( @@ -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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/apps/desktop2/src/routes/index.tsx b/apps/desktop2/src/routes/index.tsx index b2f13fcb..a3c23d21 100644 --- a/apps/desktop2/src/routes/index.tsx +++ b/apps/desktop2/src/routes/index.tsx @@ -1,5 +1,5 @@ -import { LoaderIcon, PlusIcon } from "@lume/icons"; -import { User } from "@lume/ui"; +import { PlusIcon } from "@lume/icons"; +import { Spinner, User } from "@lume/ui"; import { Link } from "@tanstack/react-router"; import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -73,7 +73,7 @@ function Screen() {
{loading ? (
- +
) : ( <> diff --git a/apps/desktop2/src/routes/newsfeed.tsx b/apps/desktop2/src/routes/newsfeed.tsx index 91dda389..8df4222d 100644 --- a/apps/desktop2/src/routes/newsfeed.tsx +++ b/apps/desktop2/src/routes/newsfeed.tsx @@ -1,8 +1,8 @@ import { RepostNote } from "@/components/repost"; 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 { Column } from "@lume/ui"; +import { Column, Spinner } from "@lume/ui"; import { useInfiniteQuery } from "@tanstack/react-query"; import { Link, createFileRoute } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; @@ -59,9 +59,7 @@ export function Screen() { {isLoading ? (
- +
) : !data.length ? ( @@ -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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/apps/desktop2/src/routes/trending.notes.tsx b/apps/desktop2/src/routes/trending.notes.tsx index 806ba3aa..cd2a7859 100644 --- a/apps/desktop2/src/routes/trending.notes.tsx +++ b/apps/desktop2/src/routes/trending.notes.tsx @@ -1,11 +1,11 @@ import { RepostNote } from "@/components/repost"; import { TextNote } from "@/components/text"; -import { LoaderIcon } from "@lume/icons"; import { Event, Kind } from "@lume/types"; import { Await, createFileRoute } from "@tanstack/react-router"; import { Virtualizer } from "virtua"; import { defer } from "@tanstack/react-router"; import { Suspense } from "react"; +import { Spinner } from "@lume/ui"; export const Route = createFileRoute("/trending/notes")({ loader: async ({ abortController }) => { @@ -50,7 +50,7 @@ export function Screen() { className="inline-flex items-center gap-2 text-sm font-medium" disabled > - + Loading...
diff --git a/apps/desktop2/src/routes/trending.users.tsx b/apps/desktop2/src/routes/trending.users.tsx index 026926ed..b2ea2692 100644 --- a/apps/desktop2/src/routes/trending.users.tsx +++ b/apps/desktop2/src/routes/trending.users.tsx @@ -1,5 +1,4 @@ -import { LoaderIcon } from "@lume/icons"; -import { User } from "@lume/ui"; +import { Spinner, User } from "@lume/ui"; import { Await, defer } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router"; import { Suspense } from "react"; @@ -34,7 +33,7 @@ export function Screen() { className="inline-flex items-center gap-2 text-sm font-medium" disabled > - + Loading...
diff --git a/apps/desktop2/src/routes/users/-components/eventList.tsx b/apps/desktop2/src/routes/users/-components/eventList.tsx index 47c8817b..3fcfb022 100644 --- a/apps/desktop2/src/routes/users/-components/eventList.tsx +++ b/apps/desktop2/src/routes/users/-components/eventList.tsx @@ -1,10 +1,11 @@ import { TextNote } from "@/components/text"; 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 { FETCH_LIMIT } from "@lume/utils"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "@lume/ui"; export function EventList({ id }: { id: string }) { const { ark } = useRouteContext({ strict: false }); @@ -39,7 +40,7 @@ export function EventList({ id }: { id: string }) {
{isLoading ? (
- +
) : !data.length ? (
@@ -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" > {isFetchingNextPage ? ( - + ) : ( <> diff --git a/packages/icons/index.ts b/packages/icons/index.ts index c8612902..d0a5f4ea 100644 --- a/packages/icons/index.ts +++ b/packages/icons/index.ts @@ -33,7 +33,6 @@ export * from "./src/threads"; export * from "./src/trash"; export * from "./src/world"; export * from "./src/zap"; -export * from "./src/loader"; export * from "./src/trending"; export * from "./src/empty"; export * from "./src/cmd"; diff --git a/packages/icons/src/loader.tsx b/packages/icons/src/loader.tsx deleted file mode 100644 index 69fbeba0..00000000 --- a/packages/icons/src/loader.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { SVGProps } from "react"; - -export function LoaderIcon( - props: JSX.IntrinsicAttributes & SVGProps, -) { - return ( - - - - - ); -} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 566e7dcb..5eaebd7b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -5,3 +5,4 @@ export * from "./column"; // UI export * from "./container"; export * from "./box"; +export * from "./spinner"; diff --git a/packages/ui/src/note/buttons/downvote.tsx b/packages/ui/src/note/buttons/downvote.tsx index 9a2b1abe..51c85b7d 100644 --- a/packages/ui/src/note/buttons/downvote.tsx +++ b/packages/ui/src/note/buttons/downvote.tsx @@ -1,10 +1,11 @@ -import { ArrowDownIcon, LoaderIcon } from "@lume/icons"; +import { ArrowDownIcon } from "@lume/icons"; import { useState } from "react"; import { useNoteContext } from "../provider"; import { cn } from "@lume/utils"; import * as Tooltip from "@radix-ui/react-tooltip"; import { useTranslation } from "react-i18next"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "../../spinner"; export function NoteDownvote() { const ark = useRouteContext({ strict: false }); @@ -41,7 +42,7 @@ export function NoteDownvote() { )} > {loading ? ( - + ) : ( )} diff --git a/packages/ui/src/note/buttons/repost.tsx b/packages/ui/src/note/buttons/repost.tsx index 53cb489a..e395f019 100644 --- a/packages/ui/src/note/buttons/repost.tsx +++ b/packages/ui/src/note/buttons/repost.tsx @@ -1,4 +1,4 @@ -import { LoaderIcon, QuoteIcon, RepostIcon } from "@lume/icons"; +import { QuoteIcon, RepostIcon } from "@lume/icons"; import { cn } from "@lume/utils"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as Tooltip from "@radix-ui/react-tooltip"; @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { useNoteContext } from "../provider"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "../../spinner"; export function NoteRepost() { 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" > {loading ? ( - + ) : ( {loading ? ( - + ) : ( )} diff --git a/packages/ui/src/spinner.tsx b/packages/ui/src/spinner.tsx new file mode 100644 index 00000000..e6fb0a83 --- /dev/null +++ b/packages/ui/src/spinner.tsx @@ -0,0 +1,47 @@ +import { cn } from "@lume/utils"; +import { ReactNode } from "react"; + +export function Spinner({ + children, + className, +}: { + children?: ReactNode; + className?: string; +}) { + const spinner = ( + + + + + + + + + + + ); + + if (children === undefined) return spinner; + + return ( +
+ + {/** + * `display: contents` removes the content from the accessibility tree in some browsers, + * so we force remove it with `aria-hidden` + */} + + {children} + +
+ {spinner} +
+
+
+ ); +} diff --git a/packages/ui/src/user/followButton.tsx b/packages/ui/src/user/followButton.tsx index 7986a137..575e3b52 100644 --- a/packages/ui/src/user/followButton.tsx +++ b/packages/ui/src/user/followButton.tsx @@ -1,9 +1,9 @@ -import { LoaderIcon } from "@lume/icons"; import { cn } from "@lume/utils"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useUserContext } from "./provider"; import { useRouteContext } from "@tanstack/react-router"; +import { Spinner } from "../spinner"; export function UserFollowButton({ className }: { className?: string }) { const { ark } = useRouteContext({ strict: false }); @@ -47,7 +47,7 @@ export function UserFollowButton({ className }: { className?: string }) { className={cn("w-max", className)} > {loading ? ( - + ) : followed ? ( t("user.unfollow") ) : (