feat: loading component new design
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
53a5bae316
commit
91d0d76efe
|
|
@ -24,6 +24,7 @@ export default defineConfig(
|
|||
rules: {
|
||||
"unicorn/prefer-math-trunc": "off",
|
||||
"@eslint-react/no-clone-element": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,19 +1,111 @@
|
|||
import { cn } from "@renderer/lib/utils"
|
||||
import React, { cloneElement } from "react"
|
||||
|
||||
interface LoadingCircleProps {
|
||||
size: "small" | "medium" | "large"
|
||||
}
|
||||
|
||||
const sizeMap = {
|
||||
small: "text-md",
|
||||
medium: "text-xl",
|
||||
large: "text-3xl",
|
||||
small: 16,
|
||||
medium: 24,
|
||||
large: 30,
|
||||
}
|
||||
export const LoadingCircle: Component<LoadingCircleProps> = ({
|
||||
className,
|
||||
size,
|
||||
}) => (
|
||||
<div className={cn(sizeMap[size], className)}>
|
||||
<div
|
||||
className={className}
|
||||
style={{
|
||||
fontSize: sizeMap[size],
|
||||
}}
|
||||
>
|
||||
<i className="i-mgc-loading-3-cute-re animate-spin" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const smallIconSizeMap = {
|
||||
small: 12,
|
||||
medium: 14,
|
||||
large: 16,
|
||||
}
|
||||
export const LoadingWithIcon: Component<
|
||||
LoadingCircleProps & {
|
||||
icon: React.JSX.Element
|
||||
order?: "loading-first" | "icon-first"
|
||||
}
|
||||
> = ({ order = "loading-first", size, className, icon, children }) => {
|
||||
const rootStyle = { width: sizeMap[size], height: sizeMap[size] }
|
||||
|
||||
const smallIconStyle = {
|
||||
height: smallIconSizeMap[size],
|
||||
width: smallIconSizeMap[size],
|
||||
}
|
||||
|
||||
const Children = children && <div className="center flex">{children}</div>
|
||||
if (order === "icon-first") {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div style={rootStyle} className="relative inline-block">
|
||||
<span className="block size-full">
|
||||
{cloneElement(icon, {
|
||||
style: {
|
||||
...icon.props.style,
|
||||
fontSize: sizeMap[size],
|
||||
},
|
||||
className: cn(icon.props.className),
|
||||
})}
|
||||
</span>
|
||||
<span
|
||||
className="absolute bottom-1"
|
||||
style={{
|
||||
...smallIconStyle,
|
||||
right: -smallIconSizeMap[size] + 4,
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="i-mgc-loading-3-cute-re animate-spin"
|
||||
style={{ fontSize: smallIconSizeMap[size] }}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{Children}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div style={rootStyle} className="relative inline-block">
|
||||
<span
|
||||
className="block size-full"
|
||||
style={{
|
||||
clipPath: `polygon(0% 0%, 0% 100%, ${smallIconSizeMap[size]}px 100%, ${smallIconSizeMap[size]}px ${smallIconSizeMap[size]}px, 100% ${smallIconSizeMap[size]}px, 100% 100%, 100% 100%, 100% 0%)`,
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="i-mgc-loading-3-cute-re animate-spin"
|
||||
style={{ fontSize: sizeMap[size] }}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute bottom-0 right-0",
|
||||
"animate-pulse duration-700",
|
||||
)}
|
||||
style={smallIconStyle}
|
||||
>
|
||||
{cloneElement(icon, {
|
||||
style: {
|
||||
...icon.props.style,
|
||||
fontSize: smallIconSizeMap[size],
|
||||
},
|
||||
className: icon.props.className,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{Children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed"
|
|||
import type { FC } from "react"
|
||||
import { useEffect, useLayoutEffect, useRef } from "react"
|
||||
|
||||
import { LoadingCircle } from "../../components/ui/loading"
|
||||
import { LoadingWithIcon } from "../../components/ui/loading"
|
||||
import { EntryPlaceholderDaily } from "../ai/ai-daily/EntryPlaceholderDaily"
|
||||
import { EntryTranslation } from "../entry-column/translation"
|
||||
import { setEntryContentScrollToTop, setEntryTitleMeta } from "./atoms"
|
||||
|
|
@ -235,7 +235,12 @@ export const EntryContentRender: Component<{ entryId: string }> = ({
|
|||
{!content && (
|
||||
<div className="center mt-16">
|
||||
{isPending ? (
|
||||
<LoadingCircle size="large" />
|
||||
<LoadingWithIcon
|
||||
size="large"
|
||||
icon={
|
||||
<i className="i-mgc-rss-cute-fi" />
|
||||
}
|
||||
/>
|
||||
) : error ?
|
||||
(
|
||||
<div className="center flex flex-col gap-2">
|
||||
|
|
@ -308,7 +313,10 @@ const ReadabilityContent = ({ entryId }: { entryId: string }) => {
|
|||
</p>
|
||||
) : (
|
||||
<div className="center mt-16 flex flex-col gap-2">
|
||||
<LoadingCircle size="large" />
|
||||
<LoadingWithIcon
|
||||
size="large"
|
||||
icon={<i className="i-mgc-sparkles-2-cute-re" />}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
Fetching original content and processing...
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import {
|
|||
AvatarImage,
|
||||
} from "@renderer/components/ui/avatar"
|
||||
import { ActionButton, Button } from "@renderer/components/ui/button"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import {
|
||||
LoadingCircle,
|
||||
LoadingWithIcon,
|
||||
} from "@renderer/components/ui/loading"
|
||||
import { useCurrentModal, useModalStack } from "@renderer/components/ui/modal"
|
||||
import { ScrollArea } from "@renderer/components/ui/scroll-area"
|
||||
import { useAuthQuery } from "@renderer/hooks/common"
|
||||
|
|
@ -277,8 +280,9 @@ export const UserProfileModalContent: FC<{
|
|||
viewportClassName="[&>div]:space-y-4 pb-4"
|
||||
>
|
||||
{subscriptions.isLoading ? (
|
||||
<LoadingCircle
|
||||
<LoadingWithIcon
|
||||
size="large"
|
||||
icon={<i className="i-mgc-user-3-cute-re" />}
|
||||
className="center h-48 w-full max-w-full"
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable @eslint-react/no-array-index-key */
|
||||
import { isNil } from "lodash-es"
|
||||
import type { FC, ReactNode } from "react"
|
||||
import * as React from "react"
|
||||
import { isValidElement } from "react"
|
||||
|
|
@ -49,7 +50,7 @@ export const createSettingBuilder =
|
|||
const settingObject = useSetting()
|
||||
|
||||
return settings
|
||||
.filter((i) => typeof i !== "boolean")
|
||||
.filter((i) => !isNil(i))
|
||||
.map((setting, index) => {
|
||||
if (isValidElement(setting)) return setting
|
||||
if (typeof setting === "function") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useWhoami } from "@renderer/atoms/user"
|
||||
import { Divider } from "@renderer/components/ui/divider"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import { LoadingWithIcon } from "@renderer/components/ui/loading"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
|
|
@ -22,7 +22,11 @@ export const MyWalletSection = () => {
|
|||
if (wallet.isPending) {
|
||||
return (
|
||||
<div className="center absolute inset-0 flex">
|
||||
<LoadingCircle size="large" className="-translate-y-full" />
|
||||
<LoadingWithIcon
|
||||
icon={<i className="i-mgc-power" />}
|
||||
size="large"
|
||||
className="-translate-y-full"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -63,9 +67,7 @@ export const MyWalletSection = () => {
|
|||
<TooltipTrigger className="block">
|
||||
<div className="flex flex-row items-center gap-x-2 text-xs text-zinc-600 dark:text-neutral-400">
|
||||
<span className="flex w-[120px] items-center gap-1 text-left">
|
||||
Cashable Power
|
||||
{" "}
|
||||
<i className="i-mingcute-question-line" />
|
||||
Cashable Power <i className="i-mingcute-question-line" />
|
||||
</span>
|
||||
|
||||
<Balance>{myWallet.cashablePowerToken}</Balance>
|
||||
|
|
@ -77,8 +79,8 @@ export const MyWalletSection = () => {
|
|||
1. Cashable Power can be withdrawn to your wallet for trading.
|
||||
</p>
|
||||
<p>
|
||||
2. Cashable Power is the Power you have recharged and the tips you
|
||||
have received.
|
||||
2. Cashable Power is the Power you have recharged and the tips
|
||||
you have received.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ export const TransactionsSection = () => {
|
|||
<TableHead className="text-center" size="sm">
|
||||
Amount
|
||||
</TableHead>
|
||||
<TableHead className="text-center" size="sm">
|
||||
<TableHead className="pl-5" size="sm">
|
||||
From
|
||||
</TableHead>
|
||||
<TableHead className="text-center" size="sm">
|
||||
<TableHead className="pl-5" size="sm">
|
||||
To
|
||||
</TableHead>
|
||||
<TableHead className="text-center" size="sm">
|
||||
|
|
@ -152,7 +152,7 @@ const UserRenderer = ({
|
|||
const name = isMe ? "You" : user?.name || APP_NAME
|
||||
|
||||
return (
|
||||
<div className="center">
|
||||
<div className="flex">
|
||||
{name === APP_NAME ? (
|
||||
<Logo className="aspect-square size-4" />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
import { useWhoami } from "@renderer/atoms/user"
|
||||
import { Button } from "@renderer/components/ui/button"
|
||||
import { Divider } from "@renderer/components/ui/divider"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import {
|
||||
LoadingWithIcon,
|
||||
} from "@renderer/components/ui/loading"
|
||||
import { useCurrentModal } from "@renderer/components/ui/modal"
|
||||
import { RadioGroup } from "@renderer/components/ui/radio-group"
|
||||
import { RadioCard } from "@renderer/components/ui/radio-group/RadioCard"
|
||||
import { Balance } from "@renderer/components/ui/wallet/balance"
|
||||
import { UserAvatar } from "@renderer/components/user-button"
|
||||
import { nextFrame } from "@renderer/lib/dom"
|
||||
import { useWallet, useWalletTipMutation, useWalletTransactions } from "@renderer/queries/wallet"
|
||||
import {
|
||||
useWallet,
|
||||
useWalletTipMutation,
|
||||
useWalletTransactions,
|
||||
} from "@renderer/queries/wallet"
|
||||
import { from, toNumber } from "dnum"
|
||||
import type { FC } from "react"
|
||||
import { useState } from "react"
|
||||
|
|
@ -18,28 +24,63 @@ import { useSettingModal } from "../settings/modal/hooks-hack"
|
|||
|
||||
const DEFAULT_RECOMMENDED_TIP = 1
|
||||
|
||||
const useMyWallet = () => {
|
||||
const user = useWhoami()
|
||||
const myWallet = useWallet({ userId: user?.id })
|
||||
return myWallet
|
||||
}
|
||||
|
||||
const Loading = () => (
|
||||
<div className="center h-32 w-[350px]">
|
||||
<LoadingWithIcon icon={<i className="i-mgc-power" />} size="large" />
|
||||
</div>
|
||||
)
|
||||
|
||||
export const TipModalContent: FC<{
|
||||
userId?: string
|
||||
feedId?: string
|
||||
}> = (props) => {
|
||||
const myWallet = useMyWallet()
|
||||
|
||||
if (!myWallet.data) {
|
||||
return (
|
||||
<Loading />
|
||||
)
|
||||
}
|
||||
return <TipModalContent_ {...props} />
|
||||
}
|
||||
const TipModalContent_: FC<{
|
||||
userId?: string
|
||||
feedId?: string
|
||||
}> = ({ userId, feedId }) => {
|
||||
const user = useWhoami()
|
||||
const myWallet = useWallet({ userId: user?.id })
|
||||
const myWallet = useMyWallet()
|
||||
const myWalletData = myWallet.data?.[0]
|
||||
|
||||
const dPowerBigInt = BigInt(myWalletData?.dailyPowerToken ?? 0)
|
||||
const cPowerBigInt = BigInt(myWalletData?.cashablePowerToken ?? 0)
|
||||
const balanceBigInt = cPowerBigInt + dPowerBigInt
|
||||
const balanceNumber = toNumber(from(balanceBigInt, 18), { digits: 2, trailingZeros: true })
|
||||
const balanceNumber = toNumber(from(balanceBigInt, 18), {
|
||||
digits: 2,
|
||||
trailingZeros: true,
|
||||
})
|
||||
|
||||
const transactionsQuery = useWalletTransactions({ toUserId: userId, toFeedId: feedId })
|
||||
const transactionsQuery = useWalletTransactions({
|
||||
toUserId: userId,
|
||||
toFeedId: feedId,
|
||||
})
|
||||
|
||||
const tipMutation = useWalletTipMutation()
|
||||
|
||||
const [amount, setAmount] = useState(DEFAULT_RECOMMENDED_TIP > balanceNumber ? balanceNumber : DEFAULT_RECOMMENDED_TIP)
|
||||
const [amount, setAmount] = useState(
|
||||
DEFAULT_RECOMMENDED_TIP > balanceNumber ?
|
||||
balanceNumber :
|
||||
DEFAULT_RECOMMENDED_TIP,
|
||||
)
|
||||
|
||||
const amountBigInt = from(amount, 18)[0]
|
||||
|
||||
const wrongNumberRange = amountBigInt > balanceBigInt || amountBigInt <= BigInt(0)
|
||||
const wrongNumberRange =
|
||||
amountBigInt > balanceBigInt || amountBigInt <= BigInt(0)
|
||||
|
||||
const { dismiss } = useCurrentModal()
|
||||
|
||||
|
|
@ -51,9 +92,7 @@ export const TipModalContent: FC<{
|
|||
|
||||
if (transactionsQuery.isPending || myWallet.isPending) {
|
||||
return (
|
||||
<div className="center h-32 w-[350px]">
|
||||
<LoadingCircle size="large" />
|
||||
</div>
|
||||
<Loading />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -88,14 +127,12 @@ export const TipModalContent: FC<{
|
|||
<p>
|
||||
<Balance withSuffix>{amountBigInt}</Balance>
|
||||
{" "}
|
||||
has been sent to the author.
|
||||
has been sent to the
|
||||
author.
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => dismiss()}
|
||||
>
|
||||
<Button variant="primary" onClick={() => dismiss()}>
|
||||
OK
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -108,15 +145,27 @@ export const TipModalContent: FC<{
|
|||
{userId ? (
|
||||
<>
|
||||
<p className="text-sm font-medium">Feed Owner</p>
|
||||
<UserAvatar className="h-8 justify-start bg-transparent p-0" userId={userId} />
|
||||
<UserAvatar
|
||||
className="h-8 justify-start bg-transparent p-0"
|
||||
userId={userId}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="leading-none">
|
||||
<span className="text-xs text-theme-foreground/80">No one has claimed this feed yet. The received Power will be securely held in the blockchain contract until it is claimed.</span>
|
||||
<span className="text-xs text-theme-foreground/80">
|
||||
No one has claimed this feed yet. The received Power will be
|
||||
securely held in the blockchain contract until it is claimed.
|
||||
</span>
|
||||
</p>
|
||||
<div className="text-center">
|
||||
<Button variant="text" className="w-fit p-0" onClick={() => claimFeed()}>Claim this feed</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
className="w-fit p-0"
|
||||
onClick={() => claimFeed()}
|
||||
>
|
||||
Claim this feed
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -136,8 +185,16 @@ export const TipModalContent: FC<{
|
|||
onValueChange={(value) => setAmount(Number(value))}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<RadioCard wrapperClassName="justify-center" label="1 Power" value="1" />
|
||||
<RadioCard wrapperClassName="justify-center" label="2 Power" value="2" />
|
||||
<RadioCard
|
||||
wrapperClassName="justify-center"
|
||||
label="1 Power"
|
||||
value="1"
|
||||
/>
|
||||
<RadioCard
|
||||
wrapperClassName="justify-center"
|
||||
label="2 Power"
|
||||
value="2"
|
||||
/>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
|
|
@ -152,7 +209,6 @@ export const TipModalContent: FC<{
|
|||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue