Full Code of askides/react-plock for AI

main 15f82a92f943 cached
41 files
53.3 KB
21.3k tokens
14 symbols
1 requests
Download .txt
Repository: askides/react-plock
Branch: main
Commit: 15f82a92f943
Files: 41
Total size: 53.3 KB

Directory structure:
gitextract_nv2tjxfx/

├── .github/
│   └── workflows/
│       ├── release.yml
│       ├── stale.yml
│       └── testing.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── assets/
│   └── data/
│       └── images.ts
├── examples/
│   ├── with-nextjs/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── next.config.mjs
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── src/
│   │   │   └── app/
│   │   │       ├── globals.css
│   │   │       ├── layout.tsx
│   │   │       ├── page.tsx
│   │   │       └── ui/
│   │   │           └── Button.tsx
│   │   ├── tailwind.config.ts
│   │   └── tsconfig.json
│   └── with-vite/
│       ├── .gitignore
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── ReactPlock.tsx
│       │   ├── main.tsx
│       │   ├── styles/
│       │   │   └── index.css
│       │   └── ui/
│       │       └── Button.tsx
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
├── libs/
│   └── react-plock/
│       ├── package.json
│       ├── src/
│       │   ├── index.spec.tsx
│       │   ├── index.tsx
│       │   └── utils/
│       │       ├── useGridStyles.ts
│       │       └── useMediaValues.ts
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       ├── vite.config.ts
│       └── vitest.config.ts
├── package.json
└── pnpm-workspace.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (e.g., 1.0.0)'
        required: true
        type: string

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - uses: pnpm/action-setup@v3
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'

      - name: Install dependencies
        run: pnpm i

      - name: Run Tests
        run: pnpm run --filter react-plock test

      - name: Build
        run: pnpm run --filter react-plock build

      - name: Manual Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          VERSION: ${{ inputs.version }}
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"

          # Change directory to the package
          cd libs/react-plock

          # Update version in package.json
          npm version $VERSION --no-git-tag-version

          # Return to root
          cd ../../

          # Commit the version change
          git add .
          git commit -m "chore: release v${VERSION}"

          # Push the version change commit
          git push

          # Publish to npm
          pnpm --filter react-plock publish

      - name: Generate Changelog
        id: changelog
        run: |
          PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^)
          REPO="${GITHUB_REPOSITORY}"
          {
            echo "CHANGELOG<<EOF"
            echo "# Changelog"
            echo ""
            git log --pretty=format:"- %s (${REPO}@%h)" ${PREVIOUS_TAG}..HEAD
            echo ""
            echo "EOF"
          } >> $GITHUB_ENV

      - name: Create GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: v${{ inputs.version }}
          release_name: v${{ inputs.version }}
          body: ${{ env.CHANGELOG }}
          draft: false
          prerelease: false


================================================
FILE: .github/workflows/stale.yml
================================================
name: Mark stale issues and pull requests

on:
  schedule:
  - cron: '29 18 * * *'

jobs:
  stale:

    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write

    steps:
    - uses: actions/stale@v5
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        stale-issue-message: 'This issue has been automatically closed due to inactivity. If you still have further updates, please feel free to reopen or create a new issue.'
        stale-pr-message: 'This pull request has been automatically closed due to inactivity. If you still intend to contribute, please feel free to reopen the PR or submit a new one.'
        stale-issue-label: 'no-issue-activity'
        stale-pr-label: 'no-pr-activity'


================================================
FILE: .github/workflows/testing.yml
================================================
name: Testing CI

on:
  pull_request:
    branches:
      - main
      - dev

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: pnpm/action-setup@v3
        with:
          version: 8

      - run: pnpm install
      - run: pnpm run --filter react-plock test


================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/**/dist
/tmp
/out-tsc

# dependencies
node_modules

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings

# System Files
.DS_Store
Thumbs.db


================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Renato Pozzi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![Plock Logo](./assets/cover.png)

React Plock is a tree-shakeable **ultra small** npm package (**less than 1kB gzipped**) that allows you to create amazing masonry layouts with an amazing developer experience. With React Plock, you can easily create responsive and customizable layouts that adapt to different screen sizes and devices.

### Features

- **Masonry Layout**: Create beautiful masonry layouts with ease.
- **Balanced Layout**: Create a more visually harmonious masonry grid by considering the height of each item.
- **Responsive**: Automatically adapts to different screen sizes and devices.
- **Customizable**: Customize the layout to match your needs.
- **TypeScript Ready**: Get the strength of type-safe languages.
- **Amazing DX**: Easy to use and well-documented.

### Examples

- Using Next.js 14 (Server Components) [See Working Demo](https://react-plock-with-nextjs.vercel.app/)
- Using ViteJS [See Working Demo](https://react-plock-with-vite.vercel.app/)

### Installation

```bash
npm install react-plock
```

### Usage

Using Plock with the new v3 APIs it's a piece of cake. Here's an example of how can you create a masonry grid. You can even see a demo of this example by clicking [here](https://react-plock-with-vite.vercel.app/).

```tsx
import { Masonry } from 'react-plock';

const ImagesMasonry = () => {
  const items = [...imageUrls];

  return (
    <Masonry
      items={items}
      config={{
        columns: [1, 2, 3],
        gap: [24, 12, 6],
        media: [640, 768, 1024],
      }}
      render={(item, idx) => (
        <img key={idx} src={item} style={{ width: '100%', height: 'auto' }} />
      )}
    />
  );
};
```

### Balanced Layout

React Plock offers a balanced layout option that creates a more visually harmonious masonry grid by considering the height of each item. When enabled with `useBalancedLayout: true`, the layout algorithm distributes items across columns while attempting to minimize height differences between columns.

This is particularly useful for content with varying heights, such as images or cards, where a traditional masonry layout might create uneven columns.

Unlike the default layout which distributes items sequentially, the balanced layout dynamically measures and adjusts item placement to create a more aesthetically pleasing result.

```tsx
<Masonry
  items={items}
  config={{
    columns: [2, 3, 4],
    gap: [16, 16, 16],
    media: [640, 768, 1024],
    useBalancedLayout: true, // Enable balanced layout
  }}
  render={(item) => (
    <img src={item.url} alt={item.alt} style={{ width: '100%' }} />
  )}
/>
```

### API Reference

Here's the TypeScript definition for the Masonry Component, below you can find a more detailed explanation.

```ts
export type MasonryProps<T> = React.ComponentPropsWithoutRef<'div'> & {
  items: T[];
  render: (item: T, idx: number) => React.ReactNode;
  config: {
    columns: number | number[];
    gap: number | number[];
    media?: number[];
    useBalancedLayout?: boolean;
  };
  as?: React.ElementType;
};
```

#### Items

This prop accepts a generic array of elements, each one will be passed to the **render** property.

#### Render

The masonry render prop. Here's where you define the styles of every tile of the grid, the function takes the current looping item and the relative index.

#### Config

A configuration object that is used to define the number of columns, media queries and gaps between items.

#### Other Props

As you can see, by using `React.ComponentPropsWithoutRef<"div">` you can simply pass every available property to the div, some examples are **id** and **className**. The only one property that will be overwritten will be the `style` because is used internally for the masonry generation.

### Important Note

Please, note that in case you are passing an array to the columns attribute of the config property, the number of elements **MUST** be equal to the number of media AND gap breakpoints provided!

```tsx
// Correct: This will be responsive with 3 breakpoints.
<Masonry
  {...otherProps}
  config={{
    columns: [1, 2, 3],
    gap: [12, 16, 20],
    media: [640, 768, 1024],
  }}
/>

// Correct: This will be responsive with 2 breakpoints.
<Masonry
  {...otherProps}
  config={{
    columns: [1, 2],
    gap: [2, 4],
    media: [640, 1024],
  }}
/>

// Correct: This will be fixed with 4 columns in every screen size.
<Masonry
  {...otherProps}
  config={{
    columns: 4,
    gap: 8
  }}
/>

// NOT Correct: This will cause trouble in rendering.
<Masonry
  {...otherProps}
  config={{
    columns: [4],
    media: [640, 768],
  }}
/>
```


================================================
FILE: assets/data/images.ts
================================================
export const images = [
  "https://images.unsplash.com/photo-1606542758304-820b04394ac2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDQy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1611419010196-a360856fc42f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzMjg2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1530919424169-4b95f917e937?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDQ2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1634703080363-98f94e5a1076?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDUw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1588007374946-c79543903e8a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxNTk1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1561411996-3794338f63cc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzNzIy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1560052775-e4f689f06f07?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDYw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1604818659463-34304eab8e70?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxNjgz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1520563683082-7ef74b616a89?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDY4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1573537805874-4cedc5d389ce?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDI0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1534817043788-41286c872b7b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxNjcz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1586461715699-1e192dcd04c6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDc5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1580428180163-76ab1efe2aed?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDgz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1595271444083-08084c6857c7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDg2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1604818659418-1c53672b00f6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NDg4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1602136773736-34d445b989cb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxNDA0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1555086156-e6c7353d283f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNzAw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1530692228265-084b21566b12?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTA0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1547434836-398fc5a73e91?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTA1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1626759486966-c067e3f79982?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTEw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1611419010019-550124aef004?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTE2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1560671021-cb36f70ce82d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTE3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1574357265250-10c88f63ebfd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNTAx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/flagged/photo-1579451442952-f0365f3f0aed?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTIy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1619796404374-aff912b43cd2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTIz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1582472978953-12929ab18f3e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTIz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1594886801338-b81548345f77?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTI5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1539606328118-80c679838702?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTMw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1573455494057-12684d151bf4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTMx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1557515126-1bf9ada5cb93?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTMy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1536890992765-f42a1ee1e2a8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTQy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1586754102101-36b67e4c5bcf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNzg5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1547355253-ff0740f6e8c1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTU0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1555679025-2b0c57d18992?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTU4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1608687087357-845abfade367?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTYw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/flagged/photo-1579451443170-44b3963c3341?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxMjgx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1535237654113-09c1e5d6e622?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTY4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1503187685617-d78d295f163e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTcx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1628753254988-da6979f94173?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTc1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1585731833344-88bf310c0db6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTc2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1579169703977-e4575236583c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTc2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1622099330140-69125ac0d6a9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzNjQ3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1576075796033-848c2a5f3696?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNTE3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1573501815578-6252ee088c47?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTg2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1553227957-454e04fa8472?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNDAw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1564836663277-c4aa761b9882?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTk2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1607419674405-256ed5bc8f69?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NTk3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1535737005411-82def79a9038?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyODY2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1582701973975-0fff9b4c06e6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0MDQz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1548387834-7bf05019e89b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjA2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1598933385397-43259d8c1554?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjA3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1606152538442-6764e7a61d3d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjEy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1529641484336-ef35148bab06?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNzE1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1585732436715-4c25f4ba85f5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjE2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1625632019469-108132f8fc60?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjE4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1574357278720-2809ce8065db?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjIw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1582150800250-347e369a020c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjIx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1595353447630-3b8758e6a386?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjI2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1563964040780-8605906e3eb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxMjg3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1620215175664-cb9a6f5b6103?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyMDI2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1564736676781-d0f57b29f67a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjM4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1556015174-ac6f87f53456?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxMTMy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1498736297812-3a08021f206f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjQ0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1588007374916-76ab3ff82a78?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjQ3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1563679200937-f4266649d170?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjU0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1559828801-04565cd31e27?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxODIy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1575290904798-0f89bf0297d5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjYw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1556367713-029b57f4a20e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjYx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1610802752018-795027c7eca9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjY0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1581250190370-6368e32dbcb5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjY5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1579024567508-3cbf27eec69b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njcy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1534946366195-7bf1dfc2fbd1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njcz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1628174383885-642404980686?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njc4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1612974904144-5c725b5c6e98?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njg2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1638621029425-6c77f2219438?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njg2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1574391884720-bbc3740c59d1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzOTMx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1608558259020-0ba7302dc3d1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njkw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1600382224527-3ab1474fbb2a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njk0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1588007375246-3ee823ef4851?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyMDc1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1622407132338-48135fd10360?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Njk5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1498036882173-b41c28a8ba34?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NjMx&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1568838572861-3d9cc2724435?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzA4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1582150809510-8098a39b1ab9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzEw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1573455494060-c5595004fb6c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzNzQ5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1515036551567-bf1198cccc35?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzEy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1502211261676-a07824b55355?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzEz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1607817895500-6edefa86bdb7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzE3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1626848810124-aa1bf62c0230?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUxNjUw&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1571182160015-2169f6e1aa5f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzODQ5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1536901766856-5d45744cd180?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzI1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1555852224-2a3e675fc47e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzI4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1594162958229-f7d5c3bf33d7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzI4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1578608738964-cd27acd5af2c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNTc5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1626339277573-ff8da132c10b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzQy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1612781367540-433dff529376?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzQ2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1485163819542-13adeb5e0068?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNzE5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1598476902279-11952e5a21b8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzYz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1615006801329-249dcef6aa0f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzY3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1610802711091-89aaa0ce4bc5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0NzY5&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1604604994333-f1b0e9471186?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyOTEz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1594974027866-46b2413fb07d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzc0&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1601601083968-da6bc248246f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0MDMy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1583430999549-6813db72eb6e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyMTcz&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1575907199965-cf4c0ea4092d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzgy&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1536098561742-ca998e48cbcc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUyNzQ2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1578608724117-4e61ac0ff89f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzg1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1476445704028-a36e0c798192?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODUzMTE4&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1571942662090-0d81d067ca19?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzk1&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1558961910-90e0503c1064?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzk2&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1626111416202-4afbfda51ce3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0Nzk3&ixlib=rb-4.0.3&q=80&w=720",
  "https://images.unsplash.com/photo-1574357273651-588a9228b9a6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc3ODU0ODAw&ixlib=rb-4.0.3&q=80&w=720",
];


================================================
FILE: examples/with-nextjs/.eslintrc.json
================================================
{
  "extends": "next/core-web-vitals"
}


================================================
FILE: examples/with-nextjs/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: examples/with-nextjs/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.


================================================
FILE: examples/with-nextjs/next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "images.unsplash.com",
        port: "",
        pathname: "/**/**",
      },
    ],
  },
};

export default nextConfig;


================================================
FILE: examples/with-nextjs/package.json
================================================
{
  "name": "with-nextjs",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "19.2.0",
    "react-dom": "19.2.0",
    "next": "15.5.9",
    "react-plock": "workspace:*"
  },
  "devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "19.2.2",
    "@types/react-dom": "19.2.1",
    "autoprefixer": "^10.0.1",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "eslint": "^8",
    "eslint-config-next": "15.5.4"
  },
  "pnpm": {
    "overrides": {
      "@types/react": "19.2.2",
      "@types/react-dom": "19.2.1"
    }
  }
}


================================================
FILE: examples/with-nextjs/postcss.config.js
================================================
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};


================================================
FILE: examples/with-nextjs/src/app/globals.css
================================================
body {
  margin: 0;
}


================================================
FILE: examples/with-nextjs/src/app/layout.tsx
================================================
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}


================================================
FILE: examples/with-nextjs/src/app/page.tsx
================================================
'use client';

import { useState } from 'react';
import { Masonry } from 'react-plock';
import { Button } from './ui/Button';

const items = [
  { height: 600, color: '#D32F2F', id: 1 }, // Extra Tall - Darker Red
  { height: 200, color: '#00796B', id: 2 }, // Short - Darker Teal
  { height: 400, color: '#1976D2', id: 3 }, // Tall - Darker Blue
  { height: 300, color: '#2E7D32', id: 4 }, // Medium - Darker Green
  { height: 500, color: '#F57F17', id: 5 }, // Very Tall - Darker Yellow
  { height: 250, color: '#C2185B', id: 6 }, // Medium-Short - Darker Pink
  { height: 450, color: '#4527A0', id: 7 }, // Tall - Darker Purple
  { height: 200, color: '#1565C0', id: 8 }, // Short - Darker Blue
  { height: 350, color: '#D84315', id: 9 }, // Medium-Tall - Darker Orange
  { height: 550, color: '#1B5E20', id: 10 }, // Very Tall - Darker Green
  { height: 150, color: '#B71C1C', id: 11 }, // Very Short - Darker Red
  { height: 400, color: '#311B92', id: 12 }, // Tall - Darker Purple
  { height: 300, color: '#00695C', id: 13 }, // Medium - Darker Turquoise
  { height: 250, color: '#E65100', id: 14 }, // Medium-Short - Darker Orange
  { height: 500, color: '#880E4F', id: 15 }, // Very Tall - Darker Pink
  { height: 200, color: '#0D47A1', id: 16 }, // Short - Darker Blue
  { height: 450, color: '#3E2723', id: 17 }, // Tall - Darker Brown
  { height: 350, color: '#4A148C', id: 18 }, // Medium-Tall - Darker Purple
  { height: 500, color: '#01579B', id: 19 }, // Medium - Darker Blue
  { height: 400, color: '#AD1457', id: 20 }, // Tall - Darker Pink
  { height: 300, color: '#E65100', id: 21 }, // Medium-Short - Darker Orange
];

export default function Home() {
  const [isBalanced, setIsBalanced] = useState(false);

  return (
    <div style={{ padding: '16px', fontFamily: 'system-ui' }}>
      <fieldset style={{ padding: 16 }}>
        <legend>Settings</legend>

        <Button onClick={() => setIsBalanced(!isBalanced)}>
          {isBalanced ? 'Balanced Layout' : 'Default Layout'}
        </Button>
      </fieldset>

      <div style={{ paddingTop: '16px' }} />

      <Masonry
        items={items}
        config={{
          columns: [1, 2, 3],
          gap: [24, 12, 6],
          media: [640, 1024, 1280],
          useBalancedLayout: isBalanced,
        }}
        render={(item) => (
          <div
            style={{
              height: item.height,
              backgroundColor: item.color,
              width: '100%',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: '#FFF',
              fontSize: '22px',
              fontWeight: 'bold',
            }}
          >
            {`${item.height}px - #${item.id}`}
          </div>
        )}
      />
    </div>
  );
}


================================================
FILE: examples/with-nextjs/src/app/ui/Button.tsx
================================================
import { ButtonHTMLAttributes, forwardRef } from 'react';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, style, ...props }, ref) => {
    return <button className={className} ref={ref} {...props} />;
  }
);

Button.displayName = 'Button';

export { Button };


================================================
FILE: examples/with-nextjs/tailwind.config.ts
================================================
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
};
export default config;


================================================
FILE: examples/with-nextjs/tsconfig.json
================================================
{
  "compilerOptions": {
    "baseUrl": ".",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"],
      "@assets/*": ["../../assets/*"]
    },
    "target": "ES2017"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}


================================================
FILE: examples/with-vite/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: examples/with-vite/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: examples/with-vite/package.json
================================================
{
  "name": "with-vite",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-plock": "workspace:*",
    "vite-tsconfig-paths": "^4.3.1"
  },
  "devDependencies": {
    "@types/react": "^18.0.27",
    "@types/react-dom": "^18.0.10",
    "@vitejs/plugin-react": "^3.1.0",
    "typescript": "^5.3.3",
    "vite": "^4.1.0"
  }
}


================================================
FILE: examples/with-vite/src/ReactPlock.tsx
================================================
import { Masonry } from 'react-plock';
import { useState } from 'react';
import { Button } from './ui/Button';

const items = [
  { height: 600, color: '#D32F2F', id: 1 }, // Extra Tall - Darker Red
  { height: 200, color: '#00796B', id: 2 }, // Short - Darker Teal
  { height: 400, color: '#1976D2', id: 3 }, // Tall - Darker Blue
  { height: 300, color: '#2E7D32', id: 4 }, // Medium - Darker Green
  { height: 500, color: '#F57F17', id: 5 }, // Very Tall - Darker Yellow
  { height: 250, color: '#C2185B', id: 6 }, // Medium-Short - Darker Pink
  { height: 450, color: '#4527A0', id: 7 }, // Tall - Darker Purple
  { height: 200, color: '#1565C0', id: 8 }, // Short - Darker Blue
  { height: 350, color: '#D84315', id: 9 }, // Medium-Tall - Darker Orange
  { height: 550, color: '#1B5E20', id: 10 }, // Very Tall - Darker Green
  { height: 150, color: '#B71C1C', id: 11 }, // Very Short - Darker Red
  { height: 400, color: '#311B92', id: 12 }, // Tall - Darker Purple
  { height: 300, color: '#00695C', id: 13 }, // Medium - Darker Turquoise
  { height: 250, color: '#E65100', id: 14 }, // Medium-Short - Darker Orange
  { height: 500, color: '#880E4F', id: 15 }, // Very Tall - Darker Pink
  { height: 200, color: '#0D47A1', id: 16 }, // Short - Darker Blue
  { height: 450, color: '#3E2723', id: 17 }, // Tall - Darker Brown
  { height: 350, color: '#4A148C', id: 18 }, // Medium-Tall - Darker Purple
  { height: 500, color: '#01579B', id: 19 }, // Medium - Darker Blue
  { height: 400, color: '#AD1457', id: 20 }, // Tall - Darker Pink
  { height: 300, color: '#E65100', id: 21 }, // Medium-Short - Darker Orange
];

export function ReactPlock() {
  const [isBalanced, setIsBalanced] = useState(false);

  return (
    <div style={{ padding: '16px', fontFamily: 'system-ui' }}>
      <fieldset style={{ padding: 16 }}>
        <legend>Settings</legend>

        <Button onClick={() => setIsBalanced(!isBalanced)}>
          {isBalanced ? 'Balanced Layout' : 'Default Layout'}
        </Button>
      </fieldset>

      <div style={{ paddingTop: '16px' }} />

      <Masonry
        items={items}
        config={{
          columns: [1, 2, 3],
          gap: [24, 12, 6],
          media: [640, 1024, 1280],
          useBalancedLayout: isBalanced,
        }}
        render={(item) => (
          <div
            style={{
              height: item.height,
              backgroundColor: item.color,
              width: '100%',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              color: '#FFF',
              fontSize: '22px',
              fontWeight: 'bold',
            }}
          >
            {`${item.height}px - #${item.id}`}
          </div>
        )}
      />
    </div>
  );
}


================================================
FILE: examples/with-vite/src/main.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ReactPlock } from './ReactPlock';

import './styles/index.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <ReactPlock />
  </React.StrictMode>
);


================================================
FILE: examples/with-vite/src/styles/index.css
================================================
body {
  margin: 0;
}


================================================
FILE: examples/with-vite/src/ui/Button.tsx
================================================
import { ButtonHTMLAttributes, forwardRef } from 'react';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, style, ...props }, ref) => {
    return <button className={className} ref={ref} {...props} />;
  }
);

Button.displayName = 'Button';

export { Button };


================================================
FILE: examples/with-vite/tsconfig.json
================================================
{
  "compilerOptions": {
    "baseUrl": ".",
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "paths": {
      "@assets/*": ["../../assets/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: examples/with-vite/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: examples/with-vite/vite.config.ts
================================================
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), tsconfigPaths()],
});


================================================
FILE: libs/react-plock/package.json
================================================
{
  "name": "react-plock",
  "version": "3.6.1",
  "type": "module",
  "description": "The 1kB Masonry Grid for React",
  "author": "Renato Pozzi <askides@proton.me>",
  "homepage": "https://github.com/askides/react-plock#readme",
  "license": "ISC",
  "main": "dist/index.es.js",
  "module": "dist/index.es.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.es.js",
      "require": "./dist/index.cjs.js"
    }
  },
  "directories": {
    "dist": "dist"
  },
  "files": [
    "dist"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/askides/react-plock.git"
  },
  "scripts": {
    "test": "vitest run",
    "build": "vite build",
    "lib:publish": "npm publish react-plock",
    "lib:publish:dev": "npm publish react-plock --tag dev"
  },
  "bugs": {
    "url": "https://github.com/askides/react-plock/issues"
  },
  "peerDependencies": {
    "react": "^18.2.0 || ^19.0.0"
  },
  "devDependencies": {
    "@types/react": "^19.0.1",
    "@types/react-dom": "^19.0.2"
  }
}


================================================
FILE: libs/react-plock/src/index.spec.tsx
================================================
import { describe, expect, it } from 'vitest';
import { createBalancedColumns, createChunks, createDataColumns } from '.';

describe('Plock', () => {
  it('should create chunks', () => {
    const data = [1, 2, 3, 4, 5, 6, 7, 8];
    const result = createChunks(data, 4);

    expect(result).toEqual([
      [1, 2, 3, 4],
      [5, 6, 7, 8],
    ]);
  });

  it('should create columns', () => {
    const result = createDataColumns(
      [
        [1, 2, 3, 4],
        [5, 6, 7, 8],
      ],
      4
    );

    expect(result).toEqual([
      [1, 5],
      [2, 6],
      [3, 7],
      [4, 8],
    ]);
  });

  it('should create columns even with not equal values', () => {
    const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    const chunks = createChunks(data, 4);
    const result = createDataColumns(chunks, 4);

    expect(result).toEqual([
      [1, 5, 9],
      [2, 6],
      [3, 7],
      [4, 8],
    ]);
  });

  it('should create columns even with not equal values', () => {
    const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    const chunks = createChunks(data, 4);
    const result = createDataColumns(chunks, 4);

    expect(result).toEqual([
      [1, 5, 9, 13],
      [2, 6, 10, 14],
      [3, 7, 11, 15],
      [4, 8, 12],
    ]);
  });

  it('should create columns even with not equal values another', () => {
    const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const chunks = createChunks(data, 2);
    const result = createDataColumns(chunks, 2);

    expect(chunks).toEqual([
      [1, 2],
      [3, 4],
      [5, 6],
      [7, 8],
      [9, 10],
      [11, 12],
      [13, 14],
    ]);

    expect(result).toEqual([
      [1, 3, 5, 7, 9, 11, 13],
      [2, 4, 6, 8, 10, 12, 14],
    ]);
  });
});

describe('Balanced Layout', () => {
  it('should distribute items to columns based on height', () => {
    const items = [1, 2, 3, 4];
    const heightMap = new Map([
      [1, 100], // tall
      [2, 50], // short
      [3, 120], // tallest
      [4, 30], // shortest
    ]);

    const result = createBalancedColumns(
      items,
      2,
      (item) => heightMap.get(item) || 0
    );

    expect(result).toEqual([
      [1, 4], // Column 1: 100 + 30 = 130
      [2, 3], // Column 2: 50 + 120 = 170
    ]);
  });

  it('should handle empty items array', () => {
    const result = createBalancedColumns([], 3, () => 0);
    expect(result).toEqual([[], [], []]);
  });

  it('should handle single column', () => {
    const items = [1, 2, 3];
    const result = createBalancedColumns(items, 1, () => 100);
    expect(result).toEqual([[1, 2, 3]]);
  });

  it('should maintain item order within columns', () => {
    const items = [1, 2, 3, 4, 5, 6];
    const heightMap = new Map([
      [1, 50],
      [2, 50],
      [3, 50],
      [4, 50],
      [5, 50],
      [6, 50],
    ]);

    const result = createBalancedColumns(
      items,
      3,
      (item) => heightMap.get(item) || 0
    );

    // With equal heights, items should be distributed evenly while maintaining order
    expect(result).toEqual([
      [1, 4],
      [2, 5],
      [3, 6],
    ]);
  });
});


================================================
FILE: libs/react-plock/src/index.tsx
================================================
import React, { useState, useMemo } from 'react';
import { useGridStyles } from './utils/useGridStyles';
import { useMediaValues } from './utils/useMediaValues';

export function asList(data: number | number[]) {
  return Array.isArray(data) ? data : [data];
}

export type MasonryProps<T> = React.ComponentPropsWithoutRef<'div'> & {
  items: T[];
  render: (item: T, idx: number) => React.ReactNode;
  config: {
    columns: number | number[];
    gap: number | number[];
    media?: number[];
    useBalancedLayout?: boolean;
  };
  as?: React.ElementType;
};

export function Masonry<T>({
  items = [],
  render,
  config,
  as: Component = 'div',
  ...rest
}: MasonryProps<T>) {
  const [heights, setHeights] = useState<Map<T, number>>(new Map());
  const _columns = useMemo(() => asList(config.columns), [config.columns]);
  const _gaps = useMemo(() => asList(config.gap), [config.gap]);
  const { columns, gap } = useMediaValues(config.media, _columns, _gaps);

  const styles = useGridStyles(columns, gap);

  if (!columns) return null;

  // Choose layout strategy based on config
  const dataColumns = config.useBalancedLayout
    ? createBalancedColumns(items, columns, (item) => heights.get(item) ?? 0)
    : createDataColumns(createChunks(items, columns), columns);

  return (
    <Component {...rest} style={styles}>
      {dataColumns.map((column, columnIdx) => (
        <MasonryRow gap={gap} key={columnIdx}>
          {column.map((item, idx) => (
            <div
              key={idx}
              ref={(node) => {
                if (node && config.useBalancedLayout) {
                  const height = node.getBoundingClientRect().height;

                  if (heights.get(item) !== height) {
                    // Update heights state if changed, triggering re-render
                    setHeights((prev) => {
                      const next = new Map(prev);
                      next.set(item, height);
                      return next;
                    });
                  }
                }
              }}
            >
              {render(item, idx)}
            </div>
          ))}
        </MasonryRow>
      ))}
    </Component>
  );
}

export function MasonryRow({
  children,
  gap,
}: {
  children: React.ReactNode;
  gap: number;
}) {
  return (
    <div
      style={{
        display: 'grid',
        rowGap: gap,
        gridTemplateColumns: 'minmax(0, 1fr)',
      }}
    >
      {children}
    </div>
  );
}

export function createChunks<T>(data: T[] = [], columns = 3) {
  const result = [];

  for (let idx = 0; idx < data.length; idx += columns) {
    const slice = data.slice(idx, idx + columns);
    result.push(slice);
  }

  return result;
}

export function createDataColumns<T>(data: T[][] = [], columns = 3) {
  const result = Array.from<T[], T[]>({ length: columns }, () => []);

  for (let idx = 0; idx < columns; idx++) {
    for (let jdx = 0; jdx < data.length; jdx += 1) {
      if (data[jdx][idx]) {
        result[idx].push(data[jdx][idx]);
      }
    }
  }

  return result;
}

export function createBalancedColumns<T>(
  items: T[],
  columns: number,
  getHeight: (item: T) => number
): T[][] {
  const result = Array.from<T[], T[]>({ length: columns }, () => []);
  const columnHeights = new Array(columns).fill(0);

  // Maintain original order, but distribute to shortest column
  for (const item of items) {
    let shortestColumnIndex = 0;
    let minHeight = columnHeights[0];

    for (let i = 1; i < columns; i++) {
      if (columnHeights[i] < minHeight) {
        minHeight = columnHeights[i];
        shortestColumnIndex = i;
      }
    }

    result[shortestColumnIndex].push(item);
    columnHeights[shortestColumnIndex] += getHeight(item);
  }

  return result;
}


================================================
FILE: libs/react-plock/src/utils/useGridStyles.ts
================================================
import { useMemo } from 'react';

export function useGridStyles(columns: number, gap: number) {
  return useMemo(
    () => ({
      display: 'grid',
      alignItems: 'start',
      gridColumnGap: gap,
      gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
    }),
    [columns, gap]
  );
}


================================================
FILE: libs/react-plock/src/utils/useMediaValues.ts
================================================
import { useState, useEffect } from 'react';

export function useMediaValues(
  medias: number[] | undefined,
  columns: number[],
  gap: number[]
) {
  const [values, setValues] = useState({ columns: 0, gap: 1 });

  useEffect(() => {
    if (!medias) {
      setValues({ columns: columns[0], gap: gap[0] });
      return;
    }

    const mediaQueries = medias.map((media) =>
      window.matchMedia(`(min-width: ${media}px)`)
    );

    const onSizeChange = () => {
      let matches = 0;

      mediaQueries.forEach((mediaQuery) => {
        if (mediaQuery.matches) {
          matches++;
        }
      });

      // Update Values
      const idx = Math.min(mediaQueries.length - 1, Math.max(0, matches));
      setValues({ columns: columns[idx], gap: gap[idx] });
    };

    // Initial Call
    onSizeChange();

    // Apply Listeners
    for (const mediaQuery of mediaQueries) {
      mediaQuery.addEventListener('change', onSizeChange);
    }

    return () => {
      for (const mediaQuery of mediaQueries) {
        mediaQuery.removeEventListener('change', onSizeChange);
      }
    };
  }, [medias, columns, gap]);

  return values;
}


================================================
FILE: libs/react-plock/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["src", "index.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: libs/react-plock/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: libs/react-plock/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import dts from 'vite-plugin-dts';

export default defineConfig({
  plugins: [react(), dts({ rollupTypes: true })],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.tsx'),
      formats: ['es', 'cjs'],
      fileName: (format) => `index.${format}.js`,
    },
    rollupOptions: {
      external: [/^react($|\/.*)/, 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
});


================================================
FILE: libs/react-plock/vitest.config.ts
================================================
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
  },
});


================================================
FILE: package.json
================================================
{
  "name": "react-plock-mono",
  "private": true,
  "devDependencies": {
    "@types/node": "^18.14.4",
    "@vitejs/plugin-react": "^4.3.4",
    "jsdom": "^25.0.1",
    "typescript": "^5.3.3",
    "vite": "^6.0.3",
    "vite-plugin-dts": "^4.3.0",
    "vitest": "^2.1.8"
  },
  "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
}


================================================
FILE: pnpm-workspace.yaml
================================================
packages:
  - libs/*
  - examples/*

ignoredBuiltDependencies:
  - sharp
Download .txt
gitextract_nv2tjxfx/

├── .github/
│   └── workflows/
│       ├── release.yml
│       ├── stale.yml
│       └── testing.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── assets/
│   └── data/
│       └── images.ts
├── examples/
│   ├── with-nextjs/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── next.config.mjs
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── src/
│   │   │   └── app/
│   │   │       ├── globals.css
│   │   │       ├── layout.tsx
│   │   │       ├── page.tsx
│   │   │       └── ui/
│   │   │           └── Button.tsx
│   │   ├── tailwind.config.ts
│   │   └── tsconfig.json
│   └── with-vite/
│       ├── .gitignore
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── ReactPlock.tsx
│       │   ├── main.tsx
│       │   ├── styles/
│       │   │   └── index.css
│       │   └── ui/
│       │       └── Button.tsx
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
├── libs/
│   └── react-plock/
│       ├── package.json
│       ├── src/
│       │   ├── index.spec.tsx
│       │   ├── index.tsx
│       │   └── utils/
│       │       ├── useGridStyles.ts
│       │       └── useMediaValues.ts
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       ├── vite.config.ts
│       └── vitest.config.ts
├── package.json
└── pnpm-workspace.yaml
Download .txt
SYMBOL INDEX (14 symbols across 8 files)

FILE: examples/with-nextjs/src/app/layout.tsx
  function RootLayout (line 12) | function RootLayout({

FILE: examples/with-nextjs/src/app/page.tsx
  function Home (line 31) | function Home() {

FILE: examples/with-nextjs/src/app/ui/Button.tsx
  type ButtonProps (line 3) | interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}

FILE: examples/with-vite/src/ReactPlock.tsx
  function ReactPlock (line 29) | function ReactPlock() {

FILE: examples/with-vite/src/ui/Button.tsx
  type ButtonProps (line 3) | interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}

FILE: libs/react-plock/src/index.tsx
  function asList (line 5) | function asList(data: number | number[]) {
  type MasonryProps (line 9) | type MasonryProps<T> = React.ComponentPropsWithoutRef<'div'> & {
  function Masonry (line 21) | function Masonry<T>({
  function MasonryRow (line 73) | function MasonryRow({
  function createChunks (line 93) | function createChunks<T>(data: T[] = [], columns = 3) {
  function createDataColumns (line 104) | function createDataColumns<T>(data: T[][] = [], columns = 3) {
  function createBalancedColumns (line 118) | function createBalancedColumns<T>(

FILE: libs/react-plock/src/utils/useGridStyles.ts
  function useGridStyles (line 3) | function useGridStyles(columns: number, gap: number) {

FILE: libs/react-plock/src/utils/useMediaValues.ts
  function useMediaValues (line 3) | function useMediaValues(
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (59K chars).
[
  {
    "path": ".github/workflows/release.yml",
    "chars": 2363,
    "preview": "name: Release\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version to release (e.g., 1.0.0"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 741,
    "preview": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n  - cron: '29 18 * * *'\n\njobs:\n  stale:\n\n    runs-on: ubuntu-"
  },
  {
    "path": ".github/workflows/testing.yml",
    "chars": 349,
    "preview": "name: Testing CI\n\non:\n  pull_request:\n    branches:\n      - main\n      - dev\n\njobs:\n  main:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".gitignore",
    "chars": 505,
    "preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/**/dist\n/tmp\n/out-tsc\n\n# d"
  },
  {
    "path": ".prettierrc",
    "chars": 26,
    "preview": "{\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2023 Renato Pozzi\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 4610,
    "preview": "![Plock Logo](./assets/cover.png)\n\nReact Plock is a tree-shakeable **ultra small** npm package (**less than 1kB gzipped*"
  },
  {
    "path": "assets/data/images.ts",
    "chars": 20200,
    "preview": "export const images = [\n  \"https://images.unsplash.com/photo-1606542758304-820b04394ac2?crop=entropy&cs=tinysrgb&fit=max"
  },
  {
    "path": "examples/with-nextjs/.eslintrc.json",
    "chars": 40,
    "preview": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": "examples/with-nextjs/.gitignore",
    "chars": 391,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "examples/with-nextjs/README.md",
    "chars": 1383,
    "preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
  },
  {
    "path": "examples/with-nextjs/next.config.mjs",
    "chars": 270,
    "preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol"
  },
  {
    "path": "examples/with-nextjs/package.json",
    "chars": 719,
    "preview": "{\n  \"name\": \"with-nextjs\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack\",\n  "
  },
  {
    "path": "examples/with-nextjs/postcss.config.js",
    "chars": 83,
    "preview": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "examples/with-nextjs/src/app/globals.css",
    "chars": 22,
    "preview": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "examples/with-nextjs/src/app/layout.tsx",
    "chars": 473,
    "preview": "import type { Metadata } from \"next\";\nimport { Inter } from \"next/font/google\";\nimport \"./globals.css\";\n\nconst inter = I"
  },
  {
    "path": "examples/with-nextjs/src/app/page.tsx",
    "chars": 2786,
    "preview": "'use client';\n\nimport { useState } from 'react';\nimport { Masonry } from 'react-plock';\nimport { Button } from './ui/But"
  },
  {
    "path": "examples/with-nextjs/src/app/ui/Button.tsx",
    "chars": 369,
    "preview": "import { ButtonHTMLAttributes, forwardRef } from 'react';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTM"
  },
  {
    "path": "examples/with-nextjs/tailwind.config.ts",
    "chars": 510,
    "preview": "import type { Config } from \"tailwindcss\";\n\nconst config: Config = {\n  content: [\n    \"./src/pages/**/*.{js,ts,jsx,tsx,m"
  },
  {
    "path": "examples/with-nextjs/tsconfig.json",
    "chars": 661,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"ski"
  },
  {
    "path": "examples/with-vite/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "examples/with-vite/index.html",
    "chars": 366,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "examples/with-vite/package.json",
    "chars": 520,
    "preview": "{\n  \"name\": \"with-vite\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n"
  },
  {
    "path": "examples/with-vite/src/ReactPlock.tsx",
    "chars": 2769,
    "preview": "import { Masonry } from 'react-plock';\nimport { useState } from 'react';\nimport { Button } from './ui/Button';\n\nconst it"
  },
  {
    "path": "examples/with-vite/src/main.tsx",
    "chars": 283,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { ReactPlock } from './ReactPlock';\n\nimport '"
  },
  {
    "path": "examples/with-vite/src/styles/index.css",
    "chars": 22,
    "preview": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "examples/with-vite/src/ui/Button.tsx",
    "chars": 369,
    "preview": "import { ButtonHTMLAttributes, forwardRef } from 'react';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTM"
  },
  {
    "path": "examples/with-vite/tsconfig.json",
    "chars": 639,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"D"
  },
  {
    "path": "examples/with-vite/tsconfig.node.json",
    "chars": 184,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSynthe"
  },
  {
    "path": "examples/with-vite/vite.config.ts",
    "chars": 232,
    "preview": "import react from \"@vitejs/plugin-react\";\nimport { defineConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-"
  },
  {
    "path": "libs/react-plock/package.json",
    "chars": 1078,
    "preview": "{\n  \"name\": \"react-plock\",\n  \"version\": \"3.6.1\",\n  \"type\": \"module\",\n  \"description\": \"The 1kB Masonry Grid for React\",\n"
  },
  {
    "path": "libs/react-plock/src/index.spec.tsx",
    "chars": 3126,
    "preview": "import { describe, expect, it } from 'vitest';\nimport { createBalancedColumns, createChunks, createDataColumns } from '."
  },
  {
    "path": "libs/react-plock/src/index.tsx",
    "chars": 3758,
    "preview": "import React, { useState, useMemo } from 'react';\nimport { useGridStyles } from './utils/useGridStyles';\nimport { useMed"
  },
  {
    "path": "libs/react-plock/src/utils/useGridStyles.ts",
    "chars": 302,
    "preview": "import { useMemo } from 'react';\n\nexport function useGridStyles(columns: number, gap: number) {\n  return useMemo(\n    ()"
  },
  {
    "path": "libs/react-plock/src/utils/useMediaValues.ts",
    "chars": 1150,
    "preview": "import { useState, useEffect } from 'react';\n\nexport function useMediaValues(\n  medias: number[] | undefined,\n  columns:"
  },
  {
    "path": "libs/react-plock/tsconfig.json",
    "chars": 534,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "libs/react-plock/tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "libs/react-plock/vite.config.ts",
    "chars": 588,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport { resolve } from 'path';\nimport dt"
  },
  {
    "path": "libs/react-plock/vitest.config.ts",
    "chars": 141,
    "preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  test: {\n    environment: 'jsdom',\n    glo"
  },
  {
    "path": "package.json",
    "chars": 451,
    "preview": "{\n  \"name\": \"react-plock-mono\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"@types/node\": \"^18.14.4\",\n    \"@vitejs/pl"
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 73,
    "preview": "packages:\n  - libs/*\n  - examples/*\n\nignoredBuiltDependencies:\n  - sharp\n"
  }
]

About this extraction

This page contains the full source code of the askides/react-plock GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (53.3 KB), approximately 21.3k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!