Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

transition: color var(--transition) ease;

&:hover {
&:hover:not([aria-disabled="true"]) {
color: var(--gray-1);
}

&:disabled {
color: var(--gray-6);
/* Loading keeps the label at full contrast; the spinner carries the state. */
&[aria-disabled="true"] {
cursor: default;
}
}
53 changes: 44 additions & 9 deletions packages/interwovenkit-react/src/components/LoadMoreButton.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,56 @@
import { useEffect } from "react"
import clsx from "clsx"
import { useEffect, useEffectEvent } from "react"
import { useInView } from "react-intersection-observer"
Comment thread
restorenode marked this conversation as resolved.
import { IconChevronDown } from "@initia/icons-react"
import Loader from "./Loader"
import { usePageScrollRoot } from "./PageScrollContext"
import styles from "./LoadMoreButton.module.css"

const LoadMoreButton = ({ onClick, disabled }: { onClick: () => void; disabled?: boolean }) => {
const { ref, inView } = useInView()
interface Props {
onClick: () => void
isLoading?: boolean
className?: string
}

const LoadMoreButton = ({ onClick, isLoading, className }: Props) => {
const scrollRoot = usePageScrollRoot()

// Load ahead of the button becoming visible so scrolling feels seamless. `root` must be
// the actual scrollable ancestor: rootMargin only expands the root's own rect, while
// intermediate scroll containers clip the target with no margin. Left at the default
// viewport root, the enclosing Scrollable would clip it and the lookahead would be lost.
const { ref, inView } = useInView({ root: scrollRoot, rootMargin: "200px 0px" })

const onVisible = useEffectEvent(() => {
if (!isLoading) onClick()
})

// Only inView transitions load a page, so once a page is too short to push the button
// past the margin band, inView stays true and auto-loading stops. Clicking takes over
// from there, which is also what keeps a tail of short pages from loading all at once.
useEffect(() => {
if (inView) {
onClick()
}
}, [inView, onClick])
if (inView) onVisible()
}, [inView])
Comment thread
restorenode marked this conversation as resolved.

return (
<button className={styles.button} onClick={onClick} disabled={disabled} ref={ref}>
<button
className={clsx(styles.button, className)}
// The click is guarded here rather than blocked by the disabled attribute: a disabled
// button drops keyboard focus to the body mid-load, and its aria-busy and the Loader's
// live region stop being announced.
onClick={() => {
if (!isLoading) onClick()
}}
aria-disabled={isLoading}
aria-busy={isLoading}
ref={ref}
>
<span>Load more</span>
<IconChevronDown size={12} aria-hidden="true" />
{isLoading ? (
<Loader color="currentColor" size={12} border={1.5} />
) : (
<IconChevronDown size={12} aria-hidden="true" />
)}
</button>
)
}
Expand Down
7 changes: 6 additions & 1 deletion packages/interwovenkit-react/src/components/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useState } from "react"
import { IconBack } from "@initia/icons-react"
import { Link, useHistory } from "@/lib/router"
import { PageScrollContext } from "./PageScrollContext"
import Scrollable from "./Scrollable"
import styles from "./Page.module.css"

Expand All @@ -13,6 +15,7 @@ interface Props {

const Page = ({ title, backButton, extra, children }: PropsWithChildren<Props>) => {
const history = useHistory()
const [scrollRoot, setScrollRoot] = useState<HTMLDivElement | null>(null)

return (
<>
Expand All @@ -31,7 +34,9 @@ const Page = ({ title, backButton, extra, children }: PropsWithChildren<Props>)
{extra}
</header>

<Scrollable>{children}</Scrollable>
<PageScrollContext.Provider value={scrollRoot}>
<Scrollable ref={setScrollRoot}>{children}</Scrollable>
</PageScrollContext.Provider>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createContext, useContext } from "react"

export const PageScrollContext = createContext<HTMLDivElement | null>(null)

export function usePageScrollRoot() {
return useContext(PageScrollContext)
}
4 changes: 2 additions & 2 deletions packages/interwovenkit-react/src/components/Scrollable.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import clsx from "clsx"
import styles from "./Scrollable.module.css"

import type { PropsWithChildren, RefObject } from "react"
import type { PropsWithChildren, Ref } from "react"

interface Props {
className?: string
ref?: RefObject<HTMLDivElement | null>
ref?: Ref<HTMLDivElement>
}

const Scrollable = ({ className, children, ref }: PropsWithChildren<Props>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@
display: grid;
gap: 10px;
}

.loadMore {
margin: -8px 0 16px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const BridgeHistory = () => {
)}

{filteredHistory.length > page * BRIDGE_HISTORY_LIMIT_PER_PAGE ? (
<LoadMoreButton onClick={() => setPage((page) => page + 1)} />
<LoadMoreButton onClick={() => setPage((page) => page + 1)} className={styles.loadMore} />
) : (
history.length >= BRIDGE_HISTORY_LIMIT && (
<Status>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const WithdrawalList = ({ chain }: { chain: NormalizedChain }) => {
const executorUrl = chain.metadata?.executor_uri
if (!executorUrl) throw new Error("Executor URL is not defined")

const { data, fetchNextPage, hasNextPage, isFetching } = useWithdrawals(executorUrl)
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useWithdrawals(executorUrl)
const list = useMemo(() => data?.pages.flat() ?? [], [data])

const { syncReminders } = useClaimableReminders()
Expand Down Expand Up @@ -47,7 +47,9 @@ const WithdrawalList = ({ chain }: { chain: NormalizedChain }) => {
)
})}

{hasNextPage && <LoadMoreButton onClick={() => fetchNextPage()} disabled={isFetching} />}
{hasNextPage && (
<LoadMoreButton onClick={() => fetchNextPage()} isLoading={isFetchingNextPage} />
)}
</>
)
}
Expand Down
37 changes: 7 additions & 30 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading