Skip to content
Merged
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
61 changes: 55 additions & 6 deletions .fallowrc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
"entry": [
"fixtures/react-compat/rsbuild.config.mjs",
"storybook/stories/**/*.mdx"
"apps/docs/blume.config.ts",
"apps/docs/components.ts",
"apps/docs/docs/**/*.mdx",
"apps/docs/docs/**/meta.ts",
"apps/docs/islands/ObserverDemo.tsx",
"apps/storybook/stories/**/*.mdx",
"packages/react-intersection-observer/fixtures/react-compat/rsbuild.config.mjs"
],
"ignoreDependencies": [
"@rsbuild/core",
Expand Down Expand Up @@ -33,25 +38,69 @@
"maxCognitive": 15,
"maxCrap": 30,
"maxUnitSize": 60,
"coverage": "coverage/coverage-final.json",
"coverage": "packages/react-intersection-observer/coverage/coverage-final.json",
"thresholdOverrides": [
{
"files": ["storybook/stories/elements.tsx"],
"files": ["apps/storybook/stories/elements.tsx"],
"functions": ["EntryDetails", "ScrollWrapper"],
"maxCrap": 31,
"reason": "Interactive Storybook demos are intentionally excluded from unit coverage; retain the default structural complexity limits while allowing these two estimated CRAP scores."
},
{
"files": ["storybook/stories/useInView.story.tsx"],
"files": ["apps/storybook/stories/useInView.story.tsx"],
"functions": ["HooksRender"],
"maxCrap": 43,
"reason": "This interactive Storybook demo is intentionally excluded from unit coverage; retain the default structural complexity limits while allowing its estimated CRAP score."
},
{
"files": ["storybook/stories/useOnInView.story.tsx"],
"files": ["apps/storybook/stories/useOnInView.story.tsx"],
"functions": ["UseOnInViewRender"],
"maxCrap": 57,
"reason": "This interactive Storybook demo is intentionally excluded from unit coverage; retain the default structural complexity limits while allowing its measured CRAP score of 56."
},
{
"files": ["apps/docs/components/Logo.astro"],
"functions": ["<template>"],
"maxCrap": 157,
"reason": "The documentation logo has template-only conditional rendering and no unit-test harness; retain the default complexity limits while allowing its estimated CRAP score of 156."
},
{
"files": ["apps/docs/islands/ObserverDemo.tsx"],
"functions": ["LiveObserverDemo"],
"maxCognitive": 23,
"maxCrap": 421,
"reason": "The interactive docs demo deliberately combines the selected threshold, observer result, and visible custom-root feed in one copyable example; it has no unit-test harness, so retain explicit complexity and CRAP ceilings for this single renderer."
},
{
"files": ["apps/docs/islands/ObserverDemo.tsx"],
"functions": ["ThresholdButton"],
"maxCrap": 57,
"reason": "The threshold-button renderer is part of the interactive documentation example and has no unit-test harness; retain its estimated CRAP score without relaxing the file globally."
},
{
"files": ["apps/docs/islands/RecipeDemo.tsx"],
"functions": ["LazyImageDemo"],
"maxCrap": 91,
"reason": "This interactive lazy-image recipe is exercised in the documentation site rather than a unit-test harness; retain its estimated CRAP score without relaxing the file globally."
},
{
"files": ["apps/docs/islands/RecipeDemo.tsx"],
"functions": ["RevealDemo"],
"maxCrap": 183,
"reason": "This interactive reveal recipe is exercised in the documentation site rather than a unit-test harness; retain its estimated CRAP score without relaxing the file globally."
},
{
"files": ["apps/docs/islands/RecipeDemo.tsx"],
"functions": ["InfiniteListDemo"],
"maxCognitive": 20,
"maxCrap": 111,
"reason": "This interactive infinite-list recipe combines retry and loading state in one visible example and has no unit-test harness; retain its measured complexity without relaxing the file globally."
},
{
"files": ["apps/docs/islands/RecipeDemo.tsx"],
"functions": ["RecipeDemo", "DemoStatus"],
"maxCrap": 31,
"reason": "These small recipe-dispatch and status renderers are exercised in the documentation site rather than a unit-test harness; retain their estimated CRAP scores without relaxing the file globally."
}
],
"suggestInlineSuppression": false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pkg-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
- name: Build
run: pnpm build
- name: Publish preview package
run: pnpx pkg-pr-new publish --no-template
run: pnpm --filter react-intersection-observer preview
22 changes: 11 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install playwright
run: pnpm exec playwright install
run: pnpm --filter react-intersection-observer exec playwright install
- name: Lint
run: pnpm biome ci .
- name: Test
run: pnpm vitest --coverage
run: pnpm --filter react-intersection-observer exec vitest --coverage
env:
CI: true
- name: Fallow audit
if: github.event_name == 'pull_request'
run: pnpm fallow audit --base "origin/${{ github.base_ref }}"
run: pnpm fallow audit --base "origin/${{ github.base_ref }}" --coverage packages/react-intersection-observer/coverage/coverage-final.json
- name: Build
run: pnpm build
run: pnpm build:all

test_matrix:
runs-on: ubuntu-latest
Expand All @@ -58,19 +58,19 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Install legacy testing-library
if: ${{ startsWith(matrix.react, '17') }}
run: pnpm add -D @testing-library/react@12.1.4
run: pnpm --filter react-intersection-observer add -D @testing-library/react@12.1.4
- name: Install React types
run: pnpm add -D @types/react@${{ matrix.react }} @types/react-dom@${{ matrix.react }}
run: pnpm --filter react-intersection-observer add -D @types/react@${{ matrix.react }} @types/react-dom@${{ matrix.react }}
- name: Install ${{ matrix.react }}
run: pnpm add -D react@${{ matrix.react }} react-dom@${{ matrix.react }}
run: pnpm --filter react-intersection-observer add -D react@${{ matrix.react }} react-dom@${{ matrix.react }}
- name: Validate types
run: pnpm tsc
run: pnpm --filter react-intersection-observer typecheck
- name: Build package
run: pnpm build
- name: Test packed package
if: ${{ matrix.react != 'latest' }}
run: pnpm run compat:packed -- ${{ matrix.react }}
run: pnpm --filter react-intersection-observer run compat:packed -- ${{ matrix.react }}
- name: Run test
run: |
pnpm exec playwright install
pnpm test
pnpm --filter react-intersection-observer exec playwright install
pnpm --filter react-intersection-observer test
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.vscode
.cache
.turbo
.blume
.npm-cache
node_modules
reports
example
Expand All @@ -21,3 +24,7 @@ test-utils.js
test-utils.d.ts
__screenshots__
.fallow/
.turbo
.blume
.npm-cache
.blume-verify/
24 changes: 16 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
Welcome to `react-intersection-observer`! I'm thrilled that you're interested in
contributing. Here are some guidelines to help you get started.

The codebase is written in TypeScript, and split into two packages using PNPM
workspaces:
The codebase is written in TypeScript and uses PNPM workspaces:

- `react-intersection-observer` - The main package, which contains the
`useInView` hook and the `InView` component.
- `storybook` - A Storybook project that is used to develop and test the
`react-intersection-observer` package.
- `packages/react-intersection-observer` - The published package, which contains
the `useInView` hook and the `InView` component.
- `apps/storybook` - The Storybook project used to develop and test the package.
- `apps/docs` - The Blume documentation site.

## Development

Expand All @@ -20,12 +19,14 @@ the dependencies using [PNPM](https://pnpm.io/):
pnpm install
```

Then you can start the Storybook development server with the `dev` task:
Then you can start the development surfaces with the `dev` task:

```shell
pnpm dev
```

Use `pnpm dev:storybook` or `pnpm dev:docs` to start one surface at a time.

## Semantic Versioning

`react-intersection-observer` follows Semantic Versioning 2.0 as defined at
Expand Down Expand Up @@ -79,8 +80,15 @@ submitting your pull request.
Please ensure that your changes are covered by tests, and that all tests pass
before submitting your pull request.

You can run the tests with the `test` task:
You can run the package tests with the `test` task. Component tests run in
Vitest Browser Mode with Playwright; SSR tests run in a separate Node project.

```shell
pnpm test
```

Build every published and documentation surface with:

```shell
pnpm build:all
```
1 change: 1 addition & 0 deletions apps/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.blume-verify/
48 changes: 48 additions & 0 deletions apps/docs/blume.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { defineConfig } from "blume";

import { githubReleaseChangelogSource } from "./sources/github-releases";

export default defineConfig({
title: "React Intersection Observer",
description:
"A lightweight React implementation of the Intersection Observer API.",
logo: {
image: "/logo.svg",
},
github: {
owner: "thebuilder",
repo: "react-intersection-observer",
dir: "apps/docs",
},
content: {
sources: [
{ type: "filesystem", root: "docs" },
{
type: "custom",
source: githubReleaseChangelogSource({
owner: "thebuilder",
repo: "react-intersection-observer",
}),
},
],
},
navigation: {
tabs: [
{ label: "Docs", path: "/" },
{ label: "Changelog", path: "/changelog", href: "/changelog" },
],
},
theme: {
accent: {
light: "oklch(0.6 0.15 290)",
dark: "oklch(0.76 0.12 290)",
},
mode: "system",
},
deployment: {
output: "static",
},
ai: {
llmsTxt: true,
},
});
9 changes: 9 additions & 0 deletions apps/docs/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineComponents } from "blume";

import Logo from "./components/Logo.astro";

export default defineComponents({
layout: {
Logo,
},
});
51 changes: 51 additions & 0 deletions apps/docs/components/Logo.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
interface LogoData {
alt: string;
dark?: string;
href: string;
light?: string;
svg?: string;
text?: string;
}

interface Props {
logo?: LogoData | null;
site: { title: string };
}

const { logo, site } = Astro.props;
const brandHref = logo?.href ?? "/";
const brandText = logo?.text ?? site.title;
const logoSvg = logo?.svg;
const logoLight = logo?.light;
const logoDark = logo?.dark ?? logo?.light;
---

<a
class="inline-flex min-w-0 items-center gap-2 font-semibold text-base text-foreground"
href={brandHref}
>
{
logoSvg && (
<span
aria-hidden="true"
class="hidden h-5 shrink-0 items-center md:inline-flex [&>svg]:h-5 [&>svg]:w-auto"
set:html={logoSvg}
/>
)
}
{
!logoSvg &&
logoLight &&
(logoLight === logoDark ? (
<img alt={logo?.alt ?? ""} class="hidden h-5 w-auto md:block" src={logoLight} />
) : (
<>
<img alt={logo?.alt ?? ""} class="hidden h-5 w-auto dark:hidden md:block" src={logoLight} />
<img alt={logo?.alt ?? ""} class="hidden h-5 w-auto dark:md:block" src={logoDark} />
</>
))
}
{brandText && <span class="hidden truncate md:inline">{brandText}</span>}
<span class="truncate md:hidden">Intersection Observer</span>
</a>
Loading
Loading