Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac94e47e67 | |||
| 551ab18406 | |||
| f397546090 | |||
| 914df9b3ad | |||
| 7453632ba5 | |||
| ea6dfeb29e | |||
| 867eae14ac | |||
| 88782fc6b4 | |||
| 6d352749c4 | |||
| 2f33c65b2b | |||
| 935124cb79 | |||
| b1f79d4310 | |||
| 1d39358bd5 | |||
| d6eee4a876 | |||
| 3a10d5ff52 | |||
| 71e2d8c601 | |||
| bbb436b89b | |||
| 648489e51b | |||
| 59095f1a66 | |||
| 7b59bbf71b | |||
| 05d546e29a | |||
| 366056647a | |||
| 3dbb0f2136 | |||
| b9e3e03859 | |||
| bc6437762c | |||
| 7cd74ade2f | |||
| bc35e137c1 | |||
| 36ee869515 | |||
| ca288d9b3d | |||
| 70080de816 | |||
| edead49a9e | |||
| fcf849595f | |||
| 6bd0301cf7 | |||
| 89f51b555e | |||
| 6c0213dfc8 | |||
| 59b88d070c | |||
| 27f5139281 | |||
| e1480504de | |||
| 26e478dd93 | |||
| adbd85ee95 | |||
| 1855ba4390 | |||
| 62b77e6823 | |||
| 729ede8de7 | |||
| d6ae2cb0dd | |||
| 8e1a9c9a92 | |||
| e8b34cdaa3 | |||
| 0b60eb2adc | |||
| c5a4b422ef | |||
| af0d20d807 | |||
| e8bfdc8524 | |||
| 6bc616caea | |||
| 94936df746 | |||
| 1c281f3809 | |||
| 3b37728244 | |||
| 973124902f | |||
| 2d6e57705d | |||
| 44958bf91a | |||
| ffb1e554f7 | |||
| e6f64f3873 | |||
| 1730088234 | |||
| ff94c122c5 | |||
| 2608f83beb | |||
| baec474143 | |||
| 5786897b99 | |||
| a75134cd1e | |||
| 0fac666206 | |||
| d8e73330ab | |||
| a3c25294e3 | |||
| 2c33b8a331 | |||
| cbe4cb1e85 | |||
| 7599c33061 | |||
| 54793bf86a | |||
| 1c46061553 | |||
| 63d07db3ff | |||
| 68f48cdf5f | |||
| 290182df26 | |||
| ccf82df032 | |||
| e1e50fa707 | |||
| 37aea79373 | |||
| 42b4bf88dc | |||
| 9652d7808b | |||
| 3637b16ae5 | |||
| ea04960644 | |||
| e400aa2c73 | |||
| ccf4663c6d | |||
| 00a17f6087 | |||
| b83b11870b | |||
| e28942817a | |||
| 08dfdaac79 | |||
| 7c9017ded9 | |||
| 60f4c69408 | |||
| cc1027ba63 | |||
| 96cc97ac82 | |||
| d88e4856d8 | |||
| 505ed63578 | |||
| 1e5842f03e | |||
| fb34040bf1 | |||
| 603b19c360 | |||
| 7863393edf | |||
| 4e8d90bbfb | |||
| 2cce0e4fb1 | |||
| 791725f85b | |||
| 8ded8f05e3 | |||
| 2810630def | |||
| 86352b5dd0 | |||
| 0f615b98af | |||
| da72ed3219 | |||
| 018c69b6a0 | |||
| 1bb7b43f69 | |||
| fb6addbbda | |||
| 7c743abcc2 | |||
| e2a5013408 | |||
| c110f0d338 | |||
| 43993e40bf | |||
| 744fb49476 | |||
| ad79545d95 | |||
| d4e40ba25f | |||
| d51d3546cd | |||
| 34fb9e51ee | |||
| e3aa9cdef9 | |||
| 34bb098be8 | |||
| 049347c122 | |||
| 95818914c7 | |||
| ce1546c983 | |||
| c6326d169d | |||
| 1091377e4d | |||
| a5cac45619 | |||
| 2aebfcca59 | |||
| 90cb5b14ab | |||
| ad7e2615d3 | |||
| 3f0d81b560 | |||
| 58a77cb6b9 | |||
| ae73de1a61 | |||
| a4a1d8fb69 | |||
| 9f6839faf9 | |||
| 80f6cf5a7b | |||
| 7c2c9c4875 | |||
| cb1b3135bc | |||
| 67ea2f65e1 | |||
| 2d6ae74c36 | |||
| d90ad05b13 | |||
| 9fe0aa79c8 | |||
| 8de427b81a | |||
| 5ac433a905 | |||
| 1c1fd39d37 | |||
| c847ffb2a3 | |||
| 8c8b70d128 | |||
| a0d7141db3 | |||
| ff04a87b6d | |||
| 765dac53cf | |||
| 38a54ab497 | |||
| 8a33eabb25 | |||
| 507887eac3 | |||
| 3bc2490156 | |||
| a2e562b03f | |||
| 8f62613701 | |||
| 0cf91395c7 | |||
| 488a18f525 | |||
| af3c4f706b | |||
| 88c97f6bf3 | |||
| 14cc26fefe | |||
| c0fb1bcc53 | |||
| a07f887971 | |||
| 780ba43162 | |||
| 781b85566e | |||
| 66f1738d9c | |||
| 7134996eab | |||
| 557f10f5ec | |||
| a4ab02006a | |||
| e308198644 | |||
| 5d9f3380cb | |||
| 2e82f165a9 | |||
| 06c4b2f763 | |||
| 606f75726c | |||
| 3f8b63e6e5 | |||
| e10ea001b6 | |||
| bed4a59b8a | |||
| 0834170206 | |||
| 559436d462 |
@@ -6,6 +6,6 @@ contact_links:
|
||||
- name: 💬 Discord Community Chat
|
||||
url: https://discord.gg/9b6yyZKmH4
|
||||
about: Ask quick questions or simply chat on the `NextUI` community Discord server.
|
||||
- name: 💬 New Updates (Twitter)
|
||||
url: https://twitter.com/getnextui
|
||||
about: Link to our twitter account if you want to follow us and stay up to date with NextUI news
|
||||
- name: 💬 New Updates (X)
|
||||
url: https://x.com/getnextui
|
||||
about: Link to our X account if you want to follow us and stay up to date with NextUI news
|
||||
|
||||
@@ -11,15 +11,15 @@ Closes # <!-- Github issue # here -->
|
||||
|
||||
## 📝 Description
|
||||
|
||||
> Add a brief description
|
||||
<!--- Add a brief description -->
|
||||
|
||||
## ⛳️ Current behavior (updates)
|
||||
|
||||
> Please describe the current behavior that you are modifying
|
||||
<!--- Please describe the current behavior that you are modifying -->
|
||||
|
||||
## 🚀 New behavior
|
||||
|
||||
> Please describe the behavior or changes this PR adds
|
||||
<!--- Please describe the behavior or changes this PR adds -->
|
||||
|
||||
## 💣 Is this a breaking change (Yes/No):
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Enter pre-release mode
|
||||
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
enter-pre-release-mode:
|
||||
if: ${{ github.ref == 'refs/heads/beta/release-next' || github.ref == 'refs/heads/canary' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Enter pre-release mode
|
||||
id: enter-pre-release-mode
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GA_ACCESS_TOKEN }}
|
||||
run: |
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
if [ ${{ github.ref }} == 'refs/heads/canary' ]; then
|
||||
pnpm changeset:canary
|
||||
else
|
||||
pnpm changeset:beta
|
||||
fi
|
||||
git add -A
|
||||
git commit -m 'chore(pre-release): enter pre-release mode'
|
||||
git push
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Exit pre-release mode
|
||||
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
exit-pre-release-mode:
|
||||
if: ${{ github.ref == 'refs/heads/beta/release-next' || github.ref == 'refs/heads/canary' }}
|
||||
name: exit pre-release mode
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: remove pre.json
|
||||
# we only remove .changeset/pre.json here
|
||||
# since we want to keep the changeset files introduced in beta/release-next or canary branch
|
||||
# once we merge it to canary / main, those files will be removed in version PR in canary
|
||||
# and converted to corresponding changelogs
|
||||
run: npx rimraf .changeset/pre.json
|
||||
|
||||
- name: Commit and push changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GA_ACCESS_TOKEN }}
|
||||
run: |
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "github-actions[bot]"
|
||||
git add -A
|
||||
git commit -m "ci(changesets): exit pre-release mode"
|
||||
git push
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Pre-release
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".changeset/**"
|
||||
- "packages/**"
|
||||
branches:
|
||||
- "beta/release-next"
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
name: changesets pre-release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Debug condition values
|
||||
run: |
|
||||
echo "pre.json exists: ${{ steps.check_if_pre_json_exists.outputs.files_exists }}"
|
||||
echo "Commit message: ${{ github.event.head_commit.message }}"
|
||||
echo "Contains version packages: ${{ contains(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
echo "Full condition would be: ${{ steps.check_if_pre_json_exists.outputs.files_exists == 'true' && contains(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
|
||||
- name: Check if pre.json exists
|
||||
id: check_if_pre_json_exists
|
||||
uses: andstor/file-existence-action@v3.0.0
|
||||
with:
|
||||
files: ".changeset/pre.json"
|
||||
|
||||
- name: Get pre-release changesets
|
||||
id: get-pre-release-changesets
|
||||
uses: notiz-dev/github-action-json-property@release
|
||||
with:
|
||||
path: ".changeset/pre.json"
|
||||
prop_path: "changesets"
|
||||
|
||||
- name: Create pre-release PR
|
||||
id: create-pre-release-pr
|
||||
if: "${{ steps.check_if_pre_json_exists.outputs.files_exists == 'true' && !startsWith(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm run version
|
||||
title: "ci(changesets): :package: version packages"
|
||||
commit: "ci(changesets): version packages"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Tests
|
||||
if: "${{ steps.check_if_pre_json_exists.outputs.files_exists == 'true' && contains(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
run: pnpm test
|
||||
|
||||
- name: Build
|
||||
if: "${{ steps.check_if_pre_json_exists.outputs.files_exists == 'true' && contains(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
run: pnpm build
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
|
||||
- name: Publish to NPM
|
||||
id: publish-to-npm
|
||||
if: "${{ steps.check_if_pre_json_exists.outputs.files_exists == 'true' && contains(github.event.head_commit.message, 'ci(changesets): version packages') }}"
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: pnpm run release
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -15,6 +15,7 @@ jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
@@ -22,12 +23,12 @@ jobs:
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Create Release Pull Request or Publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
@@ -41,17 +42,9 @@ jobs:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Sync files from canary to main branch if a publish happens
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
https://api.github.com/repos/owner/repo/dispatches \
|
||||
-d '{"event_type":"sync-canary-to-main"}'
|
||||
|
||||
- name: Create canary release
|
||||
if: steps.changesets.outputs.published != 'true'
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
git checkout canary
|
||||
pnpm version:canary
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Sync Canary to Main
|
||||
|
||||
on:
|
||||
# triggered manually in Github
|
||||
workflow_dispatch:
|
||||
# triggered by the type "sync-canary-to-main" (e.g. from release action after publishing)
|
||||
repository_dispatch:
|
||||
types: [sync-canary-to-main]
|
||||
|
||||
jobs:
|
||||
create_pull_request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Ensure all history is fetched
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
source_branch: "canary"
|
||||
destination_branch: "main"
|
||||
pr_title: "Auto-sync Canary to Main"
|
||||
pr_body: |
|
||||
## Automated: Sync from Canary to Main
|
||||
|
||||
This Pull Request is automatically generated to sync the changes from the Canary branch to the Main branch. Below are the included updates:
|
||||
|
||||
### Triggered by a Direct Push to Canary:
|
||||
- Please check the recent commits on the Canary branch directly as this sync may include multiple changes.
|
||||
|
||||
### Triggered by a Pull Request Merge:
|
||||
- Merged Pull Request: [PR#${{ github.event.pull_request.number }}](${{ github.event.pull_request.html_url }}) - ${{ github.event.pull_request.title }}
|
||||
- PR Description: ${{ github.event.pull_request.body }}
|
||||
- Merged by: ${{ github.event.pull_request.merged_by.login }}
|
||||
|
||||
### Action Required:
|
||||
- Please review the changes carefully.
|
||||
- Approve and merge the Pull Request if everything is in order.
|
||||
|
||||
Thank you for maintaining the Main branch updated and clean.
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Update Stats
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs every Monday at 00:00 UTC
|
||||
- cron: '0 0 * * 1'
|
||||
# Allow manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-stats:
|
||||
name: Update Stats
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/common-actions/install
|
||||
|
||||
- name: Update search metadata
|
||||
run: pnpm update:search-meta
|
||||
|
||||
- name: Update GitHub info
|
||||
run: pnpm update:github-info
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: "chore(stats): update project statistics"
|
||||
title: "chore(stats): 📊 Update project statistics"
|
||||
body: |
|
||||
This PR updates the project statistics including:
|
||||
- Search metadata
|
||||
- GitHub repository information
|
||||
|
||||
This is an automated PR generated weekly.
|
||||
branch: chore/update-stats
|
||||
base: main
|
||||
delete-branch: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -56,6 +56,7 @@ packages/**/*.backup.ts
|
||||
|
||||
# ignore sitemap
|
||||
apps/**/sitemap.xml
|
||||
apps/**/sitemap-0.xml
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
huskyDir=$(dirname -- "$0")
|
||||
. "$huskyDir/_/husky.sh"
|
||||
|
||||
. "$huskyDir/scripts/update-dep"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
huskyDir=$(dirname -- "$0")
|
||||
. "$huskyDir/_/husky.sh"
|
||||
|
||||
. "$huskyDir/scripts/update-dep"
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env sh
|
||||
changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
|
||||
|
||||
check_run() {
|
||||
if (echo "$changed_files" | grep --quiet "$1"); then
|
||||
printf "\033[36mDetected changes in pnpm-lock.yaml, starting dependency update\033[0m\n"
|
||||
eval "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
check_run pnpm-lock.yaml "pnpm install --color"
|
||||
Vendored
+1
-1
@@ -10,5 +10,5 @@
|
||||
},
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
@@ -16,4 +16,8 @@ NEXT_PUBLIC_PREVIEW=true/false
|
||||
|
||||
## Featurebase
|
||||
NEXT_PUBLIC_FB_FEEDBACK_ORG=
|
||||
NEXT_PUBLIC_FB_FEEDBACK_URL=
|
||||
NEXT_PUBLIC_FB_FEEDBACK_URL=
|
||||
|
||||
# PostHog
|
||||
NEXT_PUBLIC_POSTHOG_KEY=your-posthog-key
|
||||
NEXT_PUBLIC_POSTHOG_HOST=your-posthog-host
|
||||
@@ -3,7 +3,7 @@
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"parserOptions": {
|
||||
"project": ["apps/docs/tsconfig(.*)?.json"],
|
||||
"ecmaFeatures": {
|
||||
@@ -22,10 +22,6 @@
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {Metadata} from "next";
|
||||
|
||||
import {notFound} from "next/navigation";
|
||||
import {allBlogPosts} from "contentlayer/generated";
|
||||
import {allBlogPosts} from "contentlayer2/generated";
|
||||
import {Link, User} from "@nextui-org/react";
|
||||
import {format, parseISO} from "date-fns";
|
||||
import NextLink from "next/link";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {allBlogPosts} from "contentlayer/generated";
|
||||
import {allBlogPosts} from "contentlayer2/generated";
|
||||
import {compareDesc} from "date-fns";
|
||||
|
||||
import {BlogPostList} from "@/components/blog-post";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {Metadata} from "next";
|
||||
|
||||
import {notFound} from "next/navigation";
|
||||
import {allDocs} from "contentlayer/generated";
|
||||
import {allDocs} from "contentlayer2/generated";
|
||||
import {Link} from "@nextui-org/react";
|
||||
|
||||
import {MDXContent} from "@/components/mdx-content";
|
||||
|
||||
@@ -11,7 +11,7 @@ interface DocsLayoutProps {
|
||||
export default function DocsLayout({children}: DocsLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
|
||||
<main className="relative container mx-auto max-w-8xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="hidden overflow-visible relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
|
||||
<DocsSidebar routes={manifest.routes} />
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Button,
|
||||
Accordion,
|
||||
Tabs,
|
||||
Textarea,
|
||||
Input,
|
||||
Tab,
|
||||
Avatar,
|
||||
Select,
|
||||
SelectItem,
|
||||
AccordionItem,
|
||||
Pagination,
|
||||
extendVariants,
|
||||
PaginationItem,
|
||||
} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useSearchParams} from "next/navigation";
|
||||
|
||||
import {SearchLinearIcon} from "@/components/icons";
|
||||
|
||||
const MyRadioGroup = () => {
|
||||
const [radio, setRadio] = useState("1");
|
||||
|
||||
return (
|
||||
<RadioGroup value={radio} onValueChange={setRadio}>
|
||||
<Radio value="1">Radio 1</Radio>
|
||||
<Radio value="2">Radio 2</Radio>
|
||||
<Radio value="3">Radio 3</Radio>
|
||||
<Radio value="4">Radio 4</Radio>
|
||||
<Radio value="5">Radio 5</Radio>
|
||||
<Radio value="6">Radio 6</Radio>
|
||||
<Radio value="7">Radio 7</Radio>
|
||||
<Radio value="8">Radio 8</Radio>
|
||||
<Radio value="9">Radio 9</Radio>
|
||||
<Radio value="10">Radio 10</Radio>
|
||||
<Radio value="11">Radio 11</Radio>
|
||||
<Radio value="12">Radio 12</Radio>
|
||||
<Radio value="13">Radio 13</Radio>
|
||||
<Radio value="14">Radio 14</Radio>
|
||||
<Radio value="15">Radio 15</Radio>
|
||||
<Radio value="16">Radio 16</Radio>
|
||||
<Radio value="17">Radio 17</Radio>
|
||||
<Radio value="18">Radio 18</Radio>
|
||||
<Radio value="19">Radio 19</Radio>
|
||||
<Radio value="20">Radio 20</Radio>
|
||||
<Radio value="21">Radio 21</Radio>
|
||||
<Radio value="22">Radio 22</Radio>
|
||||
<Radio value="23">Radio 23</Radio>
|
||||
<Radio value="24">Radio 24</Radio>
|
||||
<Radio value="25">Radio 25</Radio>
|
||||
<Radio value="26">Radio 26</Radio>
|
||||
<Radio value="27">Radio 27</Radio>
|
||||
<Radio value="28">Radio 28</Radio>
|
||||
<Radio value="29">Radio 29</Radio>
|
||||
<Radio value="30">Radio 30</Radio>
|
||||
<Radio value="31">Radio 31</Radio>
|
||||
<Radio value="32">Radio 32</Radio>
|
||||
<Radio value="33">Radio 33</Radio>
|
||||
<Radio value="34">Radio 34</Radio>
|
||||
<Radio value="35">Radio 35</Radio>
|
||||
<Radio value="36">Radio 36</Radio>
|
||||
<Radio value="37">Radio 37</Radio>
|
||||
<Radio value="38">Radio 38</Radio>
|
||||
<Radio value="39">Radio 39</Radio>
|
||||
<Radio value="40">Radio 40</Radio>
|
||||
<Radio value="41">Radio 41</Radio>
|
||||
<Radio value="42">Radio 42</Radio>
|
||||
<Radio value="43">Radio 43</Radio>
|
||||
<Radio value="44">Radio 44</Radio>
|
||||
<Radio value="45">Radio 45</Radio>
|
||||
<Radio value="46">Radio 46</Radio>
|
||||
<Radio value="47">Radio 47</Radio>
|
||||
<Radio value="48">Radio 48</Radio>
|
||||
<Radio value="49">Radio 49</Radio>
|
||||
<Radio value="50">Radio 50</Radio>
|
||||
<Radio value="51">Radio 51</Radio>
|
||||
<Radio value="52">Radio 52</Radio>
|
||||
<Radio value="53">Radio 53</Radio>
|
||||
<Radio value="54">Radio 54</Radio>
|
||||
<Radio value="55">Radio 55</Radio>
|
||||
<Radio value="56">Radio 56</Radio>
|
||||
<Radio value="57">Radio 57</Radio>
|
||||
<Radio value="58">Radio 58</Radio>
|
||||
<Radio value="59">Radio 59</Radio>
|
||||
<Radio value="60">Radio 60</Radio>
|
||||
<Radio value="61">Radio 61</Radio>
|
||||
<Radio value="62">Radio 62</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const MyInput = extendVariants(Input, {
|
||||
variants: {
|
||||
color: {
|
||||
stone: {
|
||||
inputWrapper: [
|
||||
"bg-zinc-100",
|
||||
"border",
|
||||
"shadow",
|
||||
"transition-colors",
|
||||
"focus-within:bg-zinc-100",
|
||||
"data-[hover=true]:border-zinc-600",
|
||||
"data-[hover=true]:bg-zinc-100",
|
||||
"group-data-[focus=true]:border-zinc-600",
|
||||
// dark theme
|
||||
"dark:bg-zinc-900",
|
||||
"dark:border-zinc-800",
|
||||
"dark:data-[hover=true]:bg-zinc-900",
|
||||
"dark:focus-within:bg-zinc-900",
|
||||
],
|
||||
input: [
|
||||
"text-zinc-800",
|
||||
"placeholder:text-zinc-600",
|
||||
// dark theme
|
||||
"dark:text-zinc-400",
|
||||
"dark:placeholder:text-zinc-600",
|
||||
],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
xs: {
|
||||
inputWrapper: "h-6 min-h-6 px-1",
|
||||
input: "text-tiny",
|
||||
},
|
||||
md: {
|
||||
inputWrapper: "h-10 min-h-10",
|
||||
input: "text-small",
|
||||
},
|
||||
xl: {
|
||||
inputWrapper: "h-14 min-h-14",
|
||||
input: "text-medium",
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
xs: {
|
||||
inputWrapper: "rounded",
|
||||
},
|
||||
sm: {
|
||||
inputWrapper: "rounded-[4px]",
|
||||
},
|
||||
},
|
||||
textSize: {
|
||||
base: {
|
||||
input: "text-base",
|
||||
},
|
||||
},
|
||||
removeLabel: {
|
||||
true: {
|
||||
label: "hidden",
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "stone",
|
||||
textSize: "base",
|
||||
removeLabel: true,
|
||||
},
|
||||
});
|
||||
|
||||
const MyButton2 = extendVariants(Button, {
|
||||
variants: {
|
||||
color: {
|
||||
foreground:
|
||||
"bg-foreground text-background data-[hover=true]:bg-foreground/90 data-[pressed=true]:bg-foreground/80",
|
||||
},
|
||||
isScalable: {
|
||||
true: "scale-125",
|
||||
false: "",
|
||||
},
|
||||
size: {
|
||||
xl: "size--xl",
|
||||
"2xl": "size--2xl",
|
||||
},
|
||||
mySize: {
|
||||
lg: "px-12 py-6 text-lg",
|
||||
xl: "px-12 py-6 text-xl",
|
||||
},
|
||||
},
|
||||
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>("");
|
||||
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
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
|
||||
incididunt pariatur qui veniam dolor. Exercitation id culpa et enim mollit duis duis
|
||||
aliquip. Magna ullamco est cupidatat laboris irure pariatur aliquip duis aute cillum.
|
||||
Officia irure do laboris ea nisi sunt reprehenderit laboris irure. Ex eiusmod in duis
|
||||
veniam excepteur. Sunt et et laboris culpa. Mollit excepteur occaecat elit anim officia.
|
||||
Laborum commodo proident cupidatat pariatur eu veniam id qui do culpa. Quis consectetur
|
||||
adipisicing anim ex ea velit excepteur. Deserunt laboris ex aute sunt laborum tempor ea
|
||||
enim dolore ut in. Id aliqua Lorem exercitation qui velit nostrud anim reprehenderit enim.
|
||||
Nisi elit fugiat deserunt elit. Sit excepteur ipsum enim excepteur irure irure sint veniam
|
||||
elit consequat ea id. Lorem ea qui sunt enim occaecat excepteur officia ex consequat
|
||||
nostrud. Tempor sint Lorem est culpa do.
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Tabs classNames={{panel: "flex flex-col gap-5"}} variant="underlined">
|
||||
<Tab title="Test 1">
|
||||
<Textarea defaultValue="ASdasd" label="Default value (uncontrolled)" />
|
||||
<Input label="Text B Tab 1" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 1" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
<Tab title="Test 2">
|
||||
<Textarea label="Text B Tab 2" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 2" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
<Tab title="Test 3">
|
||||
<Textarea label="Text B Tab 3" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 3" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<h2>Outside</h2>
|
||||
<Textarea label="Text A" placeholder="Text A" value={textA} onValueChange={setTextA} />
|
||||
<Textarea label="Text B" placeholder="Text B" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C" placeholder="Text C" value={textC} onValueChange={setTextC} />
|
||||
|
||||
<MyRadioGroup />
|
||||
|
||||
<MyInput
|
||||
isClearable
|
||||
placeholder="Search..."
|
||||
radius="md"
|
||||
size="md"
|
||||
startContent={<SearchLinearIcon className="text-zinc-500" size={16} />}
|
||||
/>
|
||||
|
||||
<Button>Click Me!</Button>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -1,561 +1,11 @@
|
||||
"use client";
|
||||
import {Suspense} from "react";
|
||||
|
||||
import {
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Button,
|
||||
Accordion,
|
||||
Tabs,
|
||||
Textarea,
|
||||
Input,
|
||||
Tab,
|
||||
Avatar,
|
||||
Select,
|
||||
SelectItem,
|
||||
AccordionItem,
|
||||
Pagination,
|
||||
extendVariants,
|
||||
PaginationItem,
|
||||
} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useSearchParams} from "next/navigation";
|
||||
|
||||
import {SearchLinearIcon} from "@/components/icons";
|
||||
|
||||
const MyRadioGroup = () => {
|
||||
const [radio, setRadio] = useState("1");
|
||||
import ClientPage from "./client-page";
|
||||
|
||||
export default function PerfPage() {
|
||||
return (
|
||||
<RadioGroup value={radio} onValueChange={setRadio}>
|
||||
<Radio value="1">Radio 1</Radio>
|
||||
<Radio value="2">Radio 2</Radio>
|
||||
<Radio value="3">Radio 3</Radio>
|
||||
<Radio value="4">Radio 4</Radio>
|
||||
<Radio value="5">Radio 5</Radio>
|
||||
<Radio value="6">Radio 6</Radio>
|
||||
<Radio value="7">Radio 7</Radio>
|
||||
<Radio value="8">Radio 8</Radio>
|
||||
<Radio value="9">Radio 9</Radio>
|
||||
<Radio value="10">Radio 10</Radio>
|
||||
<Radio value="11">Radio 11</Radio>
|
||||
<Radio value="12">Radio 12</Radio>
|
||||
<Radio value="13">Radio 13</Radio>
|
||||
<Radio value="14">Radio 14</Radio>
|
||||
<Radio value="15">Radio 15</Radio>
|
||||
<Radio value="16">Radio 16</Radio>
|
||||
<Radio value="17">Radio 17</Radio>
|
||||
<Radio value="18">Radio 18</Radio>
|
||||
<Radio value="19">Radio 19</Radio>
|
||||
<Radio value="20">Radio 20</Radio>
|
||||
<Radio value="21">Radio 21</Radio>
|
||||
<Radio value="22">Radio 22</Radio>
|
||||
<Radio value="23">Radio 23</Radio>
|
||||
<Radio value="24">Radio 24</Radio>
|
||||
<Radio value="25">Radio 25</Radio>
|
||||
<Radio value="26">Radio 26</Radio>
|
||||
<Radio value="27">Radio 27</Radio>
|
||||
<Radio value="28">Radio 28</Radio>
|
||||
<Radio value="29">Radio 29</Radio>
|
||||
<Radio value="30">Radio 30</Radio>
|
||||
<Radio value="31">Radio 31</Radio>
|
||||
<Radio value="32">Radio 32</Radio>
|
||||
<Radio value="33">Radio 33</Radio>
|
||||
<Radio value="34">Radio 34</Radio>
|
||||
<Radio value="35">Radio 35</Radio>
|
||||
<Radio value="36">Radio 36</Radio>
|
||||
<Radio value="37">Radio 37</Radio>
|
||||
<Radio value="38">Radio 38</Radio>
|
||||
<Radio value="39">Radio 39</Radio>
|
||||
<Radio value="40">Radio 40</Radio>
|
||||
<Radio value="41">Radio 41</Radio>
|
||||
<Radio value="42">Radio 42</Radio>
|
||||
<Radio value="43">Radio 43</Radio>
|
||||
<Radio value="44">Radio 44</Radio>
|
||||
<Radio value="45">Radio 45</Radio>
|
||||
<Radio value="46">Radio 46</Radio>
|
||||
<Radio value="47">Radio 47</Radio>
|
||||
<Radio value="48">Radio 48</Radio>
|
||||
<Radio value="49">Radio 49</Radio>
|
||||
<Radio value="50">Radio 50</Radio>
|
||||
<Radio value="51">Radio 51</Radio>
|
||||
<Radio value="52">Radio 52</Radio>
|
||||
<Radio value="53">Radio 53</Radio>
|
||||
<Radio value="54">Radio 54</Radio>
|
||||
<Radio value="55">Radio 55</Radio>
|
||||
<Radio value="56">Radio 56</Radio>
|
||||
<Radio value="57">Radio 57</Radio>
|
||||
<Radio value="58">Radio 58</Radio>
|
||||
<Radio value="59">Radio 59</Radio>
|
||||
<Radio value="60">Radio 60</Radio>
|
||||
<Radio value="61">Radio 61</Radio>
|
||||
<Radio value="62">Radio 62</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const MyInput = extendVariants(Input, {
|
||||
variants: {
|
||||
color: {
|
||||
stone: {
|
||||
inputWrapper: [
|
||||
"bg-zinc-100",
|
||||
"border",
|
||||
"shadow",
|
||||
"transition-colors",
|
||||
"focus-within:bg-zinc-100",
|
||||
"data-[hover=true]:border-zinc-600",
|
||||
"data-[hover=true]:bg-zinc-100",
|
||||
"group-data-[focus=true]:border-zinc-600",
|
||||
// dark theme
|
||||
"dark:bg-zinc-900",
|
||||
"dark:border-zinc-800",
|
||||
"dark:data-[hover=true]:bg-zinc-900",
|
||||
"dark:focus-within:bg-zinc-900",
|
||||
],
|
||||
input: [
|
||||
"text-zinc-800",
|
||||
"placeholder:text-zinc-600",
|
||||
// dark theme
|
||||
"dark:text-zinc-400",
|
||||
"dark:placeholder:text-zinc-600",
|
||||
],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
xs: {
|
||||
inputWrapper: "h-6 min-h-6 px-1",
|
||||
input: "text-tiny",
|
||||
},
|
||||
md: {
|
||||
inputWrapper: "h-10 min-h-10",
|
||||
input: "text-small",
|
||||
},
|
||||
xl: {
|
||||
inputWrapper: "h-14 min-h-14",
|
||||
input: "text-medium",
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
xs: {
|
||||
inputWrapper: "rounded",
|
||||
},
|
||||
sm: {
|
||||
inputWrapper: "rounded-[4px]",
|
||||
},
|
||||
},
|
||||
textSize: {
|
||||
base: {
|
||||
input: "text-base",
|
||||
},
|
||||
},
|
||||
removeLabel: {
|
||||
true: {
|
||||
label: "hidden",
|
||||
},
|
||||
false: {},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "stone",
|
||||
textSize: "base",
|
||||
removeLabel: true,
|
||||
},
|
||||
});
|
||||
|
||||
const MyButton2 = extendVariants(Button, {
|
||||
variants: {
|
||||
color: {
|
||||
foreground:
|
||||
"bg-foreground text-background data-[hover=true]:bg-foreground/90 data-[pressed=true]:bg-foreground/80",
|
||||
},
|
||||
isScalable: {
|
||||
true: "scale-125",
|
||||
false: "",
|
||||
},
|
||||
size: {
|
||||
xl: "size--xl",
|
||||
"2xl": "size--2xl",
|
||||
},
|
||||
mySize: {
|
||||
lg: "px-12 py-6 text-lg",
|
||||
xl: "px-12 py-6 text-xl",
|
||||
},
|
||||
},
|
||||
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>("");
|
||||
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
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
|
||||
incididunt pariatur qui veniam dolor. Exercitation id culpa et enim mollit duis duis
|
||||
aliquip. Magna ullamco est cupidatat laboris irure pariatur aliquip duis aute cillum.
|
||||
Officia irure do laboris ea nisi sunt reprehenderit laboris irure. Ex eiusmod in duis
|
||||
veniam excepteur. Sunt et et laboris culpa. Mollit excepteur occaecat elit anim officia.
|
||||
Laborum commodo proident cupidatat pariatur eu veniam id qui do culpa. Quis consectetur
|
||||
adipisicing anim ex ea velit excepteur. Deserunt laboris ex aute sunt laborum tempor ea
|
||||
enim dolore ut in. Id aliqua Lorem exercitation qui velit nostrud anim reprehenderit enim.
|
||||
Nisi elit fugiat deserunt elit. Sit excepteur ipsum enim excepteur irure irure sint veniam
|
||||
elit consequat ea id. Lorem ea qui sunt enim occaecat excepteur officia ex consequat
|
||||
nostrud. Tempor sint Lorem est culpa do.
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
cupidatat voluptate eu officia. Ut laborum sunt nostrud magna. Ex magna esse cillum enim
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
reprehenderit commodo magna proident anim ipsum ex. Mollit id amet officia nisi excepteur
|
||||
eu. Commodo elit commodo nisi nisi aute eu aliquip aliquip voluptate exercitation ullamco
|
||||
ipsum eiusmod veniam. Magna in laborum anim amet anim ex elit aliqua nostrud mollit.
|
||||
Pariatur ullamco cillum proident aliqua nostrud. Labore ea veniam cillum duis veniam in
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Tabs classNames={{panel: "flex flex-col gap-5"}} variant="underlined">
|
||||
<Tab title="Test 1">
|
||||
<Textarea defaultValue="ASdasd" label="Default value (uncontrolled)" />
|
||||
<Input label="Text B Tab 1" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 1" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
<Tab title="Test 2">
|
||||
<Textarea label="Text B Tab 2" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 2" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
<Tab title="Test 3">
|
||||
<Textarea label="Text B Tab 3" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C Tab 3" value={textC} onValueChange={setTextC} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<h2>Outside</h2>
|
||||
<Textarea label="Text A" placeholder="Text A" value={textA} onValueChange={setTextA} />
|
||||
<Textarea label="Text B" placeholder="Text B" value={textB} onValueChange={setTextB} />
|
||||
<Textarea label="Text C" placeholder="Text C" value={textC} onValueChange={setTextC} />
|
||||
|
||||
<MyRadioGroup />
|
||||
|
||||
<MyInput
|
||||
isClearable
|
||||
placeholder="Search..."
|
||||
radius="md"
|
||||
size="md"
|
||||
startContent={<SearchLinearIcon className="text-zinc-500" size={16} />}
|
||||
/>
|
||||
|
||||
<Button>Click Me!</Button>
|
||||
|
||||
<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>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ClientPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "@nextui-org/react";
|
||||
import {ChevronDownIcon, SearchIcon} from "@nextui-org/shared-icons";
|
||||
import {useCallback, useMemo, useState} from "react";
|
||||
import {capitalize} from "lodash";
|
||||
import {capitalize} from "@nextui-org/shared-utils";
|
||||
|
||||
import {PlusLinearIcon} from "@/components/icons";
|
||||
import {VerticalDotsIcon} from "@/components/icons/vertical-dots";
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "@nextui-org/react";
|
||||
import {ChevronDownIcon, SearchIcon} from "@nextui-org/shared-icons";
|
||||
import {useCallback, useMemo, useState} from "react";
|
||||
import {capitalize} from "lodash";
|
||||
import {capitalize} from "@nextui-org/shared-utils";
|
||||
|
||||
import {PlusLinearIcon} from "@/components/icons";
|
||||
import {VerticalDotsIcon} from "@/components/icons/vertical-dots";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Rss from "rss";
|
||||
import {allBlogPosts} from "contentlayer/generated";
|
||||
import {allBlogPosts} from "contentlayer2/generated";
|
||||
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {allCoreContent} from "@/libs/contentlayer";
|
||||
|
||||
+23
-12
@@ -1,6 +1,6 @@
|
||||
import "@/styles/globals.css";
|
||||
import "@/styles/sandpack.css";
|
||||
import {Metadata} from "next";
|
||||
import {Metadata, Viewport} from "next";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {Providers} from "./providers";
|
||||
@@ -8,7 +8,7 @@ import {Providers} from "./providers";
|
||||
import {Cmdk} from "@/components/cmdk";
|
||||
import manifest from "@/config/routes.json";
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {fontSans} from "@/config/fonts";
|
||||
import {fonts} from "@/config/fonts";
|
||||
import {Navbar} from "@/components/navbar";
|
||||
import {Footer} from "@/components/footer";
|
||||
import {ProBanner} from "@/components/pro-banner";
|
||||
@@ -33,10 +33,6 @@ export const metadata: Metadata = {
|
||||
"UI Framework",
|
||||
"UI Design System",
|
||||
],
|
||||
themeColor: [
|
||||
{media: "(prefers-color-scheme: light)", color: "white"},
|
||||
{media: "(prefers-color-scheme: dark)", color: "black"},
|
||||
],
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-32x32.png",
|
||||
@@ -47,26 +43,41 @@ export const metadata: Metadata = {
|
||||
openGraph: siteConfig.openGraph,
|
||||
authors: [
|
||||
{
|
||||
name: "jrgarciadev",
|
||||
url: "https://jrgarciadev.com",
|
||||
name: "getnextui",
|
||||
url: "https://x.com/getnextui",
|
||||
},
|
||||
],
|
||||
creator: "jrgarciadev",
|
||||
creator: "getnextui",
|
||||
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",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{color: "#f4f4f5", media: "(prefers-color-scheme: light)"},
|
||||
{color: "#111111", media: "(prefers-color-scheme: dark)"},
|
||||
],
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
};
|
||||
|
||||
export default function RootLayout({children}: {children: React.ReactNode}) {
|
||||
return (
|
||||
<html suppressHydrationWarning dir="ltr" lang="en">
|
||||
<head />
|
||||
<body className={clsx("min-h-screen bg-background font-sans antialiased", fontSans.variable)}>
|
||||
<body
|
||||
className={clsx(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
fonts.sans.variable,
|
||||
fonts.mono.variable,
|
||||
)}
|
||||
>
|
||||
<Providers themeProps={{attribute: "class", defaultTheme: "dark"}}>
|
||||
<div className="relative flex flex-col" id="app-container">
|
||||
<ProBanner />
|
||||
|
||||
+7
-17
@@ -1,4 +1,5 @@
|
||||
import {Spacer} from "@nextui-org/spacer";
|
||||
import {Suspense} from "react";
|
||||
|
||||
import {Hero} from "@/components/marketing/hero";
|
||||
import {FeaturesGrid} from "@/components/marketing/features-grid";
|
||||
@@ -9,26 +10,12 @@ import {Customization} from "@/components/marketing/customization";
|
||||
import {LastButNotLeast} from "@/components/marketing/last-but-not-least";
|
||||
import {InstallBanner} from "@/components/marketing/install-banner";
|
||||
import {Community} from "@/components/marketing/community";
|
||||
import {Support} from "@/components/marketing/support";
|
||||
import Support from "@/components/marketing/support";
|
||||
import landingContent from "@/content/landing";
|
||||
import {getAllSponsors} from "@/utils/get-all-sponsors";
|
||||
import {Sponsors} from "@/components/marketing/sponsors";
|
||||
|
||||
async function getData() {
|
||||
try {
|
||||
const sponsors = await getAllSponsors();
|
||||
|
||||
return {
|
||||
sponsors,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
}
|
||||
import {NextUIProSection} from "@/components/marketing/nextui-pro-section";
|
||||
|
||||
export default async function Home() {
|
||||
const data = await getData();
|
||||
|
||||
return (
|
||||
<main className="container mx-auto max-w-7xl px-6 flex-grow">
|
||||
<section className="flex flex-col items-center justify-center">
|
||||
@@ -39,8 +26,11 @@ export default async function Home() {
|
||||
<A11yOtb />
|
||||
<DarkMode />
|
||||
<Customization />
|
||||
<NextUIProSection />
|
||||
<LastButNotLeast />
|
||||
<Support sponsors={data.sponsors} />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Support />
|
||||
</Suspense>
|
||||
<Spacer y={24} />
|
||||
<InstallBanner />
|
||||
<Community />
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
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 {ThemeProviderProps} from "next-themes";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useEffect} from "react";
|
||||
import posthog from "posthog-js";
|
||||
import {PostHogProvider} from "posthog-js/react";
|
||||
|
||||
import {__PROD__} from "@/utils";
|
||||
|
||||
export interface ProvidersProps {
|
||||
children: React.ReactNode;
|
||||
@@ -14,9 +21,29 @@ export interface ProvidersProps {
|
||||
export function Providers({children, themeProps}: ProvidersProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const ProviderWrapper = ({children}: {children: ReactNode}) => {
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined" && __PROD__) {
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
api_host: "/ingest",
|
||||
person_profiles: "identified_only",
|
||||
ui_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (__PROD__) {
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
return (
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
<ProviderWrapper>
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</ProviderWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import React, {useCallback, useEffect} from "react";
|
||||
import Script from "next/script";
|
||||
|
||||
@@ -5,7 +7,7 @@ import carbonOptimize from "./carbon-optimize";
|
||||
|
||||
import {loadScript} from "@/utils/scripts";
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {__PROD__} from "@/utils";
|
||||
import {__PROD__, __ENABLE_ADS__} from "@/utils";
|
||||
|
||||
const EA_PROVIDER_RATIO = 0.85;
|
||||
|
||||
@@ -94,7 +96,7 @@ export const CarbonAd: React.FC<unknown> = () => {
|
||||
};
|
||||
}, [isMounted]);
|
||||
|
||||
if (!__PROD__) return null;
|
||||
if (!__PROD__ || !__ENABLE_ADS__) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import {BlogPost} from "contentlayer/generated";
|
||||
import {BlogPost} from "contentlayer2/generated";
|
||||
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 {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BlogPostCard = (post: BlogPost) => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("BlogPostCard - Selection", {
|
||||
posthog.capture("BlogPostCard - Selection", {
|
||||
name: post.title,
|
||||
action: "click",
|
||||
category: "blog",
|
||||
@@ -32,6 +34,7 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
transition={{duration: 0.3}}
|
||||
>
|
||||
<Card
|
||||
disableRipple
|
||||
isBlurred
|
||||
as={NextLink}
|
||||
className="p-2 h-full border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
|
||||
|
||||
@@ -13,8 +13,9 @@ import {clsx} from "@nextui-org/shared-utils";
|
||||
import scrollIntoView from "scroll-into-view-if-needed";
|
||||
import {isAppleDevice, isWebKit} from "@react-aria/utils";
|
||||
import {create} from "zustand";
|
||||
import {intersectionBy, isEmpty} from "lodash";
|
||||
import {isEmpty, intersectionBy} from "@nextui-org/shared-utils";
|
||||
import {writeStorage, useLocalStorage} from "@rehooks/local-storage";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {
|
||||
DocumentCodeBoldIcon,
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
|
||||
import searchData from "@/config/search-meta.json";
|
||||
import {useUpdateEffect} from "@/hooks/use-update-effect";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const hideOnPaths = ["examples"];
|
||||
|
||||
@@ -139,6 +139,8 @@ export const Cmdk: FC<{}> = () => {
|
||||
|
||||
const {isOpen, onClose, onOpen} = useCmdkStore();
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
const [recentSearches] = useLocalStorage<SearchResultItem[]>(RECENT_SEARCHES_KEY);
|
||||
|
||||
const addToRecentSearches = (item: SearchResultItem) => {
|
||||
@@ -196,7 +198,7 @@ export const Cmdk: FC<{}> = () => {
|
||||
|
||||
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
|
||||
|
||||
trackEvent("Cmdk - Search", {
|
||||
posthog.capture("Cmdk - Search", {
|
||||
name: "cmdk - search",
|
||||
action: "search",
|
||||
category: "cmdk",
|
||||
@@ -219,7 +221,7 @@ export const Cmdk: FC<{}> = () => {
|
||||
e.preventDefault();
|
||||
isOpen ? onClose() : onOpen();
|
||||
|
||||
trackEvent("Cmdk - Open/Close", {
|
||||
posthog.capture("Cmdk - Open/Close", {
|
||||
name: "cmdk - open/close",
|
||||
action: "keydown",
|
||||
category: "cmdk",
|
||||
@@ -241,7 +243,7 @@ export const Cmdk: FC<{}> = () => {
|
||||
router.push(item.url);
|
||||
addToRecentSearches(item);
|
||||
|
||||
trackEvent("Cmdk - ItemSelect", {
|
||||
posthog.capture("Cmdk - ItemSelect", {
|
||||
name: item.content,
|
||||
action: "click",
|
||||
category: "cmdk",
|
||||
|
||||
@@ -6,7 +6,7 @@ import jsx from "refractor/lang/jsx";
|
||||
import bash from "refractor/lang/bash";
|
||||
import css from "refractor/lang/css";
|
||||
import diff from "refractor/lang/diff";
|
||||
import hastToHtml from "hast-util-to-html";
|
||||
import {toHtml} from "hast-util-to-html";
|
||||
import rangeParser from "parse-numeric-range";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
@@ -137,7 +137,7 @@ const CodeBlock = React.forwardRef<HTMLPreElement, CodeBlockProps>((_props, forw
|
||||
result = highlightWord(result);
|
||||
|
||||
// convert to html
|
||||
result = hastToHtml(result);
|
||||
result = toHtml(result);
|
||||
|
||||
// TODO reset theme
|
||||
const classes = `language-${language}`;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {FC} from "react";
|
||||
import {Button, ButtonProps} from "@nextui-org/react";
|
||||
import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {CheckLinearIcon, CopyLinearIcon} from "@/components/icons";
|
||||
|
||||
@@ -8,7 +9,7 @@ export interface CopyButtonProps extends ButtonProps {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export const CopyButton: FC<CopyButtonProps> = ({value, ...buttonProps}) => {
|
||||
export const CopyButton: FC<CopyButtonProps> = ({value, className, ...buttonProps}) => {
|
||||
const {copy, copied} = useClipboard();
|
||||
|
||||
const handleCopy = () => {
|
||||
@@ -18,19 +19,22 @@ export const CopyButton: FC<CopyButtonProps> = ({value, ...buttonProps}) => {
|
||||
return (
|
||||
<Button
|
||||
isIconOnly
|
||||
className="absolute z-50 right-3 top-8 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"
|
||||
className={clsx(
|
||||
"absolute z-50 right-3 text-zinc-300 top-8 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",
|
||||
className,
|
||||
)}
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
onPress={handleCopy}
|
||||
{...buttonProps}
|
||||
>
|
||||
<CheckLinearIcon
|
||||
className="absolute opacity-0 scale-50 text-zinc-300 data-[visible=true]:opacity-100 data-[visible=true]:scale-100 transition-transform-opacity"
|
||||
className="absolute opacity-0 scale-50 data-[visible=true]:opacity-100 data-[visible=true]:scale-100 transition-transform-opacity"
|
||||
data-visible={copied}
|
||||
size={16}
|
||||
/>
|
||||
<CopyLinearIcon
|
||||
className="absolute opacity-0 scale-50 text-zinc-300 data-[visible=true]:opacity-100 data-[visible=true]:scale-100 transition-transform-opacity"
|
||||
className="absolute opacity-0 scale-50 data-[visible=true]:opacity-100 data-[visible=true]:scale-100 transition-transform-opacity"
|
||||
data-visible={!copied}
|
||||
size={16}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Skeleton,
|
||||
} from "@nextui-org/react";
|
||||
import Link from "next/link";
|
||||
import {toLower} from "lodash";
|
||||
|
||||
import {CodeWindow} from "@/components/code-window";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
@@ -30,8 +29,8 @@ export const DemoCodeModal: FC<DemoCodeModalProps> = ({isOpen, code, title, subt
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const lowerTitle = toLower(title);
|
||||
const fileName = `${toLower(lowerTitle)}.tsx`;
|
||||
const lowerTitle = title.toLowerCase();
|
||||
const fileName = `${lowerTitle}.tsx`;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import {useRef} from "react";
|
||||
import {Button} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
export const CustomButton = () => {
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handleConfetti = async () => {
|
||||
const {clientWidth, clientHeight} = document.documentElement;
|
||||
@@ -29,7 +29,7 @@ export const CustomButton = () => {
|
||||
},
|
||||
});
|
||||
|
||||
trackEvent("LandingPage - Confetti Button", {
|
||||
posthog.capture("LandingPage - Confetti Button", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
});
|
||||
|
||||
@@ -27,24 +27,23 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
|
||||
{...otherProps}
|
||||
>
|
||||
<CardBody>
|
||||
<div className="grid grid-cols-6 md:grid-cols-12 gap-6 md:gap-4 items-center justify-center">
|
||||
<div className="md:max-h-[200px] grid grid-cols-6 md:grid-cols-12 gap-6 md:gap-4 items-center justify-center">
|
||||
<div className="relative col-span-6 md:col-span-4">
|
||||
<Image
|
||||
alt="Album cover"
|
||||
as={NextImage}
|
||||
className="object-cover"
|
||||
height={300}
|
||||
height={160}
|
||||
shadow="md"
|
||||
src="/images/album-cover.png"
|
||||
width={240}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col col-span-6 md:col-span-8">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex flex-col gap-0">
|
||||
<p className="font-semibold text-foreground/90">Daily Mix</p>
|
||||
<p className="text-sm text-foreground/80">12 Tracks</p>
|
||||
<p className="text-sm font-semibold text-foreground">Daily Mix</p>
|
||||
<p className="text-xs text-foreground/80">12 Tracks</p>
|
||||
<p className="text-lg font-medium mt-2">Frontend Radio</p>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import {useState, FC} from "react";
|
||||
import {useState} from "react";
|
||||
import {Card, CardHeader, Button, Avatar, CardBody, CardFooter} from "@nextui-org/react";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import NextImage from "next/image";
|
||||
|
||||
interface UserTwitterCardProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
|
||||
export const UserTwitterCard = ({className}: UserTwitterCardProps) => {
|
||||
const [isFollowed, setIsFollowed] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -18,7 +17,6 @@ export const UserTwitterCard: FC<UserTwitterCardProps> = ({className}) => {
|
||||
<div className="flex gap-5">
|
||||
<Avatar
|
||||
isBordered
|
||||
ImgComponent={NextImage}
|
||||
alt="Zoey Lang"
|
||||
imgProps={{
|
||||
width: 40,
|
||||
|
||||
@@ -109,6 +109,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
<DynamicReactLiveDemo
|
||||
className={className}
|
||||
code={code}
|
||||
files={files}
|
||||
gradientColor={gradientColor}
|
||||
height={previewHeight}
|
||||
isCentered={isPreviewCentered}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import React from "react";
|
||||
import {LivePreview, LiveProvider, LiveError} from "react-live";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import * as Components from "@nextui-org/react";
|
||||
import * as NextUI from "@nextui-org/react";
|
||||
import * as intlDateUtils from "@internationalized/date";
|
||||
import * as reactAriaI18n from "@react-aria/i18n";
|
||||
import * as reactHookFormBase from "react-hook-form";
|
||||
import {SandpackFiles} from "@codesandbox/sandpack-react/types";
|
||||
|
||||
import {BgGridContainer} from "@/components/bg-grid-container";
|
||||
import {GradientBox, GradientBoxProps} from "@/components/gradient-box";
|
||||
import {CopyButton} from "@/components/copy-button";
|
||||
|
||||
export interface ReactLiveDemoProps {
|
||||
code: string;
|
||||
files: SandpackFiles;
|
||||
noInline?: boolean;
|
||||
height?: string | number;
|
||||
isCentered?: boolean;
|
||||
@@ -19,14 +23,26 @@ export interface ReactLiveDemoProps {
|
||||
overflow?: "auto" | "visible" | "hidden";
|
||||
}
|
||||
|
||||
// 🚨 Do not pass react-hook-form to scope, it will break the live preview since
|
||||
// it also has a "Form" component that will override the one from @nextui-org/react
|
||||
const reactHookForm = {
|
||||
useForm: reactHookFormBase.useForm,
|
||||
Controller: reactHookFormBase.Controller,
|
||||
};
|
||||
|
||||
export const scope = {
|
||||
...Components,
|
||||
React,
|
||||
...NextUI,
|
||||
...intlDateUtils,
|
||||
...reactAriaI18n,
|
||||
...reactHookForm,
|
||||
} as Record<string, unknown>;
|
||||
|
||||
const DEFAULT_FILE = "/App.jsx";
|
||||
|
||||
export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
|
||||
code,
|
||||
files,
|
||||
isGradientBox,
|
||||
gradientColor = "orange",
|
||||
isCentered = false,
|
||||
@@ -36,8 +52,16 @@ export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
|
||||
}) => {
|
||||
const content = (
|
||||
<>
|
||||
{files?.[DEFAULT_FILE] && (
|
||||
<div className="absolute top-[-28px] right-[-8px] z-50">
|
||||
<CopyButton
|
||||
className="before:hidden opacity-0 group-hover/code-demo:opacity-100 transition-opacity text-zinc-400"
|
||||
value={files?.[DEFAULT_FILE] as string}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<LivePreview
|
||||
className={clsx("live-preview flex h-full w-full not-prose", {
|
||||
className={clsx("live-preview flex h-full w-full not-prose ", {
|
||||
"justify-center items-center": isCentered,
|
||||
})}
|
||||
style={{height}}
|
||||
@@ -58,12 +82,12 @@ export const ReactLiveDemo: React.FC<ReactLiveDemoProps> = ({
|
||||
color={gradientColor}
|
||||
to="top-right"
|
||||
>
|
||||
<div className="max-w-full py-4 px-2 w-full h-full scrollbar-hide overflow-x-scroll">
|
||||
<div className="group/code-demo max-w-full py-4 px-2 w-full h-full scrollbar-hide overflow-x-scroll">
|
||||
{content}
|
||||
</div>
|
||||
</GradientBox>
|
||||
) : (
|
||||
<BgGridContainer className={className}>{content}</BgGridContainer>
|
||||
<BgGridContainer className={clsx(className, "group/code-demo")}>{content}</BgGridContainer>
|
||||
)}
|
||||
</LiveProvider>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import {get} from "lodash";
|
||||
|
||||
import {FileCode} from "./types";
|
||||
|
||||
const importRegex = /^(import)\s(?!type(of\s|\s)(?!from)).*?$/gm;
|
||||
const importRegex = /^(import\s+(?!type\s+\{)[\s\S]*?;)/gm;
|
||||
|
||||
const exportDefaultRegex = /export\s+default\s+function\s+\w+\s*\(\s*\)\s*\{/;
|
||||
|
||||
export const transformCode = (code: string, imports = {}, compName = "App") => {
|
||||
export const transformCode = (
|
||||
code: string,
|
||||
imports: {[key: string]: any} = {},
|
||||
compName = "App",
|
||||
) => {
|
||||
let cleanedCode = code
|
||||
.replace(importRegex, (match) => {
|
||||
// get component name from the match ex. "import { Table } from '@nextui-org/react'"
|
||||
const componentName = match.match(/\w+/g)?.[1] || "";
|
||||
const matchingImport = get(imports, componentName);
|
||||
const matchingImport = imports[componentName];
|
||||
|
||||
if (matchingImport) {
|
||||
// remove the matching import
|
||||
@@ -29,7 +31,7 @@ export const transformCode = (code: string, imports = {}, compName = "App") => {
|
||||
// replace match with const Name = () => (
|
||||
return `const ${compName} = () => {`;
|
||||
})
|
||||
.replace("export", "");
|
||||
.replace(/export/g, "");
|
||||
|
||||
// add render(<App/>) to cleanedCode if has const App = () => {
|
||||
if (cleanedCode.includes(`const App = () => {`)) {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import type {Language, PrismTheme} from "prism-react-renderer";
|
||||
|
||||
import {useIntersectionObserver} from "usehooks-ts";
|
||||
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, omit} from "lodash";
|
||||
import BaseHighlight, {defaultProps} from "prism-react-renderer";
|
||||
import {debounce, omit} from "@nextui-org/shared-utils";
|
||||
import {cn} from "@nextui-org/react";
|
||||
|
||||
import defaultTheme from "@/libs/prism-theme";
|
||||
|
||||
@@ -55,6 +61,149 @@ const calculateLinesToHighlight = (meta?: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
const calculateHeight = (codeString: string) => {
|
||||
const lines = codeString.split("\n").length;
|
||||
|
||||
return lines * 24;
|
||||
};
|
||||
|
||||
const CodeBlockHighlight = ({
|
||||
codeString,
|
||||
language,
|
||||
codeLang,
|
||||
theme,
|
||||
showLines,
|
||||
removeIndent,
|
||||
hideScrollBar,
|
||||
preRef,
|
||||
isMultiLine,
|
||||
shouldHighlightLine,
|
||||
highlightStyle,
|
||||
className: classNameProp,
|
||||
...props
|
||||
}: CodeblockProps & {
|
||||
codeLang: Language;
|
||||
isMultiLine: boolean;
|
||||
shouldHighlightLine: (index: number) => boolean;
|
||||
highlightStyle: HighlightStyle[];
|
||||
preRef: React.Ref<HTMLElement>;
|
||||
}) => {
|
||||
const height = calculateHeight(codeString);
|
||||
|
||||
const [intersectionRef, isVisible] = useIntersectionObserver({
|
||||
threshold: 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={intersectionRef}
|
||||
style={{
|
||||
height: isVisible ? "auto" : `${height}px`,
|
||||
}}
|
||||
>
|
||||
{isVisible ? (
|
||||
<BaseHighlight
|
||||
{...defaultProps}
|
||||
code={codeString}
|
||||
language={codeLang}
|
||||
theme={theme}
|
||||
{...props}
|
||||
>
|
||||
{({className, style, tokens, getLineProps, getTokenProps}) => (
|
||||
<pre
|
||||
ref={(element) => {
|
||||
// Merge the refs
|
||||
if (typeof preRef === "function") {
|
||||
preRef(element);
|
||||
} else if (preRef) {
|
||||
// @ts-ignore
|
||||
preRef.current = element;
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
className,
|
||||
classNameProp,
|
||||
`language-${codeLang}`,
|
||||
"max-w-full contents",
|
||||
{
|
||||
"flex-col": isMultiLine,
|
||||
"overflow-x-scroll scrollbar-hide": hideScrollBar,
|
||||
},
|
||||
)}
|
||||
data-language={language}
|
||||
style={style}
|
||||
>
|
||||
{tokens.map((line, i) => {
|
||||
const lineProps = getLineProps({line, key: i});
|
||||
|
||||
return (
|
||||
<div
|
||||
{...omit(lineProps, ["key"])}
|
||||
key={`${i}-${getUniqueID("line-wrapper")}`}
|
||||
className={clsx(
|
||||
lineProps.className,
|
||||
removeIndent ? "pr-4" : "px-4",
|
||||
"relative [&>span]:relative [&>span]:z-10",
|
||||
{
|
||||
"px-2": showLines,
|
||||
},
|
||||
{
|
||||
"before:to-code-background before:absolute before:left-0 before:z-0 before:h-full before:w-full before:bg-gradient-to-r before:from-white/10 before:content-[''] before:pointer-events-none":
|
||||
shouldHighlightLine(i),
|
||||
},
|
||||
)}
|
||||
data-deleted={dataAttr(highlightStyle?.[i] === "deleted")}
|
||||
data-inserted={dataAttr(highlightStyle?.[i] === "inserted")}
|
||||
>
|
||||
{showLines && (
|
||||
<span
|
||||
className={cn(
|
||||
"mr-6 select-none text-xs opacity-30",
|
||||
i + 1 >= 10 ? "mr-4" : "",
|
||||
i + 1 >= 100 ? "mr-2" : "",
|
||||
i + 1 >= 1000 ? "mr-0" : "",
|
||||
)}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{line.map((token, key) => {
|
||||
const props = getTokenProps({token, key}) || {};
|
||||
|
||||
return (
|
||||
<span
|
||||
{...omit(props, ["key"])}
|
||||
key={`${key}-${getUniqueID("line")}`}
|
||||
className={className}
|
||||
style={{
|
||||
...props.style,
|
||||
...(highlightStyleToken.some((t) => {
|
||||
const content = token.content.trim();
|
||||
|
||||
const regex = t instanceof RegExp ? t : new RegExp(t);
|
||||
|
||||
return regex.test(content);
|
||||
})
|
||||
? {color: "rgb(var(--code-function))"}
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</pre>
|
||||
)}
|
||||
</BaseHighlight>
|
||||
) : (
|
||||
<div className={clsx(classNameProp, "w-full bg-code-background rounded-lg")} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
(
|
||||
{
|
||||
@@ -134,79 +283,21 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseHighlight
|
||||
{...defaultProps}
|
||||
code={codeString}
|
||||
language={codeLang}
|
||||
<CodeBlockHighlight
|
||||
className={classNameProp}
|
||||
codeLang={codeLang}
|
||||
codeString={codeString}
|
||||
hideScrollBar={hideScrollBar}
|
||||
highlightStyle={highlightStyle}
|
||||
isMultiLine={isMultiLine}
|
||||
language={language}
|
||||
preRef={ref}
|
||||
removeIndent={removeIndent}
|
||||
shouldHighlightLine={shouldHighlightLine}
|
||||
showLines={showLines}
|
||||
theme={theme}
|
||||
{...props}
|
||||
>
|
||||
{({className, style, tokens, getLineProps, getTokenProps}) => (
|
||||
<div className="w-full" data-language={language}>
|
||||
<pre
|
||||
ref={ref}
|
||||
className={clsx(className, classNameProp, "flex max-w-full", {
|
||||
"flex-col": isMultiLine,
|
||||
"scrollbar-hide overflow-x-scroll": hideScrollBar,
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
{tokens.map((line, i) => {
|
||||
const lineProps = getLineProps({line, key: i});
|
||||
|
||||
return (
|
||||
<div
|
||||
{...omit(lineProps, ["key"])}
|
||||
key={`${i}-${getUniqueID("line-wrapper")}`}
|
||||
className={clsx(
|
||||
lineProps.className,
|
||||
removeIndent ? "pr-4" : "px-4",
|
||||
"relative [&>span]:relative [&>span]:z-10",
|
||||
{
|
||||
"px-2": showLines,
|
||||
},
|
||||
{
|
||||
"before:content-[''] before:w-full before:h-full before:absolute before:z-0 before:left-0 before:bg-gradient-to-r before:from-white/10 before:to-code-background":
|
||||
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>
|
||||
)}
|
||||
{line.map((token, key) => {
|
||||
// Bun has no color style by default in the code block, so hack add in here
|
||||
const props = getTokenProps({token, key}) || {};
|
||||
|
||||
return (
|
||||
<span
|
||||
{...omit(props, ["key"])}
|
||||
key={`${key}-${getUniqueID("line")}`}
|
||||
className={className}
|
||||
style={{
|
||||
...props.style,
|
||||
...(highlightStyleToken.some((t) => {
|
||||
const content = token.content.trim();
|
||||
|
||||
const regex = t instanceof RegExp ? t : new RegExp(t);
|
||||
|
||||
return regex.test(content);
|
||||
})
|
||||
? {color: "rgb(var(--code-function))"}
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</BaseHighlight>
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Button, ButtonProps, Code, Link, Tooltip} from "@nextui-org/react";
|
||||
import {ReactNode} from "react";
|
||||
import Balancer from "react-wrap-balancer";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
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;
|
||||
@@ -26,10 +26,12 @@ const ButtonLink = ({
|
||||
href: string;
|
||||
tooltip?: string | ReactNode;
|
||||
}) => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handlePress = () => {
|
||||
if (!href) return;
|
||||
|
||||
trackEvent("ComponentLinks - Click", {
|
||||
posthog.capture("ComponentLinks - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import type Highlight from "prism-react-renderer";
|
||||
|
||||
export type TransformTokens = Parameters<Highlight["props"]["children"]>[0]["tokens"];
|
||||
|
||||
export type TransformTokensTypes = TransformTokens[0][0] & {
|
||||
folderContent?: TransformTokens;
|
||||
summaryContent?: TransformTokens[0];
|
||||
class?: string;
|
||||
index?: number;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
const startFlag = ["{", "["];
|
||||
const endFlag = ["}", "]"];
|
||||
const isElementStartRegex = /^\s*</;
|
||||
const isElementEndRegex = /^\s*<\//;
|
||||
|
||||
/**
|
||||
* Transform tokens from `prism-react-renderer` to wrap them in folder structure
|
||||
*
|
||||
* @example
|
||||
* transformTokens(tokens) -> wrap tokens in folder structure
|
||||
*/
|
||||
export function transformTokens(tokens: TransformTokens, folderLine = 10) {
|
||||
const result: TransformTokens = [];
|
||||
let lastIndex = 0;
|
||||
let startElementName = "";
|
||||
|
||||
tokens.forEach((token, index) => {
|
||||
if (index < lastIndex) {
|
||||
return;
|
||||
}
|
||||
token.forEach((t) => {
|
||||
(t as TransformTokensTypes).index = index;
|
||||
});
|
||||
result.push(token);
|
||||
|
||||
const lineContent = getLineContent(token);
|
||||
const {isStartTag, isEndTag} = checkIsElement(lineContent);
|
||||
|
||||
// If it has startElementName means it is within the element range
|
||||
if (startElementName) {
|
||||
if (isEndTag) {
|
||||
// Judge whether it is the end tag of the element then reset startElementName
|
||||
const {endElementName} = getElementName(lineContent);
|
||||
|
||||
if (endElementName === startElementName) {
|
||||
startElementName = "";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (isStartTag) {
|
||||
const {startElementName: elementName, endElementName} = getElementName(lineContent);
|
||||
|
||||
if (!endElementName) {
|
||||
startElementName = elementName;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let startToken: TransformTokens[0][0] = null as any;
|
||||
|
||||
token.forEach((t) => {
|
||||
if (startFlag.includes(t.content)) {
|
||||
startToken = t;
|
||||
}
|
||||
});
|
||||
|
||||
const isFolder = checkIsFolder(token);
|
||||
|
||||
if (isFolder && startToken) {
|
||||
const nextLineContent = tokens.slice(index + 1, index + 2).reduce((acc, line) => {
|
||||
return acc + getLineContent(line);
|
||||
}, "");
|
||||
const isNextLineObjectFolder = checkIsObjectContent(nextLineContent);
|
||||
const isArrayFolder = lineContent.trim().endsWith("[");
|
||||
|
||||
if (isNextLineObjectFolder || isArrayFolder) {
|
||||
const endIndex = findEndIndex(tokens, index + 1);
|
||||
|
||||
// Greater than or equal to folderLine then will folder otherwise it will show directly
|
||||
if (endIndex !== -1 && endIndex - index >= folderLine) {
|
||||
lastIndex = endIndex;
|
||||
const folder = tokens.slice(index + 1, endIndex);
|
||||
const endToken = tokens[endIndex];
|
||||
|
||||
(endToken[0] as TransformTokensTypes).class = "first-custom-folder";
|
||||
|
||||
const ellipsisToken: TransformTokensTypes = {
|
||||
types: ["ellipsis"],
|
||||
content: "",
|
||||
class: "custom-folder ellipsis-token",
|
||||
};
|
||||
const copyContent: TransformTokensTypes = {
|
||||
types: ["copy"],
|
||||
content: "",
|
||||
folderContent: folder,
|
||||
class: "custom-folder copy-token",
|
||||
};
|
||||
|
||||
endToken.forEach((t, _, arr) => {
|
||||
let className = (t as TransformTokensTypes).class || "";
|
||||
|
||||
className += " custom-folder";
|
||||
if (t.content.trim() === "" && (arr.length === 3 || arr.length === 4)) {
|
||||
// Add length check to sure it's added to } token
|
||||
className += " empty-token";
|
||||
}
|
||||
(t as TransformTokensTypes).class = className;
|
||||
});
|
||||
|
||||
startToken.types = ["folderStart"];
|
||||
(startToken as TransformTokensTypes).folderContent = folder;
|
||||
(startToken as TransformTokensTypes).summaryContent = [
|
||||
...token,
|
||||
ellipsisToken,
|
||||
copyContent,
|
||||
...endToken,
|
||||
];
|
||||
(startToken as TransformTokensTypes).index = index;
|
||||
// isShowFolder && ((startToken as TransformTokensTypes).open = true);
|
||||
|
||||
result.splice(result.length - 1, 1, [startToken]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function checkIsFolder(token: TransformTokens[0]) {
|
||||
const stack: string[] = [];
|
||||
|
||||
for (const t of token) {
|
||||
if (startFlag.includes(t.content)) {
|
||||
stack.push(t.content);
|
||||
} else if (endFlag.includes(t.content)) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return stack.length !== 0;
|
||||
}
|
||||
|
||||
function findEndIndex(tokens: TransformTokens, startIndex: number) {
|
||||
const stack: string[] = ["flag"];
|
||||
|
||||
for (let i = startIndex; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
|
||||
for (const line of token) {
|
||||
const transformLine = line.content.replace(/\$/g, "");
|
||||
|
||||
if (startFlag.includes(transformLine)) {
|
||||
stack.push("flag");
|
||||
} else if (endFlag.includes(transformLine)) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (stack.length === 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function checkIsElement(lineContent: string) {
|
||||
return {
|
||||
isStartTag: isElementStartRegex.test(lineContent),
|
||||
isEndTag: isElementEndRegex.test(lineContent),
|
||||
};
|
||||
}
|
||||
|
||||
function getElementName(lineContent: string) {
|
||||
const startElementName = lineContent.match(/^\s*<([a-zA-Z.]+)/);
|
||||
const endElementName = lineContent.match(/^\s*<\/([a-zA-Z.]+)>/);
|
||||
|
||||
return {
|
||||
startElementName: startElementName?.[1] || (lineContent.includes("<>") ? "<>" : ""),
|
||||
endElementName: endElementName?.[1] || (lineContent.includes("</>") ? "</>" : ""),
|
||||
};
|
||||
}
|
||||
|
||||
function getLineContent(token: TransformTokens[0]) {
|
||||
return token.reduce((acc, t) => acc + t.content, "");
|
||||
}
|
||||
|
||||
function checkIsObjectContent(lineContent: string) {
|
||||
lineContent = lineContent.trim();
|
||||
// first: match { a }
|
||||
// second: match { a: b }
|
||||
// third: match { a (b) }
|
||||
// fourth: match /** */
|
||||
const isObjectContent = /^([\w]+,?$)|([\w\[.\]]+:)|([\w]+\s?\(.*?\)$)|(^\/\*\*)/.test(
|
||||
lineContent,
|
||||
);
|
||||
const hasEqual = /\s=\s/.test(lineContent);
|
||||
const hasFunction = lineContent.includes("function");
|
||||
const hasVariable = /var|let|const/.test(lineContent);
|
||||
|
||||
return isObjectContent && !hasEqual && !hasFunction && !hasVariable;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {Tabs, Tab, Snippet, Code} from "@nextui-org/react";
|
||||
import {Key, useState} from "react";
|
||||
|
||||
@@ -97,9 +99,10 @@ export const PackageManagers = ({
|
||||
})}
|
||||
</Tabs>
|
||||
{showGlobalInstallWarning && (
|
||||
<Blockquote className="my-2" color="warning">
|
||||
<Blockquote className="my-2 text-small" color="warning">
|
||||
The above command is for individual installation only. You may skip this step if{" "}
|
||||
<Code>@nextui-org/react</Code> is already installed globally.
|
||||
<Code className="px-1 py-0.5 text-tiny font-mono">@nextui-org/react</Code> is already
|
||||
installed globally.
|
||||
</Blockquote>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {commonColors, semanticColors} from "@nextui-org/theme";
|
||||
import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
import {useState} from "react";
|
||||
import {useTheme} from "next-themes";
|
||||
import {get, isEmpty} from "lodash";
|
||||
import {get, isEmpty} from "@nextui-org/shared-utils";
|
||||
|
||||
type ColorsItem = {
|
||||
color: string;
|
||||
@@ -106,7 +106,7 @@ const SemanticSwatch = ({
|
||||
let value: string = "";
|
||||
const [colorName, colorScale] = color.split("-");
|
||||
|
||||
let currentPalette = get(semanticColors, theme ?? "", {});
|
||||
const currentPalette = get(semanticColors, theme ?? "", {});
|
||||
|
||||
if (!colorScale) {
|
||||
value = get(currentPalette, `${colorName}.DEFAULT`, "");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import {Link} from "@nextui-org/react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {ChevronIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
@@ -9,7 +10,6 @@ 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;
|
||||
@@ -20,8 +20,10 @@ export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
|
||||
|
||||
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handlePress = (path: string) => {
|
||||
trackEvent("DocsPager - Click", {
|
||||
posthog.capture("DocsPager - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: path || "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {FC, useEffect, useState} from "react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
import {ChevronIcon} from "@nextui-org/shared-icons";
|
||||
import {CollectionBase, Expandable, MultipleSelection, Node, ItemProps} from "@react-types/shared";
|
||||
import {BaseItem} from "@nextui-org/aria-utils";
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
dataFocusVisibleClasses,
|
||||
} from "@nextui-org/react";
|
||||
import Link from "next/link";
|
||||
import {isEmpty} from "lodash";
|
||||
import {isEmpty} from "@nextui-org/shared-utils";
|
||||
import {usePathname, useRouter} from "next/navigation";
|
||||
|
||||
import {ScrollArea} from "../scroll-area";
|
||||
@@ -27,11 +28,6 @@ import {getRoutePaths} from "./utils";
|
||||
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {FbFeedbackButton} from "@/components/featurebase/fb-feedback-button";
|
||||
import {FbChangelogButton} from "@/components/featurebase/fb-changelog-button";
|
||||
import {FbRoadmapLink} from "@/components/featurebase/fb-roadmap-link";
|
||||
import {openFeedbackWidget} from "@/utils/featurebase";
|
||||
import emitter from "@/libs/emitter";
|
||||
|
||||
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
|
||||
@@ -66,6 +62,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const posthog = usePostHog();
|
||||
|
||||
const paths = item.props.path
|
||||
? getRoutePaths(item.props.path, item.props?.tag)
|
||||
@@ -109,7 +106,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
} else {
|
||||
router.push(paths.pathname);
|
||||
|
||||
trackEvent("SidebarDocs", {
|
||||
posthog.capture("SidebarDocs", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: paths.pathname || "",
|
||||
@@ -120,28 +117,11 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
|
||||
const {focusProps, isFocused, isFocusVisible} = useFocusRing();
|
||||
|
||||
const renderFeaturebaseComponent = (key: string) => {
|
||||
if (key === "roadmap")
|
||||
return <FbRoadmapLink className={cn} innerClassName="opacity-80 dark:opacity-60" />;
|
||||
if (key === "changelog")
|
||||
return (
|
||||
<NextUILink as={Link} className={cn} color="foreground" href="#">
|
||||
<FbChangelogButton />
|
||||
</NextUILink>
|
||||
);
|
||||
|
||||
return (
|
||||
<NextUILink as={Link} className={cn} color="foreground" href="#" onClick={openFeedbackWidget}>
|
||||
<FbFeedbackButton />
|
||||
</NextUILink>
|
||||
);
|
||||
};
|
||||
|
||||
const renderComponent = () => {
|
||||
if (hasChildNodes) {
|
||||
return (
|
||||
<span className="flex items-center gap-3">
|
||||
<span>{rendered}</span>
|
||||
<span className="font-medium sm:text-sm">{rendered}</span>
|
||||
<ChevronIcon
|
||||
className={clsx("transition-transform", {
|
||||
"-rotate-90": isExpanded,
|
||||
@@ -151,14 +131,18 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof key === "string" && ["changelog", "feedback", "roadmap"].includes(key)) {
|
||||
return renderFeaturebaseComponent(key);
|
||||
}
|
||||
|
||||
return (
|
||||
<NextUILink as={Link} className={clsx(cn)} color="foreground" href={paths.pathname}>
|
||||
<NextUILink
|
||||
as={item.props?.comingSoon ? "div" : Link}
|
||||
className={clsx(cn, {
|
||||
"pointer-events-none": item.props?.comingSoon,
|
||||
})}
|
||||
color="foreground"
|
||||
href={item.props?.comingSoon ? "#" : paths.pathname}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"sm:text-sm",
|
||||
isSelected
|
||||
? "text-primary font-medium dark:text-foreground"
|
||||
: "opacity-80 dark:opacity-60",
|
||||
@@ -171,7 +155,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
</span>
|
||||
{isUpdated && (
|
||||
<Chip
|
||||
className="ml-1 py-1 text-tiny text-default-400 bg-default-100/50"
|
||||
className="ml-1 py-1 font-medium text-tiny text-default-500 dark:text-default-400 bg-default-100 dark:bg-default-100/50"
|
||||
color="default"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
@@ -180,12 +164,22 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
</Chip>
|
||||
)}
|
||||
{isNew && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="primary" size="sm" variant="flat">
|
||||
<Chip
|
||||
className="font-medium ml-1 py-1 text-tiny"
|
||||
color="primary"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
New
|
||||
</Chip>
|
||||
)}
|
||||
{item.props?.comingSoon && (
|
||||
<Chip className="ml-1 py-1 text-tiny" color="default" size="sm" variant="flat">
|
||||
<Chip
|
||||
className="font-medium ml-1 py-1 text-tiny"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Coming soon
|
||||
</Chip>
|
||||
)}
|
||||
@@ -200,8 +194,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
aria-expanded={dataAttr(hasChildNodes ? isExpanded : undefined)}
|
||||
aria-selected={dataAttr(isSelected)}
|
||||
className={clsx(
|
||||
"flex flex-col gap-3outline-none w-full tap-highlight-transparent",
|
||||
|
||||
"flex flex-col outline-none w-full tap-highlight-transparent",
|
||||
hasChildNodes ? "mb-4" : "first:mt-4",
|
||||
// focus ring
|
||||
...dataFocusVisibleClasses,
|
||||
@@ -210,7 +203,12 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
data-focused={isFocused}
|
||||
role="treeitem"
|
||||
>
|
||||
<div className="flex items-center gap-3 cursor-pointer" {...pressProps}>
|
||||
<div
|
||||
className={clsx("flex items-center gap-3 cursor-pointer", {
|
||||
"pointer-events-none": item.props?.comingSoon,
|
||||
})}
|
||||
{...(item.props?.comingSoon ? {} : pressProps)}
|
||||
>
|
||||
<Spacer x={spaceLeft} />
|
||||
{renderComponent()}
|
||||
{/* Workaround to avoid scrollbar overlapping */}
|
||||
@@ -260,7 +258,7 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
|
||||
return (
|
||||
<ScrollArea
|
||||
ref={ref}
|
||||
className="h-full lg:max-h-[calc(100vh_-_64px)]"
|
||||
className="h-full max-w-[90%] lg:max-h-[calc(100vh_-_64px)]"
|
||||
role="tree"
|
||||
{...collectionProps}
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@ const paddingLeftByLevel: Record<number, string> = {
|
||||
1: "pl-0",
|
||||
2: "pl-0",
|
||||
3: "pl-3",
|
||||
4: "pl-3",
|
||||
4: "pl-6",
|
||||
};
|
||||
|
||||
export const DocsToc: FC<DocsTocProps> = ({headings}) => {
|
||||
@@ -77,7 +77,7 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
|
||||
}, transparent 100%)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-sm">On this page</p>
|
||||
<p className="text-sm font-medium">On this page</p>
|
||||
<ul className="scrollbar-hide flex flex-col gap-2">
|
||||
{headings.map(
|
||||
(heading, i) =>
|
||||
@@ -115,7 +115,7 @@ export const DocsToc: FC<DocsTocProps> = ({headings}) => {
|
||||
<Divider />
|
||||
<Spacer y={2} />
|
||||
<a
|
||||
className="flex gap-2 items-center text-sm text-default-500 dark:text-foreground/30 hover:text-foreground/80 pl-4 transition-opacity"
|
||||
className="flex gap-2 items-center text-small text-default-500 dark:text-foreground/30 hover:text-foreground/80 pl-4 transition-opacity"
|
||||
href={`#${firstId}`}
|
||||
>
|
||||
Back to top
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// ref: https://developers.featurebase.app/install/changelog-widget/install
|
||||
export const FbChangelogButton = ({className}: Props) => {
|
||||
useEffect(() => {
|
||||
const win = window as any;
|
||||
|
||||
if (typeof win.Featurebase !== "function") {
|
||||
win.Featurebase = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
(win.Featurebase.q = win.Featurebase.q || []).push(arguments);
|
||||
};
|
||||
}
|
||||
win.Featurebase("initialize_changelog_widget", {
|
||||
organization: process.env.NEXT_PUBLIC_FB_FEEDBACK_ORG,
|
||||
theme: "dark",
|
||||
usersName: "",
|
||||
fullscreenPopup: true,
|
||||
alwaysShow: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fbButtonOnClick = () => {
|
||||
(window as any).Featurebase("manually_open_changelog_popup");
|
||||
|
||||
trackEvent("Featurebase - Changelog", {
|
||||
name: "featurebase-changelog",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={className} onClick={fbButtonOnClick}>
|
||||
Changelog <span id="fb-update-badge" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// ref: https://developers.featurebase.app/install/feedback-widget/setup
|
||||
export const FbFeedbackButton = ({className}: Props) => {
|
||||
useEffect(() => {
|
||||
const win = window as any;
|
||||
|
||||
if (typeof win.Featurebase !== "function") {
|
||||
win.Featurebase = function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
(win.Featurebase.q = win.Featurebase.q || []).push(arguments);
|
||||
};
|
||||
}
|
||||
win.Featurebase("initialize_feedback_widget", {
|
||||
organization: process.env.NEXT_PUBLIC_FB_FEEDBACK_ORG,
|
||||
theme: "dark",
|
||||
email: "",
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fbButtonOnClick = () => {
|
||||
trackEvent("Featurebase - Feedback", {
|
||||
name: "featurebase-feedback",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<button data-featurebase-feedback className={className} onClick={fbButtonOnClick}>
|
||||
Feedback
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import NextLink from "next/link";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
import arrowRightUpIcon from "@iconify/icons-solar/arrow-right-up-linear";
|
||||
import {Icon} from "@iconify/react/dist/offline";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
innerClassName?: string;
|
||||
};
|
||||
|
||||
export const FbRoadmapLink = ({className, innerClassName}: Props) => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
const fbLinkOnClick = () => {
|
||||
trackEvent("Featurebase - Roadmap", {
|
||||
posthog.capture("Featurebase - Roadmap", {
|
||||
name: "featurebase-roadmap",
|
||||
action: "press",
|
||||
category: "featurebase",
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
export const FigmaButton = () => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
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>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
isExternal
|
||||
showAnchorIcon
|
||||
as={Link}
|
||||
className="max-w-fit text-current"
|
||||
color="default"
|
||||
href="https://www.figma.com/community/file/1267584376234254760"
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
posthog.capture("FigmaPage - Open Figma Link", {
|
||||
action: "click",
|
||||
category: "figma",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open in Figma
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import {usePathname} from "next/navigation";
|
||||
import {Link} from "@nextui-org/react";
|
||||
|
||||
import {getCurrentYear} from "@/utils/time";
|
||||
import {XIcon, GithubIcon, DiscordIcon} from "@/components/icons";
|
||||
import {siteConfig} from "@/config/site";
|
||||
|
||||
export const Footer = () => {
|
||||
const pathname = usePathname();
|
||||
@@ -11,12 +14,45 @@ export const Footer = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isDocs = pathname.includes("/docs");
|
||||
|
||||
return (
|
||||
<footer className="container mx-auto max-w-7xl pb-12 px-12">
|
||||
<div className="flex flex-col justify-center items-center gap-1">
|
||||
<p className="text-sm text-default-400">
|
||||
© {getCurrentYear()} NextUI Inc. All rights reserved.
|
||||
</p>
|
||||
{isDocs ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Discord"
|
||||
className="p-1"
|
||||
href={siteConfig.links.discord}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<DiscordIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="X"
|
||||
className="p-1"
|
||||
href={siteConfig.links.twitter}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<XIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href={siteConfig.links.github}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
|
||||
export const InfoCircle = ({
|
||||
size = 24,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}: {
|
||||
size?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
[key: string]: any;
|
||||
}) => (
|
||||
<svg
|
||||
fill="none"
|
||||
height={size || height}
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path d="M12 17V11" stroke="currentColor" strokeLinecap="round" strokeWidth="1.5" />
|
||||
<path
|
||||
d="M12 7C12.5523 7 13 7.44772 13 8C13 8.55228 12.5523 9 12 9C11.4477 9 11 8.55228 11 8C11 7.44772 11.4477 7 12 7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -11,7 +11,7 @@ export const MoonIcon = ({size = 24, width, height, ...props}: IconSvgProps) =>
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M20.742,13.045c-0.677,0.18-1.376,0.271-2.077,0.271c-2.135,0-4.14-0.83-5.646-2.336c-2.008-2.008-2.799-4.967-2.064-7.723 c0.092-0.345-0.007-0.713-0.259-0.965C10.444,2.04,10.077,1.938,9.73,2.034C8.028,2.489,6.476,3.382,5.241,4.616 c-3.898,3.898-3.898,10.243,0,14.143c1.889,1.889,4.401,2.93,7.072,2.93c2.671,0,5.182-1.04,7.07-2.929 c1.236-1.237,2.13-2.791,2.583-4.491c0.092-0.345-0.008-0.713-0.26-0.965C21.454,13.051,21.085,12.951,20.742,13.045z M17.97,17.346c-1.511,1.511-3.52,2.343-5.656,2.343c-2.137,0-4.146-0.833-5.658-2.344c-3.118-3.119-3.118-8.195,0-11.314 c0.602-0.602,1.298-1.102,2.06-1.483c-0.222,2.885,0.814,5.772,2.89,7.848c2.068,2.069,4.927,3.12,7.848,2.891 C19.072,16.046,18.571,16.743,17.97,17.346z"
|
||||
d="M21.25 12C21.25 14.4533 20.2754 16.806 18.5407 18.5407C16.806 20.2754 14.4533 21.25 12 21.25V22.75C17.937 22.75 22.75 17.937 22.75 12H21.25ZM12 21.25C9.54675 21.25 7.19397 20.2754 5.45926 18.5407C3.72455 16.806 2.75 14.4533 2.75 12H1.25C1.25 17.937 6.063 22.75 12 22.75V21.25ZM2.75 12C2.75 9.54675 3.72455 7.19397 5.45926 5.45926C7.19397 3.72455 9.54675 2.75 12 2.75V1.25C6.063 1.25 1.25 6.063 1.25 12H2.75ZM15.5 14.25C13.975 14.25 12.5125 13.6442 11.4341 12.5659C10.3558 11.4875 9.75 10.025 9.75 8.5H8.25C8.25 10.4228 9.01384 12.2669 10.3735 13.6265C11.7331 14.9862 13.5772 15.75 15.5 15.75V14.25ZM20.425 11.469C19.9133 12.3176 19.191 13.0197 18.3281 13.5069C17.4652 13.9942 16.491 14.2501 15.5 14.25V15.75C16.7494 15.7504 17.9777 15.4279 19.0657 14.8138C20.1537 14.1997 21.0646 13.3148 21.71 12.245L20.425 11.469ZM9.75 8.5C9.74986 7.50903 10.0058 6.53483 10.4931 5.67193C10.9803 4.80902 11.6824 4.08669 12.531 3.575L11.755 2.291C10.6854 2.93628 9.80058 3.84701 9.18649 4.93486C8.57239 6.02271 8.2498 7.25078 8.25 8.5H9.75ZM12 2.75C11.9497 2.74903 11.9002 2.73811 11.8542 2.71785C11.8082 2.6976 11.7666 2.66842 11.732 2.632C11.6898 2.58965 11.6613 2.53568 11.65 2.477C11.646 2.446 11.648 2.356 11.755 2.291L12.531 3.575C13.034 3.271 13.196 2.714 13.137 2.276C13.075 1.821 12.717 1.25 12 1.25V2.75ZM21.71 12.245C21.644 12.352 21.554 12.354 21.523 12.35C21.4643 12.3387 21.4103 12.3102 21.368 12.268C21.3316 12.2334 21.3024 12.1918 21.2821 12.1458C21.2619 12.0998 21.251 12.0503 21.25 12H22.75C22.75 11.283 22.179 10.925 21.724 10.863C21.286 10.804 20.729 10.966 20.425 11.469L21.71 12.245Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -58,11 +58,11 @@ const DiscordIcon: React.FC<IconSvgProps> = ({size = 24, width, height, ...props
|
||||
);
|
||||
};
|
||||
|
||||
const TwitterIcon: React.FC<IconSvgProps> = ({size = 24, width, height, ...props}) => {
|
||||
const XIcon: React.FC<IconSvgProps> = ({size = 20, width, height, ...props}) => {
|
||||
return (
|
||||
<svg height={size || height} viewBox="0 0 24 24" width={size || width} {...props}>
|
||||
<path
|
||||
d="M19.633 7.997c.013.175.013.349.013.523 0 5.325-4.053 11.461-11.46 11.461-2.282 0-4.402-.661-6.186-1.809.324.037.636.05.973.05a8.07 8.07 0 0 0 5.001-1.721 4.036 4.036 0 0 1-3.767-2.793c.249.037.499.062.761.062.361 0 .724-.05 1.061-.137a4.027 4.027 0 0 1-3.23-3.953v-.05c.537.299 1.16.486 1.82.511a4.022 4.022 0 0 1-1.796-3.354c0-.748.199-1.434.548-2.032a11.457 11.457 0 0 0 8.306 4.215c-.062-.3-.1-.611-.1-.923a4.026 4.026 0 0 1 4.028-4.028c1.16 0 2.207.486 2.943 1.272a7.957 7.957 0 0 0 2.556-.973 4.02 4.02 0 0 1-1.771 2.22 8.073 8.073 0 0 0 2.319-.624 8.645 8.645 0 0 1-2.019 2.083z"
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
@@ -454,7 +454,7 @@ const TypescriptIcon: React.FC<IconSvgProps> = ({width = "1em", height = "1em",
|
||||
};
|
||||
|
||||
export {
|
||||
TwitterIcon,
|
||||
XIcon,
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
OpenCollectiveIcon,
|
||||
|
||||
@@ -89,319 +89,3 @@ export const CodeRabbitLogo = () => (
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ScrumbuissLogo = () => (
|
||||
<svg
|
||||
height="35px"
|
||||
version="1.1"
|
||||
viewBox="307 646 1434 262"
|
||||
width="200px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m0 0 4 2 12 10 11 9 8 8 8 7 15 13 27 27 5 4 7 8 13 15 9 10 13 17 18 27 8 14 10 19 8 19 9 27 2 9v7l-4 6-5 3-2 1h-6l-11-7-14-12-8-7-12-11-10-8-11-10-8-7-10-9-11-9-10-9-8-6v-2l-3-1-3-3-9-7-11-9-19-14-8-7h-2l9 22 10 18 5 10 12 18 10 14 4 6v7l-8 7h-7l-11-9-10-8-22-14-22-10-27-11-22-7 4 18 9 17 4 9-1 2-10-13-8-14-5-13v-10l4-6h9l19 4 34 11 13 4 2 1-13-23-8-16-12-27-4-11 1-8 5-6 10-4 13 1 13 5 15 9 12 8 16 12 13 10 10 9 10 8 11 9 10 9 8 7 14 13 8 7 12 11 5 5-3-9-4-9-10-20-10-17-11-17-12-18-10-13-14-18-11-13-9-11-12-13-7-8-12-14-18-20v-2l-3-1z"
|
||||
fill="#E87246"
|
||||
transform="translate(391,646)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h28l1 1v72h2l2-4 9-9 10-6 7-2h22l8 3 11 7 8 10 4 8 2 7 1 9v14l-2 15-6 12-9 10-8 8-12 6-10 3-13 1-14-3-5-2 4-1 11 3h10l8-3 7-3 9-7 8-14 3-8 1-6v-30l-4-13-6-10-8-7-7-3-5-1h-8l-10 3-10 6-8 9-1 2-1 79-10 7-9 2h-8l2-4 6-7v-143l-3-5v-2l-6-2z"
|
||||
fill="#E8764A"
|
||||
transform="translate(1287,696)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h37l-6 7-3 4v73l3 11 6 8 6 4 3 1h15l10-4 7-7 3-5 1-4 1-72-2-8-4-5-3-3h38l-6 7-3 4-1 32v45l1 15 3 5 8 5v1h-12l-8-3-8-7-4-9-5 8-5 4-10 5-5 1h-19l-12-5-7-7-6-9-3-10-1-7-1-65-5-6-3-2z"
|
||||
fill="#E87649"
|
||||
transform="translate(1412,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 13 1 10 5 11 10 4 9 2 2 1 75 3 5 6 4 1 3h-39l2-4 6-4 1-2v-77l-3-10-5-6-10-5h-13l-14 6-9 6-1 66v13l1 10 6 2 3 2v3h-37v-3l7-6 1-2v-66h-2l-2 4-12 13-7 8v-3l12-14 9-10h2l2-4 6-7 11-9 9-6 16-8z"
|
||||
fill="#E87347"
|
||||
transform="translate(1150,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 16 1 13 4 9 7 8 8 4 6-1 2-11-13-10-7-6-3-11-2h-12l-12 2-6 3-7 6-5 8-2 6v12l4 9 5 5 10 7 19 8 22 9 16 12 7 8 3 6 2 11v9l-2 13-5 10-11 12-12 6-16 4h-10l-16-4-12-6-8-8-7-13-2-10 1-4 2 3 3 12 4 8 10 10 15 6 7 2 15 1 13-5 9-6h2l2-4 4-5 4-11v-8l-4-11-5-6-11-7-26-12-16-7-12-9-8-9-4-9-1-4v-19l4-12 8-10 8-7 13-4z"
|
||||
fill="#E87246"
|
||||
transform="translate(702,694)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h38l-3 4h-2l-4 8v70l3 13 4 6 6 5 6 2h12l11-4 10-9 5-5 8-11 10-12 5-5-2 4-3 4-1 11v22l5 5 6 4v2h-10l-9-3-6-5-4-9v-2l-3 3-6 7-14 7-4 1h-19l-10-4-8-6-8-15-2-8-1-9-1-60-9-10z"
|
||||
fill="#E87246"
|
||||
transform="translate(964,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 14 1 10 5 9 8 5 10 3 13 1 61 2 10 5 2 4 4h-39l2-4 5-4 1-6v-63l-1-13-4-10-7-6-6-2h-14l-12 6-7 8-3 6-1-4 5-8 8-8 10-5z"
|
||||
fill="#E87447"
|
||||
transform="translate(1223,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h13l16 1-1 25v74l2 7 8 5v2h-38l2-4 5-5 1-3v-90l-4-6-4-5z"
|
||||
fill="#E87246"
|
||||
transform="translate(882,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 2 5 5 3 6v9l-4 11-7 13-1 4-1 60v18l1 16 5 4 7 4v2h-41l2-4 6-4 2-4 1-8v-81l-3-5-6-6 1-2 5 1h12l8-3 7-9 3-6v-11l-4-6-6-5z"
|
||||
fill="#E87145"
|
||||
transform="translate(1567,707)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h9l10 2 13 7 12 11 7 11-1 3-7-11-5-6-10-7-11-5-13-2-10 3-11 6-5 4-6 9-6 15-1 5v20l5 15 6 12 9 10 8 5 13 4h11l10-3 8-4 3-3h2v-2h2l2-4 7-10 2 1-8 12-8 8-17 8-7 2h-16l-16-5-12-6-8-8-7-10-5-15-1-6v-19l5-16 6-9 8-9 14-8 12-4z"
|
||||
fill="#E87347"
|
||||
transform="translate(817,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h37l-6 7-3 4v73l3 11 6 8 2 3v3h-6l-6-3-7-6-1-5h-2l-6-8-2-12-1-65-5-6-3-2z"
|
||||
fill="#E8774A"
|
||||
transform="translate(1412,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 15 1-3 2-12 1-16 8-8 5-1 66v13l1 10 6 2 3 2v3h-37v-3l7-6 1-2v-66h-2l-2 4-12 13-7 8v-3l12-14 9-10h2l2-4 6-7 11-9 9-6z"
|
||||
fill="#E87549"
|
||||
transform="translate(1141,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h22l8 3 11 7 8 10 4 8 2 7 1 9v14l-2 15-6 12-9 10-8 8-12 6-10 3-13 1-14-3-5-2 4-1 11 3h10l8-3 7-3 9-7 8-14 3-8 1-6v-30l-4-13-6-10-8-7-7-3-5-1h-8l-10 3-10 6-7 8-3-1h2l2-4 9-9 10-6z"
|
||||
fill="#E8764A"
|
||||
transform="translate(1346,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h12l10 4 10 8 7 9 8 16 10 31-1 4-8-24-5-13-6-11-6-8-1-3-4-2-9-6-4-2h-13l-12 6-6 7-1 3v10l4 6 8 6 11 5 12 4 19 9 7 6 5 9 1 2v13l-3 8-9 10-9 4-4 1h-19l-11-4-7-8-3-6 1-2 7 11 8 5 5 1h18l10-3 5-5 3-8v-7l-4-6-9-6-24-10-17-9-8-8-3-7-1-15 2-9 4-6 10-7z"
|
||||
fill="#EFB382"
|
||||
transform="translate(1613,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h14l12 4 14 8 4 5-5-2-14-8-10-4-11-1-16 8-5 5-1 3v12l6 7 12 7 19 7 17 8 8 8 5 10v10l-4 10-6 8-5 4-9 3h-22l-7-3-6-4-7-11-4-13-1-9 2 4 6 16 6 10 6 5 7 2h19l10-4 4-5 2-4v-11l-4-6-10-6-22-9-16-8-7-6-6-12-1-4v-9l4-11 5-6 10-6z"
|
||||
fill="#F0B585"
|
||||
transform="translate(1692,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h38l-10 10-1 3-1 52-7 8-8 11-2 1-1-74-7-8z"
|
||||
fill="#E87246"
|
||||
transform="translate(1040,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 3 7 7 9 11 10 11 13 17 18 27 8 14 10 19 8 19 9 27 2 9v7l-4 6-5 3-2 1h-6l-11-7-14-12-8-7-2-3 4 1v2l4 1v2l4 1 7 6v2l3 1 2 2 5 2 6 3 5-1 5-8-1-8-2-10-3-10-4-10-4-14-4-10-5-9v-2h-2l-4-9-3-4-5-10-14-20-2-2-3-5-5-4-12-16-9-11-10-10z"
|
||||
fill="#F0BA8A"
|
||||
transform="translate(479,727)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 1 3 11 3 14 6 3 2v2l12 2 6 3 10 10 4 5-5 3-10-4-4-3-16-7-9-4-8-3-6-4-4-5-3-5z"
|
||||
fill="#E87145"
|
||||
transform="translate(1590,782)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0v3l-4 5-1 11v22l5 5 6 4v2h-10l-9-3-6-5-4-9v-2l-3 1 1-4 5-5 7-9z"
|
||||
fill="#E87549"
|
||||
transform="translate(1072,809)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h12l10 4 10 8 7 9 8 16 10 31-1 4-8-24-5-13-6-11-6-8-1-3-4-2-9-6-4-2h-13l-12 6-6 7-1 3v10l4 6 8 6 5 3-2 1-7-2-7-3-5-5-2 4-1-14 2-9 4-6 10-7z"
|
||||
fill="#EFAF7F"
|
||||
transform="translate(1613,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 2 10 6 1 4 9 3 10 4 4 1 1 2 5 3 3 2 1 6-4 1-10-6-11-5-12-5-4-1v-2l-6-2-4-4v-8z"
|
||||
fill="#E87145"
|
||||
transform="translate(1678,786)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 2 5 5 3 6v9l-4 11-7 13-1 4-1 60v34l-2-1-2-10v-20l1-44 1-21-2-4 1-1-5-1 4-2 6-4 7-11 1-2v-11l-4-6-6-5z"
|
||||
fill="#F1BE8D"
|
||||
transform="translate(1567,707)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 27 1v8l-5 5-11 10-2-3-1-9-4-6-4-4z"
|
||||
fill="#E87549"
|
||||
transform="translate(1089,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h6l4 4 2 4-1 7-5 5-3 2-6-1-6-7v-7l7-6z"
|
||||
fill="#E87549"
|
||||
transform="translate(1557,707)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h15l6 3 2 2v7l-6 5-5-1-7-8-3-2h-10l-3 1h-9v-1l13-3z"
|
||||
fill="#E8794C"
|
||||
transform="translate(933,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 3 3v2h2l3 6 6 5 7 2h3l-1-2 6 1 3 1h15l10-4 7-7 3-5 2 1-5 9-6 5-10 5-5 1h-19l-12-5-7-7-6-10z"
|
||||
fill="#EFAE7E"
|
||||
transform="translate(1426,837)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 3 7 7 9 11 10 11 13 17 18 27 3 7-5-2-2-5-4-7-8-12-5-7-2-2-3-5-5-4-12-16-9-11-10-10z"
|
||||
fill="#F0B887"
|
||||
transform="translate(479,727)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 3 3 3 12 4 8 10 10 15 6 7 2 15 1 8-2-1 3-9 2h-10l-16-4-12-6-8-8-7-13-2-10z"
|
||||
fill="#E9794D"
|
||||
transform="translate(656,818)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h12l10 4 10 8 7 9 8 16 10 31-1 4-8-24-5-13-6-11-6-8-1-3-4-2-9-6-4-2h-13l-3 1-1-3z"
|
||||
fill="#EFB483"
|
||||
transform="translate(1613,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 5 2 5 5 3 6v9l-4 11-7 13-2 11h-2v-9l-2-4 1-1-5-1 4-2 6-4 7-11 1-2v-11l-4-6-6-5z"
|
||||
fill="#EEA878"
|
||||
transform="translate(1567,707)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h22l4 2h-11l-2 1 2 1v2l-7-1h-8l-10 3-10 6-7 8-3-1h2l2-4 9-9 10-6z"
|
||||
fill="#EFB383"
|
||||
transform="translate(1346,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 16 1 13 4 9 7 8 8 4 6-1 2-11-13-10-7-6-3-11-2h-12l-12 2-5 2-4-1 2-2 11-3z"
|
||||
fill="#EA8658"
|
||||
transform="translate(702,694)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 8 7 28 28h-4l-12-12-4-1v-3l-7-7-4-1-7-7z"
|
||||
fill="#ED9F70"
|
||||
transform="translate(439,688)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 2 4 6 16 6 10 6 5 7 2 17 1v2h-19l-7-3-6-4-7-11-4-13z"
|
||||
fill="#ED9F70"
|
||||
transform="translate(1670,819)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h9l10 2 13 7 12 11 7 11-1 3-7-11-5-6-10-7-11-5-13-2-4-1z"
|
||||
fill="#EC9465"
|
||||
transform="translate(817,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h9l6 1-1 2h-14v2l-12 6-7 8-3 6-1-4 5-8 8-8z"
|
||||
fill="#EFB383"
|
||||
transform="translate(1213,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 15 1-3 2-12 1-16 8-12 7 2-4 5-4 16-9z"
|
||||
fill="#EEA777"
|
||||
transform="translate(1141,748)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 12 10 8 7 2 4-4-2h-4l-11-11-4-5v-2l-3-1z"
|
||||
fill="#EEA676"
|
||||
transform="translate(391,646)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 2 1-3 3v2h-2l-1 8 9 1 6-2-1 5-5 3-6-1-6-7v-7z"
|
||||
fill="#F0BA89"
|
||||
transform="translate(1555,708)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 12 10 8 7 9 7 5 5 10 9-2 2-12-11-15-14-10-8-9-8z"
|
||||
fill="#EFB282"
|
||||
transform="translate(459,783)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0h12l10 4 10 8 1 4-3-1v-2l-4-2-9-6-4-2h-13l-3 1-1-3z"
|
||||
fill="#F0BA8B"
|
||||
transform="translate(1613,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 12 19 10 15-1 3-4-5-11-18-10-15z"
|
||||
fill="#EFB181"
|
||||
transform="translate(493,769)"
|
||||
/>
|
||||
<path d="m0 0 5 2 6 4v1h-12l-8-3v-2l2-1h6z" fill="#EFAD7D" transform="translate(1515,854)" />
|
||||
<path
|
||||
d="m0 0h3l9 16 5 10v2l-3-1-8-15v-2h-2l-4-9z"
|
||||
fill="#F0BD8C"
|
||||
transform="translate(542,810)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 17 2v1l-9 1v2l3 5v33h-1l-1-31-3-5-6-6z"
|
||||
fill="#F0BA89"
|
||||
transform="translate(1541,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 7 1 8 2h10l9-3 3 1-1 3-12 2-12-1-12-4z"
|
||||
fill="#EC9566"
|
||||
transform="translate(1323,855)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 8 1 11 4 13 8 3 4-5-2-14-8-10-4-6-2z"
|
||||
fill="#F1BE8D"
|
||||
transform="translate(1701,747)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 3 1 4 2-10 3-10 6-7 8-3-1h2l2-4 9-9z"
|
||||
fill="#EFB181"
|
||||
transform="translate(1339,750)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 10 3 9 7 8 8 4 6-1 2-11-13-10-7-10-5z"
|
||||
fill="#EC9365"
|
||||
transform="translate(721,696)"
|
||||
/>
|
||||
<path d="m0 0 11 7 2 1v2h-28v-1h18z" fill="#F0B887" transform="translate(1567,851)" />
|
||||
<path
|
||||
d="m0 0h9v1l-10 3-11 6-6 5 2-4 3-3-1-2 7-4z"
|
||||
fill="#ED9F72"
|
||||
transform="translate(1692,747)"
|
||||
/>
|
||||
<path d="m0 0h2l-2 6 3 3-1 3h-12l2-4 6-4z" fill="#EFB584" transform="translate(1549,849)" />
|
||||
<path d="m0 0h4l-2 4-6 5-11 4h-9v-1l16-4 5-3z" fill="#F0B989" transform="translate(1651,846)" />
|
||||
<path
|
||||
d="m0 0 2 1-8 12-8 8-8 4h-2v-2l8-4 3-3h2v-2h2l2-4z"
|
||||
fill="#EB8C5E"
|
||||
transform="translate(868,832)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 2 2-7 11-14 14-6 3 1-3 6-4 10-10 7-10z"
|
||||
fill="#F0BC8B"
|
||||
transform="translate(1396,824)"
|
||||
/>
|
||||
<path d="m0 0 9 6 7 7 7 11-1 3-7-11-5-6-10-7z" fill="#EC9B6C" transform="translate(845,754)" />
|
||||
<path d="m0 0 2 2-2 3-1 32h-1l-1-10v-21z" fill="#F0BB8A" transform="translate(1515,753)" />
|
||||
<path
|
||||
d="m0 0 4 1 11 7 8 10 3 6-2 1-6-10-7-7-11-7z"
|
||||
fill="#F0B685"
|
||||
transform="translate(1372,750)"
|
||||
/>
|
||||
<path d="m0 0 2 4 6 16 3 7-4-1-5-13-2-8z" fill="#F0B585" transform="translate(1670,819)" />
|
||||
<path d="m0 0 1 3-7 7-9 11-6 5 2-4 11-13z" fill="#EFB080" transform="translate(1094,783)" />
|
||||
<path d="m0 0 2 2 10 21 1 5h-2l-9-20-3-7z" fill="#EFB383" transform="translate(524,817)" />
|
||||
<path d="m0 0 8 6 15 14-1 2-12-11-10-9z" fill="#EEAC7C" transform="translate(508,825)" />
|
||||
<path
|
||||
d="m0 0 5 2 5 5 3 6v9l-3 3v-13l-4-6-6-5z"
|
||||
fill="#EFB080"
|
||||
transform="translate(1567,707)"
|
||||
/>
|
||||
<path
|
||||
d="m0 0 4 2 6 5v3l10 10-1 2-12-12-4-5v-2l-3-1z"
|
||||
fill="#E87145"
|
||||
transform="translate(391,646)"
|
||||
/>
|
||||
<path d="m0 0 3 3 3 12 3 7h-3l-4-8-2-10z" fill="#E97D51" transform="translate(656,818)" />
|
||||
<path d="m0 0 2 1-5 9-6 5h-2l2-4 8-9z" fill="#EEAA7A" transform="translate(1493,839)" />
|
||||
<path d="m0 0 15 1-3 2-15 1v-3z" fill="#F0B887" transform="translate(1141,748)" />
|
||||
<path d="m0 0 4 2 8 10v2l-4-2-6-7z" fill="#EFAD7D" transform="translate(439,688)" />
|
||||
<path d="m0 0 3 3v2h2l3 6 6 5v2l-4-2-8-9-3-6z" fill="#EFB282" transform="translate(1426,837)" />
|
||||
<path
|
||||
d="m0 0m-1 1m-1 1m-1 1 1 3-7 8-3 6-1-4 5-8z"
|
||||
fill="#EEA777"
|
||||
transform="translate(1203,753)"
|
||||
/>
|
||||
<path d="m0 0 3 1 4 2-10 3-4 1 2-4z" fill="#EEA475" transform="translate(1339,750)" />
|
||||
<path d="m0 0 8 1 11 4-1 2-18-6z" fill="#EFB383" transform="translate(1701,747)" />
|
||||
<path d="m0 0 4 1 7 7 4 6-1 2-11-13z" fill="#ED9F70" transform="translate(737,706)" />
|
||||
<path d="m0 0h3l6 7 4 3-6-1-5-5z" fill="#EEA575" transform="translate(1679,846)" />
|
||||
<path d="m0 0 4 1 7 6v3l-3-1v-2l-4-2-4-3z" fill="#F3C49F" transform="translate(1635,753)" />
|
||||
<path d="m0 0h6l1 3-8-1zm4 3 3 2h-17v-1z" fill="#EA875A" transform="translate(1223,747)" />
|
||||
<path d="m0 0m-1 1 3 2-12 7 2-4 5-4z" fill="#EDA273" transform="translate(1123,757)" />
|
||||
<path d="m0 0 5 4 7 9-2 2-8-10z" fill="#EFB483" transform="translate(446,707)" />
|
||||
<path d="m0 0 1 2-4 9h-3v-3z" fill="#ED9F70" transform="translate(1578,731)" />
|
||||
<path d="m0 0h10l-1 2-9 1-3 1-1-3z" fill="#F3CBA5" transform="translate(1613,747)" />
|
||||
<path d="m0 0h9l-2 4 1 4-7-6z" fill="#F0B887" transform="translate(1486,747)" />
|
||||
<path d="m0 0h4l-2 4-4 4h-2l2-5z" fill="#EC9A6B" transform="translate(1651,846)" />
|
||||
<path d="m0 0 4 2 6 5-4 1-3-3v-2l-3-1z" fill="#EA8255" transform="translate(391,646)" />
|
||||
<path d="m0 0 5 1-4 1z" fill="#F1BE8D" transform="translate(1318,853)" />
|
||||
<path d="m0 0" fill="#F1BE8D" transform="translate(1316,852)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -16,3 +16,29 @@ export const SunFilledIcon = ({size = 24, width, height, ...props}: IconSvgProps
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SunLinearIcon = ({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}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7 9.23858 7 12C7 14.7614 9.23858 17 12 17Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M12 2V4M12 20V22M4 12H2M22 12H20M19.778 4.223L17.556 6.254M4.222 4.223L6.444 6.254M6.444 17.556L4.222 19.778M19.778 19.777L17.556 17.555"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import {useMemo, useState} from "react";
|
||||
import {Tabs, Tab, Card, CardBody, Image, Button, RadioGroup, Radio} from "@nextui-org/react";
|
||||
import get from "lodash/get";
|
||||
import NextLink from "next/link";
|
||||
import NextImage from "next/image";
|
||||
|
||||
@@ -238,7 +237,7 @@ export const CustomThemes = () => {
|
||||
<CodeWindow
|
||||
showWindowIcons
|
||||
className="max-h-[440px] min-h-[390px]"
|
||||
highlightLines={get(codeHighlights, selectedTheme)}
|
||||
highlightLines={codeHighlights[selectedTheme]}
|
||||
isScrollable={false}
|
||||
language="jsx"
|
||||
title="tailwind.config.js"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import React, {ReactNode} from "react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
import {tv} from "tailwind-variants";
|
||||
import {Card, CardHeader, CardBody, LinkProps, SlotsToClasses} from "@nextui-org/react";
|
||||
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",
|
||||
@@ -17,7 +16,7 @@ const styles = tv({
|
||||
iconWrapper:
|
||||
"flex justify-center p-2 rounded-full items-center bg-secondary-100/80 text-pink-500",
|
||||
title: "text-base font-semibold",
|
||||
description: "font-normal text-base text-default-500",
|
||||
description: "font-normal text-medium text-default-500",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,10 +36,12 @@ interface FeaturesGridProps {
|
||||
export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames, ...props}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
const slots = styles();
|
||||
|
||||
const handleClick = (feat: Feature) => {
|
||||
trackEvent("FeaturesGrid - Click", {
|
||||
posthog.capture("FeaturesGrid - Click", {
|
||||
name: feat.title,
|
||||
action: "click",
|
||||
category: "docs",
|
||||
@@ -72,7 +73,7 @@ export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames,
|
||||
<CardHeader className={slots.header({class: classNames?.header})}>
|
||||
<div className={slots.iconWrapper({class: classNames?.iconWrapper})}>{feat.icon}</div>
|
||||
<p className={slots.title({class: classNames?.title})}>{feat.title}</p>
|
||||
{feat.isExternal && <LinkIcon className="text-white" height={18} width={18} />}
|
||||
{feat.isExternal && <LinkIcon height={18} width={18} />}
|
||||
</CardHeader>
|
||||
{feat.description ? (
|
||||
<CardBody className={slots.body({class: classNames?.body})}>
|
||||
|
||||
@@ -107,7 +107,7 @@ export const FloatingComponents: React.FC<{}> = () => {
|
||||
<Tooltip
|
||||
className="text-sm animate-[levitate_14s_ease_infinite]"
|
||||
color="secondary"
|
||||
content="Developers love Next.js"
|
||||
content="Build beautiful apps faster"
|
||||
isOpen={!isTablet}
|
||||
placement="top"
|
||||
style={{
|
||||
|
||||
@@ -4,46 +4,51 @@ import NextLink from "next/link";
|
||||
import {Button, Link, Chip, Snippet} from "@nextui-org/react";
|
||||
import {ArrowRightIcon} from "@nextui-org/shared-icons";
|
||||
import dynamic from "next/dynamic";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {FloatingComponents} from "./floating-components";
|
||||
|
||||
import {GithubIcon} from "@/components/icons";
|
||||
import {title, subtitle} from "@/components/primitives";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BgLooper = dynamic(() => import("./bg-looper").then((mod) => mod.BgLooper), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export const Hero = () => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handlePressAnnouncement = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
posthog.capture("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "home - gero",
|
||||
category: "home - hero",
|
||||
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">
|
||||
<div className="relative z-20 flex flex-col w-full gap-6 lg:w-1/2 xl:mt-10">
|
||||
<div className="flex justify-center w-full md:hidden">
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="bg-default-100/50 hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
|
||||
color="default"
|
||||
href="/blog/v2.3.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressAnnouncement("New version v2.4.0", "/blog/v2.4.0")}
|
||||
className="bg-primary-100/50 border-1 hover:bg-primary-100/80 border-primary-200/50 cursor-pointer"
|
||||
classNames={{
|
||||
content: "font-semibold text-primary-500 dark:text-primary-600 text-xs ",
|
||||
}}
|
||||
color="primary"
|
||||
href="/blog/v2.6.0"
|
||||
variant="flat"
|
||||
onClick={() => handlePressAnnouncement("New version v2.6.0", "/blog/v2.6.0")}
|
||||
>
|
||||
New version v2.4.0
|
||||
New version v2.6.0
|
||||
<span aria-label="emoji" role="img">
|
||||
🚀
|
||||
🔥
|
||||
</span>
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="text-center leading-8 md:leading-10 md:text-left">
|
||||
<div className="leading-8 text-center md:leading-10 md:text-left">
|
||||
<div className="inline-block">
|
||||
<h1 className={title()}>Make </h1>
|
||||
<h1 className={title({color: "violet"})}>beautiful </h1>
|
||||
@@ -53,7 +58,7 @@ export const Hero = () => {
|
||||
<h2 className={subtitle({fullWidth: true, class: "text-center md:text-left"})}>
|
||||
Beautiful, fast and modern React UI library.
|
||||
</h2>
|
||||
<div className="flex flex-col md:flex-row items-center gap-4">
|
||||
<div className="flex flex-col items-center gap-4 md:flex-row">
|
||||
<Button
|
||||
as={NextLink}
|
||||
className="w-full md:h-11 md:w-auto"
|
||||
@@ -68,7 +73,7 @@ export const Hero = () => {
|
||||
radius="full"
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Get Started", {
|
||||
posthog.capture("Hero - Get Started", {
|
||||
name: "Get Started",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
@@ -79,12 +84,12 @@ export const Hero = () => {
|
||||
Get Started
|
||||
</Button>
|
||||
<Snippet
|
||||
className="w-full rounded-full hidden md:flex sm:w-auto"
|
||||
className="hidden w-full rounded-full md:flex sm:w-auto"
|
||||
copyButtonProps={{
|
||||
radius: "full",
|
||||
}}
|
||||
onCopy={() => {
|
||||
trackEvent("Hero - Copy Install Command", {
|
||||
posthog.capture("Hero - Copy Install Command", {
|
||||
name: "Copy",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
@@ -105,7 +110,7 @@ export const Hero = () => {
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Github", {
|
||||
posthog.capture("Hero - Github", {
|
||||
name: "Github",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
|
||||
@@ -5,13 +5,13 @@ import {ArrowRightIcon} from "@nextui-org/shared-icons";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import NextLink from "next/link";
|
||||
import {Code} from "@nextui-org/react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
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 = [
|
||||
{
|
||||
@@ -25,7 +25,7 @@ const bannerSuggestions = [
|
||||
title: "NextUI + Next.js",
|
||||
description: (
|
||||
<>
|
||||
NextUI is fully compatible with the new Next.js 13 <Code>app/</Code> directory structure.
|
||||
NextUI is fully compatible with the new Next.js <Code>app/</Code> directory structure.
|
||||
</>
|
||||
),
|
||||
icon: <NextJsIcon className="text-pink-500" />,
|
||||
@@ -36,6 +36,8 @@ const bannerSuggestions = [
|
||||
export const InstallBanner = () => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
return (
|
||||
<section
|
||||
className={sectionWrapper({
|
||||
@@ -71,7 +73,7 @@ export const InstallBanner = () => {
|
||||
radius="full"
|
||||
size="md"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Get Started", {
|
||||
posthog.capture("InstallBanner - Get Started", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "/docs/guide/installation",
|
||||
@@ -90,7 +92,7 @@ export const InstallBanner = () => {
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Github", {
|
||||
posthog.capture("InstallBanner - Github", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "https://github.com/nextui-org/nextui",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import type {ReactNode} from "react";
|
||||
import type {ScrollShadowProps} from "@nextui-org/react";
|
||||
|
||||
import {Children, cloneElement} from "react";
|
||||
import {ScrollShadow} from "@nextui-org/react";
|
||||
import {cn} from "@nextui-org/react";
|
||||
|
||||
interface MarqueeProps {
|
||||
className?: string;
|
||||
reverse?: boolean;
|
||||
shadow?: boolean;
|
||||
duration?: number;
|
||||
pauseOnHover?: boolean;
|
||||
vertical?: boolean;
|
||||
children?: ReactNode;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const Marquee = ({
|
||||
className,
|
||||
reverse,
|
||||
duration = 40,
|
||||
shadow = false,
|
||||
pauseOnHover = false,
|
||||
vertical = false,
|
||||
children,
|
||||
...props
|
||||
}: MarqueeProps) => {
|
||||
const shadowProps: ScrollShadowProps = {
|
||||
isEnabled: shadow,
|
||||
offset: -20,
|
||||
size: 300,
|
||||
orientation: "vertical",
|
||||
visibility: "both",
|
||||
};
|
||||
|
||||
const Wrapper = shadow ? ScrollShadow : "div";
|
||||
|
||||
const componentProps = shadow ? {...props, ...shadowProps} : props;
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
{...componentProps}
|
||||
className={cn(
|
||||
"flex [--gap:1rem]",
|
||||
{
|
||||
"w-full": !vertical,
|
||||
"overflow-y-hidden": vertical,
|
||||
"overflow-x-hidden": !vertical,
|
||||
"max-h-[calc(100vh_-_200px)]": vertical,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
// @ts-ignore
|
||||
"--duration": `${duration}s`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn("flex w-max items-stretch gap-[--gap]", {
|
||||
"flex-col": vertical,
|
||||
"h-full": vertical,
|
||||
"animate-scrolling-banner": !vertical,
|
||||
"animate-scrolling-banner-vertical": vertical,
|
||||
"[animation-direction:reverse]": reverse,
|
||||
"hover:[animation-play-state:paused]": pauseOnHover,
|
||||
})}
|
||||
>
|
||||
{Children.map(children, (child) =>
|
||||
child && typeof child === "object" && "type" in child ? cloneElement(child) : child,
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Marquee;
|
||||
@@ -0,0 +1,182 @@
|
||||
"use client";
|
||||
|
||||
import {Button, Chip} from "@nextui-org/react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useTheme} from "next-themes";
|
||||
|
||||
import {sectionWrapper, title, titleWrapper, subtitle} from "../primitives";
|
||||
|
||||
import Marquee from "./marquee";
|
||||
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
|
||||
export const NextUIProSection = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const {theme} = useTheme();
|
||||
const isDarkMode = theme === "dark";
|
||||
|
||||
let imgSrc: string;
|
||||
|
||||
if (isDarkMode) {
|
||||
imgSrc = isMobile
|
||||
? "/images/nextuipro-section-background@mobile.webp"
|
||||
: "/images/nextuipro-section-background.webp";
|
||||
} else {
|
||||
imgSrc = isMobile
|
||||
? "/images/nextuipro-section-background-light@mobile.webp"
|
||||
: "/images/nextuipro-section-background-light.webp";
|
||||
}
|
||||
|
||||
const mobileClassName: string = isDarkMode
|
||||
? "h-full w-full bg-[radial-gradient(at_40%_80%,_rgba(255,255,255,_0)_5%,_rgba(0,0,0,_0.8)_50%,_rgba(0,0,0,1)_100%)]"
|
||||
: "h-full w-full bg-[radial-gradient(at_40%_80%,_rgba(0,0,0,_0)_5%,_rgba(255,255,255,_0.8)_50%,_rgba(255,255,255,1)_100%)]";
|
||||
|
||||
const webClassName: string = isDarkMode
|
||||
? "h-full w-full bg-[radial-gradient(at_80%_50%,_rgba(255,255,255,_0)_20%,_rgba(0,0,0,_0.8)_40%,_rgba(0,0,0,1)_100%)]"
|
||||
: "h-full w-full bg-[radial-gradient(at_80%_50%,_rgba(0,0,0,_0)_20%,_rgba(255,255,255,_0.9)_40%,_rgba(255,255,255,1)_100%)]";
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
const CheckmarkIcon = () => (
|
||||
<svg fill="none" height="11" viewBox="0 0 13 11" width="13" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1 6.4L4.14286 10L12 1"
|
||||
stroke="#006FEE"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className={sectionWrapper({class: "mt-16 lg:mt-44 overflow-hidden"})}>
|
||||
<div className="flex flex-col gap-8 min-h-[480px]">
|
||||
<div className="z-30 flex w-screen h-full flex-col items-start justify-center leading-8 pt-4">
|
||||
<Chip
|
||||
classNames={{
|
||||
base: "ml-0.5 transition-colors bg-gradient-to-br from-cyan-600 to-blue-600",
|
||||
content: "text-tiny font-semibold",
|
||||
}}
|
||||
color="primary"
|
||||
size="sm"
|
||||
>
|
||||
PRO
|
||||
</Chip>
|
||||
<div className={titleWrapper({class: "mt-2 inline md:block"})}>
|
||||
<h1 className={title({size: "lg", class: "[text-shadow:_0_3px_0_rgb(0_0_0_/_10%)]"})}>
|
||||
Ship
|
||||
</h1>
|
||||
<h1
|
||||
className={title({
|
||||
size: "lg",
|
||||
color: "blue",
|
||||
class: "[text-shadow:_0_3px_0_rgb(0_0_0_/_10%)]",
|
||||
})}
|
||||
>
|
||||
faster
|
||||
</h1>
|
||||
<h1 className={title({size: "lg", class: "[text-shadow:_0_3px_0_rgb(0_0_0_/_10%)]"})}>
|
||||
with
|
||||
</h1>
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
<h1 className={title({size: "lg", class: "[text-shadow:_0_3px_0_rgb(0_0_0_/_10%)]"})}>
|
||||
beautiful
|
||||
</h1>
|
||||
<h1 className={title({size: "lg", class: "[text-shadow:_0_3px_0_rgb(0_0_0_/_10%)]"})}>
|
||||
components
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className={subtitle({class: "pr-12 text-foreground-500"})}>
|
||||
Premade templates of over 210+ beautiful and responsive components, professionally
|
||||
created by the team behind NextUI.
|
||||
</p>
|
||||
<div className="mt-4 text-foreground-600 font-medium">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<CheckmarkIcon />
|
||||
210+ Components
|
||||
</div>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<CheckmarkIcon />
|
||||
Lifetime Access
|
||||
</div>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<CheckmarkIcon />
|
||||
Free Updates
|
||||
</div>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<CheckmarkIcon />
|
||||
Figma Files Included
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
as={"a"}
|
||||
className="px-6 flex items-center"
|
||||
color="primary"
|
||||
href="https://nextui.pro?utm_source=nextui.org&utm_medium=nextui-homepage-section"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Explore NextUI Pro
|
||||
<svg
|
||||
fill="none"
|
||||
height="21"
|
||||
viewBox="0 0 20 21"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.0254 5.44189L17.0837 10.5002L12.0254 15.5586"
|
||||
stroke="white"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M2.91602 10.5H16.941"
|
||||
stroke="white"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-hidden">
|
||||
<Marquee
|
||||
vertical
|
||||
className="h-78 hidden md:flex w-screen mt-4 md:absolute md:top-0 md:inset-0 isolate md:max-h-dvh"
|
||||
duration={isMobile ? 240 : 60}
|
||||
>
|
||||
<img
|
||||
alt="Hero Background"
|
||||
className="w-full"
|
||||
height={3255}
|
||||
loading="lazy"
|
||||
src={imgSrc}
|
||||
width={1920}
|
||||
/>
|
||||
</Marquee>
|
||||
<div className="absolute md:hidden inset-0 pointer-events-none top-0 z-20">
|
||||
<div className={mobileClassName} />
|
||||
</div>
|
||||
|
||||
<div className="absolute hidden md:block md:inset-0 md:pointer-events-none md:top-0 md:z-20">
|
||||
<div className={webClassName} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {Link} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
export type Sponsor = {
|
||||
name: string;
|
||||
@@ -11,13 +10,15 @@ export type Sponsor = {
|
||||
};
|
||||
|
||||
export const SponsorItem = ({name, href, logo}: Sponsor) => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
return (
|
||||
<Link
|
||||
isExternal
|
||||
className="flex flex-col items-center justify-center"
|
||||
href={href}
|
||||
onClick={() => {
|
||||
trackEvent("Hero - Sponsors", {
|
||||
posthog.capture("Hero - Sponsors", {
|
||||
name,
|
||||
action: "click",
|
||||
category: "hero",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
|
||||
import {sectionWrapper} from "@/components/primitives";
|
||||
import {Story2DesignLogo, CodeRabbitLogo, ScrumbuissLogo} from "@/components/icons/sponsors";
|
||||
import {Story2DesignLogo, CodeRabbitLogo} from "@/components/icons/sponsors";
|
||||
import {HeartFilledIcon} from "@/components/icons";
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {Sponsor, SponsorItem} from "@/components/marketing/sponsor-item";
|
||||
@@ -17,11 +17,6 @@ const sponsors: Sponsor[] = [
|
||||
href: "https://coderabbit.ai/?utm_source=nextui&utm_marketing=oss",
|
||||
logo: <CodeRabbitLogo />,
|
||||
},
|
||||
{
|
||||
name: "Scrumbuiss",
|
||||
href: "https://www.scrumbuiss.com/?utm_source=nextui&utm_marketing=oss",
|
||||
logo: <ScrumbuissLogo />,
|
||||
},
|
||||
];
|
||||
|
||||
export const Sponsors = () => {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
"use client";
|
||||
|
||||
import {FC, useMemo, useRef} from "react";
|
||||
import {Avatar, AvatarProps, Button, Spacer, Tooltip} from "@nextui-org/react";
|
||||
import {clamp} from "@nextui-org/shared-utils";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {sectionWrapper, titleWrapper, title, subtitle} from "../primitives";
|
||||
|
||||
import {FeaturesGrid} from "./features-grid";
|
||||
|
||||
import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/components/icons";
|
||||
import {Sponsor, SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors";
|
||||
import {SonarPulse} from "@/components/sonar-pulse";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
|
||||
export interface SupportProps {
|
||||
sponsors: Sponsor[];
|
||||
}
|
||||
|
||||
const supportAccounts = [
|
||||
{
|
||||
title: "Open Collective",
|
||||
description: "Sponsor the NextUI maintainers.",
|
||||
icon: <OpenCollectiveIcon className="text-pink-500" />,
|
||||
href: "https://opencollective.com/nextui",
|
||||
isExternal: true,
|
||||
},
|
||||
{
|
||||
title: "Patreon",
|
||||
description: "Sponsor the creator, Junior Garcia.",
|
||||
icon: <PatreonIcon className="text-pink-500" />,
|
||||
href: "https://www.patreon.com/jrgarciadev?fan_landing=true",
|
||||
isExternal: true,
|
||||
},
|
||||
];
|
||||
|
||||
const SONAR_PULSE_SIZE = 80;
|
||||
const SONAR_PULSE_CIRCLES_COUNT = 4;
|
||||
const SONAR_PULSE_RADIUS = 130;
|
||||
|
||||
const getSponsorName = (sponsor: Sponsor) => {
|
||||
if (!sponsor.name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return sponsor.name.slice(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
const getSponsorSize = (sponsor: Sponsor, isMobile: boolean) => {
|
||||
let size: AvatarProps["size"] = "md";
|
||||
const tier = sponsor.tier || getTier(sponsor.totalAmountDonated);
|
||||
|
||||
switch (tier) {
|
||||
case SPONSOR_TIERS.BRONZE:
|
||||
size = isMobile ? "sm" : "md";
|
||||
break;
|
||||
case SPONSOR_TIERS.SILVER:
|
||||
size = isMobile ? "sm" : "md";
|
||||
break;
|
||||
case SPONSOR_TIERS.GOLD:
|
||||
size = isMobile ? "md" : "lg";
|
||||
break;
|
||||
case SPONSOR_TIERS.PLATINUM:
|
||||
size = isMobile ? "md" : "lg";
|
||||
break;
|
||||
default:
|
||||
size = isMobile ? "sm" : "md";
|
||||
}
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
const getSponsorColor = (sponsor: Sponsor) => {
|
||||
const tier = sponsor.tier || getTier(sponsor.totalAmountDonated);
|
||||
|
||||
return SPONSOR_COLORS[tier] || "default";
|
||||
};
|
||||
|
||||
const getSponsorAvatarStyles = (index: number, sponsors: Sponsor[] = []) => {
|
||||
const angle = (index * 360) / sponsors.length;
|
||||
const radius = SONAR_PULSE_RADIUS;
|
||||
|
||||
// position the avatar randomly inside the sonar pulse
|
||||
const randomRadius = clamp(Math.floor((index + 1) * radius), radius * 0.4, radius);
|
||||
|
||||
const x = randomRadius * Math.cos((angle * Math.PI) / 180);
|
||||
const y = randomRadius * Math.sin((angle * Math.PI) / 180);
|
||||
|
||||
return {
|
||||
transform: `translate(${x}px, ${y}px)`,
|
||||
};
|
||||
};
|
||||
|
||||
export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
const sonarRef = useRef(null);
|
||||
const isMobile = useIsMobile();
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handleExternalLinkClick = (href: string) => {
|
||||
if (!href) return;
|
||||
window.open(href, "_blank");
|
||||
};
|
||||
|
||||
const handleBecomeSponsor = () => {
|
||||
posthog.capture("Support - Become a sponsor", {
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
});
|
||||
|
||||
handleExternalLinkClick(supportAccounts[0].href);
|
||||
};
|
||||
const renderSponsors = useMemo(() => {
|
||||
if (!sponsors.length) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute rounded-full bg-transparent"
|
||||
style={{
|
||||
width: `${SONAR_PULSE_RADIUS}px`,
|
||||
top: SONAR_PULSE_RADIUS / 6,
|
||||
left: SONAR_PULSE_RADIUS / 6,
|
||||
}}
|
||||
>
|
||||
{sponsors.map((sponsor, index) => (
|
||||
<Avatar
|
||||
key={`${sponsor.MemberId}-${index}`}
|
||||
isBordered
|
||||
showFallback
|
||||
className="absolute cursor-pointer 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-200"
|
||||
color={getSponsorColor(sponsor) as AvatarProps["color"]}
|
||||
name={getSponsorName(sponsor)}
|
||||
size={getSponsorSize(sponsor, isMobile)}
|
||||
src={sponsor.image}
|
||||
style={getSponsorAvatarStyles(index, sponsors)}
|
||||
onClick={() => handleExternalLinkClick(sponsor["website"] || sponsor["profile"])}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}, [isMobile, sponsors]);
|
||||
|
||||
return (
|
||||
<section className={sectionWrapper({class: "flex flex-col items-center z-20 mt-16 lg:mt-44"})}>
|
||||
<div className="max-w-4xl flex flex-col gap-8">
|
||||
<div>
|
||||
<div className={titleWrapper({class: "text-center items-center"})}>
|
||||
<div className="flex md:inline-flex flex-col md:flex-row items-center">
|
||||
<h1 className={title({size: "lg"})}>Support NextUI </h1>
|
||||
<HeartBoldIcon
|
||||
className="text-pink-500 animate-heartbeat"
|
||||
size={50}
|
||||
style={{
|
||||
animationDuration: "2.5s",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
className={subtitle({class: "md:w-full text-center flex justify-center items-center"})}
|
||||
>
|
||||
Using NextUI in a profit-making product, as a freelancer, or for fun projects? Your
|
||||
contributions will help to make NextUI better.
|
||||
</p>
|
||||
<Spacer y={12} />
|
||||
<FeaturesGrid
|
||||
classNames={{
|
||||
base: "lg:grid-cols-2",
|
||||
}}
|
||||
features={supportAccounts}
|
||||
/>
|
||||
<div
|
||||
ref={sonarRef}
|
||||
className="relative mt-32 md:mt-60 w-full flex items-center justify-center"
|
||||
>
|
||||
<SonarPulse
|
||||
circlesCount={SONAR_PULSE_CIRCLES_COUNT}
|
||||
color="#7928CA"
|
||||
icon={
|
||||
<Tooltip
|
||||
showArrow
|
||||
color="secondary"
|
||||
content={"Become a sponsor"}
|
||||
offset={10}
|
||||
radius="full"
|
||||
>
|
||||
<Button
|
||||
isIconOnly
|
||||
aria-label="Become a sponsor"
|
||||
className="z-50 w-auto h-auto bg-gradient-to-b from-[#FF1CF7] to-[#7928CA]"
|
||||
radius="full"
|
||||
onPress={handleBecomeSponsor}
|
||||
>
|
||||
<PlusLinearIcon
|
||||
className="flex items-center justify-center rounded-full text-white"
|
||||
size={54}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
playState="running"
|
||||
size={SONAR_PULSE_SIZE}
|
||||
>
|
||||
{renderSponsors}
|
||||
</SonarPulse>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1,212 +1,21 @@
|
||||
"use client";
|
||||
import {Support as SupportClient} from "./support-client";
|
||||
|
||||
import {FC, useMemo, useRef} from "react";
|
||||
import {Avatar, AvatarProps, Button, Spacer, Tooltip} from "@nextui-org/react";
|
||||
import {clamp, get} from "lodash";
|
||||
import {getAllSponsors} from "@/utils/get-all-sponsors";
|
||||
|
||||
import {sectionWrapper, titleWrapper, title, subtitle} from "../primitives";
|
||||
async function getData() {
|
||||
try {
|
||||
const sponsors = await getAllSponsors();
|
||||
|
||||
import {FeaturesGrid} from "./features-grid";
|
||||
|
||||
import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/components/icons";
|
||||
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[];
|
||||
return {
|
||||
sponsors,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
}
|
||||
|
||||
const supportAccounts = [
|
||||
{
|
||||
title: "Open Collective",
|
||||
description: "Sponsor the NextUI maintainers.",
|
||||
icon: <OpenCollectiveIcon className="text-pink-500" />,
|
||||
href: "https://opencollective.com/nextui",
|
||||
isExternal: true,
|
||||
},
|
||||
{
|
||||
title: "Patreon",
|
||||
description: "Sponsor the creator, Junior Garcia.",
|
||||
icon: <PatreonIcon className="text-pink-500" />,
|
||||
href: "https://www.patreon.com/jrgarciadev?fan_landing=true",
|
||||
isExternal: true,
|
||||
},
|
||||
];
|
||||
export default async function Support() {
|
||||
const data = await getData();
|
||||
|
||||
const SONAR_PULSE_SIZE = 80;
|
||||
const SONAR_PULSE_CIRCLES_COUNT = 4;
|
||||
const SONAR_PULSE_RADIUS = 130;
|
||||
|
||||
const getSponsorName = (sponsor: Sponsor) => {
|
||||
if (!sponsor.name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return sponsor.name.slice(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
const getSponsorSize = (sponsor: Sponsor, isMobile: boolean) => {
|
||||
let size: AvatarProps["size"] = "md";
|
||||
const tier = sponsor.tier || getTier(sponsor.totalAmountDonated);
|
||||
|
||||
switch (tier) {
|
||||
case SPONSOR_TIERS.BRONZE:
|
||||
size = isMobile ? "sm" : "md";
|
||||
break;
|
||||
case SPONSOR_TIERS.SILVER:
|
||||
size = isMobile ? "sm" : "md";
|
||||
break;
|
||||
case SPONSOR_TIERS.GOLD:
|
||||
size = isMobile ? "md" : "lg";
|
||||
break;
|
||||
case SPONSOR_TIERS.PLATINUM:
|
||||
size = isMobile ? "md" : "lg";
|
||||
break;
|
||||
default:
|
||||
size = isMobile ? "sm" : "md";
|
||||
}
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
const getSponsorColor = (sponsor: Sponsor) => {
|
||||
const tier = sponsor.tier || getTier(sponsor.totalAmountDonated);
|
||||
|
||||
return SPONSOR_COLORS[tier] || "default";
|
||||
};
|
||||
|
||||
const getSponsorAvatarStyles = (index: number, sponsors: Sponsor[] = []) => {
|
||||
const angle = (index * 360) / sponsors.length;
|
||||
const radius = SONAR_PULSE_RADIUS;
|
||||
|
||||
// position the avatar randomly inside the sonar pulse
|
||||
const randomRadius = clamp(Math.floor((index + 1) * radius), radius * 0.4, radius);
|
||||
|
||||
const x = randomRadius * Math.cos((angle * Math.PI) / 180);
|
||||
const y = randomRadius * Math.sin((angle * Math.PI) / 180);
|
||||
|
||||
return {
|
||||
transform: `translate(${x}px, ${y}px)`,
|
||||
};
|
||||
};
|
||||
|
||||
export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
const sonarRef = useRef(null);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const handleExternalLinkClick = (href: string) => {
|
||||
if (!href) return;
|
||||
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;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute rounded-full bg-transparent"
|
||||
style={{
|
||||
width: `${SONAR_PULSE_RADIUS}px`,
|
||||
top: SONAR_PULSE_RADIUS / 6,
|
||||
left: SONAR_PULSE_RADIUS / 6,
|
||||
}}
|
||||
>
|
||||
{sponsors.map((sponsor, index) => (
|
||||
<Avatar
|
||||
key={`${sponsor.MemberId}-${index}`}
|
||||
isBordered
|
||||
showFallback
|
||||
className="absolute cursor-pointer 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-200"
|
||||
color={getSponsorColor(sponsor) as AvatarProps["color"]}
|
||||
name={getSponsorName(sponsor)}
|
||||
size={getSponsorSize(sponsor, isMobile)}
|
||||
src={sponsor.image}
|
||||
style={getSponsorAvatarStyles(index, sponsors)}
|
||||
onClick={() =>
|
||||
handleExternalLinkClick(get(sponsor, "website") || get(sponsor, "profile"))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}, [isMobile, sponsors]);
|
||||
|
||||
return (
|
||||
<section className={sectionWrapper({class: "flex flex-col items-center z-20 mt-16 lg:mt-44"})}>
|
||||
<div className="max-w-4xl flex flex-col gap-8">
|
||||
<div>
|
||||
<div className={titleWrapper({class: "text-center items-center"})}>
|
||||
<div className="flex md:inline-flex flex-col md:flex-row items-center">
|
||||
<h1 className={title({size: "lg"})}>Support NextUI </h1>
|
||||
<HeartBoldIcon
|
||||
className="text-pink-500 animate-heartbeat"
|
||||
size={50}
|
||||
style={{
|
||||
animationDuration: "2.5s",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
className={subtitle({class: "md:w-full text-center flex justify-center items-center"})}
|
||||
>
|
||||
Using NextUI in a profit-making product, as a freelancer, or for fun projects? Your
|
||||
contributions will help to make NextUI better.
|
||||
</p>
|
||||
<Spacer y={12} />
|
||||
<FeaturesGrid
|
||||
classNames={{
|
||||
base: "lg:grid-cols-2",
|
||||
}}
|
||||
features={supportAccounts}
|
||||
/>
|
||||
<div
|
||||
ref={sonarRef}
|
||||
className="relative mt-32 md:mt-60 w-full flex items-center justify-center"
|
||||
>
|
||||
<SonarPulse
|
||||
circlesCount={SONAR_PULSE_CIRCLES_COUNT}
|
||||
color="#7928CA"
|
||||
icon={
|
||||
<Tooltip
|
||||
showArrow
|
||||
color="secondary"
|
||||
content={"Become a sponsor"}
|
||||
offset={10}
|
||||
radius="full"
|
||||
>
|
||||
<Button
|
||||
isIconOnly
|
||||
aria-label="Become a sponsor"
|
||||
className="z-50 w-auto h-auto bg-gradient-to-b from-[#FF1CF7] to-[#7928CA]"
|
||||
radius="full"
|
||||
onPress={handleBecomeSponsor}
|
||||
>
|
||||
<PlusLinearIcon
|
||||
className="flex items-center justify-center rounded-full text-white"
|
||||
size={54}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
playState="running"
|
||||
size={SONAR_PULSE_SIZE}
|
||||
>
|
||||
{renderSponsors}
|
||||
</SonarPulse>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
return <SupportClient sponsors={data.sponsors} />;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ import {clsx} from "@nextui-org/shared-utils";
|
||||
import * as Components from "@nextui-org/react";
|
||||
import {Language} from "prism-react-renderer";
|
||||
import NextImage from "next/image";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {ThemeSwitch} from "./theme-switch";
|
||||
import {InfoCircle} from "./icons/info-circle";
|
||||
|
||||
import {Sandpack} from "@/components/sandpack";
|
||||
import {CarbonAd} from "@/components/ads/carbon-ad";
|
||||
@@ -12,7 +14,15 @@ 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/virtual-anchor";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {
|
||||
Table as StaticTable,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableColumnHeader,
|
||||
TableRoot,
|
||||
} from "@/components/static-table";
|
||||
|
||||
const Table: React.FC<{children?: React.ReactNode}> = ({children}) => {
|
||||
return (
|
||||
@@ -97,15 +107,20 @@ const LinkedHeading: React.FC<LinkedHeadingProps> = ({
|
||||
|
||||
const List: React.FC<{children?: React.ReactNode}> = ({children}) => {
|
||||
return (
|
||||
<ul className="list-disc flex flex-col gap-2 ml-4 mt-2 [&>li>strong]:text-pink-500 dark:[&>li>strong]:text-cyan-600">
|
||||
<ul className="list-disc flex flex-col gap-2 ml-4 mt-2 [&>li>strong]:text-foreground [&>li>strong]:font-medium">
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const InlineCode = ({children}: {children?: React.ReactNode}) => {
|
||||
const InlineCode = ({children, className}: {children?: React.ReactNode; className?: string}) => {
|
||||
return (
|
||||
<Components.Code className="font-normal text-default-700 bg-default-200/50 dark:bg-default-100/60 px-2 py-0.5">
|
||||
<Components.Code
|
||||
className={clsx(
|
||||
'p-0 relative before:content-["`"] after:content-["`"] font-semibold font-mono text-small rounded-md text-default-900 dark:text-default-500 bg-transparent',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Components.Code>
|
||||
);
|
||||
@@ -123,6 +138,7 @@ const Code = ({
|
||||
const isMultiLine = (children as string)?.split?.("\n")?.length > 2;
|
||||
const language = (className?.replace(/language-/, "") ?? "jsx") as Language;
|
||||
const codeString = String(children).trim();
|
||||
const posthog = usePostHog();
|
||||
|
||||
if (!className) {
|
||||
return <InlineCode>{children}</InlineCode>;
|
||||
@@ -146,40 +162,151 @@ const Code = ({
|
||||
}}
|
||||
codeString={codeString}
|
||||
onCopy={() => {
|
||||
trackEvent("MDXComponents - Copy", {
|
||||
posthog.capture("MDXComponents - Copy", {
|
||||
category: "docs",
|
||||
action: "copyCode",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock codeString={codeString} language={language} metastring={meta} />
|
||||
<Codeblock
|
||||
className="sp-editor"
|
||||
codeString={codeString}
|
||||
language={language}
|
||||
metastring={meta}
|
||||
/>
|
||||
</Components.Snippet>
|
||||
);
|
||||
};
|
||||
|
||||
const Link = ({href, children}: {href?: string; children?: React.ReactNode}) => {
|
||||
const isExternal = href?.startsWith("http");
|
||||
const isExternal = href?.startsWith("http") || href?.startsWith("https");
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("MDXComponents - Click", {
|
||||
const handleClick = () => {
|
||||
posthog.capture("MDXComponents - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
});
|
||||
};
|
||||
|
||||
const externalProps = isExternal ? {target: "_blank", rel: "noopener noreferrer"} : {};
|
||||
|
||||
return (
|
||||
<Components.Link
|
||||
className="relative hover:opacity-100 text-foreground font-bold after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:rounded-full after:h-[1px] after:bg-primary-400 dark:after:bg-default-300 hover:after:h-[2px]"
|
||||
disableAnimation={true}
|
||||
href={href}
|
||||
isExternal={isExternal}
|
||||
showAnchorIcon={isExternal}
|
||||
onPress={handlePress}
|
||||
{...externalProps}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{children}
|
||||
</Components.Link>
|
||||
);
|
||||
};
|
||||
|
||||
const InlineCodeChip = ({children}: {children?: React.ReactNode}) => {
|
||||
return (
|
||||
<InlineCode className="before:hidden after:hidden text-tiny rounded-md text-default-600 bg-default-100 dark:bg-default-100/80 px-1.5 py-0.5">
|
||||
{children}
|
||||
</InlineCode>
|
||||
);
|
||||
};
|
||||
|
||||
interface APITableProps {
|
||||
data: {
|
||||
attribute: string;
|
||||
type: string;
|
||||
description: string;
|
||||
default?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const APITable: React.FC<APITableProps> = ({data}) => {
|
||||
return (
|
||||
<TableRoot className="overflow-x-auto overflow-y-hidden">
|
||||
<StaticTable aria-label="API table" className="w-full" layout="auto">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableColumnHeader>Prop</TableColumnHeader>
|
||||
<TableColumnHeader>Type</TableColumnHeader>
|
||||
<TableColumnHeader>Default</TableColumnHeader>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((item, index) => (
|
||||
<TableRow key={index} className="[&>td]:px-2 [&>td]:py-1.5 [&>td]:first:pt-4">
|
||||
<TableCell className="flex items-center gap-1 font-mono text-small whitespace-nowrap">
|
||||
<InlineCodeChip>{item.attribute}</InlineCodeChip>
|
||||
{item.description && (
|
||||
<>
|
||||
{/* Desktop tooltip */}
|
||||
<Components.Tooltip
|
||||
classNames={{
|
||||
content: "max-w-[240px]",
|
||||
}}
|
||||
content={item.description}
|
||||
delay={0}
|
||||
placement="top"
|
||||
>
|
||||
<div className="flex items-center gap-1 cursor-default hidden sm:block">
|
||||
<InfoCircle className="text-default-400" size={16} />
|
||||
</div>
|
||||
</Components.Tooltip>
|
||||
{/* Mobile popover */}
|
||||
<Components.Popover placement="top">
|
||||
<Components.PopoverTrigger>
|
||||
<button className="flex items-center gap-1 sm:hidden outline-none">
|
||||
<InfoCircle className="text-default-400" size={16} />
|
||||
</button>
|
||||
</Components.PopoverTrigger>
|
||||
<Components.PopoverContent className="max-w-[240px]">
|
||||
{item.description}
|
||||
</Components.PopoverContent>
|
||||
</Components.Popover>
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-small whitespace-nowrap text-primary">
|
||||
<InlineCodeChip>
|
||||
<div className="flex max-w-[300px] flex-wrap text-wrap">{item.type}</div>
|
||||
</InlineCodeChip>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-small whitespace-nowrap">
|
||||
{item.default && item.default !== "-" ? (
|
||||
<InlineCodeChip>
|
||||
{item.default !== "true" && item.default !== "false"
|
||||
? `"${item.default}"`
|
||||
: item.default}
|
||||
</InlineCodeChip>
|
||||
) : (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="text-default-400"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
width="15"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M2 7.5C2 7.22386 2.22386 7 2.5 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H2.5C2.22386 8 2 7.77614 2 7.5Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</StaticTable>
|
||||
</TableRoot>
|
||||
);
|
||||
};
|
||||
|
||||
export const MDXComponents = {
|
||||
/**
|
||||
* Next.js components
|
||||
@@ -209,9 +336,7 @@ export const MDXComponents = {
|
||||
h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => <LinkedHeading as="h2" {...props} />,
|
||||
h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => <LinkedHeading as="h3" {...props} />,
|
||||
h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => <LinkedHeading as="h4" {...props} />,
|
||||
strong: (props: React.HTMLAttributes<HTMLElement>) => (
|
||||
<strong className="font-medium" {...props} />
|
||||
),
|
||||
strong: (props: React.HTMLAttributes<HTMLElement>) => <strong {...props} />,
|
||||
table: Table,
|
||||
thead: Thead,
|
||||
tr: Trow,
|
||||
@@ -232,5 +357,6 @@ export const MDXComponents = {
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
APITable,
|
||||
// Block,
|
||||
} as unknown as Record<string, React.ReactNode>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable react/display-name */
|
||||
"use client";
|
||||
|
||||
import type {MDXComponents as MDXComponentsType} from "mdx/types";
|
||||
|
||||
import {useMDXComponent} from "next-contentlayer/hooks";
|
||||
import {useMDXComponent} from "next-contentlayer2/hooks";
|
||||
|
||||
import {MDXComponents} from "./mdx-components";
|
||||
|
||||
|
||||
+113
-130
@@ -17,6 +17,7 @@ import {
|
||||
DropdownItem,
|
||||
DropdownTrigger,
|
||||
Chip,
|
||||
Divider,
|
||||
} from "@nextui-org/react";
|
||||
import {dataFocusVisibleClasses} from "@nextui-org/theme";
|
||||
import {ChevronDownIcon, LinkIcon} from "@nextui-org/shared-icons";
|
||||
@@ -24,22 +25,23 @@ import {isAppleDevice} from "@react-aria/utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import NextLink from "next/link";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {includes} from "lodash";
|
||||
import {motion, AnimatePresence} from "framer-motion";
|
||||
import {useEffect} from "react";
|
||||
import {usePress} from "@react-aria/interactions";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {FbRoadmapLink} from "./featurebase/fb-roadmap-link";
|
||||
|
||||
import {currentVersion} from "@/utils/version";
|
||||
import {siteConfig} from "@/config/site";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {LargeLogo, SmallLogo, ThemeSwitch} from "@/components";
|
||||
import {TwitterIcon, GithubIcon, DiscordIcon, SearchLinearIcon} from "@/components/icons";
|
||||
import {GithubIcon, SearchLinearIcon} from "@/components/icons";
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {DocsSidebar} from "@/components/docs/sidebar";
|
||||
import {useCmdkStore} from "@/components/cmdk";
|
||||
import {FbRoadmapLink} from "@/components/featurebase/fb-roadmap-link";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import githubInfo from "@/config/github-info.json";
|
||||
|
||||
export interface NavbarProps {
|
||||
routes: Route[];
|
||||
@@ -60,6 +62,8 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
|
||||
const cmdkStore = useCmdkStore();
|
||||
|
||||
const posthog = usePostHog();
|
||||
|
||||
useEffect(() => {
|
||||
if (isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
@@ -72,7 +76,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
|
||||
const handleOpenCmdk = () => {
|
||||
cmdkStore.onOpen();
|
||||
trackEvent("Navbar - Search", {
|
||||
posthog.capture("Navbar - Search", {
|
||||
name: "navbar - search",
|
||||
action: "press",
|
||||
category: "cmdk",
|
||||
@@ -93,22 +97,26 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
const searchButton = (
|
||||
<Button
|
||||
aria-label="Quick search"
|
||||
className="text-sm font-normal text-default-500 bg-default-400/20 dark:bg-default-500/20"
|
||||
className="border-1 px-3 border-default-200 rounded-full text-small font-normal text-default-500 bg-transparent"
|
||||
endContent={
|
||||
<Kbd className="hidden py-0.5 px-2 lg:inline-block" keys={commandKey}>
|
||||
<Kbd
|
||||
className="hidden text-xs rounded-full py-0.5 px-1.5 lg:inline-block"
|
||||
keys={commandKey}
|
||||
>
|
||||
K
|
||||
</Kbd>
|
||||
}
|
||||
startContent={
|
||||
<SearchLinearIcon
|
||||
className="text-base text-default-400 pointer-events-none flex-shrink-0"
|
||||
size={18}
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
}
|
||||
variant="bordered"
|
||||
onPress={handleOpenCmdk}
|
||||
>
|
||||
Quick Search...
|
||||
Search
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -116,7 +124,10 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
return null;
|
||||
}
|
||||
|
||||
const navLinkClasses = clsx(link({color: "foreground"}), "data-[active=true]:text-primary");
|
||||
const navLinkClasses = clsx(
|
||||
link({color: "foreground"}),
|
||||
"data-[active=true]:text-primary data-[active=true]:font-semibold",
|
||||
);
|
||||
|
||||
const handleVersionChange = (key: Key) => {
|
||||
if (key === "v1") {
|
||||
@@ -127,7 +138,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
};
|
||||
|
||||
const handlePressNavbarItem = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
posthog.capture("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "navbar",
|
||||
@@ -141,13 +152,17 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
className={clsx({
|
||||
"z-[100001]": isMenuOpen,
|
||||
})}
|
||||
classNames={{
|
||||
base: "bg-white/[.90] dark:bg-black/[.65]",
|
||||
wrapper: "max-w-8xl",
|
||||
}}
|
||||
isMenuOpen={isMenuOpen}
|
||||
maxWidth="xl"
|
||||
position="sticky"
|
||||
onMenuOpenChange={setIsMenuOpen}
|
||||
>
|
||||
<NavbarContent className="basis-1/5 sm:basis-full" justify="start">
|
||||
<NavbarBrand as="li" className="gap-3 max-w-fit">
|
||||
<NavbarBrand as="li" className="gap-x-3 max-w-fit">
|
||||
<NextLink
|
||||
aria-label="Home"
|
||||
className="flex justify-start items-center gap-2 tap-highlight-transparent transition-opacity active:opacity-50"
|
||||
@@ -164,7 +179,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
<motion.div animate={{opacity: 1}} exit={{opacity: 0}} initial={{opacity: 0}}>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
className="hidden text-xs h-6 w-[74px] py-1 min-w-fit sm:flex gap-0.5 bg-default-400/20 dark:bg-default-500/20"
|
||||
className="min-w-[74px] max-w-[74px] hidden font-medium text-default-500 text-xs h-6 w-[74px] py-1 min-w-fit sm:flex gap-0.5 bg-default-400/20 dark:bg-default-500/20"
|
||||
endContent={<ChevronDownIcon className="text-tiny" />}
|
||||
radius="full"
|
||||
size="sm"
|
||||
@@ -191,98 +206,43 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
) : (
|
||||
<div className="w-[74px]" />
|
||||
)}
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="hidden sm:flex bg-primary-100/50 border-1 hover:bg-primary-100/80 border-primary-200/50 cursor-pointer"
|
||||
classNames={{
|
||||
content: "font-semibold text-primary-500 dark:text-primary-600 text-xs ",
|
||||
}}
|
||||
color="primary"
|
||||
href="/blog/v2.6.0"
|
||||
variant="flat"
|
||||
onClick={() => handlePressNavbarItem("New version v2.6.0", "/blog/v2.6.0")}
|
||||
>
|
||||
New version v2.6.0
|
||||
<span aria-label="emoji" role="img">
|
||||
🔥
|
||||
</span>
|
||||
</Chip>
|
||||
</NavbarBrand>
|
||||
<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>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "components")}
|
||||
href="/docs/components/accordion"
|
||||
onClick={() => handlePressNavbarItem("Components", "/docs/components/accordion")}
|
||||
>
|
||||
Components
|
||||
</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>
|
||||
{/* hide feedback and changelog at this moment */}
|
||||
{/* <NavbarItem>
|
||||
<NextLink className={navLinkClasses} color="foreground" href="#">
|
||||
<FbChangelogButton key="changelog" userName="" />
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink className={navLinkClasses} color="foreground" href="#">
|
||||
<FbFeedbackButton key="feedback" userEmail="" />
|
||||
</NextLink>
|
||||
</NavbarItem> */}
|
||||
<NavbarItem>
|
||||
<FbRoadmapLink className={navLinkClasses} />
|
||||
</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>
|
||||
|
||||
<NavbarContent className="flex w-full gap-2 sm:hidden" justify="end">
|
||||
<NavbarContent className="flex w-full gap-2 sm:hidden " justify="end">
|
||||
<NavbarItem className="flex h-full items-center">
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href="https://github.com/nextui-org/nextui"
|
||||
onClick={() => handlePressNavbarItem("Github", "https://github.com/nextui-org/nextui")}
|
||||
href={siteConfig.links.github}
|
||||
onPress={() => handlePressNavbarItem("Github", siteConfig.links.github)}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
</NavbarItem>
|
||||
<NavbarItem className="flex h-full items-center">
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitch
|
||||
classNames={{
|
||||
wrapper: "!text-default-500 dark:!text-default-500",
|
||||
}}
|
||||
/>
|
||||
</NavbarItem>
|
||||
<NavbarItem className="flex h-full items-center">
|
||||
<button
|
||||
@@ -307,52 +267,75 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarContent className="hidden sm:flex basis-1/5 sm:basis-full" justify="end">
|
||||
<NavbarItem className="hidden sm:flex">
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="bg-default-100/50 hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
|
||||
color="default"
|
||||
href="/blog/v2.4.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressNavbarItem("New version v2.4.0", "/blog/v2.4.0")}
|
||||
>
|
||||
New version v2.4.0
|
||||
<span aria-label="emoji" role="img">
|
||||
🚀
|
||||
</span>
|
||||
</Chip>
|
||||
</NavbarItem>
|
||||
<NavbarItem className="hidden sm:flex">
|
||||
<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}
|
||||
onPress={() => handlePressNavbarItem("Discord", siteConfig.links.discord)}
|
||||
>
|
||||
<DiscordIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<ul className="hidden lg:flex gap-4 pr-2 justify-start items-center [&>li>a]:text-sm [&>li>a]:font-medium">
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={docsPaths.includes(pathname)}
|
||||
href="/docs/guide/introduction"
|
||||
onClick={() => handlePressNavbarItem("Docs", "/docs/guide/introduction")}
|
||||
>
|
||||
Docs
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={pathname.includes("components")}
|
||||
href="/docs/components/accordion"
|
||||
onClick={() => handlePressNavbarItem("Components", "/docs/components/accordion")}
|
||||
>
|
||||
Components
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={pathname.includes("blog")}
|
||||
href="/blog"
|
||||
onClick={() => handlePressNavbarItem("Blog", "/blog")}
|
||||
>
|
||||
Blog
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<NextLink
|
||||
className={navLinkClasses}
|
||||
color="foreground"
|
||||
data-active={pathname.includes("figma")}
|
||||
href="/figma"
|
||||
onClick={() => handlePressNavbarItem("Figma", "/figma")}
|
||||
>
|
||||
Figma
|
||||
</NextLink>
|
||||
</NavbarItem>
|
||||
<NavbarItem>
|
||||
<FbRoadmapLink className={navLinkClasses} />
|
||||
</NavbarItem>
|
||||
</ul>
|
||||
<Divider className="h-7 hidden lg:flex" orientation="vertical" />
|
||||
<NavbarItem className="hidden sm:flex gap-2">
|
||||
{searchButton}
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
className="flex gap-0.5 items-center h-10 px-2 border-1 border-default-200 rounded-full text-default-600 dark:text-default-500"
|
||||
href={siteConfig.links.github}
|
||||
onPress={() => handlePressNavbarItem("Github", siteConfig.links.github)}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
<GithubIcon />
|
||||
<span className="text-xs font-medium">{githubInfo.stars.formatted}</span>
|
||||
</Link>
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitch
|
||||
className="border-1 border-default-200 rounded-full h-full min-w-10 min-h-10 flex items-center justify-center"
|
||||
classNames={{
|
||||
wrapper: "!text-default-400 dark:!text-default-500",
|
||||
}}
|
||||
/>
|
||||
</NavbarItem>
|
||||
<NavbarItem className="hidden lg:flex">{searchButton}</NavbarItem>
|
||||
{/* <NavbarItem className="hidden md:flex">
|
||||
<Button
|
||||
isExternal
|
||||
|
||||
@@ -18,7 +18,7 @@ export const title = tv({
|
||||
},
|
||||
size: {
|
||||
sm: "text-3xl lg:text-4xl",
|
||||
md: "text-[2.5rem] lg:text-5xl",
|
||||
md: "text-[clamp(1rem,10vw,2rem)] sm:text-[clamp(1rem,10vw,3rem)] lg:text-5xl",
|
||||
lg: "text-4xl lg:text-6xl",
|
||||
xl: "text-5xl md:text-6xl lg:text-7xl",
|
||||
},
|
||||
|
||||
@@ -4,15 +4,17 @@ import {Icon} from "@iconify/react/dist/offline";
|
||||
import arrowRightIcon from "@iconify/icons-solar/arrow-right-linear";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {useEffect} from "react";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import emitter from "@/libs/emitter";
|
||||
|
||||
const hideOnPaths = ["examples"];
|
||||
|
||||
export const ProBanner = () => {
|
||||
const posthog = usePostHog();
|
||||
|
||||
const handleClick = () => {
|
||||
trackEvent("NextUI Pro Banner", {
|
||||
posthog.capture("NextUI Pro Banner", {
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {capitalize, last} from "lodash";
|
||||
import {capitalize} from "@nextui-org/shared-utils";
|
||||
|
||||
import {BugIcon} from "@/components/icons";
|
||||
import {ISSUE_REPORT_URL} from "@/libs/github/constants";
|
||||
@@ -9,7 +9,7 @@ import {ISSUE_REPORT_URL} from "@/libs/github/constants";
|
||||
export const BugReportButton = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
const componentTitle = capitalize(last(pathname?.split("/")));
|
||||
const componentTitle = capitalize(pathname?.split("/")?.at(-1) ?? "");
|
||||
|
||||
const handlePress = () => {
|
||||
window.open(`${ISSUE_REPORT_URL}${componentTitle}`, "_blank");
|
||||
|
||||
@@ -5,13 +5,13 @@ import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
|
||||
import {CopyLinearIcon} from "@/components/icons";
|
||||
|
||||
export const CopyButton = () => {
|
||||
export const CopyButton = ({code: codeProp}: {code?: string}) => {
|
||||
const {copy, copied} = useClipboard();
|
||||
|
||||
const {sandpack} = useSandpack();
|
||||
|
||||
const copyHandler = () => {
|
||||
const code = sandpack.files[sandpack.activeFile].code;
|
||||
const code = codeProp ?? sandpack.files[sandpack.activeFile].code;
|
||||
|
||||
copy(code);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<NextUIProvider>
|
||||
<div className="w-screen h-screen p-8 flex items-start justify-center">
|
||||
<App />
|
||||
</div>
|
||||
</div>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
);`;
|
||||
|
||||
@@ -29,7 +29,7 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onC
|
||||
aria-label="Language selector"
|
||||
classNames={{
|
||||
base: "absolute z-10 right-3 bottom-4",
|
||||
cursor: "bg-zinc-400 dark:bg-zinc-700",
|
||||
cursor: "bg-default-600 dark:bg-default-300",
|
||||
tabList:
|
||||
"bg-transparent relative before:bg-white/5 before:w-full before:rounded-lg before:h-full before:content-[''] before:block before:z-1 before:absolute before:inset-0 before:backdrop-blur-md before:backdrop-saturate-100",
|
||||
}}
|
||||
|
||||
@@ -70,7 +70,7 @@ export const useSandpack = ({
|
||||
}, {});
|
||||
|
||||
let dependencies = {
|
||||
"framer-motion": "11.0.22",
|
||||
"framer-motion": "11.9.0",
|
||||
"@nextui-org/react": "latest",
|
||||
};
|
||||
|
||||
@@ -143,19 +143,19 @@ export const useSandpack = ({
|
||||
|
||||
// const dependencies = useMemo(() => {
|
||||
// let deps = {
|
||||
// "framer-motion": "11.0.22",
|
||||
// "framer-motion": "11.9.0",
|
||||
// };
|
||||
|
||||
// if (hasComponents) {
|
||||
// let deps = {
|
||||
// "@nextui-org/theme": "dev-v2",
|
||||
// "@nextui-org/system": "dev-v2",
|
||||
// "@nextui-org/theme": "canary",
|
||||
// "@nextui-org/system": "canary",
|
||||
// };
|
||||
|
||||
// nextUIComponents.forEach((component) => {
|
||||
// deps = {
|
||||
// ...deps,
|
||||
// [`@nextui-org/${component}`]: "dev-v2",
|
||||
// [`@nextui-org/${component}`]: "canary",
|
||||
// };
|
||||
// });
|
||||
|
||||
@@ -164,7 +164,7 @@ export const useSandpack = ({
|
||||
|
||||
// return {
|
||||
// ...deps,
|
||||
// "@nextui-org/react": "dev-v2",
|
||||
// "@nextui-org/react": "canary",
|
||||
// };
|
||||
// }, [hasComponents, nextUIComponents, component]);
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import * as React from "react";
|
||||
import {Analytics} from "@vercel/analytics/react";
|
||||
import Script from "next/script";
|
||||
|
||||
import {__PROD__} from "@/utils";
|
||||
|
||||
export function ScriptProviders() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
id="featurebase-sdk"
|
||||
src="https://do.featurebase.app/js/sdk.js"
|
||||
strategy={"beforeInteractive"}
|
||||
/>
|
||||
<Script
|
||||
defer
|
||||
data-modal-disclaimer="This is a custom LLM for NextUI with access to all developer docs (nextui.org/docs) and GitHub Issues and PRs (github.com/nextui-org/nextui)."
|
||||
@@ -23,7 +16,6 @@ export function ScriptProviders() {
|
||||
src="https://widget.kapa.ai/kapa-widget.bundle.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
{__PROD__ && <Analytics />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type {ComponentProps, FC} from "react";
|
||||
|
||||
import {cn, table} from "@nextui-org/theme";
|
||||
|
||||
const tableSlots = table();
|
||||
|
||||
export const TableRoot: FC<ComponentProps<"div">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<div {...props} className={tableSlots.base({className})}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Table: FC<
|
||||
ComponentProps<"table"> & {
|
||||
layout?: "fixed" | "auto";
|
||||
}
|
||||
> = ({children, className, layout = "auto", ...props}) => {
|
||||
return (
|
||||
<table {...props} className={tableSlots.table({className, layout})}>
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableHeader: FC<ComponentProps<"thead">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<thead {...props} className={tableSlots.thead({className})}>
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableBody: FC<ComponentProps<"tbody">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<tbody {...props} className={tableSlots.tbody({className})}>
|
||||
{children}
|
||||
</tbody>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableRow: FC<ComponentProps<"tr">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<tr {...props} className={tableSlots.tr({className})}>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableColumnHeader: FC<ComponentProps<"th">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<td {...props} className={tableSlots.th({className})}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableCell: FC<ComponentProps<"td">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<td {...props} className={tableSlots.td({class: cn("p-0", className)})}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
export const TableColumn: FC<ComponentProps<"th">> = ({children, className, ...props}) => {
|
||||
return (
|
||||
<th {...props} className={tableSlots.th({class: cn("p-0", className)})}>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import {FC} from "react";
|
||||
import {FC, ChangeEvent} from "react";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
import {SwitchProps, useSwitch} from "@nextui-org/react";
|
||||
import {useTheme} from "next-themes";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import {useIsSSR} from "@react-aria/ssr";
|
||||
import {usePostHog} from "posthog-js/react";
|
||||
|
||||
import {SunFilledIcon, MoonFilledIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {SunLinearIcon, MoonIcon} from "@/components/icons";
|
||||
|
||||
export interface ThemeSwitchProps {
|
||||
className?: string;
|
||||
@@ -18,21 +18,31 @@ export interface ThemeSwitchProps {
|
||||
export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
const {theme, setTheme} = useTheme();
|
||||
const isSSR = useIsSSR();
|
||||
const posthog = usePostHog();
|
||||
|
||||
const onChange = () => {
|
||||
theme === "light" ? setTheme("dark") : setTheme("light");
|
||||
const initialTheme = isSSR ? "light" : theme;
|
||||
|
||||
trackEvent("ThemeChange", {
|
||||
const handleThemeChange = (
|
||||
e?: ChangeEvent<HTMLInputElement> | React.MouseEvent | React.KeyboardEvent,
|
||||
) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
const newTheme = theme === "light" ? "dark" : "light";
|
||||
|
||||
setTheme(newTheme);
|
||||
|
||||
posthog.capture("ThemeChange", {
|
||||
action: "click",
|
||||
category: "theme",
|
||||
data: theme === "light" ? "dark" : "light",
|
||||
data: newTheme,
|
||||
});
|
||||
};
|
||||
|
||||
const {Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps} = useSwitch({
|
||||
isSelected: theme === "light",
|
||||
"aria-label": `Switch to ${theme === "light" ? "dark" : "light"} mode`,
|
||||
onChange,
|
||||
isSelected: initialTheme === "light",
|
||||
"aria-label": `Switch to ${initialTheme === "light" ? "dark" : "light"} mode`,
|
||||
onChange: handleThemeChange as (event: ChangeEvent<HTMLInputElement>) => void,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -43,6 +53,12 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
className,
|
||||
classNames?.base,
|
||||
),
|
||||
onClick: handleThemeChange,
|
||||
onKeyDown: (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
handleThemeChange(e);
|
||||
}
|
||||
},
|
||||
})}
|
||||
>
|
||||
<VisuallyHidden>
|
||||
@@ -58,8 +74,8 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
"rounded-lg",
|
||||
"flex items-center justify-center",
|
||||
"group-data-[selected=true]:bg-transparent",
|
||||
"!text-default-600 dark:!text-default-500",
|
||||
"pt-px",
|
||||
"!text-default-600 dark:!text-default-300",
|
||||
"pt-0",
|
||||
"px-0",
|
||||
"mx-0",
|
||||
],
|
||||
@@ -67,7 +83,7 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
),
|
||||
})}
|
||||
>
|
||||
{!isSelected || isSSR ? <SunFilledIcon size={22} /> : <MoonFilledIcon size={22} />}
|
||||
{!isSelected || isSSR ? <SunLinearIcon size={22} /> : <MoonIcon size={22} />}
|
||||
</div>
|
||||
</Component>
|
||||
);
|
||||
|
||||
+70
-24
@@ -1,27 +1,73 @@
|
||||
import {Inter} from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
|
||||
export const fontSans = Inter({
|
||||
variable: "--font-sans",
|
||||
adjustFontFallback: true,
|
||||
display: "optional",
|
||||
fallback: [
|
||||
"ui-sans-serif",
|
||||
"system-ui",
|
||||
"-apple-system",
|
||||
"BlinkMacSystemFont",
|
||||
'"Segoe UI"',
|
||||
"Roboto",
|
||||
'"Helvetica Neue"',
|
||||
"Arial",
|
||||
'"Noto Sans"',
|
||||
"sans-serif",
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
'"Noto Color Emoji"',
|
||||
const fontSans = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-Light.woff2",
|
||||
style: "normal",
|
||||
weight: "300",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-Regular.woff2",
|
||||
style: "normal",
|
||||
weight: "400",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-Medium.woff2",
|
||||
style: "normal",
|
||||
weight: "500",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-SemiBold.woff2",
|
||||
style: "normal",
|
||||
weight: "600",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-Bold.woff2",
|
||||
style: "normal",
|
||||
weight: "700",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-ExtraBold.woff2",
|
||||
style: "normal",
|
||||
weight: "800",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/Inter-Black.woff2",
|
||||
style: "normal",
|
||||
weight: "900",
|
||||
},
|
||||
],
|
||||
preload: true,
|
||||
style: "normal",
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "700"],
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
const fontMono = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../public/assets/fonts/FiraCode-Regular.woff2",
|
||||
style: "normal",
|
||||
weight: "400",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/FiraCode-Medium.woff2",
|
||||
style: "normal",
|
||||
weight: "500",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/FiraCode-SemiBold.woff2",
|
||||
style: "normal",
|
||||
weight: "600",
|
||||
},
|
||||
{
|
||||
path: "../public/assets/fonts/FiraCode-Bold.woff2",
|
||||
style: "normal",
|
||||
weight: "700",
|
||||
},
|
||||
],
|
||||
variable: "--font-mono",
|
||||
});
|
||||
|
||||
export const fonts = {
|
||||
mono: fontMono,
|
||||
sans: fontSans,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"stars": { "raw": 22002, "formatted": "22K" },
|
||||
"forks": 1536,
|
||||
"subscribers": 85,
|
||||
"openIssues": 433
|
||||
}
|
||||
@@ -12,6 +12,12 @@
|
||||
"keywords": "introduction, nextui",
|
||||
"path": "/docs/guide/introduction.mdx"
|
||||
},
|
||||
{
|
||||
"key": "design-principles",
|
||||
"title": "Design Principles",
|
||||
"keywords": "design principles, nextui",
|
||||
"path": "/docs/guide/design-principles.mdx"
|
||||
},
|
||||
{
|
||||
"key": "installation",
|
||||
"title": "Installation",
|
||||
@@ -22,20 +28,21 @@
|
||||
"key": "cli",
|
||||
"title": "CLI",
|
||||
"keywords": "cli, command line interface",
|
||||
"path": "/docs/guide/cli.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "design-principles",
|
||||
"title": "Design Principles",
|
||||
"keywords": "design principles, nextui",
|
||||
"path": "/docs/guide/design-principles.mdx"
|
||||
"path": "/docs/guide/cli.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"
|
||||
"path": "/docs/guide/routing.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "forms",
|
||||
"title": "Forms",
|
||||
"keywords": "forms, form validation, nextui",
|
||||
"path": "/docs/guide/forms.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "upgrade-to-v2",
|
||||
@@ -112,7 +119,8 @@
|
||||
{
|
||||
"key": "dark-mode",
|
||||
"title": "Dark mode",
|
||||
"path": "/docs/customization/dark-mode.mdx"
|
||||
"path": "/docs/customization/dark-mode.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "override-styles",
|
||||
@@ -142,7 +150,15 @@
|
||||
"key": "autocomplete",
|
||||
"title": "Autocomplete",
|
||||
"keywords": "autocomplete, auto suggest, search, typeahead",
|
||||
"path": "/docs/components/autocomplete.mdx"
|
||||
"path": "/docs/components/autocomplete.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "alert",
|
||||
"title": "Alert",
|
||||
"keywords": "alert, notification, message",
|
||||
"path": "/docs/components/alert.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "avatar",
|
||||
@@ -154,8 +170,7 @@
|
||||
"key": "badge",
|
||||
"title": "Badge",
|
||||
"keywords": "badge, markers, status indication, count display",
|
||||
"path": "/docs/components/badge.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/badge.mdx"
|
||||
},
|
||||
{
|
||||
"key": "breadcrumbs",
|
||||
@@ -167,14 +182,14 @@
|
||||
"key": "button",
|
||||
"title": "Button",
|
||||
"keywords": "button, interactive, action trigger, click events",
|
||||
"path": "/docs/components/button.mdx"
|
||||
"path": "/docs/components/button.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "calendar",
|
||||
"title": "Calendar",
|
||||
"keywords": "calendar, date picker, month picker, year picker",
|
||||
"path": "/docs/components/calendar.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/calendar.mdx"
|
||||
},
|
||||
{
|
||||
"key": "card",
|
||||
@@ -193,7 +208,8 @@
|
||||
"key": "checkbox-group",
|
||||
"title": "Checkbox Group",
|
||||
"keywords": "checkbox group, binary choice, selection control, toggle",
|
||||
"path": "/docs/components/checkbox-group.mdx"
|
||||
"path": "/docs/components/checkbox-group.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "chip",
|
||||
@@ -217,22 +233,19 @@
|
||||
"key": "date-input",
|
||||
"title": "Date Input",
|
||||
"keywords": "date-input, time, input, timezone",
|
||||
"path": "/docs/components/date-input.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/date-input.mdx"
|
||||
},
|
||||
{
|
||||
"key": "date-picker",
|
||||
"title": "Date Picker",
|
||||
"keywords": "date-picker, time, input, timezone",
|
||||
"path": "/docs/components/date-picker.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/date-picker.mdx"
|
||||
},
|
||||
{
|
||||
"key": "date-range-picker",
|
||||
"title": "Date Range Picker",
|
||||
"keywords": "date-range-picker, date-picker, time, input, timezone",
|
||||
"path": "/docs/components/date-range-picker.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/date-range-picker.mdx"
|
||||
},
|
||||
{
|
||||
"key": "divider",
|
||||
@@ -246,6 +259,20 @@
|
||||
"keywords": "dropdown, menu, selection, option list",
|
||||
"path": "/docs/components/dropdown.mdx"
|
||||
},
|
||||
{
|
||||
"key": "drawer",
|
||||
"title": "Drawer",
|
||||
"keywords": "drawer, panel, slide, overlay",
|
||||
"path": "/docs/components/drawer.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "form",
|
||||
"title": "Form",
|
||||
"keywords": "forms, form validation, nextui",
|
||||
"path": "/docs/components/form.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
"title": "Image",
|
||||
@@ -256,7 +283,15 @@
|
||||
"key": "input",
|
||||
"title": "Input",
|
||||
"keywords": "input, text box, form field, data entry",
|
||||
"path": "/docs/components/input.mdx"
|
||||
"path": "/docs/components/input.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "input-otp",
|
||||
"title": "Input OTP",
|
||||
"keywords": "input, otp, auth, verification code",
|
||||
"path": "/docs/components/input-otp.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "kbd",
|
||||
@@ -274,13 +309,15 @@
|
||||
"key": "listbox",
|
||||
"title": "Listbox",
|
||||
"keywords": "listbox, selection, option list, multiple choice",
|
||||
"path": "/docs/components/listbox.mdx"
|
||||
"path": "/docs/components/listbox.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "modal",
|
||||
"title": "Modal",
|
||||
"keywords": "modal, dialog box, popup, overlay, content focus",
|
||||
"path": "/docs/components/modal.mdx"
|
||||
"path": "/docs/components/modal.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "navbar",
|
||||
@@ -310,14 +347,14 @@
|
||||
"key": "radio-group",
|
||||
"title": "Radio Group",
|
||||
"keywords": "radio group, selection set, option selection, exclusive choices",
|
||||
"path": "/docs/components/radio-group.mdx"
|
||||
"path": "/docs/components/radio-group.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "range-calendar",
|
||||
"title": "Range Calendar",
|
||||
"keywords": "range calendar, date picker, month picker, year picker",
|
||||
"path": "/docs/components/range-calendar.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/range-calendar.mdx"
|
||||
},
|
||||
{
|
||||
"key": "scroll-shadow",
|
||||
@@ -329,14 +366,14 @@
|
||||
"key": "select",
|
||||
"title": "Select",
|
||||
"keywords": "select, selection, option list, multiple choice",
|
||||
"path": "/docs/components/select.mdx"
|
||||
"path": "/docs/components/select.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "skeleton",
|
||||
"title": "Skeleton",
|
||||
"keywords": "skeleton, loading state, placeholder, content preview",
|
||||
"path": "/docs/components/skeleton.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/skeleton.mdx"
|
||||
},
|
||||
{
|
||||
"key": "slider",
|
||||
@@ -366,7 +403,8 @@
|
||||
"key": "switch",
|
||||
"title": "Switch",
|
||||
"keywords": "switch, toggle, binary input, on/off control",
|
||||
"path": "/docs/components/switch.mdx"
|
||||
"path": "/docs/components/switch.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "table",
|
||||
@@ -378,28 +416,33 @@
|
||||
"key": "tabs",
|
||||
"title": "Tabs",
|
||||
"keywords": "tabs, section navigation, categorized content, tabbed interface",
|
||||
"path": "/docs/components/tabs.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/tabs.mdx"
|
||||
},
|
||||
{
|
||||
"key": "toast",
|
||||
"title": "Toast",
|
||||
"keywords": "toast, notification, message",
|
||||
"path": "/docs/components/toast.mdx",
|
||||
"comingSoon": 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": "time-input",
|
||||
"title": "Time Input",
|
||||
"keywords": "timeinput, time, input, timezone",
|
||||
"path": "/docs/components/time-input.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/time-input.mdx"
|
||||
},
|
||||
{
|
||||
"key": "tooltip",
|
||||
"title": "Tooltip",
|
||||
"keywords": "tooltip, hint, descriptive message, hover info",
|
||||
"path": "/docs/components/tooltip.mdx",
|
||||
"updated": true
|
||||
"path": "/docs/components/tooltip.mdx"
|
||||
},
|
||||
{
|
||||
"key": "user",
|
||||
@@ -472,4 +515,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1952
-1447
File diff suppressed because it is too large
Load Diff
@@ -32,8 +32,8 @@ export const siteConfig = {
|
||||
},
|
||||
links: {
|
||||
github: "https://github.com/nextui-org/nextui",
|
||||
twitter: "https://twitter.com/getnextui",
|
||||
docs: "https://nextui-docs-v2.vercel.app",
|
||||
twitter: "https://x.com/getnextui",
|
||||
docs: "https://nextui.org",
|
||||
discord: "https://discord.gg/9b6yyZKmH4",
|
||||
sponsor: "https://patreon.com/jrgarciadev",
|
||||
portfolio: "https://jrgarciadev.com",
|
||||
|
||||
@@ -7,7 +7,7 @@ tags: ["nextui", "tailwindcss", "react", "nextjs", "react-server-components"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
@@ -91,7 +91,7 @@ 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 include the `use client;` directive so you can import them directly
|
||||
NextUI components already include the `use client;` directive so you can import them directly
|
||||
in your React Server Components (RSC).
|
||||
|
||||
```jsx
|
||||
|
||||
@@ -7,7 +7,7 @@ tags: ["nextui", "select", "listbox", "scroll-shadow", "multi-select"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
@@ -124,7 +124,7 @@ The new **Listbox** component includes:
|
||||
|
||||
### Custom Styles
|
||||
|
||||
The Listbox components offers multiple customization options.
|
||||
The Listbox components offers multiple customization options.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={listboxContent.customStyles} />
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ tags: ["nextui", "autocomplete", "breadcrumbs", "client side router", "slider"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ tags: ["nextui", "cli", "date picker", "time input", "date input", "calendar"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
@@ -209,7 +209,7 @@ Go to the [DateRangePicker documentation](/docs/components/date-range-picker) to
|
||||
|
||||
### Calendar Presets
|
||||
|
||||
`Calendar` and `RangeCalendar` components support adding custom content at the top and bottom of the calendar, this is useful for adding presets or
|
||||
`Calendar` and `RangeCalendar` components support adding custom content at the top and bottom of the calendar, this is useful for adding presets or
|
||||
custom actions to the calendar.
|
||||
|
||||
Here's an example of how to add presets to the `Calendar` component:
|
||||
@@ -299,7 +299,7 @@ export function Providers({children}: ProvidersProps) {
|
||||
|
||||
### Removal of the `units` creation
|
||||
|
||||
To improve performance and reduce bundle size, we have removed the `units` creation from the
|
||||
To improve performance and reduce bundle size, we have removed the `units` creation from the
|
||||
`nextui` plugin. [TailwindCSS v3.4](https://tailwindcss.com/blog/tailwindcss-v3-4) added support for `min-h-*` and `min-w-*` classes, so it is no longer needed.
|
||||
|
||||
How to upgrade:
|
||||
@@ -339,7 +339,7 @@ export const MyButton = () => {
|
||||
|
||||
### Separation for `errorMessage` and `isInvalid`
|
||||
|
||||
We are currently working on supporting multiple types of validation, including native HTML constraint validation, custom validation, and real-time validation.
|
||||
We are currently working on supporting multiple types of validation, including native HTML constraint validation, custom validation, and real-time validation.
|
||||
Due to this reason, the requirements for displaying error messages have become more varied, and it is necessary to handle validation conditions separately from the `errorMessage`.
|
||||
|
||||
How to upgrade:
|
||||
@@ -381,7 +381,7 @@ That's it! Your project should now be using the latest version of TailwindCSS an
|
||||
**Improvements**
|
||||
- Framer Motion was updated to the latest version, improving performance and reducing bundle size. [Docs](https://www.framer.com/motion/guide-reduce-bundle-size/) [PR](https://github.com/nextui-org/nextui/pull/2464) - [@mezotv](https://github.com/mezotv)
|
||||
- `LazyMotion` was added to all components that use Framer Motion, improving performance by only loading the required motion components.
|
||||
- We removed the custom `units` creation from the `nextui` plugin, it is no longer needed with TailwindCSS v3.4 and above. [PR](https://github.com/nextui-org/nextui/pull/2713) - [@jrgarciadev](https://github.com/jrgarciadev)
|
||||
- We removed the custom `units` creation from the `nextui` plugin, it is no longer needed with TailwindCSS v3.4 and above. [PR](https://github.com/nextui-org/nextui/pull/2713) - [@jrgarciadev](https://github.com/jrgarciadev)
|
||||
- Updated `framer-motion` package across various components and utilities to version `11.0.22` for enhanced performance and consistency. [PR](https://github.com/nextui-org/nextui/pull/2596) - [@wingkwong](https://github.com/wingkwong)
|
||||
- Ensured compatibility with `react@18.2.0` and `react-dom@18.2.0` across the board. [PR](https://github.com/nextui-org/nextui/pull/2596) - [@wingkwong](https://github.com/wingkwong)
|
||||
- Introduced patches for NextUI components to improve animations, including support for keyframes with spring and inertia animations. [PR](https://github.com/nextui-org/nextui/pull/2596) - [@wingkwong](https://github.com/wingkwong)
|
||||
@@ -402,8 +402,8 @@ That's it! Your project should now be using the latest version of TailwindCSS an
|
||||
- Layout docs updated to remove the `units` configuration from the `tailwind.config.(js|ts)` file.
|
||||
|
||||
|
||||
Special thanks to NextUI Team members [@kuri-sun](https://github.com/kuri-sun), [@ryo-manba](https://github.com/ryo-manba),
|
||||
[@sudongyuer](https://github.com/sudongyuer), [@winchesHe](https://github.com/winchesHe), [@wingkwong](https://github.com/wingkwong),
|
||||
Special thanks to NextUI Team members [@kuri-sun](https://github.com/kuri-sun), [@ryo-manba](https://github.com/ryo-manba),
|
||||
[@sudongyuer](https://github.com/sudongyuer), [@winchesHe](https://github.com/winchesHe), [@wingkwong](https://github.com/wingkwong),
|
||||
[@tianenpang](https://github.com/tianenpang), [@smultar](https://github.com/smultar) and contributors for their contributions to this release.
|
||||
|
||||
For a full list of changes, please refer to the [release notes](https://github.com/nextui-org/nextui/releases/tag/%40nextui-org%2Freact%402.3.0).
|
||||
|
||||
@@ -0,0 +1,772 @@
|
||||
---
|
||||
title: "NextUI v2.6.0 🔥"
|
||||
description: "4 new components Form, Drawer, Input OTP and Alert, React 19 & Next.js 15 support, and lots of bug fixes and improvements."
|
||||
date: "2024-12-06"
|
||||
image: "/blog/v2.6.0.jpg"
|
||||
tags: ["nextui", "v2.6.0", "release", "bug fixes", "improvements", "animations"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://x.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
import {formContent} from "@/content/components/form";
|
||||
import {drawerContent} from "@/content/components/drawer";
|
||||
import {inputOtpContent} from "@/content/components/input-otp";
|
||||
import {alertContent} from "@/content/components/alert";
|
||||
import {autocompleteContent} from "@/content/components/autocomplete";
|
||||
import {modalContent} from "@/content/components/modal";
|
||||
|
||||
<img
|
||||
src="/blog/v2.6.0_2x.jpg"
|
||||
width={700}
|
||||
height={350}
|
||||
alt="NextUI v2.6.0"
|
||||
className="w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
/>
|
||||
|
||||
NextUI version **v2.6.0** comes with 4 new components **Form**, **Drawer**, **Input OTP** and **Alert**, React 19 & Next.js 15 support, and lots of bug fixes and improvements.
|
||||
|
||||
## What's New in v2.6.0?
|
||||
|
||||
- [Form Component](#form-component) - A form component with built-in validation, submission handling, and accessibility features.
|
||||
- [Drawer Component](#drawer-component) - A sliding panel component with multiple placement options and focus management.
|
||||
- [Input OTP Component](#input-otp-component) - An accessible one-time password input with focus management.
|
||||
- [Alert Component](#alert-component) - A component for displaying messages with accessibility and keyboard navigation support.
|
||||
- [Collection-based components Virtualization](#collection-based-components-virtualization) - Performance improvements for large datasets in Select, Autocomplete & Listbox components.
|
||||
- [React 19 Support & Library Upgrades](#react-19-support--library-upgrades) - Support for React 19 and upgrades to various dependencies.
|
||||
- [New use-theme hook](#new-use-theme-hook) - A new hook for runtime theme management.
|
||||
- [Draggable Modal](#draggable-modal) - Support for draggable modal functionality.
|
||||
- [Router Improvements](#router-improvements) - Enhanced routing capabilities and TypeScript support.
|
||||
- [API Improvements](#api-improvements) - New features and props across multiple components.
|
||||
- [What's Next?](#whats-next) - Upcoming features and improvements.
|
||||
- [Breaking Changes](#-breaking-changes) - Important changes that may affect existing implementations.
|
||||
- [Release Changes](#release-changes) - Detailed list of features, documentation updates, bug fixes and enhancements.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
**Upgrade today by using one of the following methods**:
|
||||
|
||||
1. Upgrading NextUI using the `cli`
|
||||
|
||||
- First, upgrade Framer Motion to at least v11.9.0:
|
||||
|
||||
<PackageManagers
|
||||
commands={{
|
||||
npm: "npm install framer-motion@latest",
|
||||
yarn: "yarn add framer-motion@latest",
|
||||
pnpm: "pnpm add framer-motion@latest",
|
||||
bun: "bun add framer-motion@latest",
|
||||
}}
|
||||
/>
|
||||
|
||||
- Then, upgrade NextUI:
|
||||
|
||||
<PackageManagers
|
||||
commands={{
|
||||
cli: "nextui upgrade --all",
|
||||
npm: "npx nextui-cli@latest upgrade --all",
|
||||
}}
|
||||
/>
|
||||
|
||||
2. Upgrading NextUI using the `pnpm`
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<PackageManagers
|
||||
commands={{
|
||||
npm: "npm install @nextui-org/react@latest framer-motion@latest",
|
||||
pnpm: "pnpm add @nextui-org/react@latest framer-motion@latest",
|
||||
yarn: "yarn add @nextui-org/react@latest framer-motion@latest",
|
||||
bun: "bun add @nextui-org/react@latest framer-motion@latest",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Form Component
|
||||
|
||||
Built on [React Aria's Form](https://react-spectrum.adobe.com/react-aria/forms.html#forms) component, the [Form](/docs/components/forms) component provides accessible form handling with built-in submission, validation and error management.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Usage" showEditor={false} files={formContent.demo} />
|
||||
|
||||
### Built-in Validation
|
||||
|
||||
You can use native HTML validation attributes or create custom validation rules.
|
||||
|
||||
```tsx
|
||||
import {Button, Form, Input} from "@nextui-org/react";
|
||||
|
||||
function Example() {
|
||||
return (
|
||||
<Form validationBehavior="native">
|
||||
<Input
|
||||
isRequired
|
||||
errorMessage="Please enter a valid email"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Usage" showEditor={false} files={formContent.usage} />
|
||||
|
||||
### Real-time Validation
|
||||
|
||||
You can validate the form data while users are typing.
|
||||
|
||||
```tsx
|
||||
function Example() {
|
||||
const [password, setPassword] = React.useState("");
|
||||
const errors = [];
|
||||
|
||||
if (password.length < 4) {
|
||||
errors.push("Password must be 4 characters or more.");
|
||||
}
|
||||
if ((password.match(/[A-Z]/g) || []).length < 1) {
|
||||
errors.push("Password must include at least 1 upper case letter");
|
||||
}
|
||||
if ((password.match(/[^a-z]/gi) || []).length < 1) {
|
||||
errors.push("Password must include at least 1 symbol.");
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
errorMessage={() => (
|
||||
<ul>
|
||||
{errors.map((error, i) => (
|
||||
<li key={i}>{error}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
isInvalid={errors.length > 0}
|
||||
label="Password"
|
||||
labelPlacement="outside"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
variant="bordered"
|
||||
onValueChange={setPassword}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Real-time Validation" showEditor={false} files={formContent.realTimeValidation} />
|
||||
|
||||
### Server Integration
|
||||
|
||||
The Form components works seamlessly with React Server Actions.
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
|
||||
import {useActionState} from "react";
|
||||
import {Button, Form, Input} from "@nextui-org/react";
|
||||
|
||||
export function AddForm() {
|
||||
const [{errors}, formAction] = useActionState(createTodo, {
|
||||
errors: {},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form action={formAction} validationErrors={errors}>
|
||||
<Input name="todo" label="Task" />
|
||||
<Button type="submit">Add</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Form Libraries Support
|
||||
|
||||
You can also use popular form libraries like `react-hook-form` and `formik`.
|
||||
|
||||
```tsx
|
||||
import {Controller, useForm} from "react-hook-form";
|
||||
import {Button, Form, Input} from "@nextui-org/react";
|
||||
|
||||
function Example() {
|
||||
const {handleSubmit, control} = useForm({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit((data) => console.log(data))}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
rules={{required: "Name is required"}}
|
||||
render={({field, fieldState}) => (
|
||||
<Input
|
||||
{...field}
|
||||
label="Name"
|
||||
isInvalid={fieldState.invalid}
|
||||
errorMessage={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility Built-in
|
||||
|
||||
The `Form` component automatically handles ARIA attributes and keyboard navigation, making your forms usable by everyone:
|
||||
|
||||
```tsx
|
||||
function Example() {
|
||||
return (
|
||||
<Form>
|
||||
<Input
|
||||
label="Username"
|
||||
description="Choose a unique username"
|
||||
isRequired
|
||||
errorMessage="Username is required"
|
||||
// Automatically adds aria-required, aria-invalid, and aria-describedby
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
description="Must be at least 8 characters"
|
||||
// Help text is automatically linked via aria-describedby
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Schema validation
|
||||
|
||||
The `Form` component supports errors from schema validation libraries like [Zod](https://zod.dev/).
|
||||
You can use Zod's `flatten` method to get error messages for each field and return them as part of the server response.
|
||||
|
||||
```tsx {14}
|
||||
// In your server.
|
||||
import {z} from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1),
|
||||
age: z.coerce.number().positive(),
|
||||
});
|
||||
|
||||
function handleRequest(formData: FormData) {
|
||||
const result = schema.safeParse(Object.fromEntries(formData));
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
errors: result.error.flatten().fieldErrors,
|
||||
};
|
||||
}
|
||||
|
||||
// Do something with the validated data.
|
||||
|
||||
return {
|
||||
errors: {},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
```tsx {13}
|
||||
// In your client.
|
||||
import {useFormState} from "react";
|
||||
import {Button, Form, Input} from "@nextui-org/react";
|
||||
|
||||
import {handleRequest} from "./server";
|
||||
|
||||
function Example() {
|
||||
const [formState, formAction] = useFormState(handleRequest, {
|
||||
errors: {},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form action={formAction} validationErrors={formState.errors}>
|
||||
<Input isRequired label="Name" name="name" />
|
||||
<Input isRequired label="Age" name="age" type="number" />
|
||||
<Button type="submit">Submit</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Server Integration**: Works seamlessly with React Server Actions
|
||||
- **Schema Validation**: Supports Zod schema validation
|
||||
- **Form Libraries Support**: Supports popular form libraries like `react-hook-form` and `formik`
|
||||
- **Accessibility**: Built-in accessibility features including ARIA attributes and keyboard navigation
|
||||
|
||||
Check out our [Forms documentation](/docs/components/forms) for a deep dive into all the features and capabilities.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Drawer Component
|
||||
|
||||
The new [Drawer](/docs/components/drawer) component displays a sliding panel from the screen edge with supplementary content, featuring built-in accessibility, focus management, and keyboard navigation.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerBody,
|
||||
DrawerFooter,
|
||||
Button,
|
||||
useDisclosure,
|
||||
} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const {isOpen, onOpen, onOpenChange} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
endContent={
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.75 1.25a.75.75 0 1 0-1.5 0v.823l-.392.044c-.9.121-1.658.38-2.26.982s-.861 1.36-.982 2.26C.5 6.225.5 7.328.5 8.695v.11l.117 3.337c.121.9.38 1.658.982 2.26s1.36.861 2.26.982c.867.117 1.969.117 3.337.117h1.658l3.337-.117c.9-.121 1.658-.38 2.26-.982s.861-1.36.982-2.26c.117-.867.117-1.969.117-3.337v-.11l-.117-3.337c-.121-.9-.38-1.658-.982-2.26s-1.36-.861-2.26-.982l-.44-.048V1.25a.75.75 0 0 0-1.5 0v.756L8.853 2H7.195q-.78-.002-1.445.006zm4.5 3v-.744L8.798 3.5H7.25l-1.5.007v.743a.75.75 0 1 1-1.5 0v-.67l-.192.023c-.734.099-1.122.279-1.399.556s-.457.665-.556 1.399C2.002 6.313 2 7.315 2 8.75l.103 3.192c.099.734.279 1.122.556 1.399s.665.457 1.399.556c.755.101 1.756.103 3.192.103h1.548l3.192-.103c.734-.099 1.122-.279 1.399-.556s.457-.665.556-1.399c.102-.755.103-1.757.103-3.192l-.103-3.192c-.099-.734-.279-1.122-.556-1.399s-.665-.457-1.399-.556l-.241-.028v.675a.75.75 0 0 1-1.5 0zm-5 3.5a.75.75 0 1 1-1.5 0 .75.75 0 1 1 1.5 0m0 3.5a.75.75 0 1 1-1.5 0 .75.75 0 1 1 1.5 0M8 8.5A.75.75 0 1 0 8 7a.75.75 0 1 0 0 1.5m.75 2.75a.75.75 0 1 1-1.5 0 .75.75 0 1 1 1.5 0M11.5 8.5a.75.75 0 1 0 0-1.5.75.75 0 1 0 0 1.5m.75 2.75a.75.75 0 1 1-1.5 0 .75.75 0 1 1 1.5 0"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
variant="flat"
|
||||
onPress={onOpen}
|
||||
>
|
||||
See Event
|
||||
</Button>
|
||||
<Drawer isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<DrawerContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<DrawerHeader>Drawer Title</DrawerHeader>
|
||||
<DrawerBody>{/* Drawer content goes here */}</DrawerBody>
|
||||
<DrawerFooter>{/* Drawer footer content goes here */}</DrawerFooter>
|
||||
</>
|
||||
)}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Drawer Usage" showEditor={false} files={drawerContent.customStyles} />
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Multiple Placements**: Can be positioned on any edge of the screen (left, right, top, bottom)
|
||||
- **Customizable Sizes**: Comes with preset sizes from xs to 5xl, plus full-width option
|
||||
- **Backdrop Options**: Supports transparent, opaque, or blur backdrop styles
|
||||
- **Focus Management**: Automatically handles focus trapping and restoration
|
||||
- **Keyboard Navigation**: Built-in support for Esc key dismissal and keyboard navigation
|
||||
- **Form Validation**: Supports built-in validation states and custom validation rules for form inputs
|
||||
|
||||
See the [Drawer documentation](/docs/components/drawer) for more details and examples.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Input OTP Component
|
||||
|
||||
The [Input OTP](/docs/components/input-otp) component provides an accessible way to enter one-time passwords with built-in focus management and keyboard navigation. It is built on top of the [input-otp](https://github.com/guilhermerodz/input-otp) library by [@guilherme_rodz](https://twitter.com/guilherme_rodz).
|
||||
|
||||
```tsx
|
||||
import {Button, InputOtp, Form} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [otp, setOtp] = React.useState("");
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="flex w-full flex-col items-start gap-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const otp = formData.get("otp");
|
||||
|
||||
setOtp(otp);
|
||||
}}
|
||||
>
|
||||
<InputOtp
|
||||
isRequired
|
||||
aria-label="OTP input field"
|
||||
length={4}
|
||||
name="otp"
|
||||
placeholder="Enter code"
|
||||
validationBehavior="native"
|
||||
/>
|
||||
<Button size="sm" type="submit" variant="bordered">
|
||||
Submit
|
||||
</Button>
|
||||
{otp && <div className="text-small text-default-500">OTP submitted: {otp}</div>}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Input OTP Usage" showEditor={false} files={inputOtpContent.required} />
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Focus Management**: Automatically handles focus trapping and restoration
|
||||
- **Keyboard Navigation**: Built-in support for Esc key dismissal and keyboard navigation
|
||||
- **Customizable Animations**: Supports custom motion animations through Framer Motion
|
||||
- **Form Integration**: Seamlessly works with form elements while maintaining proper focus management
|
||||
|
||||
See the [Input OTP documentation](/docs/components/input-otp) for more details and examples.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Alert Component
|
||||
|
||||
The [Alert](/docs/components/alert) component allows users to display messages to the user. It's built with accessibility in mind and includes features like focus management, keyboard navigation, and screen reader support.
|
||||
|
||||
```tsx
|
||||
import {Alert} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Alert
|
||||
color="success"
|
||||
variant="faded"
|
||||
title="This is an alert"
|
||||
description="Thanks for subscribing to our newsletter!"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
<CodeDemo title="Alert Usage" showEditor={false} files={alertContent.controlled} />
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Focus Management**: Automatically handles focus trapping and restoration
|
||||
- **Keyboard Navigation**: Built-in support for Esc key dismissal and keyboard navigation
|
||||
- **Customizable Animations**: Supports custom motion animations through Framer Motion
|
||||
- **Form Integration**: Seamlessly works with form elements while maintaining proper focus management
|
||||
|
||||
See the [Alert documentation](/docs/components/alert) for more details and examples.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Collection-based components Virtualization
|
||||
|
||||
[Select](/docs/components/select), [Autocomplete](/docs/components/autocomplete) & [Listbox](/docs/components/listbox) components now support virtualization to improve performance on large collections.
|
||||
|
||||
<CodeDemo title="Virtualization" highlightedLines="41" files={autocompleteContent.virtualization} />
|
||||
|
||||
> **Note**: The virtualization strategy is based on the [@tanstack/react-virtual](https://tanstack.com/virtual/latest) package, which provides efficient rendering of large lists by only rendering items that are visible in the viewport.
|
||||
|
||||
See the [Autocomplete documentation](/docs/components/autocomplete) for more details and examples.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## React 19 Support & Library Upgrades
|
||||
|
||||
- In preparation for [React 19](https://react.dev/blog/2024/12/05/react-19), NextUI is now compatible with React 19 RC and Next.js 15.
|
||||
- React Aria packages upgrades and fixes the exact versions of React Aria, React Flow, React Hook Form, React Router, React Server, React Use, Tanstack Query, Tanstack Virtual, and Tanstack Table
|
||||
- Framer Motion is now **only** added to the final bundle when the animations are enabled and we also added support for [Framer motion v12](https://motion.dev/blog/framer-motion-is-now-independent-introducing-motion)
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## New use-theme hook
|
||||
|
||||
We created our own `use-theme` hook that replaces `use-dark-theme` hook to allow users to change the theme at runtime.
|
||||
|
||||
```jsx
|
||||
// App.tsx or App.jsx
|
||||
import React from "react";
|
||||
import {useTheme} from "@nextui-org/use-theme";
|
||||
|
||||
export default function App() {
|
||||
const {theme} = useTheme();
|
||||
|
||||
return (
|
||||
<main className={`${theme} text-foreground bg-background`}>
|
||||
<App />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
```jsx
|
||||
// components/ThemeSwitcher.tsx
|
||||
import {useTheme} from "@nextui-org/use-theme";
|
||||
|
||||
export const ThemeSwitcher = () => {
|
||||
const {theme, setTheme} = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
The current theme is: {theme}
|
||||
<button onClick={() => setTheme("light")}>Light Mode</button>
|
||||
<button onClick={() => setTheme("dark")}>Dark Mode</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
See the [Dark Mode documentation](/docs/customization/dark-mode) for more details and examples.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Draggable Modal
|
||||
|
||||
Added support for draggable modals.
|
||||
|
||||
<CodeDemo title="Draggable Modal" files={modalContent.draggable} />
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
> **Note**: The modal is draggable only from the header part.
|
||||
|
||||
## Router Improvements
|
||||
|
||||
NextUI now provides enhanced routing capabilities with better TypeScript support and integration with popular routing solutions:
|
||||
|
||||
- **Path Type Safety**: All router-based components now support path intellisense when using TypeScript
|
||||
- **Router Options Support**: Added `routerOptions` prop to all link components for controlling navigation behavior
|
||||
- **Enhanced Router Configuration**: Global type configuration for router options through TypeScript:
|
||||
|
||||
```tsx
|
||||
// Example with Next.js App Router
|
||||
declare module "@react-types/shared" {
|
||||
interface RouterConfig {
|
||||
routerOptions: NonNullable<Parameters<ReturnType<typeof useRouter>["push"]>[1]>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Improved Base Path Support**: Better handling of base paths through the new `useHref` prop in `NextUIProvider`
|
||||
- **Framework-specific Optimizations**: Built-in support for Next.js (both App and Pages Router), React Router, Remix, and TanStack Router
|
||||
|
||||
See the [Routing documentation](/docs/guides/routing) for more details.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## API Improvements
|
||||
|
||||
### NextUIProvider
|
||||
|
||||
- `useHref` (added) - Converts a router-specific href to a native href for use on DOM elements. For example, if your router accepts relative paths or custom link formats, useHref will generate the full native href based on the RouterProvider's configuration.
|
||||
- `navigate` (modified) - `((path: Href, routerOptions?: RouterOptions) => void) | undefined` - we added the router options
|
||||
- `reduceMotion` (added) - Controls the motion preferences for the entire application, allowing developers to respect user settings for reduced motion. The available options are:
|
||||
- `"always"` - Always disable motion
|
||||
- `"never"` - Always enable motion
|
||||
- `"user"` - Respect user system preferences for motion
|
||||
|
||||
### DatePicker
|
||||
|
||||
- `selectorButtonPlacement` (added) - Controls the placement of selector buttons
|
||||
- `showMonthAndYearPickers` (added) - Controls visibility of month and year picker dropdowns
|
||||
|
||||
### Tabs
|
||||
|
||||
- `tabRef` (added) - Allows accessing the underlying tab element via ref
|
||||
|
||||
### Popover
|
||||
|
||||
- `shouldCloseOnScroll` (added) - Controls whether popover should close when scrolling
|
||||
|
||||
### Table
|
||||
|
||||
- `isKeyboardNavigationDisabled` (added) - Disables keyboard navigation in tables
|
||||
|
||||
### Textarea
|
||||
|
||||
- `isClearable` (added) - Adds clear button functionality to textarea
|
||||
|
||||
### Select
|
||||
|
||||
- `hideEmptyContent` (added) - Controls visibility of empty content message
|
||||
- Added virtualization support for improved performance with large datasets
|
||||
|
||||
### Autocomplete & Listbox
|
||||
|
||||
- Added virtualization support for improved performance with large datasets
|
||||
|
||||
### Modal
|
||||
|
||||
- Added support for draggable modals (draggable from header)
|
||||
|
||||
See the [NextUIProvider documentation](/docs/api-references/nextui-provider) for more details.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## What's Next?
|
||||
|
||||
- **Rebranding** - We're preparing a rebranding of NextUI to ensure long-term sustainability and avoid potential naming conflicts
|
||||
- **Update NextUI Pro** - We're working on a new version of NextUI Pro that will be compatible with Next.js 15 and React 19. Follow progress [here](https://feedback.nextui.pro/roadmap)
|
||||
- **Tailwind CSS v4** support - Read more [here](https://tailwindcss.com/docs/v4-beta)
|
||||
- **React 19** internal APIs migration - Read more [here](https://react.dev/blog/2024/12/05/react-19)
|
||||
- **Toast** component - Follow progress [here](https://nextuioss.featurebase.app/roadmap)
|
||||
- **Rating** component - Follow progress [here](https://nextuioss.featurebase.app/roadmap)
|
||||
- **File Upload** component - Follow progress [here](https://nextuioss.featurebase.app/roadmap)
|
||||
- **Upgrade** to the latest React Aria - Read more [here](https://react-spectrum.adobe.com/releases/2024-11-20.html)
|
||||
- **Complete Figma components** - Follow progress [here](https://nextuioss.featurebase.app/roadmap)
|
||||
|
||||
## 🚨 Breaking Changes
|
||||
|
||||
We try to keep the breaking changes to a minimum, but sometimes it's necessary to make changes to the API to improve the developer experience.
|
||||
|
||||
#### Table Theme Group Data Selectors
|
||||
|
||||
The nested group selectors for table themes have been updated to require explicit element selectors. You'll need to add `/tr` or `/th` to custom styles for group-data.
|
||||
|
||||
```diff-tsx
|
||||
// Before
|
||||
- group-data-[disabled=true]:text-foreground-300;
|
||||
|
||||
// After
|
||||
+ group-data-[disabled=true]/tr:text-foreground-300;
|
||||
```
|
||||
|
||||
#### Circular Progress Theme Location
|
||||
|
||||
The circular progress Tailwind variants have been moved from `circular-progress` to `progress`. You'll need to update your Tailwind CSS configuration `tailwind.config.js`:
|
||||
|
||||
```diff-tsx
|
||||
// Before
|
||||
- "./node_modules/@nextui-org/theme/dist/components/circular-progress.js";
|
||||
|
||||
// After
|
||||
+ "./node_modules/@nextui-org/theme/dist/components/progress.js";
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Release Changes
|
||||
|
||||
**Features**:
|
||||
|
||||
- Added Alert component by [@jrgarciadev](https://github.com/jrgarciadev) and [@abhinav700](https://github.com/abhinav700) in [PR #3982](https://github.com/nextui-org/nextui/pull/3982), [PR #3680](https://github.com/nextui-org/nextui/pull/3680), [PR #4071](https://github.com/nextui-org/nextui/pull/4071), and [PR #4073](https://github.com/nextui-org/nextui/pull/4073)
|
||||
- Added Draggable modal by [@jrgarciadev](https://github.com/jrgarciadev) and [@wzc520pyfm](https://github.com/wzc520pyfm) in [PR #3983](https://github.com/nextui-org/nextui/pull/3983) and [PR #2818](https://github.com/nextui-org/nextui/pull/2818)
|
||||
- Added Drawer component by [@jrgarciadev](https://github.com/jrgarciadev) and [@1111mp](https://github.com/1111mp) in [PR #3986](https://github.com/nextui-org/nextui/pull/3986), [PR #2223](https://github.com/nextui-org/nextui/pull/2223) and [PR #4057](https://github.com/nextui-org/nextui/pull/4057)
|
||||
- Added InputOTP component by [@macci001](https://github.com/macci001) in [PR #4052](https://github.com/nextui-org/nextui/pull/4052)
|
||||
- Added Form component by [@ryo-manba](https://github.com/ryo-manba) in [PR #3036](https://github.com/nextui-org/nextui/pull/3036)
|
||||
- Added `selectorButtonPlacement` prop to date-picker by [@ryo-manba](https://github.com/ryo-manba) in [PR #3248](https://github.com/nextui-org/nextui/pull/3248)
|
||||
- Added `tabRef` prop to tabs by [@winchesHe](https://github.com/winchesHe) in [PR #3974](https://github.com/nextui-org/nextui/pull/3974)
|
||||
- Added `shouldCloseOnScroll` prop to popover by [@awesome-pro](https://github.com/awesome-pro) in [PR #3595](https://github.com/nextui-org/nextui/pull/3595)
|
||||
- Added `showMonthAndYearPickers` prop to date-range-picker and range-calendar by [@ryo-manba](https://github.com/ryo-manba) in [PR #3302](https://github.com/nextui-org/nextui/pull/3302)
|
||||
- Added `use-theme` hook by [@wingkwong](https://github.com/wingkwong) in [PR #3169](https://github.com/nextui-org/nextui/pull/3169)
|
||||
- Added `isKeyboardNavigationDisabled` prop in Table by [@macci001](https://github.com/macci001) in [PR #3735](https://github.com/nextui-org/nextui/pull/3735)
|
||||
- Added `reducedMotion` to Provider by [@ryo-manba](https://github.com/ryo-manba) in [PR #3470](https://github.com/nextui-org/nextui/pull/3470)
|
||||
- Added `isClearable` to Textarea by [@jrgarciadev](https://github.com/jrgarciadev) and [@IsDyh01](https://github.com/IsDyh01) in [PR #4172](https://github.com/nextui-org/nextui/pull/4172) and [PR #3477](https://github.com/nextui-org/nextui/pull/3477)
|
||||
- Added `hideEmptyContent` to Select by [@Peterl561](https://github.com/Peterl561) in [PR #4219](https://github.com/nextui-org/nextui/pull/4219)
|
||||
|
||||
**Documentation**:
|
||||
|
||||
- Improved performance by implementing `useIntersectionObserver` for code blocks - now they only load when visible in viewport
|
||||
- Enhanced readability with style changes to provide more space and better visual hierarchy
|
||||
- Revamped API Table design:
|
||||
- Moved descriptions into tooltips that appear right after titles
|
||||
- Decreased font size for better scanning
|
||||
- Improved color scheme for better contrast
|
||||
- Fixed broken links by [@wingkwong](https://github.com/wingkwong) in [PR #3796](https://github.com/nextui-org/nextui/pull/3796)
|
||||
- Fixed canary storybook links by [@wingkwong](https://github.com/wingkwong) in [PR #4030](https://github.com/nextui-org/nextui/pull/4030)
|
||||
- Fixed typos in dark mode page by [@wingkwong](https://github.com/wingkwong) in [PR #3823](https://github.com/nextui-org/nextui/pull/3823)
|
||||
- Fixed types for classNames and itemClasses by [@wingkwong](https://github.com/wingkwong) in [PR #4209](https://github.com/nextui-org/nextui/pull/4209)
|
||||
- Fixed typos in landing page by [@PentSec](https://github.com/PentSec) in [PR #3928](https://github.com/nextui-org/nextui/pull/3928)
|
||||
- Fixed typos in date picker page by [@dperconti](https://github.com/dperconti) in [PR #3516](https://github.com/nextui-org/nextui/pull/3516)
|
||||
- Fixed incorrect highlighted line in Select page by [@Choneas](https://github.com/Choneas) in [PR #3838](https://github.com/nextui-org/nextui/pull/3838)
|
||||
- Fixed unexpected translation in code block by [@nnmax](https://github.com/nnmax) in [PR #3878](https://github.com/nextui-org/nextui/pull/3878)
|
||||
- Fixed yarn command in installation page by [@AzpektDev](https://github.com/AzpektDev) in [PR #4132](https://github.com/nextui-org/nextui/pull/4132)
|
||||
- Added ethical ads by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #3803](https://github.com/nextui-org/nextui/pull/3803)
|
||||
- Added Form guide by [@ryo-manba](https://github.com/ryo-manba) in [PR #3882](https://github.com/nextui-org/nextui/pull/3882)
|
||||
- Enhanced overall DX for all pages by [@wingkwong](https://github.com/wingkwong) in [PR #4055](https://github.com/nextui-org/nextui/pull/4055)
|
||||
- Supported virtualization for Autocomplete by [@jrgarciadev](https://github.com/jrgarciadev) and [@vinroger](https://github.com/vinroger) in [PR #4094](https://github.com/nextui-org/nextui/pull/4094) and [PR #3444](https://github.com/nextui-org/nextui/pull/3444)
|
||||
- Supported virtualization for Select by [@vinroger](https://github.com/vinroger) in [PR #4203](https://github.com/nextui-org/nextui/pull/4203)
|
||||
- Supported virtualization for Listbox by [@vinroger](https://github.com/vinroger) in [PR #4206](https://github.com/nextui-org/nextui/pull/4206)
|
||||
- Optimized code fold by [@winchesHe](https://github.com/winchesHe) in [PR #4202](https://github.com/nextui-org/nextui/pull/4202)
|
||||
- Synced API from nextui-cli v0.3.5 by [@winchesHe](https://github.com/winchesHe) in [PR #4173](https://github.com/nextui-org/nextui/pull/4173)
|
||||
|
||||
**Bug Fixes**:
|
||||
|
||||
- Fixed avatar flashing issues by [@jrgarciadev](https://github.com/jrgarciadev) and [@rkkautsar](https://github.com/rkkautsar) in [PR #3987](https://github.com/nextui-org/nextui/pull/3987) and [PR #3444](https://github.com/nextui-org/nextui/pull/3444)
|
||||
- Fixed image ReferenceError in SSR by [@wingkwong](https://github.com/wingkwong) in [PR #4122](https://github.com/nextui-org/nextui/pull/4122)
|
||||
- Fixed navigate parameters by [@wingkwong](https://github.com/wingkwong) in [PR #4163](https://github.com/nextui-org/nextui/pull/4163)
|
||||
- Fixed missing peer / dev dependency for framer-motion by [@wingkwong](https://github.com/wingkwong) in [PR #4161](https://github.com/nextui-org/nextui/pull/4161)
|
||||
- Fixed menu item classNames by [@winchesHe](https://github.com/winchesHe) in [PR #4156](https://github.com/nextui-org/nextui/pull/4156)
|
||||
- Fixed controlled IsInvalid prop by [@chirokas](https://github.com/chirokas) in [PR #4082](https://github.com/nextui-org/nextui/pull/4082)
|
||||
- Fixed inert value with boolean type for React 19 by [@wingkwong](https://github.com/wingkwong) in [PR #4039](https://github.com/nextui-org/nextui/pull/4039)
|
||||
- Fixed typecheck errors by [@wingkwong](https://github.com/wingkwong) in [PR #4171](https://github.com/nextui-org/nextui/pull/4171)
|
||||
- Fixed tw nested group by [@wingkwong](https://github.com/wingkwong) in [PR #3909](https://github.com/nextui-org/nextui/pull/3909)
|
||||
- Fixed select styles consistent with input by [@macci001](https://github.com/macci001) in [PR #3881](https://github.com/nextui-org/nextui/pull/3881)
|
||||
- Fixed missing li tag when href is specified by [@macci001](https://github.com/macci001) in [PR #4168](https://github.com/nextui-org/nextui/pull/4168)
|
||||
- Fixed sliding issue caused by the helper wrapper by [@mstfblci](https://github.com/mstfblci) in [PR #3966](https://github.com/nextui-org/nextui/pull/3966)
|
||||
- Fixed element.ref warning in React 19 by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #4003](https://github.com/nextui-org/nextui/pull/4003)
|
||||
- Fixed image load on NextJS by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #3998](https://github.com/nextui-org/nextui/pull/3998)
|
||||
- Fixed squish textarea label by [@Peterl561](https://github.com/Peterl561) in [PR #4197](https://github.com/nextui-org/nextui/pull/4197)
|
||||
- Fixed forwardRef render functions by [@winchesHe](https://github.com/winchesHe) in [PR #4198](https://github.com/nextui-org/nextui/pull/4198)
|
||||
- Fixed collection based components ref by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #4207](https://github.com/nextui-org/nextui/pull/4207)
|
||||
- Fixed data-slot for select error message by [@jubar](https://github.com/jubar) in [PR #4214](https://github.com/nextui-org/nextui/pull/4214)
|
||||
- Fixed selection functionality on touch by [@macci001](https://github.com/macci001) in [PR #4220](https://github.com/nextui-org/nextui/pull/4220)
|
||||
- Fixed cursor position when hidden on init by [@Peterl561](https://github.com/Peterl561) in [PR #4222](https://github.com/nextui-org/nextui/pull/4222)
|
||||
|
||||
**Enhancements**:
|
||||
|
||||
- Automatically focus first non-disabled item by [@juliesaia](https://github.com/juliesaia) in [PR #2186](https://github.com/nextui-org/nextui/pull/2186)
|
||||
- Refactored RTL-specific styles by [@macci001](https://github.com/macci001) in [PR #3868](https://github.com/nextui-org/nextui/pull/3868)
|
||||
- Upgraded React Aria and Support NextJS 15 by [@ryo-manba](https://github.com/ryo-manba) in [PR #3732](https://github.com/nextui-org/nextui/pull/3732)
|
||||
- Upgraded tailwind-merge version by [@wingkwong](https://github.com/wingkwong) in [PR #3657](https://github.com/nextui-org/nextui/pull/3657)
|
||||
- Upgraded Storybook to v8 by [@winchesHe](https://github.com/winchesHe) in [PR #3946](https://github.com/nextui-org/nextui/pull/3946)
|
||||
- Optimized performance by [@wingkwong](https://github.com/wingkwong) in [PR #3523](https://github.com/nextui-org/nextui/pull/3523)
|
||||
- Applied `useMenu` and `useMenuItem` from `@react-aria` by [@wingkwong](https://github.com/wingkwong) in [PR #3261](https://github.com/nextui-org/nextui/pull/3261)
|
||||
- Revised label font size for lg by [@wingkwong](https://github.com/wingkwong) in [PR #4141](https://github.com/nextui-org/nextui/pull/4141)
|
||||
- Improved Form component by [@jrgarciadev](https://github.com/jrgarciadev) in [PR #4224](https://github.com/nextui-org/nextui/pull/4224)
|
||||
|
||||
**Chore**:
|
||||
|
||||
- Added git hook to auto update dependencies by [@winchesHe](https://github.com/winchesHe) in [PR #3365](https://github.com/nextui-org/nextui/pull/3365)
|
||||
- Added pre-release workflow by [@wingkwong](https://github.com/wingkwong) in [PR #2910](https://github.com/nextui-org/nextui/pull/2910)
|
||||
- Updated testing libraries by [@ryo-manba](https://github.com/ryo-manba) in [PR #3886](https://github.com/nextui-org/nextui/pull/3886)
|
||||
- Replaced VA by Posthog by [@wingkwong](https://github.com/wingkwong) in [PR #4123](https://github.com/nextui-org/nextui/pull/4123)
|
||||
- Moved circular-progress tv to progress by [@winchesHe](https://github.com/winchesHe) in [PR #3321](https://github.com/nextui-org/nextui/pull/3321)
|
||||
|
||||
Special thanks to NextUI Team members [@wingkwong](https://github.com/wingkwong), [@macci001](https://github.com/macci001),
|
||||
[@ryo-manba](https://github.com/ryo-manba), [@winchesHe](https://github.com/winchesHe), [@tianenpang](https://github.com/tianenpang) and contributors for their contributions to this release.
|
||||
|
||||
For a full list of changes, please refer to the [release notes](https://github.com/nextui-org/nextui/releases/tag/%40nextui-org%2Freact%402.6.2).
|
||||
|
||||
<Spacer y={6} />
|
||||
|
||||
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,20 @@
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion variant="bordered">
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,4 @@
|
||||
const App = `import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion variant="bordered">
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}`;
|
||||
import App from "./bordered-variant.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem Innei ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion isCompact>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,4 @@
|
||||
const App = `import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion isCompact>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}`;
|
||||
import App from "./compact.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState(new Set(["1"]));
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys}>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type {Selection} from "@nextui-org/react";
|
||||
|
||||
import React from "react";
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(new Set(["1"]));
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys}>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -1,54 +1,5 @@
|
||||
const App = `import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState(new Set(["1"]));
|
||||
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import type {Selection} from "@nextui-org/react";
|
||||
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(new Set(["1"]));
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
return (
|
||||
<Accordion
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}`;
|
||||
import App from "./controlled.raw.jsx?raw";
|
||||
import AppTs from "./controlled.raw.tsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
motionProps={{
|
||||
variants: {
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
height: "auto",
|
||||
overflowY: "unset",
|
||||
transition: {
|
||||
height: {
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 30,
|
||||
duration: 1,
|
||||
},
|
||||
opacity: {
|
||||
easings: "ease",
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
y: -10,
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
overflowY: "hidden",
|
||||
transition: {
|
||||
height: {
|
||||
easings: "ease",
|
||||
duration: 0.25,
|
||||
},
|
||||
opacity: {
|
||||
easings: "ease",
|
||||
duration: 0.3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +1,4 @@
|
||||
const App = `import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const defaultContent =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
motionProps={{
|
||||
variants: {
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
height: "auto",
|
||||
transition: {
|
||||
height: {
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 30,
|
||||
duration: 1,
|
||||
},
|
||||
opacity: {
|
||||
easings: "ease",
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
y: -10,
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
transition: {
|
||||
height: {
|
||||
easings: "ease",
|
||||
duration: 0.25,
|
||||
},
|
||||
opacity: {
|
||||
easings: "ease",
|
||||
duration: 0.3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" aria-label="Accordion 2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" aria-label="Accordion 3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}`;
|
||||
import App from "./custom-motion.raw.jsx?raw";
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user