Compare commits
300 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f87b8d644 | |||
| 6ecdc278ab | |||
| 9189b3fbf2 | |||
| 1149345cae | |||
| 135cc21e6f | |||
| 7f6218b978 | |||
| 0eb9f1e3fe | |||
| 11ef4365da | |||
| 85a820eeff | |||
| 6a6d426b10 | |||
| a3cbb5997b | |||
| a5cc42bf80 | |||
| e728a8967b | |||
| acba2cf8f5 | |||
| 049db57bb2 | |||
| 5296d9b84d | |||
| 1619a35cf9 | |||
| 8106629dab | |||
| d55f52cc35 | |||
| 78198c32be | |||
| 71807e8831 | |||
| 9af6174dcf | |||
| 1a129db6fe | |||
| 44ed1056e7 | |||
| db0e26a003 | |||
| 38af48faf5 | |||
| 5b60840751 | |||
| 4c56839db0 | |||
| ef61a3fe09 | |||
| bc4927c901 | |||
| 4191feab2a | |||
| fe8647bd1d | |||
| 23992572ea | |||
| e7a4711d9b | |||
| e84158db62 | |||
| f59e8f6bac | |||
| 73dfb7fc93 | |||
| df9f6f5f58 | |||
| 1fd5049f5a | |||
| a386e1cadd | |||
| 3172a803e3 | |||
| dfc8bdc7af | |||
| 67ce4beb28 | |||
| 5a4a60be65 | |||
| 25e86fb417 | |||
| a95bd6558d | |||
| 080718e265 | |||
| 8217639fd4 | |||
| 7abd68941a | |||
| c3f762563b | |||
| 8d2192183b | |||
| 8b86f83802 | |||
| f38059aa44 | |||
| 311ceb0c3a | |||
| 50a9bf54d4 | |||
| 99d4d296d7 | |||
| 9245384ac3 | |||
| 5af4b10114 | |||
| 8de357cfac | |||
| 655599982d | |||
| 3aac9bac26 | |||
| 95c8fdaa2e | |||
| c0706acbca | |||
| 9d4c7ed9cf | |||
| 9ebaae083d | |||
| c9c5564c58 | |||
| d79808dbe1 | |||
| 5a9b78d234 | |||
| 9f4bdf352d | |||
| d62feb08dc | |||
| 62d307117a | |||
| 01a27293eb | |||
| a53e274d5b | |||
| 425a034bca | |||
| a5a1ea5ade | |||
| f6531c5f60 | |||
| 37d5f12162 | |||
| dd97fc3c4f | |||
| ceddd0d1d9 | |||
| bb8ed58749 | |||
| bbbdcbdfdf | |||
| bc424948c7 | |||
| cdc30db14c | |||
| 3aa86423aa | |||
| 59ed34b3de | |||
| f58fc9bb69 | |||
| 9a478b6c1f | |||
| 7fd0327657 | |||
| 05c966e8a4 | |||
| 5b014b76ff | |||
| cf32b5ae8b | |||
| 4452ed66c0 | |||
| b1b30b7976 | |||
| 97ef2d7250 | |||
| 8b39989090 | |||
| 8363a3ecfc | |||
| 5c30e04811 | |||
| 8606fbe0b9 | |||
| 1612532eee | |||
| a3be419cb3 | |||
| 72a7f71377 | |||
| 7c8341035d | |||
| 6ca0ce309a | |||
| acc2ff3913 | |||
| 4db10a47e9 | |||
| a9c4fab011 | |||
| d61428d9e6 | |||
| 455b788924 | |||
| 79cfdbd8ff | |||
| 7d294e3a04 | |||
| 43906f639e | |||
| 8fcc8b3767 | |||
| 28477447d3 | |||
| fc3c6b9431 | |||
| 59fda3772f | |||
| d8d2b87cb8 | |||
| 64571e468c | |||
| faf26acd23 | |||
| cac56faf29 | |||
| 09fe1d3141 | |||
| a9e324b351 | |||
| 043b8420cf | |||
| 57909accde | |||
| 7a17256268 | |||
| bf2bc265ee | |||
| cf6bf749a1 | |||
| 641bf0885b | |||
| 40b42001f9 | |||
| 789a7209a2 | |||
| 021aa16d96 | |||
| eb8a1fcc01 | |||
| 9b42a2fca1 | |||
| 5702287e56 | |||
| 290cce15be | |||
| 27bde30839 | |||
| 6be3bc3516 | |||
| e4113b05ce | |||
| a3a95e7196 | |||
| 8731159702 | |||
| b6adb2ae8c | |||
| cd2d9b70a9 | |||
| a371e1a5fa | |||
| 5cdc4d1d87 | |||
| 22c6e070dd | |||
| 0714192838 | |||
| ae9f300d45 | |||
| a16dac04c8 | |||
| 94db001961 | |||
| d77cf7658b | |||
| 46c47f7e6e | |||
| 35b9741663 | |||
| 2b82ba86eb | |||
| 904f538779 | |||
| 609e9f4c11 | |||
| e3dabac9f5 | |||
| 4e94c11528 | |||
| a17b6c7502 | |||
| bd72301b01 | |||
| cc839cdd1f | |||
| c9e5c2ef7f | |||
| e8f5264cf8 | |||
| df7f773f3d | |||
| af925d56d1 | |||
| baec55029d | |||
| ea2868e58e | |||
| 691f380dff | |||
| ac8afec964 | |||
| 5fe5521a7c | |||
| 15403bc67e | |||
| c186f37c98 | |||
| 1bc2a50e2c | |||
| 453f792a7a | |||
| 3f6fd48eee | |||
| d7c6c79f38 | |||
| f068552774 | |||
| 6d7372ede1 | |||
| 1487219a1c | |||
| 6e7e834c2d | |||
| 80a8f7e94c | |||
| fae7467b64 | |||
| 4e7789c1f6 | |||
| 6694b934e6 | |||
| 4d341081e3 | |||
| df562449fb | |||
| 4063362699 | |||
| 94d65df533 | |||
| 8f66c6b564 | |||
| 4a19a3c906 | |||
| 0be398ade9 | |||
| 50d23e5378 | |||
| d0341020e6 | |||
| 4170ecdc76 | |||
| e2b0076b53 | |||
| e2b0497b07 | |||
| df3b309542 | |||
| 6c35a502fe | |||
| a629f8c088 | |||
| fd4e01dcfa | |||
| e9c08abd55 | |||
| 3e263aa882 | |||
| 9add9aba97 | |||
| 0311ae43f1 | |||
| 79c5fb9a94 | |||
| abe1ba58de | |||
| e6c3747153 | |||
| d15b16ecab | |||
| fe03c42fa1 | |||
| 25a918d146 | |||
| 87918ac5b4 | |||
| a30cec4810 | |||
| 1b0653c97a | |||
| 710395f3a2 | |||
| e7e58e854a | |||
| d13a14facc | |||
| cef7235c4b | |||
| fcff1a1d36 | |||
| c7ba9e1508 | |||
| 1d8dc4b9d8 | |||
| ea5e0fb096 | |||
| 26f64d043d | |||
| a078c3dcd2 | |||
| 5a50390e3f | |||
| 894d85d059 | |||
| 0ef41d27e7 | |||
| 4200164712 | |||
| 6d4c6980d5 | |||
| f3f36a7146 | |||
| 8de209bff7 | |||
| e9d01be74d | |||
| 66ac2e67ae | |||
| 7ed88f39c1 | |||
| 32d18bfd41 | |||
| a1b32c1ceb | |||
| 9e683b32ba | |||
| b7c947805d | |||
| a76fdfa774 | |||
| ac605eb71f | |||
| 5b343b6181 | |||
| 05562dcf90 | |||
| 0a60388258 | |||
| d794225cb7 | |||
| ec2ed200f5 | |||
| 2f7703ec8e | |||
| 25cbe1146f | |||
| 3db9b6248e | |||
| b1b5eda15b | |||
| e3e13a095f | |||
| 86f921d098 | |||
| eefda8d6e2 | |||
| 2e4ce7bcee | |||
| 5a4c4f498d | |||
| ec30a9adbd | |||
| a6954c4156 | |||
| b887a2f503 | |||
| edc2bc741e | |||
| a1a02dc0db | |||
| cd1dc8b469 | |||
| 459ac5ed45 | |||
| 9c4bb22e87 | |||
| 7fa5459381 | |||
| b77b8fa9d7 | |||
| c6c4a4d6a1 | |||
| e96b1dbab0 | |||
| 1b55d27645 | |||
| bdc246a8a4 | |||
| 5064bfe4e4 | |||
| 36ddbff557 | |||
| 043b42c895 | |||
| c8330b8106 | |||
| 7f20346423 | |||
| 85b3fd7269 | |||
| 649204ceac | |||
| c7a1376872 | |||
| 3323592211 | |||
| fb570a372d | |||
| 184d3a9c49 | |||
| 849eff5715 | |||
| b400f3ee6d | |||
| dedede0be0 | |||
| e96a23c8a3 | |||
| 7efd6c97e4 | |||
| 43e5e4fb26 | |||
| 39da69cf81 | |||
| 8743a6c6b6 | |||
| c635c951d6 | |||
| cb05342a4a | |||
| 59ef7bc6b6 | |||
| cfd19415ea | |||
| 39ca892b4e | |||
| fc2dad8b39 | |||
| d9bbd2e45f | |||
| 75bc039b4b | |||
| e79bd0fa4c | |||
| 65c5af4f63 | |||
| f58c8a7d99 | |||
| 9da06aaa50 | |||
| 508f11e968 | |||
| 2812f61f4f | |||
| 0ee0e95678 | |||
| e940ec06ac |
@@ -1,12 +1,18 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": false,
|
||||
"changelog": [
|
||||
"@changesets/changelog-github",
|
||||
{ "repo": "nextui-org/nextui" }
|
||||
],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
},
|
||||
"ignore": [
|
||||
"@nextui-org/docs",
|
||||
"@nextui-org/storybook"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.now/*
|
||||
.next/*
|
||||
*.css
|
||||
.changeset
|
||||
dist
|
||||
esm/*
|
||||
public/*
|
||||
|
||||
@@ -21,6 +21,16 @@ body:
|
||||
- NextUI's [Discussions tab](https://github.com/nextui-org/nextui/discussions)
|
||||
|
||||
The more information you fill in, the better the community can help you.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: NextUI Version
|
||||
description: |
|
||||
Please provide the version of NextUI you are using.
|
||||
You can find the version number in the package.json file.
|
||||
placeholder: ex. 2.0.10
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: "Install"
|
||||
description: "Sets up Node.js and runs install"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Setup Git User
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "jrgarciadev@gmail.com"
|
||||
git config --global user.name "Junior Garcia"
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install
|
||||
@@ -0,0 +1,72 @@
|
||||
name: QA
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm build
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
|
||||
eslint:
|
||||
name: ESLint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Run ESLint
|
||||
run: pnpm lint
|
||||
|
||||
types:
|
||||
name: TypeScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Run typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
prettier:
|
||||
name: Prettier
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Run prettier
|
||||
run: pnpm format:check
|
||||
@@ -1,128 +0,0 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore: ['**/README.md']
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize]
|
||||
paths-ignore: ['**/README.md']
|
||||
|
||||
jobs:
|
||||
avoid_reduncy:
|
||||
runs-on: ubuntu-20.04
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- name: Cancel Previous Redundant Builds
|
||||
uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.next/cache
|
||||
# Generate a new cache whenever packages or source files change.
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
||||
# If source files changed but packages didn't, rebuild from a prior cache.
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}-
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
tests:
|
||||
needs: [lint, build]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Test
|
||||
run: yarn coverage:update
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
directory: './packages/react'
|
||||
|
||||
publish:
|
||||
needs: [build, tests]
|
||||
runs-on: ubuntu-20.04
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Pre puiblish NextUI package
|
||||
run: yarn pre-publish:nextui
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: './packages/react/lib/package.json'
|
||||
- name: Show published version
|
||||
if: steps.publish.outputs.type != 'none'
|
||||
run: |
|
||||
echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}"
|
||||
- name: Tag new version
|
||||
if: steps.publish.outputs.type != 'none'
|
||||
uses: Klemensas/action-autotag@stable
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
tag_prefix: "v"
|
||||
package_root: "./packages/react"
|
||||
|
||||
update_algolia_index:
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-20.04
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Upload docs on Algolia
|
||||
run: yarn build:docs-meta
|
||||
env:
|
||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||
ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
|
||||
ALGOLIA_SEARCH_API_KEY: ${{ secrets.ALGOLIA_SEARCH_API_KEY }}
|
||||
@@ -1,17 +1,52 @@
|
||||
name: Tagged Release
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
paths:
|
||||
- ".changeset/**"
|
||||
- "packages/**"
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
tagged-release:
|
||||
name: Tagged Release
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Create Release Pull Request or Publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
repo_token: "${{ github.token }}"
|
||||
prerelease: false
|
||||
publish: pnpm release
|
||||
title: "ci(changesets): :package: version packages"
|
||||
commit: "ci(changesets): version packages"
|
||||
setupGitUser: false
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create @dev release
|
||||
if: steps.changesets.outputs.published != 'true'
|
||||
run: |
|
||||
git checkout main
|
||||
pnpm version:dev
|
||||
pnpm release:dev
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -20,6 +20,7 @@ coverage
|
||||
dist/
|
||||
storybook-static
|
||||
/packages/storybook/public/tailwind.css
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@ const removeIgnoredFiles = async (files) => {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
"**/*.{cjs,mjs,js,ts,jsx,tsx}": async (files) => {
|
||||
"**/*.{js,ts,jsx,tsx}": async (files) => {
|
||||
const filesToLint = await removeIgnoredFiles(files);
|
||||
|
||||
return [`eslint -c .eslintrc.json --max-warnings=0 --fix ${filesToLint}`];
|
||||
},
|
||||
"**/*.{css,json,md}": async (files) => {
|
||||
"**/*.css": async (files) => {
|
||||
const filesToLint = await removeIgnoredFiles(files);
|
||||
|
||||
return [`prettier --config .prettierrc.json --ignore-path --write ${filesToLint}`];
|
||||
|
||||
@@ -2,6 +2,7 @@ dist
|
||||
node_modules
|
||||
plop
|
||||
coverage
|
||||
.changeset
|
||||
.next
|
||||
build
|
||||
scripts
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ pnpm test:update src/button # or npm run test:update src/button
|
||||
pnpm build # or npm run build
|
||||
```
|
||||
|
||||
> Note: ensure your version of Node is 14 or higher to run scripts
|
||||
> Note: ensure your version of Node is 16 or higher to run scripts
|
||||
|
||||
6. Send your pull request:
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {MDXContent} from "@/components/mdx-content";
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {ChevronRightLinearIcon} from "@/components/icons";
|
||||
import {__PROD__} from "@/utils";
|
||||
|
||||
interface BlogPostProps {
|
||||
params: {
|
||||
@@ -79,7 +78,7 @@ export async function generateStaticParams(): Promise<BlogPostProps["params"][]>
|
||||
export default async function DocPage({params}: BlogPostProps) {
|
||||
const {post} = await getBlogPostFromParams({params});
|
||||
|
||||
if (!post || __PROD__) {
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,10 @@ import {allBlogPosts} from "contentlayer/generated";
|
||||
import {compareDesc} from "date-fns";
|
||||
|
||||
import {BlogPostList} from "@/components/blog-post";
|
||||
import {__PROD__} from "@/utils";
|
||||
|
||||
export default function Blog() {
|
||||
const posts = allBlogPosts.sort((a, b) => compareDesc(new Date(a.date), new Date(b.date)));
|
||||
|
||||
if (__PROD__) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full lg:px-16 mt-12">
|
||||
<div className="text-center">
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function DocsLayout({children}: DocsLayoutProps) {
|
||||
<>
|
||||
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="hidden relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
|
||||
<div className="hidden overflow-visible relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
|
||||
<DocsSidebar routes={manifest.routes} />
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable no-console */
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
height: string;
|
||||
mass: string;
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(`https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable no-console */
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
|
||||
type Pokemon = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type UsePokemonListProps = {
|
||||
/** Delay to wait before fetching more items */
|
||||
fetchDelay?: number;
|
||||
};
|
||||
|
||||
function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
|
||||
const [items, setItems] = React.useState<Pokemon[]>([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset: number) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
|
||||
|
||||
const [, scrollerRef] = useInfiniteScroll({
|
||||
hasMore,
|
||||
isEnabled: isOpen,
|
||||
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
|
||||
onLoadMore,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
defaultItems={items}
|
||||
isLoading={isLoading}
|
||||
label="Pick a Pokemon"
|
||||
placeholder="Select a Pokemon"
|
||||
scrollRef={scrollerRef}
|
||||
variant="bordered"
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem, MenuTriggerAction} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
|
||||
const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];
|
||||
|
||||
type FieldState = {
|
||||
selectedKey: React.Key | null;
|
||||
inputValue: string;
|
||||
items: typeof animals;
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
// Store ComboBox input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState<FieldState>({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value: string) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen: boolean, menuTrigger: MenuTriggerAction) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Select an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {useState} from "react";
|
||||
|
||||
export default function Page() {
|
||||
const {isOpen, onOpen, onOpenChange} = useDisclosure();
|
||||
const [modalPlacement, setModalPlacement] = useState<ModalProps["placement"]>("auto");
|
||||
const [modalPlacement, setModalPlacement] = useState("auto");
|
||||
|
||||
return (
|
||||
<div className="flex px-10 min-h-[80vh] justify-center items-center flex-col gap-4">
|
||||
@@ -36,7 +36,11 @@ export default function Page() {
|
||||
<Radio value="top-center">top-center</Radio>
|
||||
<Radio value="bottom-center">bottom-center</Radio>
|
||||
</RadioGroup>
|
||||
<Modal isOpen={isOpen} placement={modalPlacement} onOpenChange={onOpenChange}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
placement={modalPlacement as ModalProps["placement"]}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
NavbarMenu,
|
||||
NavbarMenuItem,
|
||||
NavbarMenuToggle,
|
||||
Avatar,
|
||||
Button,
|
||||
Link as NextUiLink,
|
||||
LinkProps,
|
||||
} from "@nextui-org/react";
|
||||
@@ -79,16 +79,13 @@ export default function Page() {
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarContent justify="end">
|
||||
<NavbarItem className="hidden lg:flex">
|
||||
<Link href="#">Login</Link>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<Avatar
|
||||
isBordered
|
||||
as="button"
|
||||
className="transition-transform"
|
||||
color="warning"
|
||||
name="Jane Lee"
|
||||
size="sm"
|
||||
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
|
||||
/>
|
||||
<Button as={Link} color="warning" href="#" variant="flat">
|
||||
Sign Up
|
||||
</Button>
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
|
||||
|
||||
@@ -51,22 +51,19 @@ export default function Page() {
|
||||
</Link>
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
<NavbarContent justify="end">
|
||||
<NavbarContent as="div" justify="end">
|
||||
<Dropdown placement="bottom-end">
|
||||
<NavbarItem>
|
||||
<DropdownTrigger>
|
||||
<Avatar
|
||||
isBordered
|
||||
as={Link}
|
||||
className="transition-transform"
|
||||
color="secondary"
|
||||
href="#"
|
||||
name="Jason Hughes"
|
||||
size="sm"
|
||||
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
</NavbarItem>
|
||||
<DropdownTrigger>
|
||||
<Avatar
|
||||
isBordered
|
||||
as="button"
|
||||
className="transition-transform"
|
||||
color="secondary"
|
||||
name="Jason Hughes"
|
||||
size="sm"
|
||||
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Profile Actions" variant="flat">
|
||||
<DropdownItem key="profile" className="h-14 gap-2">
|
||||
<p className="font-semibold">Signed in as</p>
|
||||
|
||||
@@ -56,12 +56,13 @@ export default function Page() {
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
</NavbarContent>
|
||||
<NavbarContent className="items-center" justify="end">
|
||||
<NavbarContent as="div" className="items-center" justify="end">
|
||||
<Input
|
||||
classNames={{
|
||||
base: "max-w-full sm:max-w-[10rem] h-10",
|
||||
input: "text-small",
|
||||
inputWrapper: "font-normal text-default-500 bg-default-400/20 dark:bg-default-500/20",
|
||||
inputWrapper:
|
||||
"h-full font-normal text-default-500 bg-default-400/20 dark:bg-default-500/20",
|
||||
}}
|
||||
placeholder="Type to search..."
|
||||
startContent={<SearchLinearIcon size={18} />}
|
||||
@@ -72,10 +73,9 @@ export default function Page() {
|
||||
<DropdownTrigger>
|
||||
<Avatar
|
||||
isBordered
|
||||
as={Link}
|
||||
as="button"
|
||||
className="transition-transform"
|
||||
color="secondary"
|
||||
href="#"
|
||||
name="Jason Hughes"
|
||||
size="sm"
|
||||
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
|
||||
|
||||
@@ -9,10 +9,17 @@ import {
|
||||
Textarea,
|
||||
Input,
|
||||
Tab,
|
||||
Avatar,
|
||||
Select,
|
||||
SelectItem,
|
||||
AccordionItem,
|
||||
Pagination,
|
||||
extendVariants,
|
||||
PaginationItem,
|
||||
} from "@nextui-org/react";
|
||||
import {useState} from "react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useSearchParams} from "next/navigation";
|
||||
|
||||
import {SearchLinearIcon} from "@/components/icons";
|
||||
|
||||
@@ -175,16 +182,302 @@ const MyButton2 = extendVariants(Button, {
|
||||
xl: "px-12 py-6 text-xl",
|
||||
},
|
||||
},
|
||||
defaultVariants: {},
|
||||
defaultVariants: {
|
||||
color: "foreground",
|
||||
},
|
||||
});
|
||||
|
||||
const usersData = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
export default function NextUIPerf() {
|
||||
const [textA, setTextA] = useState<string | undefined>("");
|
||||
const [textB, setTextB] = useState<string | undefined>("");
|
||||
const [textC, setTextC] = useState<string | undefined>("");
|
||||
const [textA, setTextA] = useState<string>("");
|
||||
const [textB, setTextB] = useState<string>("");
|
||||
const [textC, setTextC] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [inputValue, setInputValue] = useState<string>();
|
||||
const [selectedKey, setSelectedKey] = useState<string>("");
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const page = Number(searchParams.get("page"));
|
||||
|
||||
let {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
const filteredItems = inputValue
|
||||
? usersData.filter((item) => startsWith(item.name, inputValue))
|
||||
: usersData;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
isOpen && inputRef?.current?.focus();
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSelectionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedKey(e.target.value);
|
||||
};
|
||||
|
||||
const topContent = useMemo(() => {
|
||||
return (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
isClearable
|
||||
aria-activedescendant={selectedKey}
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Search user"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="z-10 sticky top-1"
|
||||
placeholder="Search..."
|
||||
spellCheck={false}
|
||||
startContent={<SearchLinearIcon className="text-default-400" size={18} strokeWidth="2" />}
|
||||
type="text"
|
||||
onValueChange={setInputValue}
|
||||
/>
|
||||
);
|
||||
}, [inputRef, selectedKey, isOpen]);
|
||||
|
||||
return (
|
||||
<div className="w-full p-24 gap-4 flex flex-col">
|
||||
<Select
|
||||
classNames={{
|
||||
base: "max-w-xs",
|
||||
listboxWrapper: "scroll-pb-6 scroll-pt-28",
|
||||
}}
|
||||
items={filteredItems}
|
||||
label="Assigned to"
|
||||
labelPlacement="outside"
|
||||
listboxProps={{
|
||||
topContent,
|
||||
variant: "flat",
|
||||
classNames: {
|
||||
base: [
|
||||
"before:content-[''] before:rounded-t-medium before:fixed before:w-full before:h-14 before:z-10",
|
||||
"before:top-0 before:left-0 before:bg-gradient-to-b before:from-default-50",
|
||||
],
|
||||
},
|
||||
}}
|
||||
placeholder="Select a user"
|
||||
selectedKeys={[selectedKey]}
|
||||
showScrollIndicators={false}
|
||||
variant="flat"
|
||||
onChange={handleSelectionChange}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<SelectItem key={item.id} textValue={item.name}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={item.name} className="flex-shrink-0" size="sm" src={item.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{item.name}</span>
|
||||
<span className="text-tiny text-default-400">{item.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
|
||||
<Accordion>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
@@ -253,7 +546,16 @@ export default function NextUIPerf() {
|
||||
|
||||
<Button>Click Me!</Button>
|
||||
|
||||
<MyButton2 color="foreground">Press Me!</MyButton2>
|
||||
<MyButton2 color="primary">Press Me!</MyButton2>
|
||||
|
||||
<Pagination
|
||||
showControls
|
||||
initialPage={page ?? 1}
|
||||
renderItem={({page, ...itemProps}) => {
|
||||
return <PaginationItem href={`/examples/perf?page=${page}`} {...itemProps} />;
|
||||
}}
|
||||
total={10}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable no-console */
|
||||
"use client";
|
||||
|
||||
import {Select, SelectItem} from "@nextui-org/react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
|
||||
type Pokemon = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type UsePokemonListProps = {
|
||||
/** Delay to wait before fetching more items */
|
||||
fetchDelay?: number;
|
||||
};
|
||||
|
||||
function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
|
||||
const [items, setItems] = useState<Pokemon[]>([]);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset: number) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
|
||||
|
||||
const [, scrollerRef] = useInfiniteScroll({
|
||||
hasMore,
|
||||
isEnabled: isOpen,
|
||||
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
|
||||
onLoadMore,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Select
|
||||
className="max-w-xs"
|
||||
isLoading={isLoading}
|
||||
items={items}
|
||||
label="Pick a Pokemon"
|
||||
placeholder="Select a Pokemon"
|
||||
scrollRef={scrollerRef}
|
||||
selectionMode="single"
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<SelectItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Spinner,
|
||||
Pagination,
|
||||
} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
import {useCallback, useMemo, useState} from "react";
|
||||
import {useMemo, useState} from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
@@ -21,50 +21,23 @@ type SWCharacter = {
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
const fetcher = (...args: Parameters<typeof fetch>) => fetch(...args).then((res) => res.json());
|
||||
|
||||
export default function Page() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const {data, isLoading} = useSWR<{
|
||||
count: number;
|
||||
results: SWCharacter[];
|
||||
}>(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const rowsPerPage = 10;
|
||||
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, cursor}) {
|
||||
// If no cursor is available, then we're loading the first page.
|
||||
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
|
||||
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
|
||||
let json = await res.json();
|
||||
|
||||
setTotal(json.count);
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
cursor: json.next,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const pages = Math.ceil(total / rowsPerPage);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
|
||||
return list.items.slice(start, end);
|
||||
}, [page, list.items?.length]);
|
||||
|
||||
const onPaginationChange = useCallback(
|
||||
(page: number) => {
|
||||
setIsLoading(true);
|
||||
if (page >= list.items.length / rowsPerPage) {
|
||||
list.loadMore();
|
||||
}
|
||||
setPage(page);
|
||||
},
|
||||
[list.items.length],
|
||||
);
|
||||
const pages = useMemo(() => {
|
||||
return data?.count ? Math.ceil(data.count / rowsPerPage) : 0;
|
||||
}, [data?.count, rowsPerPage]);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
@@ -80,7 +53,7 @@ export default function Page() {
|
||||
color="primary"
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={onPaginationChange}
|
||||
onChange={(page) => setPage(page)}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
@@ -96,8 +69,8 @@ export default function Page() {
|
||||
<TableColumn key="birth_year">Birth year</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
isLoading={isLoading && !items.length}
|
||||
items={items}
|
||||
isLoading={isLoading || data?.results.length === 0}
|
||||
items={data?.results ?? []}
|
||||
loadingContent={<Spinner />}
|
||||
>
|
||||
{(item) => (
|
||||
|
||||
@@ -105,7 +105,7 @@ const users = [
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
@@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = typeof users[0];
|
||||
type User = (typeof users)[number];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
@@ -105,7 +105,7 @@ const users = [
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
@@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = typeof users[0];
|
||||
type User = (typeof users)[number];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
@@ -271,8 +271,6 @@ export default function Page() {
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const pages = Math.ceil(users.length / rowsPerPage);
|
||||
|
||||
const hasSearchFilter = Boolean(filterValue);
|
||||
|
||||
const headerColumns = useMemo(() => {
|
||||
@@ -298,6 +296,8 @@ export default function Page() {
|
||||
return filteredUsers;
|
||||
}, [users, filterValue, statusFilter]);
|
||||
|
||||
const pages = Math.ceil(filteredItems.length / rowsPerPage);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
@@ -390,6 +390,11 @@ export default function Page() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onClear = useCallback(() => {
|
||||
setFilterValue("");
|
||||
setPage(1);
|
||||
}, []);
|
||||
|
||||
const topContent = useMemo(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -400,7 +405,7 @@ export default function Page() {
|
||||
placeholder="Search by name..."
|
||||
startContent={<SearchIcon />}
|
||||
value={filterValue}
|
||||
onClear={() => setFilterValue("")}
|
||||
onClear={() => onClear()}
|
||||
onValueChange={onSearchChange}
|
||||
/>
|
||||
<div className="flex gap-3">
|
||||
@@ -483,23 +488,22 @@ export default function Page() {
|
||||
<span className="w-[30%] text-small text-default-400">
|
||||
{selectedKeys === "all"
|
||||
? "All items selected"
|
||||
: `${selectedKeys.size} of ${items.length} selected`}
|
||||
: `${selectedKeys.size} of ${filteredItems.length} selected`}
|
||||
</span>
|
||||
<Pagination
|
||||
isCompact
|
||||
showControls
|
||||
showShadow
|
||||
color="primary"
|
||||
isDisabled={hasSearchFilter}
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={setPage}
|
||||
/>
|
||||
<div className="hidden sm:flex w-[30%] justify-end gap-2">
|
||||
<Button isDisabled={hasSearchFilter} size="sm" variant="flat" onPress={onPreviousPage}>
|
||||
<Button isDisabled={pages === 1} size="sm" variant="flat" onPress={onPreviousPage}>
|
||||
Previous
|
||||
</Button>
|
||||
<Button isDisabled={hasSearchFilter} size="sm" variant="flat" onPress={onNextPage}>
|
||||
<Button isDisabled={pages === 1} size="sm" variant="flat" onPress={onNextPage}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import Rss from "rss";
|
||||
import {allBlogPosts} from "contentlayer/generated";
|
||||
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {allCoreContent} from "@/libs/contentlayer";
|
||||
|
||||
export async function GET() {
|
||||
const feed = new Rss({
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
feed_url: `${siteConfig.siteUrl}/feed.xml`,
|
||||
site_url: siteConfig.siteUrl,
|
||||
webMaster: `${siteConfig.author} <${siteConfig.email}>`,
|
||||
managingEditor: `${siteConfig.author} <${siteConfig.email}>`,
|
||||
language: "en-US",
|
||||
});
|
||||
|
||||
allCoreContent(allBlogPosts).forEach((post) => {
|
||||
const author = post.author ? post.author : siteConfig.author;
|
||||
|
||||
feed.item({
|
||||
title: post.title,
|
||||
description: post.description ?? "",
|
||||
url: `${siteConfig.siteUrl}/blog/${post.slug}`,
|
||||
guid: `${siteConfig.siteUrl}/blog/${post.slug}`,
|
||||
date: post.date,
|
||||
// @ts-ignore - name does exist
|
||||
author: `${author.name} <${siteConfig.email}>`,
|
||||
categories: post.tags ?? [],
|
||||
});
|
||||
});
|
||||
|
||||
return new Response(feed.xml(), {
|
||||
headers: {
|
||||
"Content-Type": "application/xml",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {Image} from "@nextui-org/react";
|
||||
|
||||
import {Blockquote} from "@/components/docs/components/blockquote";
|
||||
import {FigmaButton} from "@/components/figma-button";
|
||||
|
||||
export default function FigmaPage() {
|
||||
return (
|
||||
<>
|
||||
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
|
||||
<section className="w-full flex flex-col items-center lg:px-16 mt-12 gap-6">
|
||||
<div className="text-center max-w-xl">
|
||||
<h1 className="mb-2 font-bold text-4xl">NextUI Figma Kit</h1>
|
||||
<h5 className="text-default-500 text-lg">
|
||||
A Figma file that contains the basis of the NextUI design system to help you design
|
||||
your applications.
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
className="aspect-video w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
height="600"
|
||||
src="https://embed.figma.com/file/1267584376234254760/hf_embed?community_viewer=true&embed_host=nextui"
|
||||
title="NextUI Figma Kit"
|
||||
width="800"
|
||||
/>
|
||||
<div className="text-center max-w-2xl m-auto">
|
||||
<FigmaButton />
|
||||
<Blockquote color="warning">
|
||||
This file is still in development and will be continuously updated.
|
||||
</Blockquote>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="fixed hidden dark:md:block dark:opacity-70 -bottom-[40%] -left-[20%] z-0"
|
||||
>
|
||||
<Image removeWrapper alt="docs left background" src="/gradients/docs-left.png" />
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="fixed hidden dark:md:block dark:opacity-70 -top-[80%] -right-[60%] 2xl:-top-[60%] 2xl:-right-[45%] z-0 rotate-12"
|
||||
>
|
||||
<Image removeWrapper alt="docs right background" src="/gradients/docs-right.png" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const metadata: Metadata = {
|
||||
],
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-16x16.png",
|
||||
shortcut: "/favicon-32x32.png",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
manifest: "/manifest.json",
|
||||
@@ -53,6 +53,12 @@ export const metadata: Metadata = {
|
||||
},
|
||||
],
|
||||
creator: "jrgarciadev",
|
||||
alternates: {
|
||||
canonical: "https://nextui.org",
|
||||
types: {
|
||||
"application/rss+xml": [{url: "https://nextui.org/feed.xml", title: "NextUI RSS Feed"}],
|
||||
},
|
||||
},
|
||||
viewport:
|
||||
"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0",
|
||||
};
|
||||
@@ -64,7 +70,7 @@ export default function RootLayout({children}: {children: React.ReactNode}) {
|
||||
<body className={clsx("min-h-screen bg-background font-sans antialiased", fontSans.variable)}>
|
||||
<Providers themeProps={{attribute: "class", defaultTheme: "dark"}}>
|
||||
<div className="relative flex flex-col" id="app-container">
|
||||
<Navbar routes={manifest.routes} />
|
||||
<Navbar mobileRoutes={manifest.mobileRoutes} routes={manifest.routes} />
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Spacer} from "@nextui-org/react";
|
||||
import {Spacer} from "@nextui-org/spacer";
|
||||
|
||||
import {Hero} from "@/components/marketing/hero";
|
||||
import {FeaturesGrid} from "@/components/marketing/features-grid";
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||
import {ThemeProviderProps} from "next-themes/dist/types";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
export interface ProvidersProps {
|
||||
children: React.ReactNode;
|
||||
@@ -11,8 +12,10 @@ export interface ProvidersProps {
|
||||
}
|
||||
|
||||
export function Providers({children, themeProps}: ProvidersProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<NextUIProvider>
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import React, {useCallback, useEffect} from "react";
|
||||
import Script from "next/script";
|
||||
|
||||
import carbonOptimize from "./carbon-optimize";
|
||||
|
||||
import {loadScript} from "@/utils/scripts";
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {__PROD__} from "@/utils";
|
||||
|
||||
const EA_PROVIDER_RATIO = 0.85;
|
||||
|
||||
export const CarbonAd: React.FC<unknown> = () => {
|
||||
const carbonRef = React.useRef(null);
|
||||
const [showEthicalAds, setShowEthicalAds] = React.useState(false);
|
||||
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const loadEthicalAds = useCallback(() => {
|
||||
return new Promise((resolve) => {
|
||||
const script = document.createElement("script");
|
||||
|
||||
script.src = "https://media.ethicalads.io/media/client/ethicalads.min.js";
|
||||
script.async = true;
|
||||
script.onload = () => {
|
||||
// @ts-ignore
|
||||
resolve(window.ethicalads);
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
resolve(null);
|
||||
};
|
||||
|
||||
document.body.appendChild(script);
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldShowEthicalAds = Math.random() < EA_PROVIDER_RATIO;
|
||||
|
||||
let loadCarbon: any = null;
|
||||
|
||||
const loadCarbonAds = () => {
|
||||
setShowEthicalAds(false);
|
||||
// The isolation logic of carbonads is flawed.
|
||||
// Once the script starts loading, it will asynchronous resolve, with no way to stop it.
|
||||
// This leads to duplication of the ad. To solve the issue, we debounce the load action.
|
||||
loadCarbon =
|
||||
isMounted &&
|
||||
setTimeout(() => {
|
||||
const script = loadScript(
|
||||
"https://cdn.carbonads.com/carbon.js?serve=CESIC53Y&placement=nextuiorg",
|
||||
carbonRef.current,
|
||||
);
|
||||
|
||||
script.id = "_carbonads_js";
|
||||
carbonOptimize.init();
|
||||
});
|
||||
};
|
||||
|
||||
const loadAdProvider = async () => {
|
||||
if (shouldShowEthicalAds) {
|
||||
try {
|
||||
const ethicalads = await loadEthicalAds();
|
||||
|
||||
if (!ethicalads) {
|
||||
loadCarbonAds();
|
||||
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
ethicalads.wait.then((placements) => {
|
||||
if (!placements.length) {
|
||||
loadCarbonAds();
|
||||
} else {
|
||||
setShowEthicalAds(true);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
loadCarbonAds();
|
||||
}
|
||||
} else {
|
||||
loadCarbonAds();
|
||||
}
|
||||
};
|
||||
|
||||
loadAdProvider();
|
||||
|
||||
return () => {
|
||||
loadCarbon && clearTimeout(loadCarbon);
|
||||
};
|
||||
}, [isMounted]);
|
||||
|
||||
if (!__PROD__) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<Script async src="https://media.ethicalads.io/media/client/ethicalads.min.js" />
|
||||
<div
|
||||
className="ea-container horizontal"
|
||||
data-ea-campaign-types="paid|publisher-house|community"
|
||||
data-ea-publisher="nextuiorg"
|
||||
data-ea-type="image"
|
||||
style={{display: showEthicalAds ? "block" : "none"}}
|
||||
/>
|
||||
</>
|
||||
<div className="carbon-ad-container" style={{display: showEthicalAds ? "none" : "block"}}>
|
||||
<span ref={carbonRef} id="carbon-ad" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
export default {
|
||||
isRefreshAble: function () {
|
||||
return !(
|
||||
typeof document.addEventListener === "undefined" || this.browserSupport().hidden === undefined
|
||||
);
|
||||
},
|
||||
browserSupport: function () {
|
||||
let hidden;
|
||||
let visibilityChange;
|
||||
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
// Opera 12.10 and Firefox 18 and later support
|
||||
hidden = "hidden";
|
||||
visibilityChange = "visibilitychange";
|
||||
} else if (typeof document.msHidden !== "undefined") {
|
||||
hidden = "msHidden";
|
||||
visibilityChange = "msvisibilitychange";
|
||||
} else if (typeof document.webkitHidden !== "undefined") {
|
||||
hidden = "webkitHidden";
|
||||
visibilityChange = "webkitvisibilitychange";
|
||||
}
|
||||
|
||||
return {
|
||||
hidden: hidden,
|
||||
visibilityChange: visibilityChange,
|
||||
};
|
||||
},
|
||||
handleVisibilityChange: function () {
|
||||
const isElementInViewport = function (el) {
|
||||
let element = document.querySelector(el);
|
||||
let bounding = element.getBoundingClientRect();
|
||||
let isVisible;
|
||||
|
||||
if (
|
||||
bounding.top >= 0 &&
|
||||
bounding.left >= 0 &&
|
||||
bounding.right <= window.innerWidth &&
|
||||
bounding.bottom <= window.innerHeight
|
||||
) {
|
||||
isVisible = true;
|
||||
} else {
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
};
|
||||
|
||||
if (!document.hidden) {
|
||||
if (typeof _carbonads !== "undefined" && isElementInViewport("#carbonads")) {
|
||||
// eslint-disable-next-line no-undef
|
||||
_carbonads.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
init: function () {
|
||||
if (this.isRefreshAble()) {
|
||||
document.addEventListener(
|
||||
this.browserSupport().visibilityChange,
|
||||
this.handleVisibilityChange,
|
||||
false,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./carbon-ad";
|
||||
@@ -1,21 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import {BlogPost} from "contentlayer/generated";
|
||||
import {Card, CardFooter, CardBody, CardHeader, Link, Avatar} from "@nextui-org/react";
|
||||
import {Card, CardFooter, CardBody, CardHeader, Link, Avatar, Image} from "@nextui-org/react";
|
||||
import Balancer from "react-wrap-balancer";
|
||||
import {format, parseISO} from "date-fns";
|
||||
import NextLink from "next/link";
|
||||
import {AnimatePresence, motion} from "framer-motion";
|
||||
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BlogPostCard = (post: BlogPost) => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("BlogPostCard - Selection", {
|
||||
name: post.title,
|
||||
action: "click",
|
||||
category: "blog",
|
||||
data: post.url ?? "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isMounted && (
|
||||
<motion.div
|
||||
<motion.article
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0, y: 5}}
|
||||
initial={{opacity: 0, y: 5}}
|
||||
@@ -24,9 +34,10 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
<Card
|
||||
isBlurred
|
||||
as={NextLink}
|
||||
className="p-2 border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
|
||||
className="p-2 h-full border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
|
||||
href={post.url}
|
||||
isPressable={!!post.url}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<CardHeader>
|
||||
<Link
|
||||
@@ -35,14 +46,14 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
href={post.url}
|
||||
size="lg"
|
||||
underline="hover"
|
||||
onPress={handlePress}
|
||||
>
|
||||
<Balancer>{post.title}</Balancer>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardBody className="px-3 pt-0 pb-1">
|
||||
<p className="font-normal text-default-600">
|
||||
<Balancer>{post.description}</Balancer>
|
||||
</p>
|
||||
<CardBody className="pt-0 px-2 pb-1">
|
||||
<Image className="mb-4" src={post.image} />
|
||||
<p className="font-normal w-full text-default-600">{post.description}</p>
|
||||
</CardBody>
|
||||
<CardFooter className="flex justify-between items-center">
|
||||
<time className="block text-small text-default-500" dateTime={post.date}>
|
||||
@@ -51,7 +62,7 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
<Avatar size="sm" src={post.author?.avatar} />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</motion.article>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
@@ -59,7 +70,7 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
|
||||
export const BlogPostList = ({posts}: {posts: BlogPost[]}) => {
|
||||
return (
|
||||
<div className="mt-10 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="mt-10 grid gap-4 grid-cols-[repeat(auto-fill,minmax(300px,1fr))]">
|
||||
{posts.map((post, idx) => (
|
||||
<BlogPostCard key={idx} {...post} />
|
||||
))}
|
||||
|
||||
@@ -5,11 +5,14 @@ import {useInView} from "framer-motion";
|
||||
import {useRef, FC, useEffect, useState, useCallback} from "react";
|
||||
import {Button, cn, Spinner, Tooltip} from "@nextui-org/react";
|
||||
|
||||
import {PlayBoldIcon, PauseBoldIcon} from "@/components/icons";
|
||||
import {RotateLeftLinearIcon} from "@/components/icons";
|
||||
|
||||
interface VideoInViewProps {
|
||||
src: string;
|
||||
playMode?: "auto" | "manual";
|
||||
autoPlay?: boolean;
|
||||
poster?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
controls?: boolean;
|
||||
@@ -20,18 +23,25 @@ export const VideoInView: FC<VideoInViewProps> = ({
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
poster,
|
||||
autoPlay = true,
|
||||
playMode = "manual",
|
||||
controls = false,
|
||||
className,
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const isVisible = useInView(videoRef);
|
||||
|
||||
// play video when it is visible
|
||||
// play video when it is visible and playMode is auto
|
||||
useEffect(() => {
|
||||
if (playMode !== "auto") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
videoRef.current?.play();
|
||||
} else {
|
||||
@@ -39,19 +49,53 @@ export const VideoInView: FC<VideoInViewProps> = ({
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
const handleCanPlayThrough = useCallback(() => {
|
||||
const handleCanPlay = useCallback(() => {
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const videoEl = videoRef.current;
|
||||
|
||||
if (videoEl) {
|
||||
if (videoEl.readyState > 3) {
|
||||
// HAVE_FUTURE_DATA: enough data to start playing
|
||||
handleCanPlay();
|
||||
} else {
|
||||
videoEl.addEventListener("canplaythrough", handleCanPlay);
|
||||
}
|
||||
|
||||
// Cleanup the event listener
|
||||
return () => {
|
||||
videoEl.removeEventListener("canplaythrough", handleCanPlay);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onRestart = useCallback(() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime = 0;
|
||||
videoRef.current.play();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onTogglePlay = useCallback(() => {
|
||||
if (videoRef.current) {
|
||||
if (!isPlaying) {
|
||||
videoRef.current.play();
|
||||
} else {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
|
||||
setIsPlaying((v) => !v);
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="relative data-[playing=true]:after:opacity-0 data-[playing=true]:after:z-[-1] after:content-[''] after:absolute after:inset-0 after:bg-black/30 after:z-20 after:transition-opacity"
|
||||
data-playing={isPlaying}
|
||||
>
|
||||
{isLoading && (
|
||||
<Spinner
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||
@@ -59,6 +103,22 @@ export const VideoInView: FC<VideoInViewProps> = ({
|
||||
size="lg"
|
||||
/>
|
||||
)}
|
||||
<Tooltip content={isPlaying ? "Pause" : "Play"} delay={1000}>
|
||||
<Button
|
||||
isIconOnly
|
||||
className="absolute z-50 right-12 top-3 border-1 border-transparent bg-transparent before:bg-white/10 before:content-[''] before:block before:z-[-1] before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100 before:rounded-lg"
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
onPress={onTogglePlay}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<PauseBoldIcon className="text-white" size={16} />
|
||||
) : (
|
||||
<PlayBoldIcon className="text-white" size={16} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="Restart" delay={1000}>
|
||||
<Button
|
||||
isIconOnly
|
||||
@@ -67,24 +127,24 @@ export const VideoInView: FC<VideoInViewProps> = ({
|
||||
variant="bordered"
|
||||
onPress={onRestart}
|
||||
>
|
||||
<RotateLeftLinearIcon size={16} />
|
||||
<RotateLeftLinearIcon className="text-white" size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<video
|
||||
ref={videoRef}
|
||||
loop
|
||||
muted
|
||||
autoPlay={autoPlay}
|
||||
autoPlay={autoPlay && playMode === "auto"}
|
||||
className={cn(
|
||||
"w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg",
|
||||
className,
|
||||
)}
|
||||
controls={controls}
|
||||
height={height}
|
||||
poster={poster}
|
||||
src={src}
|
||||
width={width}
|
||||
onCanPlayThrough={handleCanPlayThrough}
|
||||
onPlaying={() => setIsLoading(false)}
|
||||
onCanPlay={handleCanPlay}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {usePathname, useRouter} from "next/navigation";
|
||||
import MultiRef from "react-multi-ref";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import scrollIntoView from "scroll-into-view-if-needed";
|
||||
import {isAppleDevice} from "@react-aria/utils";
|
||||
import {isAppleDevice, isWebKit} from "@react-aria/utils";
|
||||
import {create} from "zustand";
|
||||
import {intersectionBy, isEmpty} from "lodash";
|
||||
import {writeStorage, useLocalStorage} from "@rehooks/local-storage";
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
|
||||
import searchData from "@/config/search-meta.json";
|
||||
import {useUpdateEffect} from "@/hooks/use-update-effect";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const hideOnPaths = ["examples"];
|
||||
|
||||
@@ -175,6 +176,13 @@ export const Cmdk: FC<{}> = () => {
|
||||
|
||||
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
|
||||
|
||||
trackEvent("Cmdk - Search", {
|
||||
name: "cmdk - search",
|
||||
action: "search",
|
||||
category: "cmdk",
|
||||
data: {query, words, matches: matches?.map((match) => match.url).join(", ")},
|
||||
});
|
||||
|
||||
return matches;
|
||||
},
|
||||
[query],
|
||||
@@ -190,12 +198,21 @@ export const Cmdk: FC<{}> = () => {
|
||||
if (e?.key?.toLowerCase() === "k" && e[hotkey]) {
|
||||
e.preventDefault();
|
||||
isOpen ? onClose() : onOpen();
|
||||
|
||||
trackEvent("Cmdk - Open/Close", {
|
||||
name: "cmdk - open/close",
|
||||
action: "keydown",
|
||||
category: "cmdk",
|
||||
data: isOpen ? "close" : "open",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
return () => document.removeEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const onItemSelect = useCallback(
|
||||
@@ -203,6 +220,13 @@ export const Cmdk: FC<{}> = () => {
|
||||
onClose();
|
||||
router.push(item.url);
|
||||
addToRecentSearches(item);
|
||||
|
||||
trackEvent("Cmdk - ItemSelect", {
|
||||
name: item.content,
|
||||
action: "click",
|
||||
category: "cmdk",
|
||||
data: item.url,
|
||||
});
|
||||
},
|
||||
[router, recentSearches],
|
||||
);
|
||||
@@ -366,14 +390,14 @@ export const Cmdk: FC<{}> = () => {
|
||||
placement="top-center"
|
||||
scrollBehavior="inside"
|
||||
size="xl"
|
||||
onClose={onClose}
|
||||
onClose={() => onClose()}
|
||||
>
|
||||
<ModalContent>
|
||||
<Command className={slots.base()} label="Quick search command" shouldFilter={false}>
|
||||
<div className={slots.header()}>
|
||||
<SearchLinearIcon className={slots.searchIcon()} strokeWidth={2} />
|
||||
<Command.Input
|
||||
autoFocus
|
||||
autoFocus={!isWebKit()}
|
||||
className={slots.input()}
|
||||
placeholder="Search documentation"
|
||||
value={query}
|
||||
|
||||
@@ -108,9 +108,9 @@ function CodeTypewriter({value, className, css, ...props}: any) {
|
||||
return (
|
||||
<Pre className={className} css={css} {...props}>
|
||||
<code
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
ref={wrapperRef}
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
style={{opacity: 0}}
|
||||
/>
|
||||
</Pre>
|
||||
@@ -155,7 +155,7 @@ const CodeBlock = React.forwardRef<HTMLPreElement, CodeBlockProps>((_props, forw
|
||||
{...props}
|
||||
>
|
||||
{showWindowIcons && <WindowActions title={title} />}
|
||||
<code className={clsx(classes, codeClasses)} dangerouslySetInnerHTML={{__html: result}} />
|
||||
<code dangerouslySetInnerHTML={{__html: result}} className={clsx(classes, codeClasses)} />
|
||||
</Pre>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import {useRef} from "react";
|
||||
import {Button} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const CustomButton = () => {
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
@@ -26,6 +28,11 @@ export const CustomButton = () => {
|
||||
x: targetCenterX / clientWidth,
|
||||
},
|
||||
});
|
||||
|
||||
trackEvent("LandingPage - Confetti Button", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardBody, Button, Image, Progress, CardProps} from "@nextui-org/react";
|
||||
import {Card, CardBody, Button, Image, Slider, CardProps} from "@nextui-org/react";
|
||||
import {useState, FC} from "react";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import NextImage from "next/image";
|
||||
@@ -63,15 +63,15 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mt-3 gap-1">
|
||||
<Progress
|
||||
<Slider
|
||||
aria-label="Music progress"
|
||||
classNames={{
|
||||
indicator: "bg-default-800 dark:bg-white",
|
||||
track: "bg-default-500/30",
|
||||
thumb: "w-2 h-2 after:w-2 after:h-2 after:bg-foreground",
|
||||
}}
|
||||
color="default"
|
||||
color="foreground"
|
||||
defaultValue={33}
|
||||
size="sm"
|
||||
value={33}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm">1:23</p>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import {FC} from "react";
|
||||
import {tv, VariantProps} from "tailwind-variants";
|
||||
|
||||
const blockquoteStyles = tv({
|
||||
base: "border pl-4 bg-default-50 my-6 py-3 rounded-xl [&>p]:m-0",
|
||||
base: "border px-4 bg-default-50 my-6 py-3 rounded-xl [&>p]:m-0",
|
||||
variants: {
|
||||
color: {
|
||||
default: "border-default-200 dark:border-default-100 bg-default-200/20",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo";
|
||||
import WindowResizer, {WindowResizerProps} from "./window-resizer";
|
||||
|
||||
import {GradientBoxProps} from "@/components/gradient-box";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const DynamicReactLiveDemo = dynamic(
|
||||
() => import("./react-live-demo").then((m) => m.ReactLiveDemo),
|
||||
@@ -31,11 +32,12 @@ interface CodeDemoProps extends UseCodeDemoProps, WindowResizerProps {
|
||||
showSandpackPreview?: boolean;
|
||||
initialEditorOpen?: boolean;
|
||||
enableResize?: boolean;
|
||||
showTabs?: boolean;
|
||||
showPreview?: boolean;
|
||||
hideWindowActions?: boolean;
|
||||
showOpenInCodeSandbox?: boolean;
|
||||
isPreviewCentered?: boolean;
|
||||
resizeEnabled?: boolean;
|
||||
typescriptStrict?: boolean;
|
||||
displayMode?: "always" | "visible";
|
||||
isGradientBox?: boolean;
|
||||
gradientColor?: GradientBoxProps["color"];
|
||||
@@ -51,16 +53,19 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
showEditor = true,
|
||||
showPreview = true,
|
||||
asIframe = false,
|
||||
showTabs = true,
|
||||
resizeEnabled = true,
|
||||
hideWindowActions = false,
|
||||
showSandpackPreview = false,
|
||||
isPreviewCentered = false,
|
||||
// when false .js files will be used
|
||||
typescriptStrict = false,
|
||||
showOpenInCodeSandbox,
|
||||
isGradientBox = false,
|
||||
defaultExpanded = false,
|
||||
previewHeight = "auto",
|
||||
overflow = "visible",
|
||||
displayMode = "always",
|
||||
showTabs = true,
|
||||
gradientColor,
|
||||
highlightedLines,
|
||||
iframeInitialWidth,
|
||||
@@ -96,6 +101,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
|
||||
const content = asIframe ? (
|
||||
<WindowResizer
|
||||
hideWindowActions={hideWindowActions}
|
||||
iframeHeight={previewHeight}
|
||||
iframeInitialWidth={iframeInitialWidth}
|
||||
iframeSrc={iframeSrc}
|
||||
@@ -121,6 +127,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
isGradientBox,
|
||||
gradientColor,
|
||||
previewHeight,
|
||||
hideWindowActions,
|
||||
asIframe,
|
||||
showPreview,
|
||||
isInView,
|
||||
@@ -138,6 +145,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
showEditor={showEditor}
|
||||
showOpenInCodeSandbox={showOpenInCodeSandbox || showPreview}
|
||||
showPreview={showSandpackPreview}
|
||||
typescriptStrict={typescriptStrict}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -172,6 +180,14 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
panel: "pt-0",
|
||||
}}
|
||||
variant="underlined"
|
||||
onSelectionChange={(tabKey) => {
|
||||
trackEvent("CodeDemo - Selection", {
|
||||
name: tabKey as string,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: tabKey ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tab key="preview" title="Preview">
|
||||
{previewContent}
|
||||
|
||||
@@ -49,6 +49,7 @@ const resizer = tv({
|
||||
|
||||
export interface WindowResizerProps {
|
||||
resizeEnabled?: boolean;
|
||||
hideWindowActions?: boolean;
|
||||
iframeHeight?: string | number;
|
||||
iframeMinWidth?: number;
|
||||
iframeSrc?: string;
|
||||
@@ -70,6 +71,7 @@ const WindowResizer: React.FC<WindowResizerProps> = (props) => {
|
||||
iframeSrc,
|
||||
iframeTitle,
|
||||
resizeEnabled,
|
||||
hideWindowActions = false,
|
||||
iframeHeight: height = "420px",
|
||||
iframeInitialWidth,
|
||||
iframeMinWidth: minWidth = MIN_WIDTH,
|
||||
@@ -120,7 +122,7 @@ const WindowResizer: React.FC<WindowResizerProps> = (props) => {
|
||||
width: isMobile ? "100%" : browserWidth,
|
||||
}}
|
||||
>
|
||||
<WindowActions className="bg-default-100 dark:bg-default-50" />
|
||||
{!hideWindowActions && <WindowActions className="bg-default-100 dark:bg-default-50" />}
|
||||
<motion.iframe ref={iframeRef} className={iframe()} src={iframeSrc} title={iframeTitle} />
|
||||
</motion.div>
|
||||
{resizeEnabled && (
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import React, {forwardRef} from "react";
|
||||
import {clsx, getUniqueID} from "@nextui-org/shared-utils";
|
||||
import React, {forwardRef, useEffect} from "react";
|
||||
import {clsx, dataAttr, getUniqueID} from "@nextui-org/shared-utils";
|
||||
import BaseHighlight, {Language, PrismTheme, defaultProps} from "prism-react-renderer";
|
||||
import {debounce} from "lodash";
|
||||
|
||||
import defaultTheme from "@/libs/prism-theme";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
interface CodeblockProps {
|
||||
language: Language;
|
||||
codeString: string;
|
||||
metastring?: string;
|
||||
theme?: PrismTheme;
|
||||
children?: React.ReactNode;
|
||||
showLines?: boolean;
|
||||
removeIndent?: boolean;
|
||||
hideScrollBar?: boolean;
|
||||
className?: string;
|
||||
children?: (props: any) => React.ReactNode;
|
||||
}
|
||||
|
||||
type HighlightStyle = "inserted" | "deleted" | undefined;
|
||||
|
||||
const RE = /{([\d,-]+)}/;
|
||||
|
||||
const calculateLinesToHighlight = (meta?: string) => {
|
||||
@@ -60,11 +64,76 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
const shouldHighlightLine = calculateLinesToHighlight(metastring);
|
||||
const isMultiLine = codeString.split("\n").length > 2;
|
||||
|
||||
const lastSelectionText = React.useRef<string | null>(null);
|
||||
|
||||
const isDiff = language.includes("diff");
|
||||
|
||||
const codeLang = isDiff ? (language.split("-")[1] as Language) : language;
|
||||
|
||||
let highlightStyle: HighlightStyle[] = [];
|
||||
|
||||
if (isDiff) {
|
||||
let code: string[] = [];
|
||||
|
||||
highlightStyle = codeString.split?.("\n").map((line) => {
|
||||
if (line.startsWith("+")) {
|
||||
code.push(line.substr(1));
|
||||
|
||||
return "inserted";
|
||||
}
|
||||
if (line.startsWith("-")) {
|
||||
code.push(line.substr(1));
|
||||
|
||||
return "deleted";
|
||||
}
|
||||
code.push(line);
|
||||
});
|
||||
|
||||
codeString = code.join("\n");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionChange = () => {
|
||||
if (!window.getSelection) return;
|
||||
|
||||
const el = window.getSelection()?.anchorNode?.parentNode;
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const selectionText = window.getSelection()?.toString();
|
||||
|
||||
if (!selectionText) return;
|
||||
|
||||
if (
|
||||
!selectionText ||
|
||||
selectionText === lastSelectionText.current ||
|
||||
!codeString.includes(selectionText)
|
||||
)
|
||||
return;
|
||||
|
||||
lastSelectionText.current = selectionText;
|
||||
|
||||
trackEvent("Codeblock - Selection", {
|
||||
action: "selectText",
|
||||
category: "docs",
|
||||
data: selectionText,
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedHandleSelectionChange = debounce(handleSelectionChange, 1000);
|
||||
|
||||
document.addEventListener("selectionchange", debouncedHandleSelectionChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("selectionchange", debouncedHandleSelectionChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseHighlight
|
||||
{...defaultProps}
|
||||
code={codeString}
|
||||
language={language}
|
||||
language={codeLang}
|
||||
theme={theme}
|
||||
{...props}
|
||||
>
|
||||
@@ -97,6 +166,8 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
shouldHighlightLine(i),
|
||||
},
|
||||
)}
|
||||
data-deleted={dataAttr(highlightStyle?.[i] === "deleted")}
|
||||
data-inserted={dataAttr(highlightStyle?.[i] === "inserted")}
|
||||
>
|
||||
{showLines && (
|
||||
<span className="select-none text-xs mr-6 opacity-30">{i + 1}</span>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {Button, ButtonProps, Link} from "@nextui-org/react";
|
||||
import {Button, ButtonProps, Code, Link, Tooltip} from "@nextui-org/react";
|
||||
import {ReactNode} from "react";
|
||||
import Balancer from "react-wrap-balancer";
|
||||
|
||||
import {GithubIcon, NpmIcon, AdobeIcon, StorybookIcon} from "@/components/icons";
|
||||
import {GithubIcon, NpmIcon, AdobeIcon, StorybookIcon, NextJsIcon} from "@/components/icons";
|
||||
import {COMPONENT_PATH, COMPONENT_THEME_PATH} from "@/libs/github/constants";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface ComponentLinksProps {
|
||||
component: string;
|
||||
styles?: string;
|
||||
storybook?: string;
|
||||
rscCompatible?: boolean;
|
||||
reactAriaHook?: string;
|
||||
}
|
||||
|
||||
@@ -14,27 +18,51 @@ const ButtonLink = ({
|
||||
children,
|
||||
href,
|
||||
startContent,
|
||||
tooltip,
|
||||
...props
|
||||
}: ButtonProps & {
|
||||
href: string;
|
||||
}) => (
|
||||
<Button
|
||||
isExternal
|
||||
as={Link}
|
||||
className="!text-small py-4 bg-default-100 dark:bg-default-50 text-default-700"
|
||||
href={href}
|
||||
size="sm"
|
||||
startContent={startContent}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
tooltip?: string | ReactNode;
|
||||
}) => {
|
||||
const handlePress = () => {
|
||||
if (!href) return;
|
||||
|
||||
trackEvent("ComponentLinks - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
});
|
||||
};
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
isExternal
|
||||
as={Link}
|
||||
className="!text-small py-4 bg-default-100 dark:bg-default-50 text-default-700"
|
||||
href={href}
|
||||
size="sm"
|
||||
startContent={startContent}
|
||||
onPress={handlePress}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return tooltip ? (
|
||||
<Tooltip className="max-w-[230px]" content={tooltip}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
) : (
|
||||
button
|
||||
);
|
||||
};
|
||||
|
||||
export const ComponentLinks = ({
|
||||
component,
|
||||
storybook,
|
||||
styles,
|
||||
rscCompatible,
|
||||
reactAriaHook,
|
||||
}: ComponentLinksProps) => {
|
||||
if (!component) {
|
||||
@@ -44,7 +72,9 @@ export const ComponentLinks = ({
|
||||
return (
|
||||
<div className="flex flex-wrap gap-3 mt-6">
|
||||
<ButtonLink
|
||||
href={`https://storiesv2.nextui.org/?path=/story/components-${storybook || component}`}
|
||||
href={`https://storybook.nextui.org/?path=/story/components-${
|
||||
storybook || component
|
||||
}--default`}
|
||||
startContent={<StorybookIcon className="text-lg text-[#ff4785]" />}
|
||||
>
|
||||
Storybook
|
||||
@@ -63,6 +93,26 @@ export const ComponentLinks = ({
|
||||
React Aria
|
||||
</ButtonLink>
|
||||
)}
|
||||
{rscCompatible && (
|
||||
<ButtonLink
|
||||
href="https://nextjs.org/docs/app/building-your-application/rendering/server-components"
|
||||
startContent={<NextJsIcon size={18} />}
|
||||
tooltip={
|
||||
<p>
|
||||
<Balancer>
|
||||
This component doesn't use the
|
||||
<Code className="font-normal bg-transparent px-0 py-0 text-code-mdx">
|
||||
`use client;`
|
||||
</Code>
|
||||
directive making it compatible with RSC.
|
||||
</Balancer>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
Server component
|
||||
</ButtonLink>
|
||||
)}
|
||||
|
||||
<ButtonLink href={`${COMPONENT_PATH}/${component}`} startContent={<GithubIcon size={20} />}>
|
||||
Source
|
||||
</ButtonLink>
|
||||
|
||||
@@ -2,6 +2,8 @@ import {Tabs, Tab, Snippet} from "@nextui-org/react";
|
||||
|
||||
import Codeblock from "./codeblock";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type PackageManager = {
|
||||
key: string;
|
||||
name: string;
|
||||
@@ -32,6 +34,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
|
||||
tabList: "relative h-10",
|
||||
}}
|
||||
variant="underlined"
|
||||
onSelectionChange={(tabKey) => {
|
||||
trackEvent("ImportTabs - Selection", {
|
||||
name: tabKey as string,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: commands[tabKey] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{importTabs.map(({key, name}) => {
|
||||
if (!commands[key]) return null;
|
||||
@@ -47,6 +57,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
|
||||
pre: "font-light text-base",
|
||||
copyButton: "text-lg text-default-400",
|
||||
}}
|
||||
onCopy={() => {
|
||||
trackEvent("ImportTabs - Copy", {
|
||||
name,
|
||||
action: "copyInstallScript",
|
||||
category: "docs",
|
||||
data: commands[name] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock
|
||||
hideScrollBar
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {Tabs, Tab, Snippet} from "@nextui-org/react";
|
||||
import {Key} from "react";
|
||||
import {useLocalStorage} from "usehooks-ts";
|
||||
|
||||
import Codeblock from "./codeblock";
|
||||
|
||||
import {YarnIcon, NpmSmallIcon, PnpmIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type PackageManagerName = "npm" | "yarn" | "pnpm";
|
||||
|
||||
@@ -27,10 +30,26 @@ const packageManagers: PackageManager[] = [
|
||||
];
|
||||
|
||||
export interface PackageManagersProps {
|
||||
commands: Partial<Record<PackageManagerName, string>>;
|
||||
commands: Partial<Record<PackageManagerName, React.Key>>;
|
||||
}
|
||||
|
||||
export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
const [selectedManager, setSelectedManager] = useLocalStorage<PackageManagerName>(
|
||||
"selectedPackageManager",
|
||||
"npm",
|
||||
);
|
||||
|
||||
const handleSelectionChange = (tabKey: Key) => {
|
||||
trackEvent("PackageManagers - Selection", {
|
||||
name: `${tabKey}`,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: commands[tabKey as unknown as PackageManagerName] ?? "",
|
||||
});
|
||||
|
||||
setSelectedManager(tabKey as PackageManagerName);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
aria-label="NextUI installation commands"
|
||||
@@ -38,7 +57,9 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
base: "group mt-4",
|
||||
tabList: "h-10",
|
||||
}}
|
||||
selectedKey={selectedManager}
|
||||
variant="underlined"
|
||||
onSelectionChange={handleSelectionChange}
|
||||
>
|
||||
{packageManagers.map(({name, icon}) => {
|
||||
if (!commands[name]) return null;
|
||||
@@ -62,6 +83,14 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
pre: "font-light text-base",
|
||||
copyButton: "text-lg text-zinc-500 mr-2",
|
||||
}}
|
||||
onCopy={() => {
|
||||
trackEvent("PackageManagers - Copy", {
|
||||
name,
|
||||
action: "copyScript",
|
||||
category: "docs",
|
||||
data: commands[name] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock removeIndent codeString={commands[name] as string} language="bash" />
|
||||
</Snippet>
|
||||
|
||||
@@ -1,31 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import NextLink from "next/link";
|
||||
import {Link} from "@nextui-org/react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {ChevronIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
import manifest from "@/config/routes.json";
|
||||
import {removeFromLast} from "@/utils";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {useDocsRoute} from "@/hooks/use-docs-route";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface FooterNavProps {
|
||||
currentRoute?: Route;
|
||||
}
|
||||
|
||||
export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
|
||||
|
||||
const handlePress = (path: string) => {
|
||||
trackEvent("DocsPager - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: path || "",
|
||||
});
|
||||
|
||||
router.push(removeFromLast(path || "", "."));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full justify-between py-20">
|
||||
{prevRoute ? (
|
||||
<Link
|
||||
isBlock
|
||||
as={NextLink}
|
||||
className="flex gap-2"
|
||||
className="cursor-pointer flex gap-2"
|
||||
color="foreground"
|
||||
href={removeFromLast(prevRoute.path || "", ".")}
|
||||
onPress={() => handlePress(prevRoute.path || "")}
|
||||
>
|
||||
<ChevronIcon className="text-primary" height={20} width={20} />
|
||||
{prevRoute.title}
|
||||
@@ -36,10 +48,9 @@ export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
|
||||
{nextRoute && (
|
||||
<Link
|
||||
isBlock
|
||||
as={NextLink}
|
||||
className="flex gap-1 items-center"
|
||||
className="cursor-pointer flex gap-1 items-center"
|
||||
color="foreground"
|
||||
href={removeFromLast(nextRoute.path || "", ".")}
|
||||
onPress={() => handlePress(nextRoute.path || "")}
|
||||
>
|
||||
{nextRoute.title}
|
||||
<ChevronIcon className="rotate-180 text-primary" height={20} width={20} />
|
||||
|
||||
@@ -21,11 +21,13 @@ import Link from "next/link";
|
||||
import {isEmpty} from "lodash";
|
||||
import {usePathname, useRouter} from "next/navigation";
|
||||
|
||||
import {ScrollArea} from "../scroll-area";
|
||||
|
||||
import {getRoutePaths} from "./utils";
|
||||
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
|
||||
import {useScrollPosition} from "@/hooks/use-scroll-position";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
|
||||
slug?: string;
|
||||
@@ -68,6 +70,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
};
|
||||
|
||||
const isNew = item.props?.newPost;
|
||||
const isUpdated = item.props?.updated;
|
||||
|
||||
const isExpanded = state.expandedKeys.has(key);
|
||||
const isSelected =
|
||||
@@ -88,6 +91,12 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
state.toggleKey(item.key);
|
||||
} else {
|
||||
router.push(paths.pathname);
|
||||
|
||||
trackEvent("SidebarDocs", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: paths.pathname || "",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -101,7 +110,9 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
aria-expanded={dataAttr(hasChildNodes ? isExpanded : undefined)}
|
||||
aria-selected={dataAttr(isSelected)}
|
||||
className={clsx(
|
||||
"flex flex-col gap-3 outline-none w-full tap-highlight-transparent",
|
||||
"flex flex-col gap-3outline-none w-full tap-highlight-transparent",
|
||||
|
||||
hasChildNodes ? "mb-4" : "first:mt-4",
|
||||
// focus ring
|
||||
...dataFocusVisibleClasses,
|
||||
)}
|
||||
@@ -149,18 +160,30 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
>
|
||||
{rendered}
|
||||
</span>
|
||||
{isUpdated && (
|
||||
<Chip
|
||||
className="ml-1 py-1 text-tiny text-default-400 bg-default-100/50"
|
||||
color="default"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Updated
|
||||
</Chip>
|
||||
)}
|
||||
{isNew && (
|
||||
<Chip className="ml-2" color="primary" size="sm" variant="flat">
|
||||
<Chip className="ml-1 py-1 text-tiny" color="primary" size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
)}
|
||||
{item.props?.comingSoon && (
|
||||
<Chip className="ml-2" color="default" size="sm" variant="flat">
|
||||
<Chip className="ml-1 py-1 text-tiny" color="default" size="sm" variant="flat">
|
||||
Coming soon
|
||||
</Chip>
|
||||
)}
|
||||
</NextUILink>
|
||||
)}
|
||||
{/* Workaround to avoid scrollbar overlapping */}
|
||||
<Spacer x={4} />
|
||||
</div>
|
||||
{isExpanded && hasChildNodes && (
|
||||
<div className="flex flex-col gap-3 items-start" role="group">
|
||||
@@ -189,9 +212,7 @@ function TreeHeading({item}: {item: any}) {
|
||||
function Tree<T extends object>(props: CollectionBase<T> & Expandable & MultipleSelection) {
|
||||
let state = useTreeState(props);
|
||||
|
||||
let ref = useRef<HTMLUListElement>(null);
|
||||
|
||||
const scrollPosition = useScrollPosition(ref);
|
||||
let ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
let keyboardDelegate = useMemo(
|
||||
// @ts-expect-error
|
||||
@@ -206,16 +227,11 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
|
||||
});
|
||||
|
||||
return (
|
||||
<ul
|
||||
{...collectionProps}
|
||||
<ScrollArea
|
||||
ref={ref}
|
||||
className="flex flex-col gap-4 scrollbar-hide lg:overflow-y-scroll lg:max-h-[calc(100vh_-_64px)] pb-28"
|
||||
className="h-full lg:max-h-[calc(100vh_-_64px)]"
|
||||
role="tree"
|
||||
style={{
|
||||
WebkitMaskImage: `linear-gradient(to top, transparent 0%, #000 100px, #000 ${
|
||||
scrollPosition > 30 ? "90%" : "100%"
|
||||
}, transparent 100%)`,
|
||||
}}
|
||||
{...collectionProps}
|
||||
>
|
||||
{[...state.collection].map((item) => {
|
||||
if (item.type === "section") {
|
||||
@@ -224,7 +240,7 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
|
||||
|
||||
return <TreeItem key={item.key} item={item} state={state} />;
|
||||
})}
|
||||
</ul>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const FigmaButton = () => (
|
||||
<Button
|
||||
isExternal
|
||||
showAnchorIcon
|
||||
as={Link}
|
||||
className="max-w-fit text-current"
|
||||
color="default"
|
||||
href="https://www.figma.com/community/file/1267584376234254760"
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
trackEvent("FigmaPage - Open Figma Link", {
|
||||
action: "click",
|
||||
category: "figma",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open in Figma
|
||||
</Button>
|
||||
);
|
||||
@@ -14,3 +14,5 @@ export * from "./info";
|
||||
export * from "./document-code";
|
||||
export * from "./hash";
|
||||
export * from "./more-square";
|
||||
export * from "./play";
|
||||
export * from "./pause";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import {IconSvgProps} from "@/types";
|
||||
|
||||
export const PauseBoldIcon = ({size = 24, width, height, ...props}: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10.65 19.11V4.89C10.65 3.54 10.08 3 8.64 3H5.01C3.57 3 3 3.54 3 4.89V19.11C3 20.46 3.57 21 5.01 21H8.64C10.08 21 10.65 20.46 10.65 19.11Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M21 19.11V4.89C21 3.54 20.43 3 18.99 3H15.36C13.93 3 13.35 3.54 13.35 4.89V19.11C13.35 20.46 13.92 21 15.36 21H18.99C20.43 21 21 20.46 21 19.11Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
import {IconSvgProps} from "@/types";
|
||||
|
||||
export const PlayBoldIcon = ({size = 24, width, height, ...props}: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M4 11.9999V8.43989C4 4.01989 7.13 2.2099 10.96 4.4199L14.05 6.1999L17.14 7.9799C20.97 10.1899 20.97 13.8099 17.14 16.0199L14.05 17.7999L10.96 19.5799C7.13 21.7899 4 19.9799 4 15.5599V11.9999Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -14,6 +14,7 @@ import {PaletteIcon, MagicIcon, GamingConsoleIcon, StarIcon} from "@/components/
|
||||
import {NextUILogo, CodeWindow} from "@/components";
|
||||
import landingContent from "@/content/landing";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const themesTabs = (isMobile: boolean) => [
|
||||
{
|
||||
@@ -87,6 +88,12 @@ const CustomThemesExample = ({
|
||||
|
||||
const onSelectionChange = (value: React.Key) => {
|
||||
onChangeTheme(value as Theme);
|
||||
|
||||
trackEvent("CustomThemes - Selection", {
|
||||
action: "change_theme",
|
||||
category: "landing-page",
|
||||
data: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,8 @@ import {Card, CardHeader, CardBody, LinkProps, SlotsToClasses} from "@nextui-org
|
||||
import {useRouter} from "next/navigation";
|
||||
import {LinkIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const styles = tv({
|
||||
slots: {
|
||||
base: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
|
||||
@@ -38,6 +40,13 @@ export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames,
|
||||
const slots = styles();
|
||||
|
||||
const handleClick = (feat: Feature) => {
|
||||
trackEvent("FeaturesGrid - Click", {
|
||||
name: feat.title,
|
||||
action: "click",
|
||||
category: "docs",
|
||||
data: feat.href ?? "",
|
||||
});
|
||||
|
||||
if (!feat.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,12 +105,14 @@ export const FloatingComponents: React.FC<{}> = () => {
|
||||
|
||||
{isMounted && (
|
||||
<Tooltip
|
||||
showArrow
|
||||
className="text-sm animate-[levitate_14s_ease_infinite]"
|
||||
color="secondary"
|
||||
content="Developers love Next.js"
|
||||
isOpen={!isTablet}
|
||||
placement="top"
|
||||
style={{
|
||||
zIndex: 39,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className="absolute left-[200px] top-[160px] max-w-fit animate-[levitate_14s_ease_infinite_0.5s]"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import NextLink from "next/link";
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
import {Button, Link, Chip} from "@nextui-org/react";
|
||||
import {ArrowRightIcon} from "@nextui-org/shared-icons";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
@@ -9,15 +9,40 @@ import {FloatingComponents} from "./floating-components";
|
||||
|
||||
import {title, subtitle} from "@/components/primitives";
|
||||
import {GithubIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BgLooper = dynamic(() => import("./bg-looper").then((mod) => mod.BgLooper), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export const Hero = () => {
|
||||
const handlePressAnnouncement = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "home - gero",
|
||||
data: url,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex relative overflow-hidden lg:overflow-visible w-full flex-nowrap justify-between items-center h-[calc(100vh_-_64px)] 2xl:h-[calc(84vh_-_64px)]">
|
||||
<div className="flex relative z-20 flex-col gap-6 w-full lg:w-1/2 xl:mt-10">
|
||||
<div className="w-full flex justify-center md:hidden">
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="w-full hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
|
||||
color="secondary"
|
||||
href="/blog/v2.2.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressAnnouncement("Introducing v2.2.0", "/blog/v2.2.0")}
|
||||
>
|
||||
Introducing v2.2.0
|
||||
<span aria-label="rocket emoji" role="img">
|
||||
🚀
|
||||
</span>
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="text-center leading-8 md:leading-10 md:text-left">
|
||||
<div className="inline-block">
|
||||
<h1 className={title()}>Make </h1>
|
||||
@@ -42,6 +67,14 @@ export const Hero = () => {
|
||||
href="/docs/guide/introduction"
|
||||
radius="full"
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Get Started", {
|
||||
name: "Get Started",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
data: "/docs/guide/introduction",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
@@ -56,6 +89,14 @@ export const Hero = () => {
|
||||
size="lg"
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Github", {
|
||||
name: "Github",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
data: "https://github.com/nextui-org/nextui",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {FeaturesGrid} from "./features-grid";
|
||||
import {sectionWrapper, subtitle, title} from "@/components/primitives";
|
||||
import {GithubIcon, NoteLinearIcon, NextJsIcon} from "@/components/icons";
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const bannerSuggestions = [
|
||||
{
|
||||
@@ -69,6 +70,13 @@ export const InstallBanner = () => {
|
||||
href="/docs/guide/installation"
|
||||
radius="full"
|
||||
size="md"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Get Started", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "/docs/guide/installation",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
@@ -81,6 +89,13 @@ export const InstallBanner = () => {
|
||||
size="md"
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Github", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "https://github.com/nextui-org/nextui",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/
|
||||
import {Sponsor, SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors";
|
||||
import {SonarPulse} from "@/components/sonar-pulse";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface SupportProps {
|
||||
sponsors: Sponsor[];
|
||||
@@ -100,6 +101,14 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
window.open(href, "_blank");
|
||||
};
|
||||
|
||||
const handleBecomeSponsor = () => {
|
||||
trackEvent("Support - Become a sponsor", {
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
});
|
||||
|
||||
handleExternalLinkClick(supportAccounts[0].href);
|
||||
};
|
||||
const renderSponsors = useMemo(() => {
|
||||
if (!sponsors.length) return null;
|
||||
|
||||
@@ -181,7 +190,7 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
aria-label="Become a sponsor"
|
||||
className="z-50 w-auto h-auto bg-gradient-to-b from-[#FF1CF7] to-[#7928CA]"
|
||||
radius="full"
|
||||
onPress={() => handleExternalLinkClick(supportAccounts[0].href)}
|
||||
onPress={handleBecomeSponsor}
|
||||
>
|
||||
<PlusLinearIcon
|
||||
className="flex items-center justify-center rounded-full text-white"
|
||||
|
||||
@@ -7,10 +7,12 @@ import NextImage from "next/image";
|
||||
import {ThemeSwitch} from "./theme-switch";
|
||||
|
||||
import {Sandpack} from "@/components/sandpack";
|
||||
import {CarbonAd} from "@/components/ads/carbon-ad";
|
||||
import * as DocsComponents from "@/components/docs/components";
|
||||
import * as BlogComponents from "@/components/blog/components";
|
||||
import {Codeblock} from "@/components/docs/components";
|
||||
import {VirtualAnchor, virtualAnchorEncode} from "@/components";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const Table: React.FC<{children?: React.ReactNode}> = ({children}) => {
|
||||
return (
|
||||
@@ -143,6 +145,13 @@ const Code = ({
|
||||
copyButton: "text-lg text-zinc-500 mr-2",
|
||||
}}
|
||||
codeString={codeString}
|
||||
onCopy={() => {
|
||||
trackEvent("MDXComponents - Copy", {
|
||||
category: "docs",
|
||||
action: "copyCode",
|
||||
data: codeString,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock codeString={codeString} language={language} metastring={meta} />
|
||||
</Components.Snippet>
|
||||
@@ -152,8 +161,21 @@ const Code = ({
|
||||
const Link = ({href, children}: {href?: string; children?: React.ReactNode}) => {
|
||||
const isExternal = href?.startsWith("http");
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("MDXComponents - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Components.Link href={href} isExternal={isExternal} showAnchorIcon={isExternal}>
|
||||
<Components.Link
|
||||
href={href}
|
||||
isExternal={isExternal}
|
||||
showAnchorIcon={isExternal}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{children}
|
||||
</Components.Link>
|
||||
);
|
||||
@@ -195,7 +217,7 @@ export const MDXComponents = {
|
||||
thead: Thead,
|
||||
tr: Trow,
|
||||
td: Tcol,
|
||||
// CarbonAd,
|
||||
CarbonAd,
|
||||
code: Code,
|
||||
ul: List,
|
||||
a: (props: React.HTMLAttributes<HTMLAnchorElement>) => <Link {...props} />,
|
||||
@@ -207,7 +229,7 @@ export const MDXComponents = {
|
||||
),
|
||||
Steps: ({...props}) => (
|
||||
<div
|
||||
className="[&>h3]:step [&>h3>a]:pt-0.5 mb-12 ml-4 relative border-l border-default-100 pl-[1.625rem] [counter-reset:step]"
|
||||
className="[&>h3]:step [&>h3>a]:pt-0.5 [&>h4]:step [&>h4>a]:pt-0.5 mb-12 ml-4 relative border-l border-default-100 pl-[1.625rem] [counter-reset:step]"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
||||
+102
-32
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {useRef, useState, FC, ReactNode} from "react";
|
||||
import {useRef, useState, FC, ReactNode, Key} from "react";
|
||||
import {
|
||||
link,
|
||||
Navbar as NextUINavbar,
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
DropdownTrigger,
|
||||
Chip,
|
||||
} from "@nextui-org/react";
|
||||
import {dataFocusVisibleClasses} from "@nextui-org/theme";
|
||||
import {ChevronDownIcon, LinkIcon} from "@nextui-org/shared-icons";
|
||||
@@ -29,6 +30,7 @@ import {useEffect} from "react";
|
||||
import {usePress} from "@react-aria/interactions";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
|
||||
import {currentVersion} from "@/utils/version";
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {LargeLogo, SmallLogo, ThemeSwitch} from "@/components";
|
||||
@@ -42,16 +44,17 @@ import {
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {DocsSidebar} from "@/components/docs/sidebar";
|
||||
import {useCmdkStore} from "@/components/cmdk";
|
||||
import {__PROD__} from "@/utils";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface NavbarProps {
|
||||
routes: Route[];
|
||||
mobileRoutes?: Route[];
|
||||
tag?: string;
|
||||
slug?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], slug, tag}) => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean | undefined>(false);
|
||||
const [commandKey, setCommandKey] = useState<"ctrl" | "command">("command");
|
||||
|
||||
@@ -60,7 +63,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
const cmkdStore = useCmdkStore();
|
||||
const cmdkStore = useCmdkStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
@@ -72,8 +75,17 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
setCommandKey(isAppleDevice() ? "command" : "ctrl");
|
||||
}, []);
|
||||
|
||||
const handleOpenCmdk = () => {
|
||||
cmdkStore.onOpen();
|
||||
trackEvent("Navbar - Search", {
|
||||
name: "navbar - search",
|
||||
action: "press",
|
||||
category: "cmdk",
|
||||
});
|
||||
};
|
||||
|
||||
const {pressProps} = usePress({
|
||||
onPress: () => cmkdStore.onOpen(),
|
||||
onPress: handleOpenCmdk,
|
||||
});
|
||||
const {focusProps, isFocusVisible} = useFocusRing();
|
||||
|
||||
@@ -99,7 +111,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
strokeWidth={2}
|
||||
/>
|
||||
}
|
||||
onPress={() => cmkdStore.onOpen()}
|
||||
onPress={handleOpenCmdk}
|
||||
>
|
||||
Quick Search...
|
||||
</Button>
|
||||
@@ -109,10 +121,24 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const navLinkClasses = clsx(
|
||||
link({color: "foreground"}),
|
||||
"data-[active=true]:text-primary data-[active=true]:font-medium",
|
||||
);
|
||||
const navLinkClasses = clsx(link({color: "foreground"}), "data-[active=true]:text-primary");
|
||||
|
||||
const handleVersionChange = (key: Key) => {
|
||||
if (key === "v1") {
|
||||
const newWindow = window.open("https://v1.nextui.org", "_blank", "noopener,noreferrer");
|
||||
|
||||
if (newWindow) newWindow.opener = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePressNavbarItem = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "navbar",
|
||||
data: url,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NextUINavbar
|
||||
@@ -131,6 +157,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
aria-label="Home"
|
||||
className="flex justify-start items-center gap-2 tap-highlight-transparent transition-opacity active:opacity-50"
|
||||
href="/"
|
||||
onClick={() => handlePressNavbarItem("Home", "/")}
|
||||
>
|
||||
<SmallLogo className="w-6 h-6 md:hidden" />
|
||||
<LargeLogo className="h-5 md:h-6" />
|
||||
@@ -148,7 +175,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
v2.0.0
|
||||
v{currentVersion}
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
</motion.div>
|
||||
@@ -158,8 +185,9 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
aria-label="NextUI versions"
|
||||
defaultSelectedKeys={["v2"]}
|
||||
selectionMode="single"
|
||||
onAction={handleVersionChange}
|
||||
>
|
||||
<DropdownItem key="v2">v2.0.0</DropdownItem>
|
||||
<DropdownItem key="v2">v{currentVersion}</DropdownItem>
|
||||
<DropdownItem key="v1" endContent={<LinkIcon />}>
|
||||
v1.0.0
|
||||
</DropdownItem>
|
||||
@@ -169,13 +197,14 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
<div className="w-[74px]" />
|
||||
)}
|
||||
</NavbarBrand>
|
||||
<ul className="hidden lg:flex gap-4 justify-start">
|
||||
<ul className="hidden lg:flex gap-4 justify-start items-center">
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={includes(docsPaths, pathname)}
|
||||
href="/docs/guide/introduction"
|
||||
onClick={() => handlePressNavbarItem("Docs", "/docs/guide/introduction")}
|
||||
>
|
||||
Docs
|
||||
</NextLink>
|
||||
@@ -186,32 +215,48 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "components")}
|
||||
href="/docs/components/avatar"
|
||||
onClick={() => handlePressNavbarItem("Components", "/docs/components/avatar")}
|
||||
>
|
||||
Components
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
{!__PROD__ && (
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "blog")}
|
||||
href="/blog"
|
||||
>
|
||||
Blog
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
)}
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "blog")}
|
||||
href="/blog"
|
||||
onClick={() => handlePressNavbarItem("Blog", "/blog")}
|
||||
>
|
||||
Blog
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "figma")}
|
||||
href="/figma"
|
||||
onClick={() => handlePressNavbarItem("Figma", "/figma")}
|
||||
>
|
||||
Figma
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
|
||||
color="secondary"
|
||||
href="/blog/v2.2.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressNavbarItem("Introducing v2.2.0", "/blog/v2.2.0")}
|
||||
>
|
||||
Introducing v2.2.0
|
||||
<span aria-label="rocket emoji" role="img">
|
||||
🚀
|
||||
</span>
|
||||
</Chip>
|
||||
</NavbarItem>
|
||||
</ul>
|
||||
</NavbarContent>
|
||||
|
||||
@@ -222,6 +267,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href="https://github.com/nextui-org/nextui"
|
||||
onClick={() => handlePressNavbarItem("Github", "https://github.com/nextui-org/nextui")}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
@@ -243,18 +289,41 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
<SearchLinearIcon className="mt-px text-default-600 dark:text-default-500" size={20} />
|
||||
</button>
|
||||
</NavbarItem>
|
||||
<NavbarMenuToggle aria-label={isMenuOpen ? "Close menu" : "Open menu"} className="ml-2" />
|
||||
<NavbarItem className="w-10 h-full">
|
||||
<NavbarMenuToggle
|
||||
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
|
||||
className="w-full h-full pt-1"
|
||||
/>
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarContent className="hidden sm:flex basis-1/5 sm:basis-full" justify="end">
|
||||
<NavbarItem className="hidden sm:flex">
|
||||
<Link isExternal aria-label="Twitter" className="p-1" href={siteConfig.links.twitter}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Twitter"
|
||||
className="p-1"
|
||||
href={siteConfig.links.twitter}
|
||||
onPress={() => handlePressNavbarItem("Twitter", siteConfig.links.twitter)}
|
||||
>
|
||||
<TwitterIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link isExternal aria-label="Discord" className="p-1" href={siteConfig.links.discord}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Discord"
|
||||
className="p-1"
|
||||
href={siteConfig.links.discord}
|
||||
onPress={() => handlePressNavbarItem("Discord", siteConfig.links.discord)}
|
||||
>
|
||||
<DiscordIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link isExternal aria-label="Github" className="p-1" href={siteConfig.links.github}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href={siteConfig.links.github}
|
||||
onPress={() => handlePressNavbarItem("Github", siteConfig.links.github)}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<ThemeSwitch />
|
||||
@@ -270,18 +339,19 @@ export const Navbar: FC<NavbarProps> = ({children, routes, slug, tag}) => {
|
||||
<HeartFilledIcon className="text-danger group-data-[hover=true]:animate-heartbeat" />
|
||||
}
|
||||
variant="flat"
|
||||
onPress={() => handlePressNavbarItem("Sponsor", siteConfig.links.sponsor)}
|
||||
>
|
||||
Sponsor
|
||||
</Button>
|
||||
</NavbarItem>{" "}
|
||||
</NavbarItem>
|
||||
<NavbarMenuToggle
|
||||
aria-label={isMenuOpen ? "Close menu" : "Open menu"}
|
||||
className="hidden sm:flex lg:hidden"
|
||||
className="hidden sm:flex lg:hidden ml-4"
|
||||
/>
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarMenu>
|
||||
<DocsSidebar className="mt-4" routes={routes} slug={slug} tag={tag} />
|
||||
<DocsSidebar className="mt-4" routes={[...mobileRoutes, ...routes]} slug={slug} tag={tag} />
|
||||
{children}
|
||||
</NavbarMenu>
|
||||
</NextUINavbar>
|
||||
|
||||
@@ -17,9 +17,9 @@ export const title = tv({
|
||||
foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]",
|
||||
},
|
||||
size: {
|
||||
sm: "text-3xl lg:text-4xl",
|
||||
md: "text-[2.5rem] lg:text-5xl",
|
||||
lg: "text-4xl lg:text-6xl",
|
||||
sm: "text-2xl lg:text-4xl",
|
||||
md: "text-[2.1rem] lg:text-5xl",
|
||||
lg: "text-3xl lg:text-6xl",
|
||||
},
|
||||
fullWidth: {
|
||||
true: "w-full block",
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import React from "react";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {Tooltip, Link, Button} from "@nextui-org/react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {capitalize, last} from "lodash";
|
||||
|
||||
import {BugIcon} from "@/components/icons";
|
||||
import {ISSUE_REPORT_URL} from "@/libs/github/constants";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const BugReportButton = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
const componentTitle = capitalize(last(pathname?.split("/")));
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("BugReportButton - Sandpack", {
|
||||
name: "sandpack - bug report",
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: `${ISSUE_REPORT_URL}${componentTitle}`,
|
||||
});
|
||||
|
||||
window.open(`${ISSUE_REPORT_URL}${componentTitle}`, "_blank");
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
className="text-xs px-2"
|
||||
@@ -19,15 +31,7 @@ export const BugReportButton = () => {
|
||||
placement="top"
|
||||
radius="md"
|
||||
>
|
||||
<Button
|
||||
isExternal
|
||||
isIconOnly
|
||||
as={Link}
|
||||
href={`${ISSUE_REPORT_URL}${componentTitle}`}
|
||||
size="sm"
|
||||
title="Report a bug"
|
||||
variant="light"
|
||||
>
|
||||
<Button isIconOnly size="sm" title="Report a bug" variant="light" onPress={handlePress}>
|
||||
<BugIcon className="text-white dark:text-zinc-500" height={16} width={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {Language} from "prism-react-renderer";
|
||||
import {HighlightedLines} from "./types";
|
||||
import {Decorators} from "./types";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {Codeblock} from "@/components/docs/components";
|
||||
|
||||
export interface CodeViewerProps {
|
||||
@@ -83,6 +84,13 @@ export const SandpackCodeViewer = React.forwardRef<any, CodeViewerProps>(
|
||||
const handleExpand = () => {
|
||||
const nextIsExpanded = !isExpanded;
|
||||
|
||||
trackEvent("CodeViewer - Expand", {
|
||||
name: activeFile,
|
||||
action: "expand",
|
||||
category: "docs",
|
||||
data: nextIsExpanded ? "expanded" : "collapsed",
|
||||
});
|
||||
|
||||
setIsExpanded(nextIsExpanded);
|
||||
if (containerRef && containerRef?.current !== null) {
|
||||
const container = containerRef?.current;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React from "react";
|
||||
import {UnstyledOpenInCodeSandboxButton} from "@codesandbox/sandpack-react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {useSandpack} from "@codesandbox/sandpack-react";
|
||||
|
||||
import {CodeSandboxIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const CodeSandboxButton = () => {
|
||||
const {sandpack} = useSandpack();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
className="text-xs px-2"
|
||||
@@ -13,7 +17,20 @@ export const CodeSandboxButton = () => {
|
||||
placement="top"
|
||||
radius="md"
|
||||
>
|
||||
<Button isIconOnly as="span" size="sm" title="Open in CodeSandbox" variant="light">
|
||||
<Button
|
||||
isIconOnly
|
||||
as="span"
|
||||
size="sm"
|
||||
title="Open in CodeSandbox"
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
trackEvent("CodeSandboxButton - Sandpack", {
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: sandpack.files[sandpack.activeFile],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<UnstyledOpenInCodeSandboxButton
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {useSandpack} from "@codesandbox/sandpack-react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {CopyLinearIcon} from "@/components/icons";
|
||||
|
||||
export const CopyButton = () => {
|
||||
@@ -14,6 +15,13 @@ export const CopyButton = () => {
|
||||
const code = sandpack.files[sandpack.activeFile].code;
|
||||
|
||||
copy(code);
|
||||
|
||||
trackEvent("CopyButton - Sandpack", {
|
||||
name: "sandpack - copy code",
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: sandpack.activeFile,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const getFileEntry = (theme: string) => `
|
||||
export const rootFile = `
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { NextUIProvider } from "@nextui-org/react";
|
||||
@@ -8,13 +8,26 @@ import "./styles.css";
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<div className="w-screen h-screen ${theme} text-foreground bg-background p-8 flex items-start justify-center">
|
||||
<div className="w-screen h-screen p-8 flex items-start justify-center">
|
||||
<App />
|
||||
</div>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
);`;
|
||||
|
||||
export const getHtmlFile = (theme: string, entryFile: string) => `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body class="${theme} text-foreground bg-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/${entryFile}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
export const tailwindConfig = `const { nextui } = require("@nextui-org/react");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Tabs, Tab} from "@nextui-org/react";
|
||||
import {SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
|
||||
|
||||
import {TypescriptIcon, JavascriptIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
interface Props {
|
||||
template: SandpackPredefinedTemplate;
|
||||
@@ -20,6 +21,12 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onC
|
||||
setSelectedTemplate(newTemplate);
|
||||
|
||||
setTimeout(() => {
|
||||
trackEvent("LanguageSelector - Selection", {
|
||||
name: "template",
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: newTemplate ?? "",
|
||||
});
|
||||
onChange?.(newTemplate);
|
||||
}, 250);
|
||||
}, [template, onChange]);
|
||||
|
||||
@@ -26,6 +26,7 @@ export const Sandpack: FC<SandpackProps> = ({
|
||||
files: filesProp,
|
||||
template,
|
||||
highlightedLines,
|
||||
typescriptStrict = false,
|
||||
showPreview = false,
|
||||
showEditor = true,
|
||||
defaultExpanded = false,
|
||||
@@ -41,6 +42,7 @@ export const Sandpack: FC<SandpackProps> = ({
|
||||
useSandpack({
|
||||
files: filesProp,
|
||||
template,
|
||||
typescriptStrict,
|
||||
highlightedLines,
|
||||
});
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ export const nextuiTheme: SandpackTheme = {
|
||||
},
|
||||
font: {
|
||||
body: "Inter var",
|
||||
mono:
|
||||
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
size: "14px",
|
||||
lineHeight: "1.5rem",
|
||||
},
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import {useMemo} from "react";
|
||||
import {SandpackFiles, SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
|
||||
import {useTheme} from "next-themes";
|
||||
import {useLocalStorage} from "usehooks-ts";
|
||||
|
||||
import {HighlightedLines} from "./types";
|
||||
import {getHighlightedLines, getFileName} from "./utils";
|
||||
import {getFileEntry, stylesConfig, postcssConfig, tailwindConfig} from "./entries";
|
||||
|
||||
import {useLocalStorage} from "@/hooks/use-local-storage";
|
||||
import {stylesConfig, postcssConfig, tailwindConfig, getHtmlFile, rootFile} from "./entries";
|
||||
|
||||
export interface UseSandpackProps {
|
||||
files?: SandpackFiles;
|
||||
typescriptStrict?: boolean;
|
||||
template?: SandpackPredefinedTemplate;
|
||||
highlightedLines?: HighlightedLines;
|
||||
}
|
||||
|
||||
const importReact = 'import React from "react";';
|
||||
const importAllReact = 'import * as React from "react";';
|
||||
|
||||
export const useSandpack = ({
|
||||
files = {},
|
||||
typescriptStrict = false,
|
||||
template = "vite-react",
|
||||
highlightedLines,
|
||||
}: UseSandpackProps) => {
|
||||
@@ -57,6 +57,12 @@ export const useSandpack = ({
|
||||
if (key.includes("App") && !key.includes(mimeType)) {
|
||||
return acc;
|
||||
}
|
||||
if (typescriptStrict && currentTemplate === "vite-react-ts" && key.includes(".js")) {
|
||||
return acc;
|
||||
}
|
||||
if (currentTemplate === "vite-react" && key.includes(".ts")) {
|
||||
return acc;
|
||||
}
|
||||
// @ts-ignore
|
||||
acc[key] = files[key];
|
||||
|
||||
@@ -65,7 +71,7 @@ export const useSandpack = ({
|
||||
|
||||
let dependencies = {
|
||||
"framer-motion": "10.12.16",
|
||||
"@nextui-org/react": "dev-v2",
|
||||
"@nextui-org/react": "latest",
|
||||
};
|
||||
|
||||
// sort files by dependency
|
||||
@@ -76,6 +82,11 @@ export const useSandpack = ({
|
||||
const aName = getFileName(a);
|
||||
const bName = getFileName(b);
|
||||
|
||||
// if bName includes "App" should be first
|
||||
if (bName.includes("App")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (aFile?.includes(bName)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -89,7 +100,7 @@ export const useSandpack = ({
|
||||
let fileContent = files[key] as string;
|
||||
|
||||
// Check if the file content includes 'React' import statements, if not, add it
|
||||
if (!fileContent.includes(importReact) && !fileContent.includes(importAllReact)) {
|
||||
if (!fileContent.includes("from 'react'") && !fileContent.includes('from "react"')) {
|
||||
fileContent = `${importReact}\n${fileContent}\n`;
|
||||
}
|
||||
|
||||
@@ -173,7 +184,11 @@ export const useSandpack = ({
|
||||
files: {
|
||||
...sortedFiles,
|
||||
[entryFile]: {
|
||||
code: getFileEntry(theme ?? "light"),
|
||||
code: rootFile,
|
||||
hidden: true,
|
||||
},
|
||||
"index.html": {
|
||||
code: getHtmlFile(theme ?? "light", entryFile),
|
||||
hidden: true,
|
||||
},
|
||||
"tailwind.config.js": {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import {cn} from "@nextui-org/react";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({className, children, ...props}, ref) => {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] pb-28">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
);
|
||||
});
|
||||
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({className, orientation = "vertical", ...props}, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" && "h-2.5 border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
orientation={orientation}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
className={cn(
|
||||
"relative rounded-full bg-default-400/50",
|
||||
orientation === "vertical" && "flex-1",
|
||||
)}
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export {ScrollArea, ScrollBar};
|
||||
@@ -8,6 +8,7 @@ import {clsx} from "@nextui-org/shared-utils";
|
||||
import {useIsSSR} from "@react-aria/ssr";
|
||||
|
||||
import {SunFilledIcon, MoonFilledIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface ThemeSwitchProps {
|
||||
className?: string;
|
||||
@@ -20,6 +21,12 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
|
||||
const onChange = () => {
|
||||
theme === "light" ? setTheme("dark") : setTheme("light");
|
||||
|
||||
trackEvent("ThemeChange", {
|
||||
action: "click",
|
||||
category: "theme",
|
||||
data: theme === "light" ? "dark" : "light",
|
||||
});
|
||||
};
|
||||
|
||||
const {Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps} = useSwitch({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "@nextui-org/react";
|
||||
|
||||
import {VercelIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const VercelCallout: React.FC<unknown> = () => {
|
||||
return (
|
||||
@@ -9,6 +12,13 @@ export const VercelCallout: React.FC<unknown> = () => {
|
||||
isExternal
|
||||
className="flex justify-end items-center gap-2 text-foreground"
|
||||
href="https://www.vercel.com?utm_source=nextui&utm_marketing=oss"
|
||||
onClick={() => {
|
||||
trackEvent("VercelCallout", {
|
||||
name: "vercel callout",
|
||||
action: "click",
|
||||
category: "footer",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="font-normal">Deployed on</p>
|
||||
<VercelIcon className="text-black dark:text-white" height={18} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {DM_Sans} from "next/font/google";
|
||||
import {Inter} from "next/font/google";
|
||||
|
||||
export const fontSans = DM_Sans({
|
||||
export const fontSans = Inter({
|
||||
variable: "--font-sans",
|
||||
adjustFontFallback: true,
|
||||
display: "optional",
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
"keywords": "design principles, nextui",
|
||||
"path": "/docs/guide/design-principles.mdx"
|
||||
},
|
||||
{
|
||||
"key": "routing",
|
||||
"title": "Routing",
|
||||
"keywords": "client side routing, routing, browser routing, nextui, next.js router, react router, remix router",
|
||||
"path": "/docs/guide/routing.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "upgrade-to-v2",
|
||||
"title": "Upgrade to v2",
|
||||
@@ -130,6 +137,13 @@
|
||||
"keywords": "accordion, collapse, expandable sections, content hiding",
|
||||
"path": "/docs/components/accordion.mdx"
|
||||
},
|
||||
{
|
||||
"key": "autocomplete",
|
||||
"title": "Autocomplete",
|
||||
"keywords": "autocomplete, auto suggest, search, typeahead",
|
||||
"path": "/docs/components/autocomplete.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "badge",
|
||||
"title": "Badge",
|
||||
@@ -142,6 +156,13 @@
|
||||
"keywords": "button, interactive, action trigger, click events",
|
||||
"path": "/docs/components/button.mdx"
|
||||
},
|
||||
{
|
||||
"key": "breadcrumbs",
|
||||
"title": "Breadcrumbs",
|
||||
"keywords": "breadcrumbs, navigation, path, trail, location",
|
||||
"path": "/docs/components/breadcrumbs.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "card",
|
||||
"title": "Card",
|
||||
@@ -164,8 +185,7 @@
|
||||
"key": "chip",
|
||||
"title": "Chip",
|
||||
"keywords": "chip, tag, label, small actionable entity",
|
||||
"path": "/docs/components/chip.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/chip.mdx"
|
||||
},
|
||||
{
|
||||
"key": "circular-progress",
|
||||
@@ -183,14 +203,14 @@
|
||||
"key": "divider",
|
||||
"title": "Divider",
|
||||
"keywords": "divider, boundary, separator, section divider",
|
||||
"path": "/docs/components/divider.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/divider.mdx"
|
||||
},
|
||||
{
|
||||
"key": "dropdown",
|
||||
"title": "Dropdown",
|
||||
"keywords": "dropdown, menu, selection, option list",
|
||||
"path": "/docs/components/dropdown.mdx"
|
||||
"path": "/docs/components/dropdown.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
@@ -208,14 +228,21 @@
|
||||
"key": "kbd",
|
||||
"title": "Kbd",
|
||||
"keywords": "keyboard input, shortcut, keys, user input display",
|
||||
"path": "/docs/components/kbd.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/kbd.mdx"
|
||||
},
|
||||
{
|
||||
"key": "link",
|
||||
"title": "Link",
|
||||
"keywords": "link, navigation, href, web page connection",
|
||||
"path": "/docs/components/link.mdx"
|
||||
"path": "/docs/components/link.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "listbox",
|
||||
"title": "Listbox",
|
||||
"keywords": "listbox, selection, option list, multiple choice",
|
||||
"path": "/docs/components/listbox.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "modal",
|
||||
@@ -253,19 +280,29 @@
|
||||
"keywords": "radio group, selection set, option selection, exclusive choices",
|
||||
"path": "/docs/components/radio-group.mdx"
|
||||
},
|
||||
{
|
||||
"key": "select",
|
||||
"title": "Select",
|
||||
"keywords": "select, selection, option list, multiple choice",
|
||||
"path": "/docs/components/select.mdx"
|
||||
},
|
||||
{
|
||||
"key": "skeleton",
|
||||
"title": "Skeleton",
|
||||
"keywords": "skeleton, loading state, placeholder, content preview",
|
||||
"path": "/docs/components/skeleton.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/skeleton.mdx"
|
||||
},
|
||||
{
|
||||
"key": "snippet",
|
||||
"title": "Snippet",
|
||||
"keywords": "snippet, code block, programming, code example",
|
||||
"path": "/docs/components/snippet.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/snippet.mdx"
|
||||
},
|
||||
{
|
||||
"key": "scroll-shadow",
|
||||
"title": "Scroll Shadow",
|
||||
"keywords": "scroll shadow, scroll indicator, scroll bar, scroll position",
|
||||
"path": "/docs/components/scroll-shadow.mdx"
|
||||
},
|
||||
{
|
||||
"key": "spacer",
|
||||
@@ -285,6 +322,13 @@
|
||||
"keywords": "switch, toggle, binary input, on/off control",
|
||||
"path": "/docs/components/switch.mdx"
|
||||
},
|
||||
{
|
||||
"key": "slider",
|
||||
"title": "Slider",
|
||||
"keywords": "slider, range input, value selector, sliding control",
|
||||
"path": "/docs/components/slider.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "table",
|
||||
"title": "Table",
|
||||
@@ -296,13 +340,14 @@
|
||||
"title": "Tabs",
|
||||
"keywords": "tabs, section navigation, categorized content, tabbed interface",
|
||||
"path": "/docs/components/tabs.mdx",
|
||||
"newPost": true
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "textarea",
|
||||
"title": "Textarea",
|
||||
"keywords": "textarea, multi-line text input, large text field, form control",
|
||||
"path": "/docs/components/textarea.mdx"
|
||||
"path": "/docs/components/textarea.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "tooltip",
|
||||
@@ -318,5 +363,39 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mobileRoutes": [
|
||||
{
|
||||
"key": "getting-started",
|
||||
"title": "Getting Started",
|
||||
"defaultOpen": true,
|
||||
"keywords": "getting started, main routes, blog, docs, components, nextui",
|
||||
"routes": [
|
||||
{
|
||||
"key": "docs",
|
||||
"title": "Documentation",
|
||||
"keywords": "documentation, guide, nextui",
|
||||
"path": "/docs/guide/introduction.mdx"
|
||||
},
|
||||
{
|
||||
"key": "components",
|
||||
"title": "Components",
|
||||
"keywords": "components, nextui",
|
||||
"path": "/docs/components/avatar"
|
||||
},
|
||||
{
|
||||
"key": "blog",
|
||||
"title": "Blog",
|
||||
"keywords": "blog, nextui, news, updates",
|
||||
"path": "/blog"
|
||||
},
|
||||
{
|
||||
"key": "figma",
|
||||
"title": "Figma",
|
||||
"keywords": "figma, nextui, design, ui kit",
|
||||
"path": "/figma"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+3181
-1204
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,9 @@ export const siteConfig = {
|
||||
name: "NextUI - Beautiful, fast and modern React UI Library",
|
||||
description: "Make beautiful websites regardless of your design experience.",
|
||||
ogImage: "https://nextui.org/twitter-cards/nextui.jpeg",
|
||||
author: "Junior Garcia",
|
||||
email: "jrgarciadev@gmail.com",
|
||||
siteUrl: "https://nextui.org",
|
||||
creator: "@getnextui",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
title: "Introducing NextUI Version 2"
|
||||
description: "Discover NextUI v2. TailwindCSS integration, enhanced theming, improved animations, and more."
|
||||
date: "2023-07-29"
|
||||
title: "Introducing NextUI Version 2.0"
|
||||
description: "Discover NextUI v2.0 TailwindCSS integration, enhanced theming, improved animations, and more."
|
||||
date: "2023-07-31"
|
||||
image: "/blog/nextuiv2.jpg"
|
||||
tags: ["nextui", "tailwindcss", "react", "nextjs", "react-server-components"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
@@ -24,18 +25,18 @@ towards achieving that.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Transition to TailwindCSS](#transition-to-tailwindcss) - Our shift to TailwindCSS.
|
||||
- [Transition to TailwindCSS](#transition-from-stitches-to-tailwindcss) - Our shift to TailwindCSS.
|
||||
- [TailwindCSS Plugin](#tailwindcss-plugin) - Introducing theme customization plugin.
|
||||
- [React Server Components](#react-server-components) - Enhanced performance with React Server Components.
|
||||
- [Custom Themes, Layouts, Colors](#custom-themes-layouts-colors) - New customizability options.
|
||||
- [React Server Components](#react-server-components-support) - Enhanced performance with React Server Components.
|
||||
- [Custom Themes, Layouts, Colors](#custom-themes-layouts-and-colors) - New customizability options.
|
||||
- [Enhanced Documentation](#enhanced-documentation) - A better navigation experience.
|
||||
- [New Templates & Guides](#new-templates-guides) - Easier app creation process.
|
||||
- [New Templates & Guides](#new-templates-and-guides) - Easier app creation process.
|
||||
- [Custom Variants](#custom-variants) - Customization of components' variants.
|
||||
- [Enhanced Styling Model](#enhanced-styling-model) - Effortless component styling.
|
||||
- [Tailwind Variants](#tailwind-variants) - Our new library for better TailwindCSS handling.
|
||||
- [Revamped Components](#revamped-components) - Rewritten components with react-aria hooks.
|
||||
- [Optimized Animations](#optimized-animations) - Smoother app animations.
|
||||
- [Individual Packages](#individual-packages) - Install only what you need.
|
||||
- [Individual Packages](#individual-packages-support) - Install only what you need.
|
||||
- [Dark Mode Support](#dark-mode-support) - Easier implementation of dark mode.
|
||||
- [New Components](#new-components) - Six new components added.
|
||||
- [Figma Community File](#figma-community-file) - Streamlining design-to-development process.
|
||||
@@ -85,8 +86,8 @@ Thanks to the switch to TailwindCSS, **NextUI v2.0** now supports React Server C
|
||||
improves performance and allows you to use it with the latest versions of [React](https://reactjs.org/) and
|
||||
[Next.js](https://nextjs.org/).
|
||||
|
||||
NextUI components already inclue the `use client;` directive so you can import them
|
||||
server components.
|
||||
NextUI components already include the `use client;` directive so you can import them directly
|
||||
in your React Server Components (RSC).
|
||||
|
||||
```jsx
|
||||
// app/page.tsx
|
||||
@@ -144,7 +145,8 @@ links at the top of each component documentation, such as the component storyboo
|
||||
the source code and styles source code.
|
||||
|
||||
<VideoInView
|
||||
src="https://nextui-public-resources.s3.amazonaws.com/new-docs.mp4"
|
||||
src="https://d2u8k2ocievbld.cloudfront.net/new-docs.mp4"
|
||||
poster="https://d2u8k2ocievbld.cloudfront.net/new-docs-poster.png"
|
||||
width={700}
|
||||
height={350}
|
||||
/>
|
||||
@@ -167,21 +169,23 @@ installation guides, are introduced to aid your app creation process.
|
||||
We've added a new function, `extendVariants`, that allows you to customize any NextUI components's variants.
|
||||
|
||||
<VideoInView
|
||||
src="https://nextui-public-resources.s3.amazonaws.com/custom-variants.mp4"
|
||||
src="https://d2u8k2ocievbld.cloudfront.net/custom-variants.mp4"
|
||||
poster="https://d2u8k2ocievbld.cloudfront.net/custom-variants-poster.png"
|
||||
width={700}
|
||||
height={350}
|
||||
/>
|
||||
|
||||
You can create or override the component `variants`, `defaultVariants` and `componentVariants`.
|
||||
You can create or override the component `variants`, `defaultVariants` and `compoundVariants`.
|
||||
|
||||
> Check out the [Custom Variants](/docs/customization/variants) documentation for more information.
|
||||
> Check out the [Custom Variants](/docs/customization/custom-variants) documentation for more information.
|
||||
|
||||
### Enhanced Styling Model
|
||||
|
||||
With the new slots model, you can style every part of your components effortlessly.
|
||||
|
||||
<VideoInView
|
||||
src="https://nextui-public-resources.s3.amazonaws.com/override-styles.mp4"
|
||||
src="https://d2u8k2ocievbld.cloudfront.net/override-styles.mp4"
|
||||
poster="https://d2u8k2ocievbld.cloudfront.net/override-styles-poster.png"
|
||||
width={700}
|
||||
height={350}
|
||||
/>
|
||||
@@ -206,7 +210,8 @@ Thanks to [framer-motion](https://www.framer.com/motion/), we've significantly i
|
||||
app feel more fluid and smooth.
|
||||
|
||||
<VideoInView
|
||||
src="https://nextui-public-resources.s3.amazonaws.com/dropdown-animation.mp4"
|
||||
src="https://d2u8k2ocievbld.cloudfront.net/dropdown-animation.mp4"
|
||||
poster="https://d2u8k2ocievbld.cloudfront.net/dropdown-animation-poster.png"
|
||||
width={700}
|
||||
height={350}
|
||||
/>
|
||||
@@ -251,7 +256,8 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
```
|
||||
|
||||
<VideoInView
|
||||
src="https://nextui-public-resources.s3.amazonaws.com/dark-mode.mp4"
|
||||
src="https://d2u8k2ocievbld.cloudfront.net/dark-mode.mp4"
|
||||
poster="https://d2u8k2ocievbld.cloudfront.net/dark-mode-poster.png"
|
||||
width={700}
|
||||
height={350}
|
||||
/>
|
||||
@@ -280,7 +286,7 @@ file helps streamline the process of transforming design ideas into functioning
|
||||
</Blockquote>
|
||||
|
||||
<iframe
|
||||
height="450"
|
||||
height="600"
|
||||
width="800"
|
||||
src="https://embed.figma.com/file/1267584376234254760/hf_embed?community_viewer=true&embed_host=nextui"
|
||||
className="aspect-video w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
@@ -289,7 +295,12 @@ file helps streamline the process of transforming design ideas into functioning
|
||||
<Spacer y={10} />
|
||||
|
||||
<Button color="default" variant="bordered">
|
||||
<Link isExternal className="text-current" href="https://www.figma.com/community/file/1267584376234254760">
|
||||
<Link
|
||||
isExternal
|
||||
showAnchorIcon
|
||||
className="text-current"
|
||||
href="https://www.figma.com/community/file/1267584376234254760"
|
||||
>
|
||||
Open in Figma
|
||||
</Link>
|
||||
</Button>
|
||||
@@ -299,3 +310,16 @@ file helps streamline the process of transforming design ideas into functioning
|
||||
To start using **NextUI v2.0**, head over to our [installation guide](/docs/guide/installation).
|
||||
|
||||
We can't wait to see the amazing things you'll build with **NextUI v2.0**!
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
We're excited to see the community adopt NextUI, raise issues, and provide feedback.
|
||||
Whether it's a feature request, bug report, or a project to showcase, please get involved!
|
||||
|
||||
<Community />
|
||||
|
||||
## Contributing
|
||||
|
||||
PR's on NextUI are always welcome, please see our [contribution guidelines](https://github.com/nextui-org/nextui/blob/main/CONTRIBUTING.MD) to learn how you can contribute to this project.
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
title: "New components v2.1.0 🎉"
|
||||
description: "NextUI v2.1.0 is here with new components, Select, Listbox and ScrollShadow."
|
||||
date: "2023-08-21"
|
||||
image: "/blog/v2.1.0.jpg"
|
||||
tags: ["nextui", "select", "listbox", "scroll-shadow", "multi-select"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
import {selectContent} from "@/content/components/select";
|
||||
import {listboxContent} from "@/content/components/listbox";
|
||||
import {scrollShadowContent} from "@/content/components/scroll-shadow";
|
||||
|
||||
|
||||
<img
|
||||
src="/blog/v2.1.0.jpg"
|
||||
width={700}
|
||||
height={350}
|
||||
alt="NextUI v2"
|
||||
className="w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
/>
|
||||
|
||||
We are thrilled to announce the latest update to NextUI, version **2.1.0**! This release introduces some game-changing
|
||||
additions that many of you have eagerly been waiting for.
|
||||
|
||||
First on the list is the highly-anticipated **Select** component. Fully customizable and beautifully designed, supports both single and
|
||||
multi-select modes and is accessible out of the box.
|
||||
|
||||
But that's not all. We're also rolling out two more incredible components **Listbox** and **ScrollShadow**. The new
|
||||
**Listbox** allows you to make list manipulations more efficient and visually appealing. Meanwhile, the
|
||||
**ScrollShadow** component adds an elegant shadow effect to scrollable areas, enhancing the UI aesthetics while
|
||||
also improving usability.
|
||||
|
||||
## Select
|
||||
|
||||
Creating a select component that is both accessible and customizable is a challenging task. We've spent a lot of time
|
||||
researching and testing different approaches to come up with a solution that works for everyone. The result is a
|
||||
component that is easy to use, fully accessible, and highly customizable.
|
||||
|
||||
The new **Select** component includes:
|
||||
|
||||
- Support for selecting a single option.
|
||||
- Support for selecting multiple options.
|
||||
- Support for disabled options.
|
||||
- Support for sections.
|
||||
- Labeling support for accessibility.
|
||||
- Exposed to assistive technology as a button with a listbox popup using ARIA (combined with [Listbox](/docs/components/listbox)).
|
||||
- Support for description and error message help text linked to the input via ARIA.
|
||||
- Support for mouse, touch, and keyboard interactions.
|
||||
- Tab stop focus management.
|
||||
- Asynchronous options loading.
|
||||
- Keyboard support for opening the listbox using the arrow keys, including automatically focusing the first or last item accordingly.
|
||||
- Typeahead to allow selecting options by typing text, even without opening the listbox.
|
||||
- Browser autofill integration via a hidden native `<select>` element.
|
||||
- Support for mobile form navigation via software keyboard.
|
||||
- Mobile screen reader listbox dismissal support.
|
||||
- And much more...
|
||||
|
||||
### Single Select
|
||||
|
||||
The single select component is used to select a single option from a list of options. It is a combination of a button
|
||||
and a listbox. The button displays the currently selected option and the listbox displays the available options.
|
||||
|
||||
<CodeDemo title="Usage" files={selectContent.usage} />
|
||||
|
||||
### Multiple Select
|
||||
|
||||
The multiple select component can be used to select multiple options from a list of options.
|
||||
|
||||
You only need to pass the `selectionMode="multiple"` prop to the `Select` component.
|
||||
|
||||
<CodeDemo title="Multiple Selection" files={selectContent.multiple} />
|
||||
|
||||
### Multiple Variants
|
||||
|
||||
The select component comes with multiple variants.
|
||||
|
||||
<CodeDemo title="Variants" files={selectContent.variants} />
|
||||
|
||||
|
||||
### Chips Support
|
||||
|
||||
The select component is flexible and allows you to render any component as an option and as a selected option.
|
||||
|
||||
<CodeDemo title="Multiple Selection with Chips" files={selectContent.multipleWithChips} />
|
||||
|
||||
|
||||
### Customizable
|
||||
|
||||
The select component is highly customizable, you can customize the selected option, the options, the listbox,
|
||||
the popover and the scrollable area.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={selectContent.customStyles} />
|
||||
|
||||
|
||||
Go to the [Select](/docs/components/select) component page to learn more about sizes, colors, and more.
|
||||
|
||||
|
||||
## Listbox
|
||||
|
||||
The listbox component allows you to make list manipulations more efficient and visually appealing.
|
||||
|
||||
The new **Listbox** component includes:
|
||||
|
||||
- Support for single, multiple, or no selection.
|
||||
- Exposed to assistive technology as a `listbox` using ARIA.
|
||||
- Support for disabled items.
|
||||
- Support for sections.
|
||||
- Labeling support for accessibility.
|
||||
- Support for mouse, touch, and keyboard interactions.
|
||||
- Tab stop focus management.
|
||||
- Keyboard navigation support including arrow keys, home/end, page up/down, select all, and clear.
|
||||
- Automatic scrolling support during keyboard navigation.
|
||||
- Typeahead to allow focusing options by typing text.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={listboxContent.usage} />
|
||||
|
||||
### Custom Styles
|
||||
|
||||
The Listbox components offers multiple customization options.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={listboxContent.customStyles} />
|
||||
|
||||
> **Note**: In the above example, we've utilized the [Boxicons](https://boxicons.com/) icons collection.
|
||||
|
||||
Go to the [Listbox](/docs/components/listbox) component page to learn more about it.
|
||||
|
||||
|
||||
## ScrollShadow
|
||||
|
||||
The ScrollShadow component gives a nice shadow effect to scrollable areas. These shadows are handled by using
|
||||
the CSS `mask-image` property, which makes the shadows adapt to the background color.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={scrollShadowContent.usage} />
|
||||
|
||||
You can hide the scrollbars, customize the shadows size, change the orientation, and more.
|
||||
|
||||
Go to the [ScrollShadow](/docs/components/scroll-shadow) component page to learn more about it.
|
||||
|
||||
|
||||
<Spacer y={6}/>
|
||||
|
||||
We hope you enjoy these new components and the new features. We're excited to see what you build with them!
|
||||
|
||||
Thanks for reading and happy coding! 🚀
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
We're excited to see the community adopt NextUI, raise issues, and provide feedback.
|
||||
Whether it's a feature request, bug report, or a project to showcase, please get involved!
|
||||
|
||||
<Community />
|
||||
|
||||
## Contributing
|
||||
|
||||
PR's on NextUI are always welcome, please see our [contribution guidelines](https://github.com/nextui-org/nextui/blob/main/CONTRIBUTING.MD) to learn how you can contribute to this project.
|
||||
@@ -0,0 +1,443 @@
|
||||
---
|
||||
title: "Introducing v2.2.0 🚀"
|
||||
description: "NextUI v2.2.0 is here! Dive into client-side router support, discover 3 new components including the Autocomplete, and more."
|
||||
date: "2023-11-03"
|
||||
image: "/blog/v2.2.0.jpg"
|
||||
tags: ["nextui", "autocomplete", "breadcrumbs", "client side router", "slider"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
import {autocompleteContent} from "@/content/components/autocomplete";
|
||||
import {sliderContent} from "@/content/components/slider";
|
||||
import {breadcrumbsContent} from "@/content/components/breadcrumbs";
|
||||
|
||||
<img
|
||||
src="/blog/v2.2.0_2x.jpg"
|
||||
width={700}
|
||||
height={350}
|
||||
alt="NextUI v2.2.0"
|
||||
className="w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
/>
|
||||
|
||||
We are excited to announce the latest update to NextUI, version **2.2.0**! This release introduces 3 new components,
|
||||
support for client-side routing, and more.
|
||||
|
||||
## What's New in v2.2.0?
|
||||
|
||||
- [Client Side Routing](#client-side-routing) - Allows you to seamlessly incorporate client-side routers.
|
||||
- [Autocomplete](/docs/components/autocomplete) - Combines a text input with a listbox, allowing users to filter a list of options.
|
||||
- [Slider](/docs/components/slider) - Allows users to make selections from a range of values.
|
||||
- [Breadcrumbs](/docs/components/breadcrumbs) - Displays a hierarchy of links to the current page or resource in an application.
|
||||
- [Other Changes](#other-changes) - Includes styling improvements, accessibility and usability enhancements.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Client Side Routing
|
||||
|
||||
By default, links perform native browser navigation when they are interacted with. However, many apps and
|
||||
frameworks use client side routers to avoid a full page reload when navigating between pages.
|
||||
|
||||
NextUI now natively supports client-side routing in components such as [Link](/docs/components/link), [Tabs](/docs/components/tabs),
|
||||
[Breadcrumbs](/docs/components/breadcrumbs), [Listbox](/docs/components/listbox), [Dropdown](/docs/components/dropdown) and many others offering
|
||||
the flexibility to be rendered as HTML links, allowing you to seamlessly incorporate client-side routers. See the [Routing](/docs/guide/routing) guide to
|
||||
learn how it set it up in your app.
|
||||
|
||||
The `NextUIProvider` component configures all NextUI components within it to navigate using the
|
||||
client side router you provide.
|
||||
|
||||
> **Note**: Client side routing is based on [React Aria Routing](https://react-spectrum.adobe.com/react-aria/routing.html).
|
||||
|
||||
### Next.js Example
|
||||
|
||||
- App Router
|
||||
|
||||
Go to your `app/providers.tsx` or `app/providers.jsx` (create it if it doesn't exist) and add the
|
||||
`useRouter` hook from `next/navigation`, it returns a router object that can be used to perform navigation.
|
||||
|
||||
```tsx
|
||||
// app/providers.tsx
|
||||
"use client";
|
||||
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
|
||||
export function Providers({children}: {children: React.ReactNode}) {
|
||||
const router = useRouter();
|
||||
|
||||
return <NextUIProvider navigate={router.push}>{children}</NextUIProvider>;
|
||||
}
|
||||
```
|
||||
|
||||
- Pages Router
|
||||
|
||||
Go to pages`/_app.js` or `pages/_app.tsx` (create it if it doesn't exist) and add the`useRouter` hook
|
||||
from `next/router`, it returns a router object that can be used to perform navigation.
|
||||
|
||||
```tsx
|
||||
// pages/_app.tsx
|
||||
import type {AppProps} from "next/app";
|
||||
import {useRouter} from "next/router";
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<Component {...pageProps} />
|
||||
</NextUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
```
|
||||
|
||||
- Usage
|
||||
|
||||
Now that you have set up the `NextUIProvider` in your app, you can use the `href` prop in the `Tabs`,
|
||||
`Listbox` and `Dropdown` items to navigate between pages.
|
||||
|
||||
The [Link](/docs/components/link) component will also use the `navigate` function from the
|
||||
`NextUIProvider` to navigate between pages.
|
||||
|
||||
```jsx
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
Listbox,
|
||||
ListboxItem,
|
||||
Dropdown,
|
||||
DropdownTrigger,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
Button,
|
||||
Link,
|
||||
} from "@nextui-org/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Tabs aria-label="Navigation">
|
||||
<Tab key="home" href="/home">
|
||||
Home
|
||||
</Tab>
|
||||
<Tab key="about" href="/about">
|
||||
About
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Listbox aria-label="Navigation">
|
||||
<ListboxItem key="home" href="/home">
|
||||
Home
|
||||
</ListboxItem>
|
||||
<ListboxItem key="about" href="/about">
|
||||
About
|
||||
</ListboxItem>
|
||||
</Listbox>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button>Open</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Navigation">
|
||||
<DropdownItem key="home" href="/home">
|
||||
Home
|
||||
</DropdownItem>
|
||||
<DropdownItem key="about" href="/about">
|
||||
About
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<Link href="/home">Home</Link>
|
||||
<Link href="/about">About</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Autocomplete
|
||||
|
||||
Creating an autocomplete component that is both accessible and customizable is a challenging task. We've spent a lot of time
|
||||
researching and testing different approaches to come up with a solution that works for everyone. The result is a
|
||||
component that is easy to use, fully accessible, and highly customizable.
|
||||
|
||||
The new **Autocomplete** component includes:
|
||||
|
||||
- Support for selecting a single option.
|
||||
- Support for disabled options.
|
||||
- Support for groups of items in sections.
|
||||
- Support for filtering a list of options by typing.
|
||||
- Support for controlled and uncontrolled options, selection and input value.
|
||||
- Support for custom filter functions.
|
||||
- Async loading and infinite scrolling support.
|
||||
- Required and invalid states exposed to assistive technology via ARIA.
|
||||
- Support for description and error message help text linked to the input via ARIA.
|
||||
- And much more...
|
||||
|
||||
We recommend to read this [Blog post](https://react-spectrum.adobe.com/blog/building-a-combobox.html) from [react-aria](https://react-spectrum.adobe.com/react-aria) team to learn more
|
||||
about the Autocomplete component.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={autocompleteContent.usage} />
|
||||
|
||||
### Custom Items
|
||||
|
||||
You can customize the autocomplete items by modifying the `AutocompleteItem` children.
|
||||
|
||||
<CodeDemo title="Custom Items" files={autocompleteContent.customItems} />
|
||||
|
||||
### Customizable
|
||||
|
||||
The autocomplete component is highly customizable, you can customize the popover, listbox and
|
||||
input components.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={autocompleteContent.customStyles} />
|
||||
|
||||
Go to the [Autocomplete](/docs/components/autocomplete) component page to learn more about it.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Slider
|
||||
|
||||
The **Slider** component allows users to make selections from a range of values. It is a great way to allow users to
|
||||
select a value from a fixed range, such as volume or temperature, or to select a value from a range that changes
|
||||
frequently, such as stock prices or budgets.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={sliderContent.usage} />
|
||||
|
||||
### Range Support
|
||||
|
||||
If you pass an array of values to the `value` prop or to the `defaultValue` prop, the slider will become a range slider.
|
||||
|
||||
<CodeDemo title="Range Slider" files={sliderContent.range} />
|
||||
|
||||
### With Tooltip
|
||||
|
||||
The slider component also supports a tooltip to display the current value.
|
||||
|
||||
<CodeDemo title="With Tooltip" files={sliderContent.tooltip} />
|
||||
|
||||
### Slider steps
|
||||
|
||||
You can use the `showSteps` prop to display small dots on each step.
|
||||
|
||||
<CodeDemo title="With Visible Steps" files={sliderContent.visibleSteps} />
|
||||
|
||||
### Slider Custom Styles
|
||||
|
||||
You can customize the `Slider` component by passing custom Tailwind CSS classes to the component slots.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={sliderContent.customStyles} />
|
||||
|
||||
Go to the [Slider](/docs/components/slider) component page to learn more about it.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
The **Breadcrumbs** is an essential component for navigating hierarchical content. It displays a hierarchy of links to the
|
||||
current page or resource in an application.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={breadcrumbsContent.usage} />
|
||||
|
||||
### Breadcrumbs as menu
|
||||
|
||||
It is possible to use the `Breadcrumbs` component as a horizontal menu. This is useful when you want to display a list
|
||||
of possible navigations and let the user choose one of them.
|
||||
|
||||
<CodeDemo title="Menu Type" files={breadcrumbsContent.menuType} />
|
||||
|
||||
### Start & End Content support
|
||||
|
||||
You can add any element to the start or end of the breadcrumbs by using the `startContent` and `endContent` props. The
|
||||
above example uses the `startContent` prop to add icons to the start of the breadcrumbs.
|
||||
|
||||
<CodeDemo title="Start & End Content" files={breadcrumbsContent.startEndContent} />
|
||||
|
||||
### Collapsing Items support
|
||||
|
||||
The `Breadcrumbs` component supports collapsing items, it is useful when you have a lot of items and you want to
|
||||
collapse them into a dropdown menu.
|
||||
|
||||
<CodeDemo title="Customizing the Ellipsis Item" files={breadcrumbsContent.customizingEllipsis} />
|
||||
|
||||
### Apllying custom styles
|
||||
|
||||
You can customize the `Breadcrumbs` component by passing custom Tailwind CSS classes to the component slots.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={breadcrumbsContent.customStyles} />
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
Go to the [Breadcrumbs](/docs/components/breadcrumbs) component page to learn more about it.
|
||||
|
||||
## Other Changes
|
||||
|
||||
- Styling Improvements:
|
||||
|
||||
- Transitioned spacing units from pixels (px) to rem units to optimize mobile component sizing.
|
||||
- Introduced a new `shouldSelectOnPressUp` property for `Tabs/Tab` with a default value of true, allowing tab selection on press-up instead of press-down.
|
||||
- Updated Chip component's font size to text-tiny for the sm size variant.
|
||||
- Enhanced the Button component to display only the spinner during loading when it contains an icon.
|
||||
|
||||
- Accessibility and Usability Enhancements:
|
||||
|
||||
- Resolved Popover component's open state issues for blur/opaque backdrops.
|
||||
- Enhanced the ScrollShadow API, introducing visibility and onVisibilityChange properties to manage shadow visibility.
|
||||
- Added `emptyContent`, `topContent`, and `bottomContent` properties to Listbox/Menu/Dropdown for customized content rendering.
|
||||
- Introduced baseRef to the Input component, allowing control over the parent element's reference.
|
||||
- Upgraded tailwind-variants to version `0.1.18`, incorporating slot props control.
|
||||
|
||||
- Right-to-Left (RTL) Support:
|
||||
|
||||
- Implemented RTL support for Accordion,
|
||||
- Implemented RTL support for Accordion Avatar & AvatarGroup components,
|
||||
- Implemented RTL support for ButtonGroup.
|
||||
|
||||
- Custom Implementations and Fixes:
|
||||
|
||||
- Implemented a custom `usePress` hook to address the corner press issue, with a corresponding pull request and issue submitted to the react-aria project.
|
||||
- Applied the custom `usePress` across all NextUI interactive components for consistent behavior.
|
||||
- Improved animations and positioning for Input & Select labels.
|
||||
- Upgraded `react-aria` packages and `framer-motion` for enhanced performance.
|
||||
- Enhanced TypeScript support for generic items in Menu and DropdownMenu.
|
||||
- Streamlined package dependencies to exclude globally or individually installed packages, reducing bundle sizes.
|
||||
|
||||
- Visual and Interactive Tweaks:
|
||||
|
||||
- Removed the outline on Input focus-visible for a cleaner aesthetic.
|
||||
- Fixed the radius property issue in `ButtonGroup`.
|
||||
- Introduced the `isActive` prop for `NavbarMenuItem` for active state management.
|
||||
- Corrected the Pagination custom-items example by adding the key prop.
|
||||
- Enabled Collection Items support within the `extendVariants` function.
|
||||
- Added transitions to menu/listbox items for smoother interaction.
|
||||
- Added a `disableAutosize` property to `Textarea` to control auto-resizing.
|
||||
- Resolved styling issues in Textarea and animated height adjustments for a fluid user experience.
|
||||
- Included a `hoverOpacity` key in the themes object within the plugin for customized hover effects.
|
||||
- Implemented a hover effect for the Button component to enhance user interaction feedback.
|
||||
- The padding of the CardBody has been updated from `p-5` to `p-3` for consistency with the padding of other Card elements.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Unfortunately, we had to make some small styles breaking changes in this release to improve the [Popover](/docs/components/popover) arrow
|
||||
dynamic positioning. Instead of having a specific slot for the popover arrow, we now use the `before` pseudo element, this allows the
|
||||
popover to also move the arrow altogether.
|
||||
|
||||
This change impacts the Popover, Dropdown, Tooltip and Select implementations.
|
||||
|
||||
Popover changes:
|
||||
|
||||
```diff-jsx
|
||||
<Popover
|
||||
showArrow
|
||||
backdrop="opaque"
|
||||
placement="right"
|
||||
classNames={{
|
||||
- base: "py-3 px-4 border border-default-200 bg-gradient-to-br from-white to-default-300 dark:from-default-100 dark:to-default-50",
|
||||
- arrow: "bg-default-200",
|
||||
+ base: [
|
||||
+ // the "before" pseudo element is now the popover' arrow
|
||||
+ "before:bg-default-200"
|
||||
+ ],
|
||||
+ content: [ // now we need to use the "content" slot to actually modify the popover' content styles
|
||||
+ "py-3 px-4 border border-default-200",
|
||||
+ "bg-gradient-to-br from-white to-default-300",
|
||||
+ "dark:from-default-100 dark:to-default-50",
|
||||
],
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Popover>
|
||||
```
|
||||
|
||||
Tooltip changes:
|
||||
|
||||
```diff-jsx
|
||||
<Tooltip
|
||||
showArrow
|
||||
placement="right"
|
||||
content="I am a tooltip"
|
||||
classNames={{
|
||||
- base: "py-2 px-4 shadow-xl text-black bg-gradient-to-br from-white to-neutral-400",
|
||||
- arrow: "bg-neutral-400 dark:bg-white",
|
||||
+ base: [
|
||||
+ // the "before" pseudo element is now the popover' arrow
|
||||
+ "before:bg-neutral-400 dark:before:bg-white",
|
||||
+ ],
|
||||
+ content: [ // now we need to use the "content" slot to actually modify the popover' content styles
|
||||
+ "py-2 px-4 shadow-xl",
|
||||
+ "text-black bg-gradient-to-br from-white to-neutral-400",
|
||||
+ ],
|
||||
}}
|
||||
>
|
||||
<Button variant="flat">Hover me</Button>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
Select changes:
|
||||
|
||||
```diff-jsx
|
||||
<Select
|
||||
items={users}
|
||||
label="Assigned to"
|
||||
className="max-w-xs"
|
||||
variant="bordered"
|
||||
popoverProps={{
|
||||
classNames: {
|
||||
- base: "p-0 border-small border-divider bg-background",
|
||||
- arrow: "bg-default-200",
|
||||
+ base: "before:bg-default-200", // the before pseudo element controls the popover's arrow
|
||||
+ content: "p-0 border-small border-divider bg-background", // now instead of the "base" slot we use the "content" slot
|
||||
},
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Select>
|
||||
);
|
||||
}`;
|
||||
```
|
||||
|
||||
Dropdown Changes
|
||||
|
||||
```diff-jsx
|
||||
<Dropdown
|
||||
showArrow
|
||||
classNames={{
|
||||
- base: "py-1 px-1 border border-default-200 bg-gradient-to-br from-white to-default-200 dark:from-default-50 dark:to-black",
|
||||
- arrow: "bg-default-200",
|
||||
+ content: "py-1 px-1 border border-default-200 bg-gradient-to-br from-white to-default-200 dark:from-default-50 dark:to-black", // now instead of the "base" slot we use the "content"
|
||||
+ base: "before:bg-default-200", // the before pseudo element controls the popover's arrow
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Dropdown>
|
||||
```
|
||||
|
||||
<Spacer y={6} />
|
||||
|
||||
We hope you enjoy these new components and the new features. We're excited to see what you build with them!
|
||||
|
||||
Thanks for reading and happy coding! 🚀
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
We're excited to see the community adopt NextUI, raise issues, and provide feedback.
|
||||
Whether it's a feature request, bug report, or a project to showcase, please get involved!
|
||||
|
||||
<Community />
|
||||
|
||||
## Contributing
|
||||
|
||||
PR's on NextUI are always welcome, please see our [contribution guidelines](https://github.com/nextui-org/nextui/blob/main/CONTRIBUTING.MD) to learn how you can contribute to this project.
|
||||
@@ -0,0 +1,89 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
export default function App() {
|
||||
let list = useAsyncList({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(\`https://swapi.py4e.com/api/people/?search=\${filterText}\`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
height: string;
|
||||
mass: string;
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(\`https://swapi.py4e.com/api/people/?search=\${filterText}\`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
const usePokemonListTs = `export type Pokemon = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type UsePokemonListProps = {
|
||||
/** Delay to wait before fetching more items */
|
||||
fetchDelay?: number;
|
||||
};
|
||||
|
||||
export function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
|
||||
const [items, setItems] = React.useState<Pokemon[]>([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset: number) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const usePokemonList = `export function usePokemonList({fetchDelay = 0} = {}) {
|
||||
const [items, setItems] = React.useState([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
};`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
import {usePokemonList} from "./usePokemonList";
|
||||
|
||||
export default function App() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
|
||||
|
||||
const [, scrollerRef] = useInfiniteScroll({
|
||||
hasMore,
|
||||
isEnabled: isOpen,
|
||||
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
|
||||
onLoadMore,
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
variant="bordered"
|
||||
isLoading={isLoading}
|
||||
defaultItems={items}
|
||||
label="Pick a Pokemon"
|
||||
placeholder="Select a Pokemon"
|
||||
scrollRef={scrollerRef}
|
||||
selectionMode="single"
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/usePokemonList.js": usePokemonList,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": App,
|
||||
"/usePokemonList.ts": usePokemonListTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const colors = [
|
||||
"default",
|
||||
"primary",
|
||||
"secondary",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-row flex-wrap gap-4">
|
||||
{colors.map((color) => (
|
||||
<Autocomplete
|
||||
key={color}
|
||||
color={color}
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey={"cat"}
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState<React.Key>("cat");
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
selectedKey={value}
|
||||
onSelectionChange={setValue}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="text-default-500 text-small">Selected: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState("cat");
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
defaultItems={animals}
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
selectedKey={value}
|
||||
onSelectionChange={setValue}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="text-default-500 text-small">Selected: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const myFilter = (textValue, inputValue) => {
|
||||
if (inputValue.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize both strings so we can slice safely
|
||||
// take into account the ignorePunctuation option as well...
|
||||
textValue = textValue.normalize("NFC").toLocaleLowerCase();
|
||||
inputValue = inputValue.normalize("NFC").toLocaleLowerCase();
|
||||
|
||||
return textValue.slice(0, inputValue.length) === inputValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
allowsCustomValue
|
||||
className="max-w-xs"
|
||||
defaultFilter={myFilter}
|
||||
defaultItems={animals}
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const myFilter = (textValue: string, inputValue: string) => {
|
||||
if (inputValue.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize both strings so we can slice safely
|
||||
// take into account the ignorePunctuation option as well...
|
||||
textValue = textValue.normalize("NFC").toLocaleLowerCase();
|
||||
inputValue = inputValue.normalize("NFC").toLocaleLowerCase();
|
||||
|
||||
return textValue.slice(0, inputValue.length) === inputValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
defaultFilter={myFilter}
|
||||
defaultItems={animals}
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
const data = `export const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar} from "@nextui-org/react";
|
||||
import {users} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
defaultItems={users}
|
||||
variant="bordered"
|
||||
label="Assigned to"
|
||||
placeholder="Select a user"
|
||||
labelPlacement="inside"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(user) => (
|
||||
<AutocompleteItem key={user.id} textValue={user.name}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={user.name} className="flex-shrink-0" size="sm" src={user.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{user.name}</span>
|
||||
<span className="text-tiny text-default-400">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem, AutocompleteSection} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const headingClasses = "flex w-full sticky top-1 z-20 py-1.5 px-2 bg-default-100 shadow-small rounded-small";
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
scrollShadowProps={{
|
||||
isEnabled: false,
|
||||
}}
|
||||
>
|
||||
<AutocompleteSection
|
||||
title="Mammals"
|
||||
classNames={{
|
||||
heading: headingClasses,
|
||||
}}
|
||||
>
|
||||
<AutocompleteItem key="Lion">Lion</AutocompleteItem>
|
||||
<AutocompleteItem key="Tiger">Tiger</AutocompleteItem>
|
||||
<AutocompleteItem key="Elephant">Elephant</AutocompleteItem>
|
||||
<AutocompleteItem key="Kangaroo">Kangaroo</AutocompleteItem>
|
||||
<AutocompleteItem key="Panda">Panda</AutocompleteItem>
|
||||
<AutocompleteItem key="Giraffe">Giraffe</AutocompleteItem>
|
||||
<AutocompleteItem key="Zebra">Zebra</AutocompleteItem>
|
||||
<AutocompleteItem key="Cheetah">Cheetah</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
<AutocompleteSection
|
||||
title="Birds"
|
||||
classNames={{
|
||||
heading: headingClasses,
|
||||
}}
|
||||
>
|
||||
<AutocompleteItem key="Eagle">Eagle</AutocompleteItem>
|
||||
<AutocompleteItem key="Parrot">Parrot</AutocompleteItem>
|
||||
<AutocompleteItem key="Penguin">Penguin</AutocompleteItem>
|
||||
<AutocompleteItem key="Ostrich">Ostrich</AutocompleteItem>
|
||||
<AutocompleteItem key="Peacock">Peacock</AutocompleteItem>
|
||||
<AutocompleteItem key="Swan">Swan</AutocompleteItem>
|
||||
<AutocompleteItem key="Falcon">Falcon</AutocompleteItem>
|
||||
<AutocompleteItem key="Flamingo">Flamingo</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const SelectorIcon = `export const SelectorIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" stroke="none" />
|
||||
<path d="M8 9l4 -4l4 4" />
|
||||
<path d="M16 15l-4 4l-4 -4" />
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {SelectorIcon} from "./SelectorIcon";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultItems={animals}
|
||||
labelPlacement="outside"
|
||||
className="max-w-xs"
|
||||
disableSelectorIconRotation
|
||||
selectorIcon={<SelectorIcon />}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
"/SelectorIcon.jsx": SelectorIcon,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,319 @@
|
||||
const data = `export const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];`;
|
||||
|
||||
const SearchIcon = `export const SearchIcon = ({
|
||||
size = 24,
|
||||
strokeWidth = 1.5,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={height || size}
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width={width || size}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<path
|
||||
d="M22 22L20 20"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar, Button} from "@nextui-org/react";
|
||||
import {SearchIcon} from "./SearchIcon";
|
||||
import {users} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
classNames={{
|
||||
base: "max-w-xs",
|
||||
listboxWrapper: "max-h-[320px]",
|
||||
selectorButton: "text-default-500"
|
||||
}}
|
||||
defaultItems={users}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
input: "ml-1",
|
||||
inputWrapper: "h-[48px]",
|
||||
},
|
||||
}}
|
||||
listboxProps={{
|
||||
hideSelectedIcon: true,
|
||||
itemClasses: {
|
||||
base: [
|
||||
"rounded-medium",
|
||||
"text-default-500",
|
||||
"transition-opacity",
|
||||
"data-[hover=true]:text-foreground",
|
||||
"dark:data-[hover=true]:bg-default-50",
|
||||
"data-[pressed=true]:opacity-70",
|
||||
"data-[hover=true]:bg-default-200",
|
||||
"data-[selectable=true]:focus:bg-default-100",
|
||||
"data-[focus-visible=true]:ring-default-500",
|
||||
],
|
||||
},
|
||||
}}
|
||||
aria-label="Select an employee"
|
||||
placeholder="Enter employee name"
|
||||
popoverProps={{
|
||||
offset: 10,
|
||||
classNames: {
|
||||
base: "rounded-large",
|
||||
content: "p-1 border-small border-default-100 bg-background",
|
||||
},
|
||||
}}
|
||||
startContent={<SearchIcon className="text-default-400" strokeWidth={2.5} size={20} />}
|
||||
radius="full"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.id} textValue={item.name}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={item.name} className="flex-shrink-0" size="sm" src={item.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{item.name}</span>
|
||||
<span className="text-tiny text-default-400">{item.team}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="border-small mr-0.5 font-medium shadow-small"
|
||||
radius="full"
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/SearchIcon.jsx": SearchIcon,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
allowsCustomValue
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
className="max-w-xs"
|
||||
defaultItems={animals}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
description="The second most popular pet in the world"
|
||||
defaultItems={animals}
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultItems={animals}
|
||||
disabledKeys={["zebra", "tiger", "lion", "elephant", "crocodile", "whale"]}
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
isDisabled
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(animal) => <AutocompleteItem key={animal.value}>{animal.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState("");
|
||||
const [touched, setTouched] = React.useState(false);
|
||||
|
||||
const isValid = value === "cat";
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
description="The second most popular pet in the world"
|
||||
errorMessage={isValid || !touched ? "" : "You must select a cat"}
|
||||
isInvalid={isValid || !touched ? false : true}
|
||||
defaultItems={animals}
|
||||
selectedKey={value}
|
||||
className="max-w-xs"
|
||||
onSelectionChange={setValue}
|
||||
onClose={() => setTouched(true)}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState('');
|
||||
const [selectedKey, setSelectedKey] = React.useState(null);
|
||||
|
||||
const onSelectionChange = (id) => {
|
||||
setSelectedKey(id);
|
||||
};
|
||||
|
||||
const onInputChange = (value) => {
|
||||
setValue(value)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Autocomplete
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
defaultItems={animals}
|
||||
className="max-w-xs"
|
||||
allowsCustomValue={true}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="mt-1 text-small text-default-500">Current selected animal: {selectedKey}</p>
|
||||
<p className="text-small text-default-500">Current input text: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState<string>('');
|
||||
const [selectedKey, setSelectedKey] = React.useState<React.Key | null>(null);
|
||||
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setSelectedKey(key);
|
||||
};
|
||||
|
||||
const onInputChange = (value: string) => {
|
||||
setValue(value)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Autocomplete
|
||||
label="Search an animal"
|
||||
variant="underlined"
|
||||
defaultItems={animals}
|
||||
className="max-w-xs"
|
||||
allowsCustomValue={true}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="mt-1 text-small text-default-500">Current selected animal: {selectedKey}</p>
|
||||
<p className="text-small text-default-500">Current input text: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
// Store Autocomplete input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen, menuTrigger) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem, MenuTriggerAction} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {animals} from "./data";
|
||||
|
||||
type FieldState = {
|
||||
selectedKey: React.Key | null;
|
||||
inputValue: string;
|
||||
items: typeof animals;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
// Store Autocomplete input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState<FieldState>({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value: string) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen: boolean, menuTrigger: MenuTriggerAction) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import usage from "./usage";
|
||||
import dynamic from "./dynamic";
|
||||
import disabled from "./disabled";
|
||||
import disabledItems from "./disabled-items";
|
||||
import required from "./required";
|
||||
import sizes from "./sizes";
|
||||
import colors from "./colors";
|
||||
import variants from "./variants";
|
||||
import labelPlacements from "./label-placements";
|
||||
import startContent from "./start-content";
|
||||
import itemStartContent from "./item-start-content";
|
||||
import customValue from "./custom-value";
|
||||
import customSelectorIcon from "./custom-selector-icon";
|
||||
import withoutScrollShadow from "./without-scroll-shadow";
|
||||
import description from "./description";
|
||||
import errorMessage from "./error-message";
|
||||
import events from "./events";
|
||||
import controlled from "./controlled";
|
||||
import fullyControlled from "./fully-controlled";
|
||||
import customItems from "./custom-items";
|
||||
import customFiltering from "./custom-filtering";
|
||||
import asyncFiltering from "./async-filtering";
|
||||
import asyncLoadingItems from "./async-loading-items";
|
||||
import sections from "./sections";
|
||||
import customSectionsStyle from "./custom-sections-style";
|
||||
import customStyles from "./custom-styles";
|
||||
|
||||
export const autocompleteContent = {
|
||||
usage,
|
||||
dynamic,
|
||||
disabled,
|
||||
disabledItems,
|
||||
required,
|
||||
sizes,
|
||||
colors,
|
||||
variants,
|
||||
labelPlacements,
|
||||
startContent,
|
||||
customValue,
|
||||
itemStartContent,
|
||||
customSelectorIcon,
|
||||
withoutScrollShadow,
|
||||
description,
|
||||
errorMessage,
|
||||
events,
|
||||
controlled,
|
||||
fullyControlled,
|
||||
customItems,
|
||||
customFiltering,
|
||||
asyncFiltering,
|
||||
asyncLoadingItems,
|
||||
sections,
|
||||
customSectionsStyle,
|
||||
customStyles,
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
label="Select country"
|
||||
>
|
||||
<AutocompleteItem
|
||||
key="argentina"
|
||||
startContent={<Avatar alt="Argentina" className="w-6 h-6" src="https://flagcdn.com/ar.svg" />}
|
||||
>
|
||||
Argentina
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="venezuela"
|
||||
startContent={<Avatar alt="Venezuela" className="w-6 h-6" src="https://flagcdn.com/ve.svg" />}
|
||||
>
|
||||
Venezuela
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="brazil"
|
||||
startContent={<Avatar alt="Brazil" className="w-6 h-6" src="https://flagcdn.com/br.svg" />}
|
||||
>
|
||||
Brazil
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="switzerland"
|
||||
startContent={
|
||||
<Avatar alt="Switzerland" className="w-6 h-6" src="https://flagcdn.com/ch.svg" />
|
||||
}
|
||||
>
|
||||
Switzerland
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="germany"
|
||||
startContent={<Avatar alt="Germany" className="w-6 h-6" src="https://flagcdn.com/de.svg" />}
|
||||
>
|
||||
Germany
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="spain"
|
||||
startContent={<Avatar alt="Spain" className="w-6 h-6" src="https://flagcdn.com/es.svg" />}
|
||||
>
|
||||
Spain
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="france"
|
||||
startContent={<Avatar alt="France" className="w-6 h-6" src="https://flagcdn.com/fr.svg" />}
|
||||
>
|
||||
France
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="italy"
|
||||
startContent={<Avatar alt="Italy" className="w-6 h-6" src="https://flagcdn.com/it.svg" />}
|
||||
>
|
||||
Italy
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="mexico"
|
||||
startContent={<Avatar alt="Mexico" className="w-6 h-6" src="https://flagcdn.com/mx.svg" />}
|
||||
>
|
||||
Mexico
|
||||
</AutocompleteItem>
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user