Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-dom): add <FadeIn/> #1362

Merged
merged 14 commits into from
Nov 23, 2024
Merged

feat(react-dom): add <FadeIn/> #1362

merged 14 commits into from
Nov 23, 2024

Conversation

manudeli
Copy link
Member

@manudeli manudeli commented Nov 18, 2024

related with #1109

<FadeIn/>

Influenced by blog posts such as https://tech.kakaopay.com/post/skeleton-ui-idea/, we provide a component called <Delay/> in @suspensive/react. However, since <Delay/> blocks the rendering of children for a certain period of time, it affects the time when the <PostListSkeleton/> component is reflected on the html. Therefore, the skeleton itself can appear suddenly, and I thought this part was not good in terms of UX. Therefore, I thought that if we use a component like <FadeIn/> (name undecided), it will be reflected on the html immediately and the animation will be used to gradually display it, making it easier to implement a more natural UI.

Example

export default function Page() {
  return (
    <div>
      <ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
        {Array.from({ length: 20 }).map((_, i) => {
          const userId = i + 1
          return (
            <Suspense
              key={userId}
              clientOnly
              fallback={
                <FadeIn delay={200} duration={1000}>
                  {skeleton}
                </FadeIn>
              }
            >
              <SuspenseQuery {...query.user(userId)}>
                {({ data: user }) => (
                  <FadeIn duration={200} inViewOptions={{ triggerOnce: true }} className="max-w-[344px]">
                    <h1 className="text-lg font-bold">{user.username}</h1>
                    <p className="text-xs">{user.userAgent}</p>
                    <p>{user.age}</p>
                    <p>{user.maidenName}</p>
                    <div className="mb-6" />
                  </FadeIn>
                )}
              </SuspenseQuery>
            </Suspense>
          )
        })}
      </ErrorBoundary>
    </div>
  )
}

const skeleton = (
  <div role="status" className="mb-6 animate-pulse space-y-2">
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
  </div>
)

chrome-capture-2024-11-18 (2)

PR Checklist

  • I did below actions if need
  1. I read the Contributing Guide
  2. I added documents and tests.

@manudeli manudeli self-assigned this Nov 18, 2024
Copy link

changeset-bot bot commented Nov 18, 2024

🦋 Changeset detected

Latest commit: 15f62f7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@suspensive/react-dom Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Nov 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
suspensive.org ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 23, 2024 7:51am
v1.suspensive.org ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 23, 2024 7:51am
visualization.suspensive.org ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 23, 2024 7:51am

Copy link

github-actions bot commented Nov 18, 2024

Size Change: +291 B (+0.42%)

Total Size: 69 kB

Filename Size Change
packages/react-dom/dist/index.cjs 2.36 kB +253 B (+12%) ⚠️
packages/react-dom/dist/index.js 177 B +38 B (+27.34%) 🚨
ℹ️ View Unchanged
Filename Size
packages/jotai/dist/Atom.cjs 660 B
packages/jotai/dist/Atom.js 115 B
packages/jotai/dist/AtomValue.cjs 647 B
packages/jotai/dist/AtomValue.js 120 B
packages/jotai/dist/index.cjs 755 B
packages/jotai/dist/index.js 158 B
packages/jotai/dist/SetAtom.cjs 645 B
packages/jotai/dist/SetAtom.js 118 B
packages/react-dom/dist/FadeIn.cjs 2.14 kB
packages/react-dom/dist/FadeIn.js 140 B
packages/react-dom/dist/InView.cjs 2.1 kB
packages/react-dom/dist/InView.js 130 B
packages/react-dom/dist/useFadeIn.cjs 2.04 kB
packages/react-dom/dist/useFadeIn.js 133 B
packages/react-dom/dist/useInView.cjs 1.87 kB
packages/react-dom/dist/useInView.js 120 B
packages/react-native/dist/index.cjs 619 B
packages/react-native/dist/index.js 122 B
packages/react-native/dist/TestText.cjs 612 B
packages/react-native/dist/TestText.js 119 B
packages/react-query-4/dist/index.cjs 1.86 kB
packages/react-query-4/dist/index.js 394 B
packages/react-query-4/dist/infiniteQueryOptions.cjs 548 B
packages/react-query-4/dist/infiniteQueryOptions.js 144 B
packages/react-query-4/dist/Mutation.cjs 821 B
packages/react-query-4/dist/Mutation.js 132 B
packages/react-query-4/dist/PrefetchInfiniteQuery.cjs 722 B
packages/react-query-4/dist/PrefetchInfiniteQuery.js 155 B
packages/react-query-4/dist/PrefetchQuery.cjs 712 B
packages/react-query-4/dist/PrefetchQuery.js 147 B
packages/react-query-4/dist/QueryClientConsumer.cjs 665 B
packages/react-query-4/dist/QueryClientConsumer.js 139 B
packages/react-query-4/dist/QueryErrorBoundary.cjs 1.12 kB
packages/react-query-4/dist/QueryErrorBoundary.js 142 B
packages/react-query-4/dist/queryOptions.cjs 540 B
packages/react-query-4/dist/queryOptions.js 135 B
packages/react-query-4/dist/SuspenseInfiniteQuery.cjs 1.05 kB
packages/react-query-4/dist/SuspenseInfiniteQuery.js 155 B
packages/react-query-4/dist/SuspenseQueries.cjs 923 B
packages/react-query-4/dist/SuspenseQueries.js 149 B
packages/react-query-4/dist/SuspenseQuery.cjs 1.04 kB
packages/react-query-4/dist/SuspenseQuery.js 147 B
packages/react-query-4/dist/usePrefetchInfiniteQuery.cjs 648 B
packages/react-query-4/dist/usePrefetchInfiniteQuery.js 148 B
packages/react-query-4/dist/usePrefetchQuery.cjs 639 B
packages/react-query-4/dist/usePrefetchQuery.js 140 B
packages/react-query-4/dist/useSuspenseInfiniteQuery.cjs 833 B
packages/react-query-4/dist/useSuspenseInfiniteQuery.js 148 B
packages/react-query-4/dist/useSuspenseQueries.cjs 833 B
packages/react-query-4/dist/useSuspenseQueries.js 142 B
packages/react-query-4/dist/useSuspenseQuery.cjs 824 B
packages/react-query-4/dist/useSuspenseQuery.js 140 B
packages/react-query-5/dist/index.cjs 1.76 kB
packages/react-query-5/dist/index.js 391 B
packages/react-query-5/dist/infiniteQueryOptions.cjs 573 B
packages/react-query-5/dist/infiniteQueryOptions.js 144 B
packages/react-query-5/dist/Mutation.cjs 821 B
packages/react-query-5/dist/Mutation.js 132 B
packages/react-query-5/dist/PrefetchInfiniteQuery.cjs 647 B
packages/react-query-5/dist/PrefetchInfiniteQuery.js 145 B
packages/react-query-5/dist/PrefetchQuery.cjs 639 B
packages/react-query-5/dist/PrefetchQuery.js 137 B
packages/react-query-5/dist/QueryClientConsumer.cjs 663 B
packages/react-query-5/dist/QueryClientConsumer.js 140 B
packages/react-query-5/dist/QueryErrorBoundary.cjs 1.12 kB
packages/react-query-5/dist/QueryErrorBoundary.js 142 B
packages/react-query-5/dist/queryOptions.cjs 563 B
packages/react-query-5/dist/queryOptions.js 136 B
packages/react-query-5/dist/SuspenseInfiniteQuery.cjs 833 B
packages/react-query-5/dist/SuspenseInfiniteQuery.js 145 B
packages/react-query-5/dist/SuspenseQueries.cjs 671 B
packages/react-query-5/dist/SuspenseQueries.js 139 B
packages/react-query-5/dist/SuspenseQuery.cjs 825 B
packages/react-query-5/dist/SuspenseQuery.js 137 B
packages/react-query-5/dist/usePrefetchInfiniteQuery.cjs 577 B
packages/react-query-5/dist/usePrefetchInfiniteQuery.js 148 B
packages/react-query-5/dist/usePrefetchQuery.cjs 569 B
packages/react-query-5/dist/usePrefetchQuery.js 140 B
packages/react-query-5/dist/useSuspenseInfiniteQuery.cjs 577 B
packages/react-query-5/dist/useSuspenseInfiniteQuery.js 148 B
packages/react-query-5/dist/useSuspenseQueries.cjs 571 B
packages/react-query-5/dist/useSuspenseQueries.js 142 B
packages/react-query-5/dist/useSuspenseQuery.cjs 569 B
packages/react-query-5/dist/useSuspenseQuery.js 140 B
packages/react-query/dist/index.cjs 551 B
packages/react-query/dist/index.js 121 B
packages/react-query/dist/v4.cjs 550 B
packages/react-query/dist/v4.js 116 B
packages/react-query/dist/v5.cjs 550 B
packages/react-query/dist/v5.js 116 B
packages/react/dist/ClientOnly.cjs 734 B
packages/react/dist/ClientOnly.js 141 B
packages/react/dist/DefaultProps.cjs 1.05 kB
packages/react/dist/DefaultProps.js 167 B
packages/react/dist/Delay.cjs 1.23 kB
packages/react/dist/Delay.js 159 B
packages/react/dist/DevMode.cjs 530 B
packages/react/dist/DevMode.js 131 B
packages/react/dist/ErrorBoundary.cjs 2.74 kB
packages/react/dist/ErrorBoundary.js 205 B
packages/react/dist/ErrorBoundaryGroup.cjs 1.38 kB
packages/react/dist/ErrorBoundaryGroup.js 195 B
packages/react/dist/index.cjs 4.32 kB
packages/react/dist/index.js 368 B
packages/react/dist/Suspense.cjs 1.29 kB
packages/react/dist/Suspense.js 171 B
packages/react/dist/Suspensive.cjs 1.26 kB
packages/react/dist/Suspensive.js 175 B
packages/react/dist/wrap.cjs 3.78 kB
packages/react/dist/wrap.js 209 B

compressed-size-action

Copy link

codspeed-hq bot commented Nov 18, 2024

CodSpeed Performance Report

Merging #1362 will create unknown performance changes

Comparing react-dom/FadeIn (15f62f7) with main (390e82d)

Summary

⚠️ No benchmarks were detected in both the base of the PR and the PR.\

@codecov-commenter
Copy link

codecov-commenter commented Nov 18, 2024

Codecov Report

Attention: Patch coverage is 66.66667% with 4 lines in your changes missing coverage. Please review.

Project coverage is 72.00%. Comparing base (e4af4a2) to head (15f62f7).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1362      +/-   ##
==========================================
- Coverage   72.11%   72.00%   -0.12%     
==========================================
  Files          67       69       +2     
  Lines         581      593      +12     
  Branches      129      134       +5     
==========================================
+ Hits          419      427       +8     
- Misses        149      153       +4     
  Partials       13       13              
Components Coverage Δ
@suspensive/react 100.00% <ø> (ø)
@suspensive/react-dom 95.55% <66.66%> (-4.45%) ⬇️
@suspensive/react-native 100.00% <ø> (ø)
@suspensive/react-query 74.01% <ø> (ø)
@suspensive/react-query-4 0.00% <ø> (ø)
@suspensive/react-query-5 0.00% <ø> (ø)
@suspensive/jotai 0.00% <ø> (ø)
@suspensive/codemods 41.97% <ø> (ø)
---- 🚨 Try these New Features:

@manudeli
Copy link
Member Author

I'm thinking about adding this implementation! I'd like to hear your thoughts.

packages/react-dom/src/FadeIn.tsx Outdated Show resolved Hide resolved
packages/react-dom/src/FadeIn.tsx Outdated Show resolved Hide resolved
packages/react-dom/src/FadeIn.tsx Outdated Show resolved Hide resolved
packages/react-dom/src/FadeIn.tsx Outdated Show resolved Hide resolved
@manudeli
Copy link
Member Author

manudeli commented Nov 21, 2024

@kangju2000 @gwansikk I'm currently thinking about changing the interface like this
I wonder what you think about this change in 7b55958

AS-IS

We need to manage "as" prop, Component from "as" prop must have "ref" & "style" prop

const Example = () => (
  <FadeIn as={Skeleton} duration={300} timingFunction="ease-in" />
)

const Skeleton = fowardRef((props, ref) => ( // We should use forwardRef
  <div ref={ref} {...props} role="status" className="mb-6 animate-pulse space-y-2">
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
  </div>
))

// But How can we make Skeleton contain FadeIn?
// 🚨 There is no way in my opinion

TO-BE

I think it would be easier to understand if you use it like a children render prop, or like a register in react-hook-form.
But I think this implementation could be noisy too...! 🥲 I don't have

const Example = () => (
  <FadeIn duration={300} timingFunction="ease-in">
    {(fadeIn) => <Skeleton {...fadeIn} />}
  </FadeIn>
)

const Example2 = () => (
  <FadeIn duration={300} timingFunction="ease-in">
    {({ ref, style }) => <Skeleton ref={ref} style={{ ..., ...style}} />}
  </FadeIn>
)

const Skeleton = fowardRef((props, ref) => (
  <div ref={ref} {...props} role="status" className="mb-6 animate-pulse space-y-2">
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
  </div>
))

// But How can we make Skeleton contain FadeIn?
const Skeleton = () => ( // We don't need to use forwardRef too
  <FadeIn duration={300} timingFunction="ease-in">
    {(fadeIn) =>  (
      <div {...fadeIn} role="status" className="mb-6 animate-pulse space-y-2">
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
      </div>
    )}
  </FadeIn>
)

@manudeli manudeli requested a review from gwansikk November 21, 2024 02:33
@gwansikk
Copy link
Collaborator

gwansikk commented Nov 22, 2024

I think it would be easier to understand if you use it like a children render prop, or like a register in react-hook-form.

I believe a simple interface is better. The TO-BE case(like a register in react-hook-form) is easier for users to use compared to the AS-IS case, with a simpler and more user-friendly interface! (once users understand how to use it)

But I think this implementation could be noisy too...!

What kind of noise are you concerned about? Is it because of the returned fadeIn((fadeIn) => {}) and the need to create and wrap a Fragment(e.g. <div {...fadeIn}>) to inject(register) styles?

@kangju2000
Copy link
Collaborator

I was thinking about the name "FadeIn" - when developers see this component name, they'd probably expect a simple wrapper that handles fade-in animation. But the current implementation seems more complex.

<FadeIn duration={300} timingFunction="ease-in">
  {(fadeIn) => <Skeleton {...fadeIn} />}
</FadeIn>

Looking at the actual usage

<FadeIn delay={200} duration={1000}>
  {skeleton}
</FadeIn>

We need to manage "as" prop, Component from "as" prop must have "ref" & "style" prop

I'm curious about what specific cases require the render props pattern and "as" prop here (which adds the constraint of handling ref and style)? If there aren't any special requirements, maybe we could simplify it to a wrapper component? 😀

@manudeli
Copy link
Member Author

manudeli commented Nov 23, 2024

<FadeIn delay={200} duration={1000}>
  {skeleton}
</FadeIn>

To do this implementation, we need to make a decision to include rendering the component received as a div or as prop in FadeIn. I think including such a thing in FadeIn is not a flexible design.

Case: style prop Okay

// AS-IS
const Skeleton = () => (
  <FadeIn role="status" className="mb-6 animate-pulse space-y-2"> // contain div
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
    <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
  </FadeIn>
)

// TO-BE
const Skeleton = () => (
  <FadeIn duration={300} timingFunction="ease-in"> // don't contain div, just give ref, style prop
    {(fadeIn) =>  (
      <div {...fadeIn} role="status" className="mb-6 animate-pulse space-y-2">
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
      </div>
    )}
  </FadeIn>
)

Case: emotion css prop

// AS-IS
// We can't implement emotion css prop case

// TO-BE
const Skeleton = () => (
  <FadeIn duration={300} timingFunction="ease-in"> // don't contain div, just give ref, style prop
    {(fadeIn) =>  (
      <div ref={fadeIn.ref} css={fadeIn.style} role="status" className="mb-6 animate-pulse space-y-2">
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-2 w-[344px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[42px] rounded-sm bg-gray-300 dark:bg-gray-600" />
        <div className="h-4 w-[34px] rounded-sm bg-gray-300 dark:bg-gray-600" />
      </div>
    )}
  </FadeIn>
)

The goal of Suspensive is to work independently of all cases, so wouldn't it be better to provide a flexible design that can be used in all cases rather than being limited to "style props" by including "div"?

@gwansikk
Copy link
Collaborator

gwansikk commented Nov 23, 2024

But the current implementation seems more complex.

In my opinion, the usage in FadeIn is not particularly complex(In Suspensive package). There are many APIs that provide similar functionality, making it familiar and easy to learn quickly. (e.g., ErrorBoundaryGroup, SuspenseQuery, etc..)

I was thinking about the name "FadeIn" - when developers see this component name, they'd probably expect a simple wrapper that handles fade-in animation.

@kangju2000 Would it be better to change it to a more appropriate name?

The goal of Suspensive is to work independently of all cases, so wouldn't it be better to provide a flexible design that can be used in all cases rather than being limited to "style props" by including "div"?

@manudeli I agree this point

@manudeli
Copy link
Member Author

In my opinion, the usage in FadeIn is not particularly complex(In Suspensive package). There are many APIs that provide similar functionality, making it familiar and easy to learn quickly. (e.g., ErrorBoundaryGroup, SuspenseQuery, etc..)

I agree this point

@manudeli
Copy link
Member Author

@gwansikk @kangju2000 Then, could we just keep this implementation and deploy it first? If there's a better implementation, I think it's possible to fix it since @suspensive/react-dom is version 0!

Copy link
Collaborator

@kangju2000 kangju2000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good! After reading the comments, I understand. I think it would be fine to deploy first and discuss it again later 🙇‍♂️

@manudeli manudeli merged commit 244b305 into main Nov 23, 2024
18 checks passed
@manudeli manudeli deleted the react-dom/FadeIn branch November 23, 2024 07:57
manudeli added a commit that referenced this pull request Nov 23, 2024
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @suspensive/[email protected]

### Minor Changes

- [#1362](#1362)
[`244b305`](244b305)
Thanks [@manudeli](https://github.com/manudeli)! - feat(react-dom): add
`<FadeIn/>`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants