Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57cafa06d0 | |||
| d2bd008ba3 | |||
| 2f87b8d644 | |||
| 6ecdc278ab | |||
| 9189b3fbf2 | |||
| 1149345cae | |||
| 135cc21e6f | |||
| 7f6218b978 | |||
| 0eb9f1e3fe | |||
| 11ef4365da | |||
| 85a820eeff | |||
| 6a6d426b10 | |||
| a3cbb5997b | |||
| a5cc42bf80 | |||
| e728a8967b | |||
| acba2cf8f5 | |||
| 049db57bb2 | |||
| 5296d9b84d | |||
| 1619a35cf9 | |||
| 8106629dab | |||
| d55f52cc35 | |||
| 78198c32be | |||
| 71807e8831 | |||
| 9af6174dcf | |||
| 1a129db6fe | |||
| 44ed1056e7 | |||
| db0e26a003 | |||
| 38af48faf5 | |||
| 5b60840751 | |||
| 4c56839db0 | |||
| ef61a3fe09 | |||
| bc4927c901 | |||
| 4191feab2a | |||
| fe8647bd1d | |||
| 23992572ea | |||
| e7a4711d9b | |||
| e84158db62 | |||
| f59e8f6bac | |||
| 73dfb7fc93 | |||
| df9f6f5f58 | |||
| 1fd5049f5a | |||
| a386e1cadd | |||
| 3172a803e3 | |||
| dfc8bdc7af | |||
| 67ce4beb28 | |||
| 5a4a60be65 | |||
| 25e86fb417 | |||
| a95bd6558d | |||
| 080718e265 | |||
| 8217639fd4 | |||
| 7abd68941a | |||
| c3f762563b | |||
| 8d2192183b | |||
| 8b86f83802 | |||
| f38059aa44 | |||
| 311ceb0c3a | |||
| 50a9bf54d4 | |||
| 99d4d296d7 | |||
| 9245384ac3 | |||
| 5af4b10114 | |||
| 8de357cfac | |||
| 655599982d | |||
| 3aac9bac26 | |||
| 95c8fdaa2e | |||
| c0706acbca | |||
| 9d4c7ed9cf | |||
| 9ebaae083d | |||
| c9c5564c58 | |||
| d79808dbe1 | |||
| 5a9b78d234 | |||
| 9f4bdf352d | |||
| d62feb08dc | |||
| 62d307117a | |||
| 01a27293eb | |||
| a53e274d5b | |||
| 425a034bca | |||
| a5a1ea5ade | |||
| f6531c5f60 | |||
| 37d5f12162 | |||
| dd97fc3c4f | |||
| ceddd0d1d9 | |||
| bb8ed58749 | |||
| bbbdcbdfdf | |||
| bc424948c7 | |||
| cdc30db14c | |||
| 3aa86423aa | |||
| 59ed34b3de | |||
| f58fc9bb69 | |||
| 9a478b6c1f | |||
| 7fd0327657 | |||
| 05c966e8a4 | |||
| 5b014b76ff | |||
| cf32b5ae8b | |||
| 4452ed66c0 | |||
| b1b30b7976 | |||
| 97ef2d7250 | |||
| 8b39989090 | |||
| 8363a3ecfc | |||
| 5c30e04811 | |||
| 8606fbe0b9 | |||
| 1612532eee | |||
| a3be419cb3 | |||
| 72a7f71377 | |||
| 7c8341035d | |||
| 6ca0ce309a | |||
| acc2ff3913 | |||
| 4db10a47e9 | |||
| a9c4fab011 | |||
| d61428d9e6 | |||
| 455b788924 | |||
| 79cfdbd8ff | |||
| 7d294e3a04 | |||
| 43906f639e | |||
| 8fcc8b3767 | |||
| 28477447d3 | |||
| fc3c6b9431 | |||
| 59fda3772f | |||
| d8d2b87cb8 | |||
| 64571e468c | |||
| faf26acd23 | |||
| cac56faf29 | |||
| 09fe1d3141 | |||
| a9e324b351 | |||
| 043b8420cf | |||
| 57909accde | |||
| 7a17256268 | |||
| bf2bc265ee | |||
| cf6bf749a1 | |||
| 641bf0885b | |||
| 40b42001f9 | |||
| 789a7209a2 | |||
| 021aa16d96 | |||
| eb8a1fcc01 | |||
| 9b42a2fca1 | |||
| 5702287e56 | |||
| 290cce15be | |||
| 27bde30839 | |||
| 6be3bc3516 | |||
| e4113b05ce | |||
| a3a95e7196 | |||
| 8731159702 | |||
| b6adb2ae8c | |||
| cd2d9b70a9 | |||
| a371e1a5fa | |||
| 5cdc4d1d87 | |||
| 22c6e070dd | |||
| 0714192838 | |||
| ae9f300d45 | |||
| a16dac04c8 | |||
| 94db001961 | |||
| d77cf7658b | |||
| 46c47f7e6e | |||
| 35b9741663 | |||
| 2b82ba86eb | |||
| 904f538779 | |||
| 609e9f4c11 |
@@ -10,6 +10,9 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
},
|
||||
"ignore": [
|
||||
"@nextui-org/docs",
|
||||
"@nextui-org/storybook"
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ pnpm test:update src/button # or npm run test:update src/button
|
||||
pnpm build # or npm run build
|
||||
```
|
||||
|
||||
> Note: ensure your version of Node is 14 or higher to run scripts
|
||||
> Note: ensure your version of Node is 16 or higher to run scripts
|
||||
|
||||
6. Send your pull request:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function DocsLayout({children}: DocsLayoutProps) {
|
||||
<>
|
||||
<main className="relative container mx-auto max-w-7xl z-10 px-6 min-h-[calc(100vh_-_64px_-_108px)] mb-12 flex-grow">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="hidden relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
|
||||
<div className="hidden overflow-visible relative z-10 lg:block lg:col-span-2 mt-8 pr-4">
|
||||
<DocsSidebar routes={manifest.routes} />
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable no-console */
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
height: string;
|
||||
mass: string;
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(`https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable no-console */
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
|
||||
type Pokemon = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type UsePokemonListProps = {
|
||||
/** Delay to wait before fetching more items */
|
||||
fetchDelay?: number;
|
||||
};
|
||||
|
||||
function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
|
||||
const [items, setItems] = React.useState<Pokemon[]>([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset: number) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
|
||||
|
||||
const [, scrollerRef] = useInfiniteScroll({
|
||||
hasMore,
|
||||
isEnabled: isOpen,
|
||||
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
|
||||
onLoadMore,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
defaultItems={items}
|
||||
isLoading={isLoading}
|
||||
label="Pick a Pokemon"
|
||||
placeholder="Select a Pokemon"
|
||||
scrollRef={scrollerRef}
|
||||
variant="bordered"
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {Autocomplete, AutocompleteItem, MenuTriggerAction} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
|
||||
const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];
|
||||
|
||||
type FieldState = {
|
||||
selectedKey: React.Key | null;
|
||||
inputValue: string;
|
||||
items: typeof animals;
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
// Store ComboBox input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState<FieldState>({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value: string) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen: boolean, menuTrigger: MenuTriggerAction) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Select an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,10 +9,17 @@ import {
|
||||
Textarea,
|
||||
Input,
|
||||
Tab,
|
||||
Avatar,
|
||||
Select,
|
||||
SelectItem,
|
||||
AccordionItem,
|
||||
Pagination,
|
||||
extendVariants,
|
||||
PaginationItem,
|
||||
} from "@nextui-org/react";
|
||||
import {useState} from "react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useSearchParams} from "next/navigation";
|
||||
|
||||
import {SearchLinearIcon} from "@/components/icons";
|
||||
|
||||
@@ -175,16 +182,302 @@ const MyButton2 = extendVariants(Button, {
|
||||
xl: "px-12 py-6 text-xl",
|
||||
},
|
||||
},
|
||||
defaultVariants: {},
|
||||
defaultVariants: {
|
||||
color: "foreground",
|
||||
},
|
||||
});
|
||||
|
||||
const usersData = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
export default function NextUIPerf() {
|
||||
const [textA, setTextA] = useState<string>("");
|
||||
const [textB, setTextB] = useState<string>("");
|
||||
const [textC, setTextC] = useState<string>("");
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [inputValue, setInputValue] = useState<string>();
|
||||
const [selectedKey, setSelectedKey] = useState<string>("");
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const page = Number(searchParams.get("page"));
|
||||
|
||||
let {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
const filteredItems = inputValue
|
||||
? usersData.filter((item) => startsWith(item.name, inputValue))
|
||||
: usersData;
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
isOpen && inputRef?.current?.focus();
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSelectionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedKey(e.target.value);
|
||||
};
|
||||
|
||||
const topContent = useMemo(() => {
|
||||
return (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
isClearable
|
||||
aria-activedescendant={selectedKey}
|
||||
aria-expanded={isOpen}
|
||||
aria-label="Search user"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="z-10 sticky top-1"
|
||||
placeholder="Search..."
|
||||
spellCheck={false}
|
||||
startContent={<SearchLinearIcon className="text-default-400" size={18} strokeWidth="2" />}
|
||||
type="text"
|
||||
onValueChange={setInputValue}
|
||||
/>
|
||||
);
|
||||
}, [inputRef, selectedKey, isOpen]);
|
||||
|
||||
return (
|
||||
<div className="w-full p-24 gap-4 flex flex-col">
|
||||
<Select
|
||||
classNames={{
|
||||
base: "max-w-xs",
|
||||
listboxWrapper: "scroll-pb-6 scroll-pt-28",
|
||||
}}
|
||||
items={filteredItems}
|
||||
label="Assigned to"
|
||||
labelPlacement="outside"
|
||||
listboxProps={{
|
||||
topContent,
|
||||
variant: "flat",
|
||||
classNames: {
|
||||
base: [
|
||||
"before:content-[''] before:rounded-t-medium before:fixed before:w-full before:h-14 before:z-10",
|
||||
"before:top-0 before:left-0 before:bg-gradient-to-b before:from-default-50",
|
||||
],
|
||||
},
|
||||
}}
|
||||
placeholder="Select a user"
|
||||
selectedKeys={[selectedKey]}
|
||||
showScrollIndicators={false}
|
||||
variant="flat"
|
||||
onChange={handleSelectionChange}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<SelectItem key={item.id} textValue={item.name}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={item.name} className="flex-shrink-0" size="sm" src={item.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{item.name}</span>
|
||||
<span className="text-tiny text-default-400">{item.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
|
||||
<Accordion>
|
||||
<AccordionItem key="1" aria-label="Accordion 1" title="Accordion 1">
|
||||
Non est aliqua tempor occaecat laborum. Lorem culpa minim irure mollit. Est qui
|
||||
@@ -253,7 +546,16 @@ export default function NextUIPerf() {
|
||||
|
||||
<Button>Click Me!</Button>
|
||||
|
||||
<MyButton2 color="foreground">Press Me!</MyButton2>
|
||||
<MyButton2 color="primary">Press Me!</MyButton2>
|
||||
|
||||
<Pagination
|
||||
showControls
|
||||
initialPage={page ?? 1}
|
||||
renderItem={({page, ...itemProps}) => {
|
||||
return <PaginationItem href={`/examples/perf?page=${page}`} {...itemProps} />;
|
||||
}}
|
||||
total={10}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Spinner,
|
||||
Pagination,
|
||||
} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
import {useCallback, useMemo, useState} from "react";
|
||||
import {useMemo, useState} from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
@@ -21,50 +21,23 @@ type SWCharacter = {
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
const fetcher = (...args: Parameters<typeof fetch>) => fetch(...args).then((res) => res.json());
|
||||
|
||||
export default function Page() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const {data, isLoading} = useSWR<{
|
||||
count: number;
|
||||
results: SWCharacter[];
|
||||
}>(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const rowsPerPage = 10;
|
||||
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, cursor}) {
|
||||
// If no cursor is available, then we're loading the first page.
|
||||
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
|
||||
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
|
||||
let json = await res.json();
|
||||
|
||||
setTotal(json.count);
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
cursor: json.next,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const pages = Math.ceil(total / rowsPerPage);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
|
||||
return list.items.slice(start, end);
|
||||
}, [page, list.items?.length]);
|
||||
|
||||
const onPaginationChange = useCallback(
|
||||
(page: number) => {
|
||||
setIsLoading(true);
|
||||
if (page >= list.items.length / rowsPerPage) {
|
||||
list.loadMore();
|
||||
}
|
||||
setPage(page);
|
||||
},
|
||||
[list.items.length],
|
||||
);
|
||||
const pages = useMemo(() => {
|
||||
return data?.count ? Math.ceil(data.count / rowsPerPage) : 0;
|
||||
}, [data?.count, rowsPerPage]);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
@@ -80,7 +53,7 @@ export default function Page() {
|
||||
color="primary"
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={onPaginationChange}
|
||||
onChange={(page) => setPage(page)}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
@@ -96,8 +69,8 @@ export default function Page() {
|
||||
<TableColumn key="birth_year">Birth year</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
isLoading={isLoading && !items.length}
|
||||
items={items}
|
||||
isLoading={isLoading || data?.results.length === 0}
|
||||
items={data?.results ?? []}
|
||||
loadingContent={<Spinner />}
|
||||
>
|
||||
{(item) => (
|
||||
|
||||
@@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = typeof users[0];
|
||||
type User = (typeof users)[number];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
@@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = typeof users[0];
|
||||
type User = (typeof users)[number];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
@@ -24,7 +24,8 @@ export async function GET() {
|
||||
url: `${siteConfig.siteUrl}/blog/${post.slug}`,
|
||||
guid: `${siteConfig.siteUrl}/blog/${post.slug}`,
|
||||
date: post.date,
|
||||
author: `${author} <${siteConfig.email}>`,
|
||||
// @ts-ignore - name does exist
|
||||
author: `${author.name} <${siteConfig.email}>`,
|
||||
categories: post.tags ?? [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Button, Image, Link} from "@nextui-org/react";
|
||||
import {Image} from "@nextui-org/react";
|
||||
|
||||
import {Blockquote} from "@/components/docs/components/blockquote";
|
||||
import {FigmaButton} from "@/components/figma-button";
|
||||
|
||||
export default function FigmaPage() {
|
||||
return (
|
||||
@@ -23,16 +24,7 @@ export default function FigmaPage() {
|
||||
width="800"
|
||||
/>
|
||||
<div className="text-center max-w-2xl m-auto">
|
||||
<Button className="max-w-fit" color="default" variant="bordered">
|
||||
<Link
|
||||
isExternal
|
||||
showAnchorIcon
|
||||
className="text-current"
|
||||
href="https://www.figma.com/community/file/1267584376234254760"
|
||||
>
|
||||
Open in Figma
|
||||
</Link>
|
||||
</Button>
|
||||
<FigmaButton />
|
||||
<Blockquote color="warning">
|
||||
This file is still in development and will be continuously updated.
|
||||
</Blockquote>
|
||||
|
||||
@@ -53,6 +53,12 @@ export const metadata: Metadata = {
|
||||
},
|
||||
],
|
||||
creator: "jrgarciadev",
|
||||
alternates: {
|
||||
canonical: "https://nextui.org",
|
||||
types: {
|
||||
"application/rss+xml": [{url: "https://nextui.org/feed.xml", title: "NextUI RSS Feed"}],
|
||||
},
|
||||
},
|
||||
viewport:
|
||||
"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0",
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
import {ThemeProvider as NextThemesProvider} from "next-themes";
|
||||
import {ThemeProviderProps} from "next-themes/dist/types";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
export interface ProvidersProps {
|
||||
children: React.ReactNode;
|
||||
@@ -11,8 +12,10 @@ export interface ProvidersProps {
|
||||
}
|
||||
|
||||
export function Providers({children, themeProps}: ProvidersProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<NextUIProvider>
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
);
|
||||
|
||||
@@ -8,10 +8,20 @@ import NextLink from "next/link";
|
||||
import {AnimatePresence, motion} from "framer-motion";
|
||||
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BlogPostCard = (post: BlogPost) => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("BlogPostCard - Selection", {
|
||||
name: post.title,
|
||||
action: "click",
|
||||
category: "blog",
|
||||
data: post.url ?? "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isMounted && (
|
||||
@@ -27,6 +37,7 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
className="p-2 h-full border-transparent text-start bg-white/5 dark:bg-default-400/10 backdrop-blur-lg backdrop-saturate-[1.8]"
|
||||
href={post.url}
|
||||
isPressable={!!post.url}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<CardHeader>
|
||||
<Link
|
||||
@@ -35,6 +46,7 @@ const BlogPostCard = (post: BlogPost) => {
|
||||
href={post.url}
|
||||
size="lg"
|
||||
underline="hover"
|
||||
onPress={handlePress}
|
||||
>
|
||||
<Balancer>{post.title}</Balancer>
|
||||
</Link>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
|
||||
import searchData from "@/config/search-meta.json";
|
||||
import {useUpdateEffect} from "@/hooks/use-update-effect";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const hideOnPaths = ["examples"];
|
||||
|
||||
@@ -175,6 +176,13 @@ export const Cmdk: FC<{}> = () => {
|
||||
|
||||
const matches = intersectionBy(...matchesForEachWord, "objectID").slice(0, MAX_RESULTS);
|
||||
|
||||
trackEvent("Cmdk - Search", {
|
||||
name: "cmdk - search",
|
||||
action: "search",
|
||||
category: "cmdk",
|
||||
data: {query, words, matches: matches?.map((match) => match.url).join(", ")},
|
||||
});
|
||||
|
||||
return matches;
|
||||
},
|
||||
[query],
|
||||
@@ -190,10 +198,21 @@ export const Cmdk: FC<{}> = () => {
|
||||
if (e?.key?.toLowerCase() === "k" && e[hotkey]) {
|
||||
e.preventDefault();
|
||||
isOpen ? onClose() : onOpen();
|
||||
|
||||
trackEvent("Cmdk - Open/Close", {
|
||||
name: "cmdk - open/close",
|
||||
action: "keydown",
|
||||
category: "cmdk",
|
||||
data: isOpen ? "close" : "open",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const onItemSelect = useCallback(
|
||||
@@ -201,6 +220,13 @@ export const Cmdk: FC<{}> = () => {
|
||||
onClose();
|
||||
router.push(item.url);
|
||||
addToRecentSearches(item);
|
||||
|
||||
trackEvent("Cmdk - ItemSelect", {
|
||||
name: item.content,
|
||||
action: "click",
|
||||
category: "cmdk",
|
||||
data: item.url,
|
||||
});
|
||||
},
|
||||
[router, recentSearches],
|
||||
);
|
||||
|
||||
@@ -108,9 +108,9 @@ function CodeTypewriter({value, className, css, ...props}: any) {
|
||||
return (
|
||||
<Pre className={className} css={css} {...props}>
|
||||
<code
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
ref={wrapperRef}
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
style={{opacity: 0}}
|
||||
/>
|
||||
</Pre>
|
||||
@@ -155,7 +155,7 @@ const CodeBlock = React.forwardRef<HTMLPreElement, CodeBlockProps>((_props, forw
|
||||
{...props}
|
||||
>
|
||||
{showWindowIcons && <WindowActions title={title} />}
|
||||
<code className={clsx(classes, codeClasses)} dangerouslySetInnerHTML={{__html: result}} />
|
||||
<code dangerouslySetInnerHTML={{__html: result}} className={clsx(classes, codeClasses)} />
|
||||
</Pre>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import {useRef} from "react";
|
||||
import {Button} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const CustomButton = () => {
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
@@ -26,6 +28,11 @@ export const CustomButton = () => {
|
||||
x: targetCenterX / clientWidth,
|
||||
},
|
||||
});
|
||||
|
||||
trackEvent("LandingPage - Confetti Button", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardBody, Button, Image, Progress, CardProps} from "@nextui-org/react";
|
||||
import {Card, CardBody, Button, Image, Slider, CardProps} from "@nextui-org/react";
|
||||
import {useState, FC} from "react";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import NextImage from "next/image";
|
||||
@@ -63,15 +63,15 @@ export const MusicPlayer: FC<MusicPlayerProps> = ({className, ...otherProps}) =>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col mt-3 gap-1">
|
||||
<Progress
|
||||
<Slider
|
||||
aria-label="Music progress"
|
||||
classNames={{
|
||||
indicator: "bg-default-800 dark:bg-white",
|
||||
track: "bg-default-500/30",
|
||||
thumb: "w-2 h-2 after:w-2 after:h-2 after:bg-foreground",
|
||||
}}
|
||||
color="default"
|
||||
color="foreground"
|
||||
defaultValue={33}
|
||||
size="sm"
|
||||
value={33}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<p className="text-sm">1:23</p>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo";
|
||||
import WindowResizer, {WindowResizerProps} from "./window-resizer";
|
||||
|
||||
import {GradientBoxProps} from "@/components/gradient-box";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const DynamicReactLiveDemo = dynamic(
|
||||
() => import("./react-live-demo").then((m) => m.ReactLiveDemo),
|
||||
@@ -31,7 +32,6 @@ interface CodeDemoProps extends UseCodeDemoProps, WindowResizerProps {
|
||||
showSandpackPreview?: boolean;
|
||||
initialEditorOpen?: boolean;
|
||||
enableResize?: boolean;
|
||||
showTabs?: boolean;
|
||||
showPreview?: boolean;
|
||||
hideWindowActions?: boolean;
|
||||
showOpenInCodeSandbox?: boolean;
|
||||
@@ -53,6 +53,7 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
showEditor = true,
|
||||
showPreview = true,
|
||||
asIframe = false,
|
||||
showTabs = true,
|
||||
resizeEnabled = true,
|
||||
hideWindowActions = false,
|
||||
showSandpackPreview = false,
|
||||
@@ -65,7 +66,6 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
previewHeight = "auto",
|
||||
overflow = "visible",
|
||||
displayMode = "always",
|
||||
showTabs = true,
|
||||
gradientColor,
|
||||
highlightedLines,
|
||||
iframeInitialWidth,
|
||||
@@ -180,6 +180,14 @@ export const CodeDemo: React.FC<CodeDemoProps> = ({
|
||||
panel: "pt-0",
|
||||
}}
|
||||
variant="underlined"
|
||||
onSelectionChange={(tabKey) => {
|
||||
trackEvent("CodeDemo - Selection", {
|
||||
name: tabKey as string,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: tabKey ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Tab key="preview" title="Preview">
|
||||
{previewContent}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import React, {forwardRef} from "react";
|
||||
import {clsx, getUniqueID} from "@nextui-org/shared-utils";
|
||||
import React, {forwardRef, useEffect} from "react";
|
||||
import {clsx, dataAttr, getUniqueID} from "@nextui-org/shared-utils";
|
||||
import BaseHighlight, {Language, PrismTheme, defaultProps} from "prism-react-renderer";
|
||||
import {debounce} from "lodash";
|
||||
|
||||
import defaultTheme from "@/libs/prism-theme";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
interface CodeblockProps {
|
||||
language: Language;
|
||||
codeString: string;
|
||||
metastring?: string;
|
||||
theme?: PrismTheme;
|
||||
children?: React.ReactNode;
|
||||
showLines?: boolean;
|
||||
removeIndent?: boolean;
|
||||
hideScrollBar?: boolean;
|
||||
className?: string;
|
||||
children?: (props: any) => React.ReactNode;
|
||||
}
|
||||
|
||||
type HighlightStyle = "inserted" | "deleted" | undefined;
|
||||
|
||||
const RE = /{([\d,-]+)}/;
|
||||
|
||||
const calculateLinesToHighlight = (meta?: string) => {
|
||||
@@ -60,11 +64,76 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
const shouldHighlightLine = calculateLinesToHighlight(metastring);
|
||||
const isMultiLine = codeString.split("\n").length > 2;
|
||||
|
||||
const lastSelectionText = React.useRef<string | null>(null);
|
||||
|
||||
const isDiff = language.includes("diff");
|
||||
|
||||
const codeLang = isDiff ? (language.split("-")[1] as Language) : language;
|
||||
|
||||
let highlightStyle: HighlightStyle[] = [];
|
||||
|
||||
if (isDiff) {
|
||||
let code: string[] = [];
|
||||
|
||||
highlightStyle = codeString.split?.("\n").map((line) => {
|
||||
if (line.startsWith("+")) {
|
||||
code.push(line.substr(1));
|
||||
|
||||
return "inserted";
|
||||
}
|
||||
if (line.startsWith("-")) {
|
||||
code.push(line.substr(1));
|
||||
|
||||
return "deleted";
|
||||
}
|
||||
code.push(line);
|
||||
});
|
||||
|
||||
codeString = code.join("\n");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionChange = () => {
|
||||
if (!window.getSelection) return;
|
||||
|
||||
const el = window.getSelection()?.anchorNode?.parentNode;
|
||||
|
||||
if (!el) return;
|
||||
|
||||
const selectionText = window.getSelection()?.toString();
|
||||
|
||||
if (!selectionText) return;
|
||||
|
||||
if (
|
||||
!selectionText ||
|
||||
selectionText === lastSelectionText.current ||
|
||||
!codeString.includes(selectionText)
|
||||
)
|
||||
return;
|
||||
|
||||
lastSelectionText.current = selectionText;
|
||||
|
||||
trackEvent("Codeblock - Selection", {
|
||||
action: "selectText",
|
||||
category: "docs",
|
||||
data: selectionText,
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedHandleSelectionChange = debounce(handleSelectionChange, 1000);
|
||||
|
||||
document.addEventListener("selectionchange", debouncedHandleSelectionChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("selectionchange", debouncedHandleSelectionChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseHighlight
|
||||
{...defaultProps}
|
||||
code={codeString}
|
||||
language={language}
|
||||
language={codeLang}
|
||||
theme={theme}
|
||||
{...props}
|
||||
>
|
||||
@@ -97,6 +166,8 @@ const Codeblock = forwardRef<HTMLPreElement, CodeblockProps>(
|
||||
shouldHighlightLine(i),
|
||||
},
|
||||
)}
|
||||
data-deleted={dataAttr(highlightStyle?.[i] === "deleted")}
|
||||
data-inserted={dataAttr(highlightStyle?.[i] === "inserted")}
|
||||
>
|
||||
{showLines && (
|
||||
<span className="select-none text-xs mr-6 opacity-30">{i + 1}</span>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Balancer from "react-wrap-balancer";
|
||||
|
||||
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;
|
||||
@@ -23,6 +24,16 @@ const ButtonLink = ({
|
||||
href: string;
|
||||
tooltip?: string | ReactNode;
|
||||
}) => {
|
||||
const handlePress = () => {
|
||||
if (!href) return;
|
||||
|
||||
trackEvent("ComponentLinks - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
});
|
||||
};
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
isExternal
|
||||
@@ -31,6 +42,7 @@ const ButtonLink = ({
|
||||
href={href}
|
||||
size="sm"
|
||||
startContent={startContent}
|
||||
onPress={handlePress}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -60,7 +72,9 @@ export const ComponentLinks = ({
|
||||
return (
|
||||
<div className="flex flex-wrap gap-3 mt-6">
|
||||
<ButtonLink
|
||||
href={`https://storybook.nextui.org/?path=/story/components-${storybook || component}`}
|
||||
href={`https://storybook.nextui.org/?path=/story/components-${
|
||||
storybook || component
|
||||
}--default`}
|
||||
startContent={<StorybookIcon className="text-lg text-[#ff4785]" />}
|
||||
>
|
||||
Storybook
|
||||
@@ -81,7 +95,7 @@ export const ComponentLinks = ({
|
||||
)}
|
||||
{rscCompatible && (
|
||||
<ButtonLink
|
||||
href="https://nextjs.org/docs/getting-started/react-essentials#server-components"
|
||||
href="https://nextjs.org/docs/app/building-your-application/rendering/server-components"
|
||||
startContent={<NextJsIcon size={18} />}
|
||||
tooltip={
|
||||
<p>
|
||||
|
||||
@@ -2,6 +2,8 @@ import {Tabs, Tab, Snippet} from "@nextui-org/react";
|
||||
|
||||
import Codeblock from "./codeblock";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type PackageManager = {
|
||||
key: string;
|
||||
name: string;
|
||||
@@ -32,6 +34,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
|
||||
tabList: "relative h-10",
|
||||
}}
|
||||
variant="underlined"
|
||||
onSelectionChange={(tabKey) => {
|
||||
trackEvent("ImportTabs - Selection", {
|
||||
name: tabKey as string,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: commands[tabKey] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{importTabs.map(({key, name}) => {
|
||||
if (!commands[key]) return null;
|
||||
@@ -47,6 +57,14 @@ export const ImportTabs = ({commands}: ImportTabsProps) => {
|
||||
pre: "font-light text-base",
|
||||
copyButton: "text-lg text-default-400",
|
||||
}}
|
||||
onCopy={() => {
|
||||
trackEvent("ImportTabs - Copy", {
|
||||
name,
|
||||
action: "copyInstallScript",
|
||||
category: "docs",
|
||||
data: commands[name] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock
|
||||
hideScrollBar
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {Tabs, Tab, Snippet} from "@nextui-org/react";
|
||||
import {Key} from "react";
|
||||
import {useLocalStorage} from "usehooks-ts";
|
||||
|
||||
import Codeblock from "./codeblock";
|
||||
|
||||
import {YarnIcon, NpmSmallIcon, PnpmIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
type PackageManagerName = "npm" | "yarn" | "pnpm";
|
||||
|
||||
@@ -27,10 +30,26 @@ const packageManagers: PackageManager[] = [
|
||||
];
|
||||
|
||||
export interface PackageManagersProps {
|
||||
commands: Partial<Record<PackageManagerName, string>>;
|
||||
commands: Partial<Record<PackageManagerName, React.Key>>;
|
||||
}
|
||||
|
||||
export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
const [selectedManager, setSelectedManager] = useLocalStorage<PackageManagerName>(
|
||||
"selectedPackageManager",
|
||||
"npm",
|
||||
);
|
||||
|
||||
const handleSelectionChange = (tabKey: Key) => {
|
||||
trackEvent("PackageManagers - Selection", {
|
||||
name: `${tabKey}`,
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: commands[tabKey as unknown as PackageManagerName] ?? "",
|
||||
});
|
||||
|
||||
setSelectedManager(tabKey as PackageManagerName);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
aria-label="NextUI installation commands"
|
||||
@@ -38,7 +57,9 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
base: "group mt-4",
|
||||
tabList: "h-10",
|
||||
}}
|
||||
selectedKey={selectedManager}
|
||||
variant="underlined"
|
||||
onSelectionChange={handleSelectionChange}
|
||||
>
|
||||
{packageManagers.map(({name, icon}) => {
|
||||
if (!commands[name]) return null;
|
||||
@@ -62,6 +83,14 @@ export const PackageManagers = ({commands}: PackageManagersProps) => {
|
||||
pre: "font-light text-base",
|
||||
copyButton: "text-lg text-zinc-500 mr-2",
|
||||
}}
|
||||
onCopy={() => {
|
||||
trackEvent("PackageManagers - Copy", {
|
||||
name,
|
||||
action: "copyScript",
|
||||
category: "docs",
|
||||
data: commands[name] ?? "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock removeIndent codeString={commands[name] as string} language="bash" />
|
||||
</Snippet>
|
||||
|
||||
@@ -1,31 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import NextLink from "next/link";
|
||||
import {Link} from "@nextui-org/react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {ChevronIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
import manifest from "@/config/routes.json";
|
||||
import {removeFromLast} from "@/utils";
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {useDocsRoute} from "@/hooks/use-docs-route";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface FooterNavProps {
|
||||
currentRoute?: Route;
|
||||
}
|
||||
|
||||
export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const {prevRoute, nextRoute} = useDocsRoute(manifest.routes, currentRoute);
|
||||
|
||||
const handlePress = (path: string) => {
|
||||
trackEvent("DocsPager - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: path || "",
|
||||
});
|
||||
|
||||
router.push(removeFromLast(path || "", "."));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full justify-between py-20">
|
||||
{prevRoute ? (
|
||||
<Link
|
||||
isBlock
|
||||
as={NextLink}
|
||||
className="flex gap-2"
|
||||
className="cursor-pointer flex gap-2"
|
||||
color="foreground"
|
||||
href={removeFromLast(prevRoute.path || "", ".")}
|
||||
onPress={() => handlePress(prevRoute.path || "")}
|
||||
>
|
||||
<ChevronIcon className="text-primary" height={20} width={20} />
|
||||
{prevRoute.title}
|
||||
@@ -36,10 +48,9 @@ export const DocsPager: React.FC<FooterNavProps> = ({currentRoute}) => {
|
||||
{nextRoute && (
|
||||
<Link
|
||||
isBlock
|
||||
as={NextLink}
|
||||
className="flex gap-1 items-center"
|
||||
className="cursor-pointer flex gap-1 items-center"
|
||||
color="foreground"
|
||||
href={removeFromLast(nextRoute.path || "", ".")}
|
||||
onPress={() => handlePress(nextRoute.path || "")}
|
||||
>
|
||||
{nextRoute.title}
|
||||
<ChevronIcon className="rotate-180 text-primary" height={20} width={20} />
|
||||
|
||||
@@ -21,11 +21,13 @@ import Link from "next/link";
|
||||
import {isEmpty} from "lodash";
|
||||
import {usePathname, useRouter} from "next/navigation";
|
||||
|
||||
import {ScrollArea} from "../scroll-area";
|
||||
|
||||
import {getRoutePaths} from "./utils";
|
||||
|
||||
import {Route} from "@/libs/docs/page";
|
||||
import {TreeKeyboardDelegate} from "@/utils/tree-keyboard-delegate";
|
||||
import {useScrollPosition} from "@/hooks/use-scroll-position";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface Props<T> extends Omit<ItemProps<T>, "title">, Route {
|
||||
slug?: string;
|
||||
@@ -68,6 +70,7 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
};
|
||||
|
||||
const isNew = item.props?.newPost;
|
||||
const isUpdated = item.props?.updated;
|
||||
|
||||
const isExpanded = state.expandedKeys.has(key);
|
||||
const isSelected =
|
||||
@@ -88,6 +91,12 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
state.toggleKey(item.key);
|
||||
} else {
|
||||
router.push(paths.pathname);
|
||||
|
||||
trackEvent("SidebarDocs", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: paths.pathname || "",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -101,7 +110,9 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
aria-expanded={dataAttr(hasChildNodes ? isExpanded : undefined)}
|
||||
aria-selected={dataAttr(isSelected)}
|
||||
className={clsx(
|
||||
"flex flex-col gap-3 outline-none w-full tap-highlight-transparent",
|
||||
"flex flex-col gap-3outline-none w-full tap-highlight-transparent",
|
||||
|
||||
hasChildNodes ? "mb-4" : "first:mt-4",
|
||||
// focus ring
|
||||
...dataFocusVisibleClasses,
|
||||
)}
|
||||
@@ -149,18 +160,30 @@ function TreeItem<T>(props: TreeItemProps<T>) {
|
||||
>
|
||||
{rendered}
|
||||
</span>
|
||||
{isUpdated && (
|
||||
<Chip
|
||||
className="ml-1 py-1 text-tiny text-default-400 bg-default-100/50"
|
||||
color="default"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
Updated
|
||||
</Chip>
|
||||
)}
|
||||
{isNew && (
|
||||
<Chip className="ml-2" color="primary" size="sm" variant="flat">
|
||||
<Chip className="ml-1 py-1 text-tiny" color="primary" size="sm" variant="flat">
|
||||
New
|
||||
</Chip>
|
||||
)}
|
||||
{item.props?.comingSoon && (
|
||||
<Chip className="ml-2" color="default" size="sm" variant="flat">
|
||||
<Chip className="ml-1 py-1 text-tiny" color="default" size="sm" variant="flat">
|
||||
Coming soon
|
||||
</Chip>
|
||||
)}
|
||||
</NextUILink>
|
||||
)}
|
||||
{/* Workaround to avoid scrollbar overlapping */}
|
||||
<Spacer x={4} />
|
||||
</div>
|
||||
{isExpanded && hasChildNodes && (
|
||||
<div className="flex flex-col gap-3 items-start" role="group">
|
||||
@@ -189,9 +212,7 @@ function TreeHeading({item}: {item: any}) {
|
||||
function Tree<T extends object>(props: CollectionBase<T> & Expandable & MultipleSelection) {
|
||||
let state = useTreeState(props);
|
||||
|
||||
let ref = useRef<HTMLUListElement>(null);
|
||||
|
||||
const scrollPosition = useScrollPosition(ref);
|
||||
let ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
let keyboardDelegate = useMemo(
|
||||
// @ts-expect-error
|
||||
@@ -206,16 +227,11 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
|
||||
});
|
||||
|
||||
return (
|
||||
<ul
|
||||
{...collectionProps}
|
||||
<ScrollArea
|
||||
ref={ref}
|
||||
className="flex flex-col gap-4 scrollbar-hide lg:overflow-y-scroll lg:max-h-[calc(100vh_-_64px)] pb-28"
|
||||
className="h-full lg:max-h-[calc(100vh_-_64px)]"
|
||||
role="tree"
|
||||
style={{
|
||||
WebkitMaskImage: `linear-gradient(to top, transparent 0%, #000 100px, #000 ${
|
||||
scrollPosition > 30 ? "90%" : "100%"
|
||||
}, transparent 100%)`,
|
||||
}}
|
||||
{...collectionProps}
|
||||
>
|
||||
{[...state.collection].map((item) => {
|
||||
if (item.type === "section") {
|
||||
@@ -224,7 +240,7 @@ function Tree<T extends object>(props: CollectionBase<T> & Expandable & Multiple
|
||||
|
||||
return <TreeItem key={item.key} item={item} state={state} />;
|
||||
})}
|
||||
</ul>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const FigmaButton = () => (
|
||||
<Button
|
||||
isExternal
|
||||
showAnchorIcon
|
||||
as={Link}
|
||||
className="max-w-fit text-current"
|
||||
color="default"
|
||||
href="https://www.figma.com/community/file/1267584376234254760"
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
trackEvent("FigmaPage - Open Figma Link", {
|
||||
action: "click",
|
||||
category: "figma",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open in Figma
|
||||
</Button>
|
||||
);
|
||||
@@ -14,6 +14,7 @@ import {PaletteIcon, MagicIcon, GamingConsoleIcon, StarIcon} from "@/components/
|
||||
import {NextUILogo, CodeWindow} from "@/components";
|
||||
import landingContent from "@/content/landing";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const themesTabs = (isMobile: boolean) => [
|
||||
{
|
||||
@@ -87,6 +88,12 @@ const CustomThemesExample = ({
|
||||
|
||||
const onSelectionChange = (value: React.Key) => {
|
||||
onChangeTheme(value as Theme);
|
||||
|
||||
trackEvent("CustomThemes - Selection", {
|
||||
action: "change_theme",
|
||||
category: "landing-page",
|
||||
data: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,8 @@ import {Card, CardHeader, CardBody, LinkProps, SlotsToClasses} from "@nextui-org
|
||||
import {useRouter} from "next/navigation";
|
||||
import {LinkIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const styles = tv({
|
||||
slots: {
|
||||
base: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4",
|
||||
@@ -38,6 +40,13 @@ export const FeaturesGrid: React.FC<FeaturesGridProps> = ({features, classNames,
|
||||
const slots = styles();
|
||||
|
||||
const handleClick = (feat: Feature) => {
|
||||
trackEvent("FeaturesGrid - Click", {
|
||||
name: feat.title,
|
||||
action: "click",
|
||||
category: "docs",
|
||||
data: feat.href ?? "",
|
||||
});
|
||||
|
||||
if (!feat.href) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,6 @@ export const FloatingComponents: React.FC<{}> = () => {
|
||||
|
||||
{isMounted && (
|
||||
<Tooltip
|
||||
showArrow
|
||||
className="text-sm animate-[levitate_14s_ease_infinite]"
|
||||
color="secondary"
|
||||
content="Developers love Next.js"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import NextLink from "next/link";
|
||||
import {Button, Link} from "@nextui-org/react";
|
||||
import {Button, Link, Chip} from "@nextui-org/react";
|
||||
import {ArrowRightIcon} from "@nextui-org/shared-icons";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
@@ -9,15 +9,40 @@ import {FloatingComponents} from "./floating-components";
|
||||
|
||||
import {title, subtitle} from "@/components/primitives";
|
||||
import {GithubIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const BgLooper = dynamic(() => import("./bg-looper").then((mod) => mod.BgLooper), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export const Hero = () => {
|
||||
const handlePressAnnouncement = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "home - gero",
|
||||
data: url,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex relative overflow-hidden lg:overflow-visible w-full flex-nowrap justify-between items-center h-[calc(100vh_-_64px)] 2xl:h-[calc(84vh_-_64px)]">
|
||||
<div className="flex relative z-20 flex-col gap-6 w-full lg:w-1/2 xl:mt-10">
|
||||
<div className="w-full flex justify-center md:hidden">
|
||||
<Chip
|
||||
as={NextLink}
|
||||
className="w-full hover:bg-default-100 border-default-200/80 dark:border-default-100/80 transition-colors cursor-pointer"
|
||||
color="secondary"
|
||||
href="/blog/v2.2.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressAnnouncement("Introducing v2.2.0", "/blog/v2.2.0")}
|
||||
>
|
||||
Introducing v2.2.0
|
||||
<span aria-label="rocket emoji" role="img">
|
||||
🚀
|
||||
</span>
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="text-center leading-8 md:leading-10 md:text-left">
|
||||
<div className="inline-block">
|
||||
<h1 className={title()}>Make </h1>
|
||||
@@ -42,6 +67,14 @@ export const Hero = () => {
|
||||
href="/docs/guide/introduction"
|
||||
radius="full"
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Get Started", {
|
||||
name: "Get Started",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
data: "/docs/guide/introduction",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
@@ -56,6 +89,14 @@ export const Hero = () => {
|
||||
size="lg"
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onPress={() => {
|
||||
trackEvent("Hero - Github", {
|
||||
name: "Github",
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
data: "https://github.com/nextui-org/nextui",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {FeaturesGrid} from "./features-grid";
|
||||
import {sectionWrapper, subtitle, title} from "@/components/primitives";
|
||||
import {GithubIcon, NoteLinearIcon, NextJsIcon} from "@/components/icons";
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const bannerSuggestions = [
|
||||
{
|
||||
@@ -69,6 +70,13 @@ export const InstallBanner = () => {
|
||||
href="/docs/guide/installation"
|
||||
radius="full"
|
||||
size="md"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Get Started", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "/docs/guide/installation",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
@@ -81,6 +89,13 @@ export const InstallBanner = () => {
|
||||
size="md"
|
||||
startContent={<GithubIcon />}
|
||||
variant="bordered"
|
||||
onClick={() => {
|
||||
trackEvent("InstallBanner - Github", {
|
||||
action: "press",
|
||||
category: "landing-page",
|
||||
data: "https://github.com/nextui-org/nextui",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Github
|
||||
</Button>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {OpenCollectiveIcon, PatreonIcon, HeartBoldIcon, PlusLinearIcon} from "@/
|
||||
import {Sponsor, SPONSOR_TIERS, SPONSOR_COLORS, getTier} from "@/libs/docs/sponsors";
|
||||
import {SonarPulse} from "@/components/sonar-pulse";
|
||||
import {useIsMobile} from "@/hooks/use-media-query";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface SupportProps {
|
||||
sponsors: Sponsor[];
|
||||
@@ -100,6 +101,14 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
window.open(href, "_blank");
|
||||
};
|
||||
|
||||
const handleBecomeSponsor = () => {
|
||||
trackEvent("Support - Become a sponsor", {
|
||||
action: "click",
|
||||
category: "landing-page",
|
||||
});
|
||||
|
||||
handleExternalLinkClick(supportAccounts[0].href);
|
||||
};
|
||||
const renderSponsors = useMemo(() => {
|
||||
if (!sponsors.length) return null;
|
||||
|
||||
@@ -181,7 +190,7 @@ export const Support: FC<SupportProps> = ({sponsors = []}) => {
|
||||
aria-label="Become a sponsor"
|
||||
className="z-50 w-auto h-auto bg-gradient-to-b from-[#FF1CF7] to-[#7928CA]"
|
||||
radius="full"
|
||||
onPress={() => handleExternalLinkClick(supportAccounts[0].href)}
|
||||
onPress={handleBecomeSponsor}
|
||||
>
|
||||
<PlusLinearIcon
|
||||
className="flex items-center justify-center rounded-full text-white"
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as DocsComponents from "@/components/docs/components";
|
||||
import * as BlogComponents from "@/components/blog/components";
|
||||
import {Codeblock} from "@/components/docs/components";
|
||||
import {VirtualAnchor, virtualAnchorEncode} from "@/components";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
const Table: React.FC<{children?: React.ReactNode}> = ({children}) => {
|
||||
return (
|
||||
@@ -144,6 +145,13 @@ const Code = ({
|
||||
copyButton: "text-lg text-zinc-500 mr-2",
|
||||
}}
|
||||
codeString={codeString}
|
||||
onCopy={() => {
|
||||
trackEvent("MDXComponents - Copy", {
|
||||
category: "docs",
|
||||
action: "copyCode",
|
||||
data: codeString,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Codeblock codeString={codeString} language={language} metastring={meta} />
|
||||
</Components.Snippet>
|
||||
@@ -153,8 +161,21 @@ const Code = ({
|
||||
const Link = ({href, children}: {href?: string; children?: React.ReactNode}) => {
|
||||
const isExternal = href?.startsWith("http");
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("MDXComponents - Click", {
|
||||
category: "docs",
|
||||
action: "click",
|
||||
data: href || "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Components.Link href={href} isExternal={isExternal} showAnchorIcon={isExternal}>
|
||||
<Components.Link
|
||||
href={href}
|
||||
isExternal={isExternal}
|
||||
showAnchorIcon={isExternal}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{children}
|
||||
</Components.Link>
|
||||
);
|
||||
@@ -208,7 +229,7 @@ export const MDXComponents = {
|
||||
),
|
||||
Steps: ({...props}) => (
|
||||
<div
|
||||
className="[&>h3]:step [&>h3>a]:pt-0.5 mb-12 ml-4 relative border-l border-default-100 pl-[1.625rem] [counter-reset:step]"
|
||||
className="[&>h3]:step [&>h3>a]:pt-0.5 [&>h4]:step [&>h4>a]:pt-0.5 mb-12 ml-4 relative border-l border-default-100 pl-[1.625rem] [counter-reset:step]"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import {useIsMounted} from "@/hooks/use-is-mounted";
|
||||
import {DocsSidebar} from "@/components/docs/sidebar";
|
||||
import {useCmdkStore} from "@/components/cmdk";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface NavbarProps {
|
||||
routes: Route[];
|
||||
@@ -74,8 +75,17 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
setCommandKey(isAppleDevice() ? "command" : "ctrl");
|
||||
}, []);
|
||||
|
||||
const handleOpenCmdk = () => {
|
||||
cmdkStore.onOpen();
|
||||
trackEvent("Navbar - Search", {
|
||||
name: "navbar - search",
|
||||
action: "press",
|
||||
category: "cmdk",
|
||||
});
|
||||
};
|
||||
|
||||
const {pressProps} = usePress({
|
||||
onPress: () => cmdkStore.onOpen(),
|
||||
onPress: handleOpenCmdk,
|
||||
});
|
||||
const {focusProps, isFocusVisible} = useFocusRing();
|
||||
|
||||
@@ -101,7 +111,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
strokeWidth={2}
|
||||
/>
|
||||
}
|
||||
onPress={() => cmdkStore.onOpen()}
|
||||
onPress={handleOpenCmdk}
|
||||
>
|
||||
Quick Search...
|
||||
</Button>
|
||||
@@ -121,6 +131,15 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
}
|
||||
};
|
||||
|
||||
const handlePressNavbarItem = (name: string, url: string) => {
|
||||
trackEvent("NavbarItem", {
|
||||
name,
|
||||
action: "press",
|
||||
category: "navbar",
|
||||
data: url,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NextUINavbar
|
||||
ref={ref}
|
||||
@@ -138,6 +157,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
aria-label="Home"
|
||||
className="flex justify-start items-center gap-2 tap-highlight-transparent transition-opacity active:opacity-50"
|
||||
href="/"
|
||||
onClick={() => handlePressNavbarItem("Home", "/")}
|
||||
>
|
||||
<SmallLogo className="w-6 h-6 md:hidden" />
|
||||
<LargeLogo className="h-5 md:h-6" />
|
||||
@@ -184,6 +204,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
color="foreground"
|
||||
data-active={includes(docsPaths, pathname)}
|
||||
href="/docs/guide/introduction"
|
||||
onClick={() => handlePressNavbarItem("Docs", "/docs/guide/introduction")}
|
||||
>
|
||||
Docs
|
||||
</NextLink>
|
||||
@@ -194,6 +215,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "components")}
|
||||
href="/docs/components/avatar"
|
||||
onClick={() => handlePressNavbarItem("Components", "/docs/components/avatar")}
|
||||
>
|
||||
Components
|
||||
</NextLink>
|
||||
@@ -204,6 +226,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "blog")}
|
||||
href="/blog"
|
||||
onClick={() => handlePressNavbarItem("Blog", "/blog")}
|
||||
>
|
||||
Blog
|
||||
</NextLink>
|
||||
@@ -214,6 +237,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
color="foreground"
|
||||
data-active={includes(pathname, "figma")}
|
||||
href="/figma"
|
||||
onClick={() => handlePressNavbarItem("Figma", "/figma")}
|
||||
>
|
||||
Figma
|
||||
</NextLink>
|
||||
@@ -223,12 +247,13 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
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.1.0"
|
||||
href="/blog/v2.2.0"
|
||||
variant="dot"
|
||||
onClick={() => handlePressNavbarItem("Introducing v2.2.0", "/blog/v2.2.0")}
|
||||
>
|
||||
New components v2.1.0
|
||||
<span aria-label="party emoji" role="img">
|
||||
🎉
|
||||
Introducing v2.2.0
|
||||
<span aria-label="rocket emoji" role="img">
|
||||
🚀
|
||||
</span>
|
||||
</Chip>
|
||||
</NavbarItem>
|
||||
@@ -242,6 +267,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href="https://github.com/nextui-org/nextui"
|
||||
onClick={() => handlePressNavbarItem("Github", "https://github.com/nextui-org/nextui")}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
@@ -273,13 +299,31 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
|
||||
<NavbarContent className="hidden sm:flex basis-1/5 sm:basis-full" justify="end">
|
||||
<NavbarItem className="hidden sm:flex">
|
||||
<Link isExternal aria-label="Twitter" className="p-1" href={siteConfig.links.twitter}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Twitter"
|
||||
className="p-1"
|
||||
href={siteConfig.links.twitter}
|
||||
onPress={() => handlePressNavbarItem("Twitter", siteConfig.links.twitter)}
|
||||
>
|
||||
<TwitterIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link isExternal aria-label="Discord" className="p-1" href={siteConfig.links.discord}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Discord"
|
||||
className="p-1"
|
||||
href={siteConfig.links.discord}
|
||||
onPress={() => handlePressNavbarItem("Discord", siteConfig.links.discord)}
|
||||
>
|
||||
<DiscordIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<Link isExternal aria-label="Github" className="p-1" href={siteConfig.links.github}>
|
||||
<Link
|
||||
isExternal
|
||||
aria-label="Github"
|
||||
className="p-1"
|
||||
href={siteConfig.links.github}
|
||||
onPress={() => handlePressNavbarItem("Github", siteConfig.links.github)}
|
||||
>
|
||||
<GithubIcon className="text-default-600 dark:text-default-500" />
|
||||
</Link>
|
||||
<ThemeSwitch />
|
||||
@@ -295,6 +339,7 @@ export const Navbar: FC<NavbarProps> = ({children, routes, mobileRoutes = [], sl
|
||||
<HeartFilledIcon className="text-danger group-data-[hover=true]:animate-heartbeat" />
|
||||
}
|
||||
variant="flat"
|
||||
onPress={() => handlePressNavbarItem("Sponsor", siteConfig.links.sponsor)}
|
||||
>
|
||||
Sponsor
|
||||
</Button>
|
||||
|
||||
@@ -17,9 +17,9 @@ export const title = tv({
|
||||
foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]",
|
||||
},
|
||||
size: {
|
||||
sm: "text-3xl lg:text-4xl",
|
||||
md: "text-[2.5rem] lg:text-5xl",
|
||||
lg: "text-4xl lg:text-6xl",
|
||||
sm: "text-2xl lg:text-4xl",
|
||||
md: "text-[2.1rem] lg:text-5xl",
|
||||
lg: "text-3xl lg:text-6xl",
|
||||
},
|
||||
fullWidth: {
|
||||
true: "w-full block",
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import React from "react";
|
||||
import {usePathname} from "next/navigation";
|
||||
import {Tooltip, Link, Button} from "@nextui-org/react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {capitalize, last} from "lodash";
|
||||
|
||||
import {BugIcon} from "@/components/icons";
|
||||
import {ISSUE_REPORT_URL} from "@/libs/github/constants";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const BugReportButton = () => {
|
||||
const pathname = usePathname();
|
||||
|
||||
const componentTitle = capitalize(last(pathname?.split("/")));
|
||||
|
||||
const handlePress = () => {
|
||||
trackEvent("BugReportButton - Sandpack", {
|
||||
name: "sandpack - bug report",
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: `${ISSUE_REPORT_URL}${componentTitle}`,
|
||||
});
|
||||
|
||||
window.open(`${ISSUE_REPORT_URL}${componentTitle}`, "_blank");
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
className="text-xs px-2"
|
||||
@@ -19,15 +31,7 @@ export const BugReportButton = () => {
|
||||
placement="top"
|
||||
radius="md"
|
||||
>
|
||||
<Button
|
||||
isExternal
|
||||
isIconOnly
|
||||
as={Link}
|
||||
href={`${ISSUE_REPORT_URL}${componentTitle}`}
|
||||
size="sm"
|
||||
title="Report a bug"
|
||||
variant="light"
|
||||
>
|
||||
<Button isIconOnly size="sm" title="Report a bug" variant="light" onPress={handlePress}>
|
||||
<BugIcon className="text-white dark:text-zinc-500" height={16} width={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {Language} from "prism-react-renderer";
|
||||
import {HighlightedLines} from "./types";
|
||||
import {Decorators} from "./types";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {Codeblock} from "@/components/docs/components";
|
||||
|
||||
export interface CodeViewerProps {
|
||||
@@ -83,6 +84,13 @@ export const SandpackCodeViewer = React.forwardRef<any, CodeViewerProps>(
|
||||
const handleExpand = () => {
|
||||
const nextIsExpanded = !isExpanded;
|
||||
|
||||
trackEvent("CodeViewer - Expand", {
|
||||
name: activeFile,
|
||||
action: "expand",
|
||||
category: "docs",
|
||||
data: nextIsExpanded ? "expanded" : "collapsed",
|
||||
});
|
||||
|
||||
setIsExpanded(nextIsExpanded);
|
||||
if (containerRef && containerRef?.current !== null) {
|
||||
const container = containerRef?.current;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React from "react";
|
||||
import {UnstyledOpenInCodeSandboxButton} from "@codesandbox/sandpack-react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {useSandpack} from "@codesandbox/sandpack-react";
|
||||
|
||||
import {CodeSandboxIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const CodeSandboxButton = () => {
|
||||
const {sandpack} = useSandpack();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
className="text-xs px-2"
|
||||
@@ -13,7 +17,20 @@ export const CodeSandboxButton = () => {
|
||||
placement="top"
|
||||
radius="md"
|
||||
>
|
||||
<Button isIconOnly as="span" size="sm" title="Open in CodeSandbox" variant="light">
|
||||
<Button
|
||||
isIconOnly
|
||||
as="span"
|
||||
size="sm"
|
||||
title="Open in CodeSandbox"
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
trackEvent("CodeSandboxButton - Sandpack", {
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: sandpack.files[sandpack.activeFile],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<UnstyledOpenInCodeSandboxButton
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {useSandpack} from "@codesandbox/sandpack-react";
|
||||
import {Tooltip, Button} from "@nextui-org/react";
|
||||
import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
|
||||
import {trackEvent} from "@/utils/va";
|
||||
import {CopyLinearIcon} from "@/components/icons";
|
||||
|
||||
export const CopyButton = () => {
|
||||
@@ -14,6 +15,13 @@ export const CopyButton = () => {
|
||||
const code = sandpack.files[sandpack.activeFile].code;
|
||||
|
||||
copy(code);
|
||||
|
||||
trackEvent("CopyButton - Sandpack", {
|
||||
name: "sandpack - copy code",
|
||||
action: "press",
|
||||
category: "docs",
|
||||
data: sandpack.activeFile,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const getFileEntry = (theme: string) => `
|
||||
export const rootFile = `
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { NextUIProvider } from "@nextui-org/react";
|
||||
@@ -8,13 +8,26 @@ import "./styles.css";
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<div className="w-screen h-screen ${theme} text-foreground bg-background p-8 flex items-start justify-center">
|
||||
<div className="w-screen h-screen p-8 flex items-start justify-center">
|
||||
<App />
|
||||
</div>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
);`;
|
||||
|
||||
export const getHtmlFile = (theme: string, entryFile: string) => `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body class="${theme} text-foreground bg-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/${entryFile}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
export const tailwindConfig = `const { nextui } = require("@nextui-org/react");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Tabs, Tab} from "@nextui-org/react";
|
||||
import {SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
|
||||
|
||||
import {TypescriptIcon, JavascriptIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
interface Props {
|
||||
template: SandpackPredefinedTemplate;
|
||||
@@ -20,6 +21,12 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({template, onC
|
||||
setSelectedTemplate(newTemplate);
|
||||
|
||||
setTimeout(() => {
|
||||
trackEvent("LanguageSelector - Selection", {
|
||||
name: "template",
|
||||
action: "tabChange",
|
||||
category: "docs",
|
||||
data: newTemplate ?? "",
|
||||
});
|
||||
onChange?.(newTemplate);
|
||||
}, 250);
|
||||
}, [template, onChange]);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import {useMemo} from "react";
|
||||
import {SandpackFiles, SandpackPredefinedTemplate} from "@codesandbox/sandpack-react";
|
||||
import {useTheme} from "next-themes";
|
||||
import {useLocalStorage} from "usehooks-ts";
|
||||
|
||||
import {HighlightedLines} from "./types";
|
||||
import {getHighlightedLines, getFileName} from "./utils";
|
||||
import {getFileEntry, stylesConfig, postcssConfig, tailwindConfig} from "./entries";
|
||||
|
||||
import {useLocalStorage} from "@/hooks/use-local-storage";
|
||||
import {stylesConfig, postcssConfig, tailwindConfig, getHtmlFile, rootFile} from "./entries";
|
||||
|
||||
export interface UseSandpackProps {
|
||||
files?: SandpackFiles;
|
||||
@@ -16,7 +15,6 @@ export interface UseSandpackProps {
|
||||
}
|
||||
|
||||
const importReact = 'import React from "react";';
|
||||
const importAllReact = 'import * as React from "react";';
|
||||
|
||||
export const useSandpack = ({
|
||||
files = {},
|
||||
@@ -59,7 +57,10 @@ export const useSandpack = ({
|
||||
if (key.includes("App") && !key.includes(mimeType)) {
|
||||
return acc;
|
||||
}
|
||||
if (typescriptStrict && key.includes("js")) {
|
||||
if (typescriptStrict && currentTemplate === "vite-react-ts" && key.includes(".js")) {
|
||||
return acc;
|
||||
}
|
||||
if (currentTemplate === "vite-react" && key.includes(".ts")) {
|
||||
return acc;
|
||||
}
|
||||
// @ts-ignore
|
||||
@@ -99,7 +100,7 @@ export const useSandpack = ({
|
||||
let fileContent = files[key] as string;
|
||||
|
||||
// Check if the file content includes 'React' import statements, if not, add it
|
||||
if (!fileContent.includes(importReact) && !fileContent.includes(importAllReact)) {
|
||||
if (!fileContent.includes("from 'react'") && !fileContent.includes('from "react"')) {
|
||||
fileContent = `${importReact}\n${fileContent}\n`;
|
||||
}
|
||||
|
||||
@@ -183,7 +184,11 @@ export const useSandpack = ({
|
||||
files: {
|
||||
...sortedFiles,
|
||||
[entryFile]: {
|
||||
code: getFileEntry(theme ?? "light"),
|
||||
code: rootFile,
|
||||
hidden: true,
|
||||
},
|
||||
"index.html": {
|
||||
code: getHtmlFile(theme ?? "light", entryFile),
|
||||
hidden: true,
|
||||
},
|
||||
"tailwind.config.js": {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import {cn} from "@nextui-org/react";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({className, children, ...props}, ref) => {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] pb-28">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
);
|
||||
});
|
||||
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({className, orientation = "vertical", ...props}, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" && "h-2.5 border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
orientation={orientation}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
className={cn(
|
||||
"relative rounded-full bg-default-400/50",
|
||||
orientation === "vertical" && "flex-1",
|
||||
)}
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export {ScrollArea, ScrollBar};
|
||||
@@ -8,6 +8,7 @@ import {clsx} from "@nextui-org/shared-utils";
|
||||
import {useIsSSR} from "@react-aria/ssr";
|
||||
|
||||
import {SunFilledIcon, MoonFilledIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export interface ThemeSwitchProps {
|
||||
className?: string;
|
||||
@@ -20,6 +21,12 @@ export const ThemeSwitch: FC<ThemeSwitchProps> = ({className, classNames}) => {
|
||||
|
||||
const onChange = () => {
|
||||
theme === "light" ? setTheme("dark") : setTheme("light");
|
||||
|
||||
trackEvent("ThemeChange", {
|
||||
action: "click",
|
||||
category: "theme",
|
||||
data: theme === "light" ? "dark" : "light",
|
||||
});
|
||||
};
|
||||
|
||||
const {Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps} = useSwitch({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "@nextui-org/react";
|
||||
|
||||
import {VercelIcon} from "@/components/icons";
|
||||
import {trackEvent} from "@/utils/va";
|
||||
|
||||
export const VercelCallout: React.FC<unknown> = () => {
|
||||
return (
|
||||
@@ -9,6 +12,13 @@ export const VercelCallout: React.FC<unknown> = () => {
|
||||
isExternal
|
||||
className="flex justify-end items-center gap-2 text-foreground"
|
||||
href="https://www.vercel.com?utm_source=nextui&utm_marketing=oss"
|
||||
onClick={() => {
|
||||
trackEvent("VercelCallout", {
|
||||
name: "vercel callout",
|
||||
action: "click",
|
||||
category: "footer",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="font-normal">Deployed on</p>
|
||||
<VercelIcon className="text-black dark:text-white" height={18} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {DM_Sans} from "next/font/google";
|
||||
import {Inter} from "next/font/google";
|
||||
|
||||
export const fontSans = DM_Sans({
|
||||
export const fontSans = Inter({
|
||||
variable: "--font-sans",
|
||||
adjustFontFallback: true,
|
||||
display: "optional",
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
"keywords": "design principles, nextui",
|
||||
"path": "/docs/guide/design-principles.mdx"
|
||||
},
|
||||
{
|
||||
"key": "routing",
|
||||
"title": "Routing",
|
||||
"keywords": "client side routing, routing, browser routing, nextui, next.js router, react router, remix router",
|
||||
"path": "/docs/guide/routing.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "upgrade-to-v2",
|
||||
"title": "Upgrade to v2",
|
||||
@@ -130,6 +137,13 @@
|
||||
"keywords": "accordion, collapse, expandable sections, content hiding",
|
||||
"path": "/docs/components/accordion.mdx"
|
||||
},
|
||||
{
|
||||
"key": "autocomplete",
|
||||
"title": "Autocomplete",
|
||||
"keywords": "autocomplete, auto suggest, search, typeahead",
|
||||
"path": "/docs/components/autocomplete.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "badge",
|
||||
"title": "Badge",
|
||||
@@ -142,6 +156,13 @@
|
||||
"keywords": "button, interactive, action trigger, click events",
|
||||
"path": "/docs/components/button.mdx"
|
||||
},
|
||||
{
|
||||
"key": "breadcrumbs",
|
||||
"title": "Breadcrumbs",
|
||||
"keywords": "breadcrumbs, navigation, path, trail, location",
|
||||
"path": "/docs/components/breadcrumbs.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "card",
|
||||
"title": "Card",
|
||||
@@ -188,7 +209,8 @@
|
||||
"key": "dropdown",
|
||||
"title": "Dropdown",
|
||||
"keywords": "dropdown, menu, selection, option list",
|
||||
"path": "/docs/components/dropdown.mdx"
|
||||
"path": "/docs/components/dropdown.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "image",
|
||||
@@ -212,14 +234,15 @@
|
||||
"key": "link",
|
||||
"title": "Link",
|
||||
"keywords": "link, navigation, href, web page connection",
|
||||
"path": "/docs/components/link.mdx"
|
||||
"path": "/docs/components/link.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "listbox",
|
||||
"title": "Listbox",
|
||||
"keywords": "listbox, selection, option list, multiple choice",
|
||||
"path": "/docs/components/listbox.mdx",
|
||||
"newPost": true
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "modal",
|
||||
@@ -261,8 +284,7 @@
|
||||
"key": "select",
|
||||
"title": "Select",
|
||||
"keywords": "select, selection, option list, multiple choice",
|
||||
"path": "/docs/components/select.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/select.mdx"
|
||||
},
|
||||
{
|
||||
"key": "skeleton",
|
||||
@@ -280,8 +302,7 @@
|
||||
"key": "scroll-shadow",
|
||||
"title": "Scroll Shadow",
|
||||
"keywords": "scroll shadow, scroll indicator, scroll bar, scroll position",
|
||||
"path": "/docs/components/scroll-shadow.mdx",
|
||||
"newPost": true
|
||||
"path": "/docs/components/scroll-shadow.mdx"
|
||||
},
|
||||
{
|
||||
"key": "spacer",
|
||||
@@ -301,6 +322,13 @@
|
||||
"keywords": "switch, toggle, binary input, on/off control",
|
||||
"path": "/docs/components/switch.mdx"
|
||||
},
|
||||
{
|
||||
"key": "slider",
|
||||
"title": "Slider",
|
||||
"keywords": "slider, range input, value selector, sliding control",
|
||||
"path": "/docs/components/slider.mdx",
|
||||
"newPost": true
|
||||
},
|
||||
{
|
||||
"key": "table",
|
||||
"title": "Table",
|
||||
@@ -311,13 +339,15 @@
|
||||
"key": "tabs",
|
||||
"title": "Tabs",
|
||||
"keywords": "tabs, section navigation, categorized content, tabbed interface",
|
||||
"path": "/docs/components/tabs.mdx"
|
||||
"path": "/docs/components/tabs.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "textarea",
|
||||
"title": "Textarea",
|
||||
"keywords": "textarea, multi-line text input, large text field, form control",
|
||||
"path": "/docs/components/textarea.mdx"
|
||||
"path": "/docs/components/textarea.mdx",
|
||||
"updated": true
|
||||
},
|
||||
{
|
||||
"key": "tooltip",
|
||||
|
||||
+2088
-864
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,443 @@
|
||||
---
|
||||
title: "Introducing v2.2.0 🚀"
|
||||
description: "NextUI v2.2.0 is here! Dive into client-side router support, discover 3 new components including the Autocomplete, and more."
|
||||
date: "2023-11-03"
|
||||
image: "/blog/v2.2.0.jpg"
|
||||
tags: ["nextui", "autocomplete", "breadcrumbs", "client side router", "slider"]
|
||||
author:
|
||||
name: "Junior Garcia"
|
||||
username: "@jrgarciadev"
|
||||
link: "https://twitter.com/jrgarciadev"
|
||||
avatar: "/avatars/junior-garcia.jpeg"
|
||||
---
|
||||
|
||||
import {autocompleteContent} from "@/content/components/autocomplete";
|
||||
import {sliderContent} from "@/content/components/slider";
|
||||
import {breadcrumbsContent} from "@/content/components/breadcrumbs";
|
||||
|
||||
<img
|
||||
src="/blog/v2.2.0_2x.jpg"
|
||||
width={700}
|
||||
height={350}
|
||||
alt="NextUI v2.2.0"
|
||||
className="w-full border border-transparent dark:border-default-200/50 object-fit rounded-xl shadow-lg"
|
||||
/>
|
||||
|
||||
We are excited to announce the latest update to NextUI, version **2.2.0**! This release introduces 3 new components,
|
||||
support for client-side routing, and more.
|
||||
|
||||
## What's New in v2.2.0?
|
||||
|
||||
- [Client Side Routing](#client-side-routing) - Allows you to seamlessly incorporate client-side routers.
|
||||
- [Autocomplete](/docs/components/autocomplete) - Combines a text input with a listbox, allowing users to filter a list of options.
|
||||
- [Slider](/docs/components/slider) - Allows users to make selections from a range of values.
|
||||
- [Breadcrumbs](/docs/components/breadcrumbs) - Displays a hierarchy of links to the current page or resource in an application.
|
||||
- [Other Changes](#other-changes) - Includes styling improvements, accessibility and usability enhancements.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Client Side Routing
|
||||
|
||||
By default, links perform native browser navigation when they are interacted with. However, many apps and
|
||||
frameworks use client side routers to avoid a full page reload when navigating between pages.
|
||||
|
||||
NextUI now natively supports client-side routing in components such as [Link](/docs/components/link), [Tabs](/docs/components/tabs),
|
||||
[Breadcrumbs](/docs/components/breadcrumbs), [Listbox](/docs/components/listbox), [Dropdown](/docs/components/dropdown) and many others offering
|
||||
the flexibility to be rendered as HTML links, allowing you to seamlessly incorporate client-side routers. See the [Routing](/docs/guide/routing) guide to
|
||||
learn how it set it up in your app.
|
||||
|
||||
The `NextUIProvider` component configures all NextUI components within it to navigate using the
|
||||
client side router you provide.
|
||||
|
||||
> **Note**: Client side routing is based on [React Aria Routing](https://react-spectrum.adobe.com/react-aria/routing.html).
|
||||
|
||||
### Next.js Example
|
||||
|
||||
- App Router
|
||||
|
||||
Go to your `app/providers.tsx` or `app/providers.jsx` (create it if it doesn't exist) and add the
|
||||
`useRouter` hook from `next/navigation`, it returns a router object that can be used to perform navigation.
|
||||
|
||||
```tsx
|
||||
// app/providers.tsx
|
||||
"use client";
|
||||
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
|
||||
export function Providers({children}: {children: React.ReactNode}) {
|
||||
const router = useRouter();
|
||||
|
||||
return <NextUIProvider navigate={router.push}>{children}</NextUIProvider>;
|
||||
}
|
||||
```
|
||||
|
||||
- Pages Router
|
||||
|
||||
Go to pages`/_app.js` or `pages/_app.tsx` (create it if it doesn't exist) and add the`useRouter` hook
|
||||
from `next/router`, it returns a router object that can be used to perform navigation.
|
||||
|
||||
```tsx
|
||||
// pages/_app.tsx
|
||||
import type {AppProps} from "next/app";
|
||||
import {useRouter} from "next/router";
|
||||
import {NextUIProvider} from "@nextui-org/react";
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<NextUIProvider navigate={router.push}>
|
||||
<Component {...pageProps} />
|
||||
</NextUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
```
|
||||
|
||||
- Usage
|
||||
|
||||
Now that you have set up the `NextUIProvider` in your app, you can use the `href` prop in the `Tabs`,
|
||||
`Listbox` and `Dropdown` items to navigate between pages.
|
||||
|
||||
The [Link](/docs/components/link) component will also use the `navigate` function from the
|
||||
`NextUIProvider` to navigate between pages.
|
||||
|
||||
```jsx
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
Listbox,
|
||||
ListboxItem,
|
||||
Dropdown,
|
||||
DropdownTrigger,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
Button,
|
||||
Link,
|
||||
} from "@nextui-org/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Tabs aria-label="Navigation">
|
||||
<Tab key="home" href="/home">
|
||||
Home
|
||||
</Tab>
|
||||
<Tab key="about" href="/about">
|
||||
About
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Listbox aria-label="Navigation">
|
||||
<ListboxItem key="home" href="/home">
|
||||
Home
|
||||
</ListboxItem>
|
||||
<ListboxItem key="about" href="/about">
|
||||
About
|
||||
</ListboxItem>
|
||||
</Listbox>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button>Open</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Navigation">
|
||||
<DropdownItem key="home" href="/home">
|
||||
Home
|
||||
</DropdownItem>
|
||||
<DropdownItem key="about" href="/about">
|
||||
About
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<Link href="/home">Home</Link>
|
||||
<Link href="/about">About</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Autocomplete
|
||||
|
||||
Creating an autocomplete component that is both accessible and customizable is a challenging task. We've spent a lot of time
|
||||
researching and testing different approaches to come up with a solution that works for everyone. The result is a
|
||||
component that is easy to use, fully accessible, and highly customizable.
|
||||
|
||||
The new **Autocomplete** component includes:
|
||||
|
||||
- Support for selecting a single option.
|
||||
- Support for disabled options.
|
||||
- Support for groups of items in sections.
|
||||
- Support for filtering a list of options by typing.
|
||||
- Support for controlled and uncontrolled options, selection and input value.
|
||||
- Support for custom filter functions.
|
||||
- Async loading and infinite scrolling support.
|
||||
- Required and invalid states exposed to assistive technology via ARIA.
|
||||
- Support for description and error message help text linked to the input via ARIA.
|
||||
- And much more...
|
||||
|
||||
We recommend to read this [Blog post](https://react-spectrum.adobe.com/blog/building-a-combobox.html) from [react-aria](https://react-spectrum.adobe.com/react-aria) team to learn more
|
||||
about the Autocomplete component.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={autocompleteContent.usage} />
|
||||
|
||||
### Custom Items
|
||||
|
||||
You can customize the autocomplete items by modifying the `AutocompleteItem` children.
|
||||
|
||||
<CodeDemo title="Custom Items" files={autocompleteContent.customItems} />
|
||||
|
||||
### Customizable
|
||||
|
||||
The autocomplete component is highly customizable, you can customize the popover, listbox and
|
||||
input components.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={autocompleteContent.customStyles} />
|
||||
|
||||
Go to the [Autocomplete](/docs/components/autocomplete) component page to learn more about it.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Slider
|
||||
|
||||
The **Slider** component allows users to make selections from a range of values. It is a great way to allow users to
|
||||
select a value from a fixed range, such as volume or temperature, or to select a value from a range that changes
|
||||
frequently, such as stock prices or budgets.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={sliderContent.usage} />
|
||||
|
||||
### Range Support
|
||||
|
||||
If you pass an array of values to the `value` prop or to the `defaultValue` prop, the slider will become a range slider.
|
||||
|
||||
<CodeDemo title="Range Slider" files={sliderContent.range} />
|
||||
|
||||
### With Tooltip
|
||||
|
||||
The slider component also supports a tooltip to display the current value.
|
||||
|
||||
<CodeDemo title="With Tooltip" files={sliderContent.tooltip} />
|
||||
|
||||
### Slider steps
|
||||
|
||||
You can use the `showSteps` prop to display small dots on each step.
|
||||
|
||||
<CodeDemo title="With Visible Steps" files={sliderContent.visibleSteps} />
|
||||
|
||||
### Slider Custom Styles
|
||||
|
||||
You can customize the `Slider` component by passing custom Tailwind CSS classes to the component slots.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={sliderContent.customStyles} />
|
||||
|
||||
Go to the [Slider](/docs/components/slider) component page to learn more about it.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
The **Breadcrumbs** is an essential component for navigating hierarchical content. It displays a hierarchy of links to the
|
||||
current page or resource in an application.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeDemo title="Usage" files={breadcrumbsContent.usage} />
|
||||
|
||||
### Breadcrumbs as menu
|
||||
|
||||
It is possible to use the `Breadcrumbs` component as a horizontal menu. This is useful when you want to display a list
|
||||
of possible navigations and let the user choose one of them.
|
||||
|
||||
<CodeDemo title="Menu Type" files={breadcrumbsContent.menuType} />
|
||||
|
||||
### Start & End Content support
|
||||
|
||||
You can add any element to the start or end of the breadcrumbs by using the `startContent` and `endContent` props. The
|
||||
above example uses the `startContent` prop to add icons to the start of the breadcrumbs.
|
||||
|
||||
<CodeDemo title="Start & End Content" files={breadcrumbsContent.startEndContent} />
|
||||
|
||||
### Collapsing Items support
|
||||
|
||||
The `Breadcrumbs` component supports collapsing items, it is useful when you have a lot of items and you want to
|
||||
collapse them into a dropdown menu.
|
||||
|
||||
<CodeDemo title="Customizing the Ellipsis Item" files={breadcrumbsContent.customizingEllipsis} />
|
||||
|
||||
### Apllying custom styles
|
||||
|
||||
You can customize the `Breadcrumbs` component by passing custom Tailwind CSS classes to the component slots.
|
||||
|
||||
<CodeDemo title="Custom Styles" files={breadcrumbsContent.customStyles} />
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
Go to the [Breadcrumbs](/docs/components/breadcrumbs) component page to learn more about it.
|
||||
|
||||
## Other Changes
|
||||
|
||||
- Styling Improvements:
|
||||
|
||||
- Transitioned spacing units from pixels (px) to rem units to optimize mobile component sizing.
|
||||
- Introduced a new `shouldSelectOnPressUp` property for `Tabs/Tab` with a default value of true, allowing tab selection on press-up instead of press-down.
|
||||
- Updated Chip component's font size to text-tiny for the sm size variant.
|
||||
- Enhanced the Button component to display only the spinner during loading when it contains an icon.
|
||||
|
||||
- Accessibility and Usability Enhancements:
|
||||
|
||||
- Resolved Popover component's open state issues for blur/opaque backdrops.
|
||||
- Enhanced the ScrollShadow API, introducing visibility and onVisibilityChange properties to manage shadow visibility.
|
||||
- Added `emptyContent`, `topContent`, and `bottomContent` properties to Listbox/Menu/Dropdown for customized content rendering.
|
||||
- Introduced baseRef to the Input component, allowing control over the parent element's reference.
|
||||
- Upgraded tailwind-variants to version `0.1.18`, incorporating slot props control.
|
||||
|
||||
- Right-to-Left (RTL) Support:
|
||||
|
||||
- Implemented RTL support for Accordion,
|
||||
- Implemented RTL support for Accordion Avatar & AvatarGroup components,
|
||||
- Implemented RTL support for ButtonGroup.
|
||||
|
||||
- Custom Implementations and Fixes:
|
||||
|
||||
- Implemented a custom `usePress` hook to address the corner press issue, with a corresponding pull request and issue submitted to the react-aria project.
|
||||
- Applied the custom `usePress` across all NextUI interactive components for consistent behavior.
|
||||
- Improved animations and positioning for Input & Select labels.
|
||||
- Upgraded `react-aria` packages and `framer-motion` for enhanced performance.
|
||||
- Enhanced TypeScript support for generic items in Menu and DropdownMenu.
|
||||
- Streamlined package dependencies to exclude globally or individually installed packages, reducing bundle sizes.
|
||||
|
||||
- Visual and Interactive Tweaks:
|
||||
|
||||
- Removed the outline on Input focus-visible for a cleaner aesthetic.
|
||||
- Fixed the radius property issue in `ButtonGroup`.
|
||||
- Introduced the `isActive` prop for `NavbarMenuItem` for active state management.
|
||||
- Corrected the Pagination custom-items example by adding the key prop.
|
||||
- Enabled Collection Items support within the `extendVariants` function.
|
||||
- Added transitions to menu/listbox items for smoother interaction.
|
||||
- Added a `disableAutosize` property to `Textarea` to control auto-resizing.
|
||||
- Resolved styling issues in Textarea and animated height adjustments for a fluid user experience.
|
||||
- Included a `hoverOpacity` key in the themes object within the plugin for customized hover effects.
|
||||
- Implemented a hover effect for the Button component to enhance user interaction feedback.
|
||||
- The padding of the CardBody has been updated from `p-5` to `p-3` for consistency with the padding of other Card elements.
|
||||
|
||||
<Spacer y={4} />
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Unfortunately, we had to make some small styles breaking changes in this release to improve the [Popover](/docs/components/popover) arrow
|
||||
dynamic positioning. Instead of having a specific slot for the popover arrow, we now use the `before` pseudo element, this allows the
|
||||
popover to also move the arrow altogether.
|
||||
|
||||
This change impacts the Popover, Dropdown, Tooltip and Select implementations.
|
||||
|
||||
Popover changes:
|
||||
|
||||
```diff-jsx
|
||||
<Popover
|
||||
showArrow
|
||||
backdrop="opaque"
|
||||
placement="right"
|
||||
classNames={{
|
||||
- base: "py-3 px-4 border border-default-200 bg-gradient-to-br from-white to-default-300 dark:from-default-100 dark:to-default-50",
|
||||
- arrow: "bg-default-200",
|
||||
+ base: [
|
||||
+ // the "before" pseudo element is now the popover' arrow
|
||||
+ "before:bg-default-200"
|
||||
+ ],
|
||||
+ content: [ // now we need to use the "content" slot to actually modify the popover' content styles
|
||||
+ "py-3 px-4 border border-default-200",
|
||||
+ "bg-gradient-to-br from-white to-default-300",
|
||||
+ "dark:from-default-100 dark:to-default-50",
|
||||
],
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Popover>
|
||||
```
|
||||
|
||||
Tooltip changes:
|
||||
|
||||
```diff-jsx
|
||||
<Tooltip
|
||||
showArrow
|
||||
placement="right"
|
||||
content="I am a tooltip"
|
||||
classNames={{
|
||||
- base: "py-2 px-4 shadow-xl text-black bg-gradient-to-br from-white to-neutral-400",
|
||||
- arrow: "bg-neutral-400 dark:bg-white",
|
||||
+ base: [
|
||||
+ // the "before" pseudo element is now the popover' arrow
|
||||
+ "before:bg-neutral-400 dark:before:bg-white",
|
||||
+ ],
|
||||
+ content: [ // now we need to use the "content" slot to actually modify the popover' content styles
|
||||
+ "py-2 px-4 shadow-xl",
|
||||
+ "text-black bg-gradient-to-br from-white to-neutral-400",
|
||||
+ ],
|
||||
}}
|
||||
>
|
||||
<Button variant="flat">Hover me</Button>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
Select changes:
|
||||
|
||||
```diff-jsx
|
||||
<Select
|
||||
items={users}
|
||||
label="Assigned to"
|
||||
className="max-w-xs"
|
||||
variant="bordered"
|
||||
popoverProps={{
|
||||
classNames: {
|
||||
- base: "p-0 border-small border-divider bg-background",
|
||||
- arrow: "bg-default-200",
|
||||
+ base: "before:bg-default-200", // the before pseudo element controls the popover's arrow
|
||||
+ content: "p-0 border-small border-divider bg-background", // now instead of the "base" slot we use the "content" slot
|
||||
},
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Select>
|
||||
);
|
||||
}`;
|
||||
```
|
||||
|
||||
Dropdown Changes
|
||||
|
||||
```diff-jsx
|
||||
<Dropdown
|
||||
showArrow
|
||||
classNames={{
|
||||
- base: "py-1 px-1 border border-default-200 bg-gradient-to-br from-white to-default-200 dark:from-default-50 dark:to-black",
|
||||
- arrow: "bg-default-200",
|
||||
+ content: "py-1 px-1 border border-default-200 bg-gradient-to-br from-white to-default-200 dark:from-default-50 dark:to-black", // now instead of the "base" slot we use the "content"
|
||||
+ base: "before:bg-default-200", // the before pseudo element controls the popover's arrow
|
||||
}}
|
||||
>
|
||||
...
|
||||
</Dropdown>
|
||||
```
|
||||
|
||||
<Spacer y={6} />
|
||||
|
||||
We hope you enjoy these new components and the new features. We're excited to see what you build with them!
|
||||
|
||||
Thanks for reading and happy coding! 🚀
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
We're excited to see the community adopt NextUI, raise issues, and provide feedback.
|
||||
Whether it's a feature request, bug report, or a project to showcase, please get involved!
|
||||
|
||||
<Community />
|
||||
|
||||
## Contributing
|
||||
|
||||
PR's on NextUI are always welcome, please see our [contribution guidelines](https://github.com/nextui-org/nextui/blob/main/CONTRIBUTING.MD) to learn how you can contribute to this project.
|
||||
@@ -0,0 +1,89 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
export default function App() {
|
||||
let list = useAsyncList({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(\`https://swapi.py4e.com/api/people/?search=\${filterText}\`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
height: string;
|
||||
mass: string;
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, filterText}) {
|
||||
let res = await fetch(\`https://swapi.py4e.com/api/people/?search=\${filterText}\`, {signal});
|
||||
let json = await res.json();
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={list.filterText}
|
||||
isLoading={list.isLoading}
|
||||
items={list.items}
|
||||
label="Select a character"
|
||||
placeholder="Type to search..."
|
||||
variant="bordered"
|
||||
onInputChange={list.setFilterText}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
const usePokemonListTs = `export type Pokemon = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type UsePokemonListProps = {
|
||||
/** Delay to wait before fetching more items */
|
||||
fetchDelay?: number;
|
||||
};
|
||||
|
||||
export function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
|
||||
const [items, setItems] = React.useState<Pokemon[]>([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset: number) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const usePokemonList = `export function usePokemonList({fetchDelay = 0} = {}) {
|
||||
const [items, setItems] = React.useState([]);
|
||||
const [hasMore, setHasMore] = React.useState(true);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const limit = 10; // Number of items per page, adjust as necessary
|
||||
|
||||
const loadPokemon = async (currentOffset) => {
|
||||
const controller = new AbortController();
|
||||
const {signal} = controller;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (offset > 0) {
|
||||
// Delay to simulate network latency
|
||||
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
|
||||
}
|
||||
|
||||
let res = await fetch(
|
||||
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
|
||||
{signal},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
let json = await res.json();
|
||||
|
||||
setHasMore(json.next !== null);
|
||||
// Append new results to existing ones
|
||||
setItems((prevItems) => [...prevItems, ...json.results]);
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
console.log("Fetch aborted");
|
||||
} else {
|
||||
console.error("There was an error with the fetch operation:", error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
loadPokemon(offset);
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
const newOffset = offset + limit;
|
||||
|
||||
setOffset(newOffset);
|
||||
loadPokemon(newOffset);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasMore,
|
||||
isLoading,
|
||||
onLoadMore,
|
||||
};
|
||||
};`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
import {usePokemonList} from "./usePokemonList";
|
||||
|
||||
export default function App() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
|
||||
|
||||
const [, scrollerRef] = useInfiniteScroll({
|
||||
hasMore,
|
||||
isEnabled: isOpen,
|
||||
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
|
||||
onLoadMore,
|
||||
});
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
variant="bordered"
|
||||
isLoading={isLoading}
|
||||
defaultItems={items}
|
||||
label="Pick a Pokemon"
|
||||
placeholder="Select a Pokemon"
|
||||
scrollRef={scrollerRef}
|
||||
selectionMode="single"
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.name} className="capitalize">
|
||||
{item.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/usePokemonList.js": usePokemonList,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": App,
|
||||
"/usePokemonList.ts": usePokemonListTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const colors = [
|
||||
"default",
|
||||
"primary",
|
||||
"secondary",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-row flex-wrap gap-4">
|
||||
{colors.map((color) => (
|
||||
<Autocomplete
|
||||
key={color}
|
||||
color={color}
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey={"cat"}
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState<React.Key>("cat");
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
selectedKey={value}
|
||||
onSelectionChange={setValue}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="text-default-500 text-small">Selected: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState("cat");
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
defaultItems={animals}
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
selectedKey={value}
|
||||
onSelectionChange={setValue}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="text-default-500 text-small">Selected: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const myFilter = (textValue, inputValue) => {
|
||||
if (inputValue.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize both strings so we can slice safely
|
||||
// take into account the ignorePunctuation option as well...
|
||||
textValue = textValue.normalize("NFC").toLocaleLowerCase();
|
||||
inputValue = inputValue.normalize("NFC").toLocaleLowerCase();
|
||||
|
||||
return textValue.slice(0, inputValue.length) === inputValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
allowsCustomValue
|
||||
className="max-w-xs"
|
||||
defaultFilter={myFilter}
|
||||
defaultItems={animals}
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const myFilter = (textValue: string, inputValue: string) => {
|
||||
if (inputValue.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normalize both strings so we can slice safely
|
||||
// take into account the ignorePunctuation option as well...
|
||||
textValue = textValue.normalize("NFC").toLocaleLowerCase();
|
||||
inputValue = inputValue.normalize("NFC").toLocaleLowerCase();
|
||||
|
||||
return textValue.slice(0, inputValue.length) === inputValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
defaultFilter={myFilter}
|
||||
defaultItems={animals}
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
const data = `export const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar} from "@nextui-org/react";
|
||||
import {users} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
defaultItems={users}
|
||||
variant="bordered"
|
||||
label="Assigned to"
|
||||
placeholder="Select a user"
|
||||
labelPlacement="inside"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(user) => (
|
||||
<AutocompleteItem key={user.id} textValue={user.name}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={user.name} className="flex-shrink-0" size="sm" src={user.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{user.name}</span>
|
||||
<span className="text-tiny text-default-400">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem, AutocompleteSection} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const headingClasses = "flex w-full sticky top-1 z-20 py-1.5 px-2 bg-default-100 shadow-small rounded-small";
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
scrollShadowProps={{
|
||||
isEnabled: false,
|
||||
}}
|
||||
>
|
||||
<AutocompleteSection
|
||||
title="Mammals"
|
||||
classNames={{
|
||||
heading: headingClasses,
|
||||
}}
|
||||
>
|
||||
<AutocompleteItem key="Lion">Lion</AutocompleteItem>
|
||||
<AutocompleteItem key="Tiger">Tiger</AutocompleteItem>
|
||||
<AutocompleteItem key="Elephant">Elephant</AutocompleteItem>
|
||||
<AutocompleteItem key="Kangaroo">Kangaroo</AutocompleteItem>
|
||||
<AutocompleteItem key="Panda">Panda</AutocompleteItem>
|
||||
<AutocompleteItem key="Giraffe">Giraffe</AutocompleteItem>
|
||||
<AutocompleteItem key="Zebra">Zebra</AutocompleteItem>
|
||||
<AutocompleteItem key="Cheetah">Cheetah</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
<AutocompleteSection
|
||||
title="Birds"
|
||||
classNames={{
|
||||
heading: headingClasses,
|
||||
}}
|
||||
>
|
||||
<AutocompleteItem key="Eagle">Eagle</AutocompleteItem>
|
||||
<AutocompleteItem key="Parrot">Parrot</AutocompleteItem>
|
||||
<AutocompleteItem key="Penguin">Penguin</AutocompleteItem>
|
||||
<AutocompleteItem key="Ostrich">Ostrich</AutocompleteItem>
|
||||
<AutocompleteItem key="Peacock">Peacock</AutocompleteItem>
|
||||
<AutocompleteItem key="Swan">Swan</AutocompleteItem>
|
||||
<AutocompleteItem key="Falcon">Falcon</AutocompleteItem>
|
||||
<AutocompleteItem key="Flamingo">Flamingo</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const SelectorIcon = `export const SelectorIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M0 0h24v24H0z" fill="none" stroke="none" />
|
||||
<path d="M8 9l4 -4l4 4" />
|
||||
<path d="M16 15l-4 4l-4 -4" />
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {SelectorIcon} from "./SelectorIcon";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultItems={animals}
|
||||
labelPlacement="outside"
|
||||
className="max-w-xs"
|
||||
disableSelectorIconRotation
|
||||
selectorIcon={<SelectorIcon />}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
"/SelectorIcon.jsx": SelectorIcon,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,319 @@
|
||||
const data = `export const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/1.png",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Tech Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/1.png",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Sr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/2.png",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "C.M.",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/2.png",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "S. Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/3.png",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Brian Kim",
|
||||
role: "P. Manager",
|
||||
team: "Management",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/3.png",
|
||||
email: "brian.kim@example.com",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Michael Hunt",
|
||||
role: "Designer",
|
||||
team: "Design",
|
||||
status: "paused",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/4.png",
|
||||
email: "michael.hunt@example.com",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Samantha Brooks",
|
||||
role: "HR Manager",
|
||||
team: "HR",
|
||||
status: "active",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/4.png",
|
||||
email: "samantha.brooks@example.com",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Frank Harrison",
|
||||
role: "F. Manager",
|
||||
team: "Finance",
|
||||
status: "vacation",
|
||||
age: "33",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/5.png",
|
||||
email: "frank.harrison@example.com",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "Emma Adams",
|
||||
role: "Ops Manager",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "35",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/5.png",
|
||||
email: "emma.adams@example.com",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "Brandon Stevens",
|
||||
role: "Jr. Dev",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/7.png",
|
||||
email: "brandon.stevens@example.com",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Megan Richards",
|
||||
role: "P. Manager",
|
||||
team: "Product",
|
||||
status: "paused",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/7.png",
|
||||
email: "megan.richards@example.com",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Oliver Scott",
|
||||
role: "S. Manager",
|
||||
team: "Security",
|
||||
status: "active",
|
||||
age: "37",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/8.png",
|
||||
email: "oliver.scott@example.com",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Grace Allen",
|
||||
role: "M. Specialist",
|
||||
team: "Marketing",
|
||||
status: "active",
|
||||
age: "30",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/8.png",
|
||||
email: "grace.allen@example.com",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "Noah Carter",
|
||||
role: "IT Specialist",
|
||||
team: "I. Technology",
|
||||
status: "paused",
|
||||
age: "31",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/9.png",
|
||||
email: "noah.carter@example.com",
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: "Ava Perez",
|
||||
role: "Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/9.png",
|
||||
email: "ava.perez@example.com",
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: "Liam Johnson",
|
||||
role: "Data Analyst",
|
||||
team: "Analysis",
|
||||
status: "active",
|
||||
age: "28",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/11.png",
|
||||
email: "liam.johnson@example.com",
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: "Sophia Taylor",
|
||||
role: "QA Analyst",
|
||||
team: "Testing",
|
||||
status: "active",
|
||||
age: "27",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/11.png",
|
||||
email: "sophia.taylor@example.com",
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: "Lucas Harris",
|
||||
role: "Administrator",
|
||||
team: "Information Technology",
|
||||
status: "paused",
|
||||
age: "32",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/male/12.png",
|
||||
email: "lucas.harris@example.com",
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: "Mia Robinson",
|
||||
role: "Coordinator",
|
||||
team: "Operations",
|
||||
status: "active",
|
||||
age: "26",
|
||||
avatar: "https://d2u8k2ocievbld.cloudfront.net/memojis/female/12.png",
|
||||
email: "mia.robinson@example.com",
|
||||
},
|
||||
];`;
|
||||
|
||||
const SearchIcon = `export const SearchIcon = ({
|
||||
size = 24,
|
||||
strokeWidth = 1.5,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={height || size}
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width={width || size}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
<path
|
||||
d="M22 22L20 20"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar, Button} from "@nextui-org/react";
|
||||
import {SearchIcon} from "./SearchIcon";
|
||||
import {users} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
classNames={{
|
||||
base: "max-w-xs",
|
||||
listboxWrapper: "max-h-[320px]",
|
||||
selectorButton: "text-default-500"
|
||||
}}
|
||||
defaultItems={users}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
input: "ml-1",
|
||||
inputWrapper: "h-[48px]",
|
||||
},
|
||||
}}
|
||||
listboxProps={{
|
||||
hideSelectedIcon: true,
|
||||
itemClasses: {
|
||||
base: [
|
||||
"rounded-medium",
|
||||
"text-default-500",
|
||||
"transition-opacity",
|
||||
"data-[hover=true]:text-foreground",
|
||||
"dark:data-[hover=true]:bg-default-50",
|
||||
"data-[pressed=true]:opacity-70",
|
||||
"data-[hover=true]:bg-default-200",
|
||||
"data-[selectable=true]:focus:bg-default-100",
|
||||
"data-[focus-visible=true]:ring-default-500",
|
||||
],
|
||||
},
|
||||
}}
|
||||
aria-label="Select an employee"
|
||||
placeholder="Enter employee name"
|
||||
popoverProps={{
|
||||
offset: 10,
|
||||
classNames: {
|
||||
base: "rounded-large",
|
||||
content: "p-1 border-small border-default-100 bg-background",
|
||||
},
|
||||
}}
|
||||
startContent={<SearchIcon className="text-default-400" strokeWidth={2.5} size={20} />}
|
||||
radius="full"
|
||||
variant="bordered"
|
||||
>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item.id} textValue={item.name}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar alt={item.name} className="flex-shrink-0" size="sm" src={item.avatar} />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-small">{item.name}</span>
|
||||
<span className="text-tiny text-default-400">{item.team}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="border-small mr-0.5 font-medium shadow-small"
|
||||
radius="full"
|
||||
size="sm"
|
||||
variant="bordered"
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/SearchIcon.jsx": SearchIcon,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
allowsCustomValue
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
className="max-w-xs"
|
||||
defaultItems={animals}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
description="The second most popular pet in the world"
|
||||
defaultItems={animals}
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultItems={animals}
|
||||
disabledKeys={["zebra", "tiger", "lion", "elephant", "crocodile", "whale"]}
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
isDisabled
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(animal) => <AutocompleteItem key={animal.value}>{animal.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState("");
|
||||
const [touched, setTouched] = React.useState(false);
|
||||
|
||||
const isValid = value === "cat";
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
variant="bordered"
|
||||
placeholder="Search an animal"
|
||||
description="The second most popular pet in the world"
|
||||
errorMessage={isValid || !touched ? "" : "You must select a cat"}
|
||||
isInvalid={isValid || !touched ? false : true}
|
||||
defaultItems={animals}
|
||||
selectedKey={value}
|
||||
className="max-w-xs"
|
||||
onSelectionChange={setValue}
|
||||
onClose={() => setTouched(true)}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState('');
|
||||
const [selectedKey, setSelectedKey] = React.useState(null);
|
||||
|
||||
const onSelectionChange = (id) => {
|
||||
setSelectedKey(id);
|
||||
};
|
||||
|
||||
const onInputChange = (value) => {
|
||||
setValue(value)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Autocomplete
|
||||
label="Search an animal"
|
||||
variant="bordered"
|
||||
defaultItems={animals}
|
||||
className="max-w-xs"
|
||||
allowsCustomValue={true}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="mt-1 text-small text-default-500">Current selected animal: {selectedKey}</p>
|
||||
<p className="text-small text-default-500">Current input text: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const [value, setValue] = React.useState<string>('');
|
||||
const [selectedKey, setSelectedKey] = React.useState<React.Key | null>(null);
|
||||
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setSelectedKey(key);
|
||||
};
|
||||
|
||||
const onInputChange = (value: string) => {
|
||||
setValue(value)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Autocomplete
|
||||
label="Search an animal"
|
||||
variant="underlined"
|
||||
defaultItems={animals}
|
||||
className="max-w-xs"
|
||||
allowsCustomValue={true}
|
||||
onSelectionChange={onSelectionChange}
|
||||
onInputChange={onInputChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<p className="mt-1 text-small text-default-500">Current selected animal: {selectedKey}</p>
|
||||
<p className="text-small text-default-500">Current input text: {value}</p>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
// Store Autocomplete input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen, menuTrigger) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Autocomplete, AutocompleteItem, MenuTriggerAction} from "@nextui-org/react";
|
||||
import {useFilter} from "@react-aria/i18n";
|
||||
import {animals} from "./data";
|
||||
|
||||
type FieldState = {
|
||||
selectedKey: React.Key | null;
|
||||
inputValue: string;
|
||||
items: typeof animals;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
// Store Autocomplete input value, selected option, open state, and items
|
||||
// in a state tracker
|
||||
const [fieldState, setFieldState] = React.useState<FieldState>({
|
||||
selectedKey: "",
|
||||
inputValue: "",
|
||||
items: animals,
|
||||
});
|
||||
|
||||
// Implement custom filtering logic and control what items are
|
||||
// available to the Autocomplete.
|
||||
const {startsWith} = useFilter({sensitivity: "base"});
|
||||
|
||||
// Specify how each of the Autocomplete values should change when an
|
||||
// option is selected from the list box
|
||||
const onSelectionChange = (key: React.Key) => {
|
||||
setFieldState((prevState) => {
|
||||
let selectedItem = prevState.items.find((option) => option.value === key);
|
||||
|
||||
return {
|
||||
inputValue: selectedItem?.label || "",
|
||||
selectedKey: key,
|
||||
items: animals.filter((item) => startsWith(item.label, selectedItem?.label || "")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Specify how each of the Autocomplete values should change when the input
|
||||
// field is altered by the user
|
||||
const onInputChange = (value: string) => {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: value,
|
||||
selectedKey: value === "" ? null : prevState.selectedKey,
|
||||
items: animals.filter((item) => startsWith(item.label, value)),
|
||||
}));
|
||||
};
|
||||
|
||||
// Show entire list if user opens the menu manually
|
||||
const onOpenChange = (isOpen: boolean, menuTrigger: MenuTriggerAction) => {
|
||||
if (menuTrigger === "manual" && isOpen) {
|
||||
setFieldState((prevState) => ({
|
||||
inputValue: prevState.inputValue,
|
||||
selectedKey: prevState.selectedKey,
|
||||
items: animals,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
inputValue={fieldState.inputValue}
|
||||
items={fieldState.items}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
selectedKey={fieldState.selectedKey}
|
||||
variant="bordered"
|
||||
onInputChange={onInputChange}
|
||||
onOpenChange={onOpenChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import usage from "./usage";
|
||||
import dynamic from "./dynamic";
|
||||
import disabled from "./disabled";
|
||||
import disabledItems from "./disabled-items";
|
||||
import required from "./required";
|
||||
import sizes from "./sizes";
|
||||
import colors from "./colors";
|
||||
import variants from "./variants";
|
||||
import labelPlacements from "./label-placements";
|
||||
import startContent from "./start-content";
|
||||
import itemStartContent from "./item-start-content";
|
||||
import customValue from "./custom-value";
|
||||
import customSelectorIcon from "./custom-selector-icon";
|
||||
import withoutScrollShadow from "./without-scroll-shadow";
|
||||
import description from "./description";
|
||||
import errorMessage from "./error-message";
|
||||
import events from "./events";
|
||||
import controlled from "./controlled";
|
||||
import fullyControlled from "./fully-controlled";
|
||||
import customItems from "./custom-items";
|
||||
import customFiltering from "./custom-filtering";
|
||||
import asyncFiltering from "./async-filtering";
|
||||
import asyncLoadingItems from "./async-loading-items";
|
||||
import sections from "./sections";
|
||||
import customSectionsStyle from "./custom-sections-style";
|
||||
import customStyles from "./custom-styles";
|
||||
|
||||
export const autocompleteContent = {
|
||||
usage,
|
||||
dynamic,
|
||||
disabled,
|
||||
disabledItems,
|
||||
required,
|
||||
sizes,
|
||||
colors,
|
||||
variants,
|
||||
labelPlacements,
|
||||
startContent,
|
||||
customValue,
|
||||
itemStartContent,
|
||||
customSelectorIcon,
|
||||
withoutScrollShadow,
|
||||
description,
|
||||
errorMessage,
|
||||
events,
|
||||
controlled,
|
||||
fullyControlled,
|
||||
customItems,
|
||||
customFiltering,
|
||||
asyncFiltering,
|
||||
asyncLoadingItems,
|
||||
sections,
|
||||
customSectionsStyle,
|
||||
customStyles,
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem, Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
className="max-w-xs"
|
||||
label="Select country"
|
||||
>
|
||||
<AutocompleteItem
|
||||
key="argentina"
|
||||
startContent={<Avatar alt="Argentina" className="w-6 h-6" src="https://flagcdn.com/ar.svg" />}
|
||||
>
|
||||
Argentina
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="venezuela"
|
||||
startContent={<Avatar alt="Venezuela" className="w-6 h-6" src="https://flagcdn.com/ve.svg" />}
|
||||
>
|
||||
Venezuela
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="brazil"
|
||||
startContent={<Avatar alt="Brazil" className="w-6 h-6" src="https://flagcdn.com/br.svg" />}
|
||||
>
|
||||
Brazil
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="switzerland"
|
||||
startContent={
|
||||
<Avatar alt="Switzerland" className="w-6 h-6" src="https://flagcdn.com/ch.svg" />
|
||||
}
|
||||
>
|
||||
Switzerland
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="germany"
|
||||
startContent={<Avatar alt="Germany" className="w-6 h-6" src="https://flagcdn.com/de.svg" />}
|
||||
>
|
||||
Germany
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="spain"
|
||||
startContent={<Avatar alt="Spain" className="w-6 h-6" src="https://flagcdn.com/es.svg" />}
|
||||
>
|
||||
Spain
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="france"
|
||||
startContent={<Avatar alt="France" className="w-6 h-6" src="https://flagcdn.com/fr.svg" />}
|
||||
>
|
||||
France
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="italy"
|
||||
startContent={<Avatar alt="Italy" className="w-6 h-6" src="https://flagcdn.com/it.svg" />}
|
||||
>
|
||||
Italy
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem
|
||||
key="mexico"
|
||||
startContent={<Avatar alt="Mexico" className="w-6 h-6" src="https://flagcdn.com/mx.svg" />}
|
||||
>
|
||||
Mexico
|
||||
</AutocompleteItem>
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const placements = [
|
||||
"inside",
|
||||
"outside",
|
||||
"outside-left",
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-default-500 text-small">Without placeholder</h3>
|
||||
<div className="flex w-full flex-wrap items-end md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
{placements.map((placement) => (
|
||||
<Autocomplete
|
||||
defaultItems={animals}
|
||||
labelPlacement={placement}
|
||||
label="Favorite Animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-default-500 text-small">With placeholder</h3>
|
||||
<div className="flex w-full flex-wrap items-end md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
{placements.map((placement) => (
|
||||
<Autocomplete
|
||||
labelPlacement={placement}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{animals.map((animal) => (
|
||||
<AutocompleteItem key={animal.value} value={animal.value}>
|
||||
{animal.label}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
isRequired
|
||||
label="Favorite Animal"
|
||||
defaultItems={animals}
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
const App = `import {Autocomplete, AutocompleteItem, AutocompleteSection} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
<AutocompleteSection showDivider title="Mammals">
|
||||
<AutocompleteItem key="Lion">Lion</AutocompleteItem>
|
||||
<AutocompleteItem key="Tiger">Tiger</AutocompleteItem>
|
||||
<AutocompleteItem key="Elephant">Elephant</AutocompleteItem>
|
||||
<AutocompleteItem key="Kangaroo">Kangaroo</AutocompleteItem>
|
||||
<AutocompleteItem key="Panda">Panda</AutocompleteItem>
|
||||
<AutocompleteItem key="Giraffe">Giraffe</AutocompleteItem>
|
||||
<AutocompleteItem key="Zebra">Zebra</AutocompleteItem>
|
||||
<AutocompleteItem key="Cheetah">Cheetah</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
<AutocompleteSection title="Birds">
|
||||
<AutocompleteItem key="Eagle">Eagle</AutocompleteItem>
|
||||
<AutocompleteItem key="Parrot">Parrot</AutocompleteItem>
|
||||
<AutocompleteItem key="Penguin">Penguin</AutocompleteItem>
|
||||
<AutocompleteItem key="Ostrich">Ostrich</AutocompleteItem>
|
||||
<AutocompleteItem key="Peacock">Peacock</AutocompleteItem>
|
||||
<AutocompleteItem key="Swan">Swan</AutocompleteItem>
|
||||
<AutocompleteItem key="Falcon">Falcon</AutocompleteItem>
|
||||
<AutocompleteItem key="Flamingo">Flamingo</AutocompleteItem>
|
||||
</AutocompleteSection>
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const sizes = ["sm", "md", "lg"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<Autocomplete
|
||||
size={size}
|
||||
defaultItems={animals}
|
||||
label="Select an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<Autocomplete
|
||||
size={size}
|
||||
defaultItems={animals}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const PetIcon = `export const PetIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M19.0803 15.7203C18.4903 12.1903 15.1003 9.32031 11.5203 9.32031C7.63028 9.32031 4.21028 12.4703 3.88028 16.3503C3.75028 17.8503 4.23028 19.2703 5.22028 20.3403C6.20028 21.4103 7.58028 22.0003 9.08028 22.0003H13.7603C15.4503 22.0003 16.9303 21.3403 17.9403 20.1503C18.9503 18.9603 19.3503 17.3803 19.0803 15.7203Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M10.2796 7.86C11.8978 7.86 13.2096 6.54819 13.2096 4.93C13.2096 3.31181 11.8978 2 10.2796 2C8.66141 2 7.34961 3.31181 7.34961 4.93C7.34961 6.54819 8.66141 7.86 10.2796 7.86Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M16.94 9.02844C18.2876 9.02844 19.38 7.93601 19.38 6.58844C19.38 5.24086 18.2876 4.14844 16.94 4.14844C15.5924 4.14844 14.5 5.24086 14.5 6.58844C14.5 7.93601 15.5924 9.02844 16.94 9.02844Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M20.5496 12.9313C21.6266 12.9313 22.4996 12.0582 22.4996 10.9812C22.4996 9.90429 21.6266 9.03125 20.5496 9.03125C19.4727 9.03125 18.5996 9.90429 18.5996 10.9812C18.5996 12.0582 19.4727 12.9313 20.5496 12.9313Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.94 10.9816C5.28757 10.9816 6.38 9.88914 6.38 8.54156C6.38 7.19399 5.28757 6.10156 3.94 6.10156C2.59243 6.10156 1.5 7.19399 1.5 8.54156C1.5 9.88914 2.59243 10.9816 3.94 10.9816Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {PetIcon} from "./PetIcon";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
variant="bordered"
|
||||
defaultItems={animals}
|
||||
startContent={<PetIcon className="text-xl" />}
|
||||
defaultSelectedKey="cat"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/PetIcon.jsx": PetIcon,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||
<Autocomplete
|
||||
label="Select an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{animals.map((animal) => (
|
||||
<AutocompleteItem key={animal.value} value={animal.value}>
|
||||
{animal.label}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
defaultItems={animals}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
const variants = ["flat", "bordered", "underlined", "faded"];
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||
<Autocomplete
|
||||
variant={variant}
|
||||
defaultItems={animals}
|
||||
label="Select an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
<Autocomplete
|
||||
variant={variant}
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
className="max-w-xs"
|
||||
>
|
||||
{animals.map((animal) => (
|
||||
<AutocompleteItem key={animal.value} value={animal.value}>
|
||||
{animal.label}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
const data = `export const animals = [
|
||||
{label: "Cat", value: "cat", description: "The second most popular pet in the world"},
|
||||
{label: "Dog", value: "dog", description: "The most popular pet in the world"},
|
||||
{label: "Elephant", value: "elephant", description: "The largest land animal"},
|
||||
{label: "Lion", value: "lion", description: "The king of the jungle"},
|
||||
{label: "Tiger", value: "tiger", description: "The largest cat species"},
|
||||
{label: "Giraffe", value: "giraffe", description: "The tallest land animal"},
|
||||
{
|
||||
label: "Dolphin",
|
||||
value: "dolphin",
|
||||
description: "A widely distributed and diverse group of aquatic mammals",
|
||||
},
|
||||
{label: "Penguin", value: "penguin", description: "A group of aquatic flightless birds"},
|
||||
{label: "Zebra", value: "zebra", description: "A several species of African equids"},
|
||||
{
|
||||
label: "Shark",
|
||||
value: "shark",
|
||||
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
|
||||
},
|
||||
{
|
||||
label: "Whale",
|
||||
value: "whale",
|
||||
description: "Diverse group of fully aquatic placental marine mammals",
|
||||
},
|
||||
{label: "Otter", value: "otter", description: "A carnivorous mammal in the subfamily Lutrinae"},
|
||||
{label: "Crocodile", value: "crocodile", description: "A large semiaquatic reptile"},
|
||||
];`;
|
||||
|
||||
const App = `import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
|
||||
import {animals} from "./data";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Autocomplete
|
||||
label="Favorite Animal"
|
||||
placeholder="Search an animal"
|
||||
defaultSelectedKey="cat"
|
||||
defaultItems={animals}
|
||||
className="max-w-xs"
|
||||
scrollShadowProps={{
|
||||
isEnabled: false
|
||||
}}
|
||||
>
|
||||
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
|
||||
</Autocomplete>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/data.js": data,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
const App = `import {Avatar} from "@nextui-org/react
|
||||
const App = `import {Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const App = `import {Avatar} from "@nextui-org/react
|
||||
const App = `import {Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
||||
@@ -18,7 +18,7 @@ const CameraIcon = `export const CameraIcon = ({fill, size, height, width, ...pr
|
||||
);
|
||||
};`;
|
||||
|
||||
const App = `import {Avatar} from "@nextui-org/react
|
||||
const App = `import {Avatar} from "@nextui-org/react";
|
||||
import {CameraIcon} from './CameraIcon';
|
||||
|
||||
export default function App() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const App = `import {Avatar} from "@nextui-org/react
|
||||
const App = `import {Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function App() {
|
||||
max={3}
|
||||
total={10}
|
||||
renderCount={(count) => (
|
||||
<p className="text-small text-foreground font-medium ml-2">+{count} others</p>
|
||||
<p className="text-small text-foreground font-medium ms-2">+{count} others</p>
|
||||
)}
|
||||
>
|
||||
<Avatar src="https://i.pravatar.cc/150?u=a042581f4e29026024d" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const App = `import {Avatar} from "@nextui-org/react
|
||||
const App = `import {Avatar} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ import placements from "./placements";
|
||||
import shapes from "./shapes";
|
||||
import visibility from "./visibility";
|
||||
import contentExamples from "./content-examples";
|
||||
import disableOutline from "./disable-outline";
|
||||
import showOutline from "./show-outline";
|
||||
import a11y from "./a11y";
|
||||
|
||||
export const badgeContent = {
|
||||
@@ -18,6 +18,6 @@ export const badgeContent = {
|
||||
shapes,
|
||||
visibility,
|
||||
contentExamples,
|
||||
disableOutline,
|
||||
showOutline,
|
||||
a11y,
|
||||
};
|
||||
|
||||
+2
-2
@@ -3,14 +3,14 @@ const App = `import {Badge, Avatar} from "@nextui-org/react";
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="flex gap-4 items-center">
|
||||
<Badge content="5" color="danger" shape="rectangle" disableOutline>
|
||||
<Badge content="5" color="danger" shape="rectangle" showOutline={false}>
|
||||
<Avatar
|
||||
isBordered
|
||||
radius="md"
|
||||
src="https://i.pravatar.cc/150?u=a042f81f4e29026024d"
|
||||
/>
|
||||
</Badge>
|
||||
<Badge content="5" color="danger" shape="circle" disableOutline>
|
||||
<Badge content="5" color="danger" shape="circle" showOutline={false}>
|
||||
<Avatar
|
||||
isBordered
|
||||
radius="full"
|
||||
@@ -0,0 +1,21 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs maxItems={3} itemsBeforeCollapse={1} itemsAfterCollapse={2}>
|
||||
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#music">Music</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#artist">Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#album">Album</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#song">Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const colors = ["foreground", "primary", "secondary", "success", "warning", "danger"];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-4">
|
||||
{colors.map((color) => (
|
||||
<Breadcrumbs key={color} color={color}>
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = React.useState("song");
|
||||
|
||||
return (
|
||||
<Breadcrumbs underline="active" onAction={(key) => setCurrentPage(key)}>
|
||||
<BreadcrumbItem key="home" isCurrent={currentPage === "home"}>
|
||||
Home
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="music" isCurrent={currentPage === "music"}>
|
||||
Music
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="artist" isCurrent={currentPage === "artist"}>
|
||||
Artist
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="album" isCurrent={currentPage === "album"}>
|
||||
Album
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="song" isCurrent={currentPage === "song"}>
|
||||
Song
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = React.useState<React.Key>("song");
|
||||
|
||||
return (
|
||||
<Breadcrumbs underline="active" onAction={(key) => setCurrentPage(key)}>
|
||||
<BreadcrumbItem key="home" isCurrent={currentPage === "home"}>
|
||||
Home
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="music" isCurrent={currentPage === "music"}>
|
||||
Music
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="artist" isCurrent={currentPage === "artist"}>
|
||||
Artist
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="album" isCurrent={currentPage === "album"}>
|
||||
Album
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="song" isCurrent={currentPage === "song"}>
|
||||
Song
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
const ChevronDownIcon = `export const ChevronDownIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem, Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button} from "@nextui-org/react";
|
||||
import {ChevronDownIcon} from "./ChevronDownIcon";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
itemClasses={{
|
||||
item: "px-2",
|
||||
separator: "px-0",
|
||||
}}
|
||||
>
|
||||
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#music">Music</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#artist">Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#album">Album</BreadcrumbItem>
|
||||
<BreadcrumbItem
|
||||
classNames={{
|
||||
item: "px-0",
|
||||
}}
|
||||
>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
className="h-6 pr-2 text-small"
|
||||
endContent={<ChevronDownIcon className="text-default-500" />}
|
||||
radius="full"
|
||||
size="sm"
|
||||
variant="light"
|
||||
>
|
||||
Songs
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Routes">
|
||||
<DropdownItem href="#song-1">
|
||||
Song 1
|
||||
</DropdownItem>
|
||||
<DropdownItem href="#song2">
|
||||
Song 2
|
||||
</DropdownItem>
|
||||
<DropdownItem href="#song3">
|
||||
Song 3
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/ChevronDownIcon.jsx": ChevronDownIcon,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
const ShoppingCartIcon = `export const ShoppingCartIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M16.25 22.5C17.2165 22.5 18 21.7165 18 20.75C18 19.7835 17.2165 19 16.25 19C15.2835 19 14.5 19.7835 14.5 20.75C14.5 21.7165 15.2835 22.5 16.25 22.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.25 22.5C9.2165 22.5 10 21.7165 10 20.75C10 19.7835 9.2165 19 8.25 19C7.2835 19 6.5 19.7835 6.5 20.75C6.5 21.7165 7.2835 22.5 8.25 22.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M4.84 3.94L4.64 6.39C4.6 6.86 4.97 7.25 5.44 7.25H20.75C21.17 7.25 21.52 6.93 21.55 6.51C21.68 4.74 20.33 3.3 18.56 3.3H6.27C6.17 2.86 5.97 2.44 5.66 2.09C5.16 1.56 4.46 1.25 3.74 1.25H2C1.59 1.25 1.25 1.59 1.25 2C1.25 2.41 1.59 2.75 2 2.75H3.74C4.05 2.75 4.34 2.88 4.55 3.1C4.76 3.33 4.86 3.63 4.84 3.94Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M20.5101 8.75H5.17005C4.75005 8.75 4.41005 9.07 4.37005 9.48L4.01005 13.83C3.87005 15.54 5.21005 17 6.92005 17H18.0401C19.5401 17 20.8601 15.77 20.9701 14.27L21.3001 9.6C21.3401 9.14 20.9801 8.75 20.5101 8.75Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
import {ShoppingCartIcon} from "./ShoppingCartIcon";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
underline="hover"
|
||||
classNames={{
|
||||
list: "bg-gradient-to-br from-violet-500 to-fuchsia-500 shadow-small",
|
||||
}}
|
||||
itemClasses={{
|
||||
item: "text-white/60 data-[current=true]:text-white",
|
||||
separator: "text-white/40",
|
||||
}}
|
||||
variant="solid"
|
||||
>
|
||||
<BreadcrumbItem href="#shopping-cart">
|
||||
<ShoppingCartIcon />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#checkout">Checkout</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#payment">Payment</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#delivery-address">Delivery Address</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/ShoppingCartIcon.jsx": ShoppingCartIcon,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem, Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Button} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
maxItems={3}
|
||||
itemsBeforeCollapse={1}
|
||||
itemsAfterCollapse={2}
|
||||
renderEllipsis={({items, ellipsisIcon, separator}) => (
|
||||
<div className="flex items-center">
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
isIconOnly
|
||||
className="min-w-unit-6 w-unit-6 h-unit-6"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
{ellipsisIcon}
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Routes">
|
||||
{items.map((item, index) => (
|
||||
<DropdownItem key={index} href={item.href}>
|
||||
{item.children}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
{separator}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#music">Music</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#artist">Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#album">Album</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#featured">Featured</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#song">Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs isDisabled>
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import usage from "./usage";
|
||||
import disabled from "./disabled";
|
||||
import sizes from "./sizes";
|
||||
import colors from "./colors";
|
||||
import variants from "./variants";
|
||||
import radius from "./radius";
|
||||
import routing from "./routing";
|
||||
import underlines from "./underlines";
|
||||
import controlled from "./controlled";
|
||||
import menuType from "./menu-type";
|
||||
import startEndContent from "./start-end-content";
|
||||
import separator from "./separator";
|
||||
import customItems from "./custom-items";
|
||||
import collapsingItems from "./collapsing-items";
|
||||
import customizingEllipsis from "./customizing-ellipsis";
|
||||
import customStyles from "./custom-styles";
|
||||
|
||||
export const breadcrumbsContent = {
|
||||
usage,
|
||||
disabled,
|
||||
sizes,
|
||||
colors,
|
||||
variants,
|
||||
radius,
|
||||
routing,
|
||||
underlines,
|
||||
controlled,
|
||||
menuType,
|
||||
startEndContent,
|
||||
separator,
|
||||
customItems,
|
||||
collapsingItems,
|
||||
customizingEllipsis,
|
||||
customStyles,
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = React.useState("music");
|
||||
|
||||
return (
|
||||
<Breadcrumbs
|
||||
size="sm"
|
||||
onAction={(key) => setCurrentPage(key)}
|
||||
classNames={{
|
||||
list: "gap-2",
|
||||
}}
|
||||
itemClasses={{
|
||||
item: [
|
||||
"px-2 py-0.5 border-small border-default-400 rounded-small",
|
||||
"data-[current=true]:border-foreground data-[current=true]:bg-foreground data-[current=true]:text-background transition-colors",
|
||||
"data-[disabled=true]:border-default-400 data-[disabled=true]:bg-default-100",
|
||||
],
|
||||
separator: "hidden",
|
||||
}}
|
||||
>
|
||||
<BreadcrumbItem key="home" isCurrent={currentPage === "home"}>
|
||||
Home
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="music" isCurrent={currentPage === "music"}>
|
||||
Music
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="artist" isCurrent={currentPage === "artist"}>
|
||||
Artist
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="album" isCurrent={currentPage === "album"}>
|
||||
Album
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="song" isDisabled isCurrent={currentPage === "song"}>
|
||||
Song
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const AppTs = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const [currentPage, setCurrentPage] = React.useState<React.Key>("music");
|
||||
|
||||
return (
|
||||
<Breadcrumbs
|
||||
hideSeparator
|
||||
onAction={(key) => setCurrentPage(key)}
|
||||
classNames={{
|
||||
list: "gap-2",
|
||||
}}
|
||||
itemClasses={{
|
||||
item: [
|
||||
"px-2 py-0.5 border-small border-default-400 rounded-small",
|
||||
"data-[current=true]:border-default-800 data-[current=true]:bg-foreground data-[current=true]:text-background transition-colors",
|
||||
"data-[disabled=true]:border-default-400 data-[disabled=true]:bg-default-100",
|
||||
],
|
||||
}}
|
||||
>
|
||||
<BreadcrumbItem key="home" isCurrent={currentPage === "home"}>
|
||||
Home
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="music" isCurrent={currentPage === "music"}>
|
||||
Music
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="artist" isCurrent={currentPage === "artist"}>
|
||||
Artist
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="album" isCurrent={currentPage === "album"}>
|
||||
Album
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem key="song" isDisabled isCurrent={currentPage === "song"}>
|
||||
Song
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
const reactTs = {
|
||||
"/App.tsx": AppTs,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
...reactTs,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const radius = [ "full", "lg", "md", "sm", "none"];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-4">
|
||||
{radius.map((r) => (
|
||||
<Breadcrumbs key={r} radius={r} variant="solid">
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem href="/docs/components/button">Button</BreadcrumbItem>
|
||||
<BreadcrumbItem href="/docs/components/breadcrumbs">Breadcrumbs</BreadcrumbItem>
|
||||
<BreadcrumbItem href="/docs/components/card">Card</BreadcrumbItem>
|
||||
<BreadcrumbItem href="/docs/components/checkbox">Checkbox</BreadcrumbItem>
|
||||
<BreadcrumbItem href="/docs/components/code">Code</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
separator="/"
|
||||
itemClasses={{
|
||||
separator: "px-2"
|
||||
}}
|
||||
>
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const sizes = ["sm", "md", "lg"];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-4">
|
||||
{sizes.map((size) => (
|
||||
<Breadcrumbs key={size} size={size}>
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
const HomeIcon = `export const HomeIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M20.0402 6.82165L14.2802 2.79165C12.7102 1.69165 10.3002 1.75165 8.79023 2.92165L3.78023 6.83165C2.78023 7.61165 1.99023 9.21165 1.99023 10.4716V17.3716C1.99023 19.9216 4.06023 22.0016 6.61023 22.0016H17.3902C19.9402 22.0016 22.0102 19.9316 22.0102 17.3816V10.6016C22.0102 9.25165 21.1402 7.59165 20.0402 6.82165ZM12.7502 18.0016C12.7502 18.4116 12.4102 18.7516 12.0002 18.7516C11.5902 18.7516 11.2502 18.4116 11.2502 18.0016V15.0016C11.2502 14.5916 11.5902 14.2516 12.0002 14.2516C12.4102 14.2516 12.7502 14.5916 12.7502 15.0016V18.0016Z" fill="currentColor"/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const MusicIcon = `export const MusicIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M14.5406 13.6719C14.1806 13.6719 13.8906 13.9619 13.8906 14.3219C13.8906 14.6819 14.1806 14.9719 14.5406 14.9719C14.9006 14.9719 15.1906 14.6819 15.1906 14.3219C15.1806 13.9619 14.8906 13.6719 14.5406 13.6719Z" fill="currentColor"/>
|
||||
<path d="M9.06016 14.6602C8.70016 14.6602 8.41016 14.9502 8.41016 15.3102C8.41016 15.6702 8.70016 15.9602 9.06016 15.9602C9.42016 15.9602 9.71016 15.6702 9.71016 15.3102C9.71016 14.9502 9.42016 14.6602 9.06016 14.6602Z" fill="currentColor"/>
|
||||
<path d="M16.19 2H7.81C4.17 2 2 4.17 2 7.81V16.18C2 19.83 4.17 22 7.81 22H16.18C19.82 22 21.99 19.83 21.99 16.19V7.81C22 4.17 19.83 2 16.19 2ZM16.88 9.55V14.31C16.88 15.6 15.83 16.65 14.54 16.65C13.25 16.65 12.2 15.6 12.2 14.31C12.2 13.02 13.25 11.97 14.54 11.97C14.77 11.97 14.98 12.01 15.19 12.07V10.65L11.41 11.68V15.3C11.41 15.31 11.4 15.32 11.4 15.33C11.39 16.61 10.34 17.64 9.06 17.64C7.77 17.64 6.72 16.59 6.72 15.3C6.72 14.01 7.77 12.96 9.06 12.96C9.29 12.96 9.5 13 9.71 13.06V11.03V9.29C9.71 8.32 10.31 7.53 11.25 7.28L14.24 6.46C15.2 6.2 15.8 6.45 16.14 6.71C16.47 6.97 16.88 7.48 16.88 8.48V9.55Z" fill="currentColor"/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const ArtistIcon = `export const ArtistIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M19.5119 5.85L13.5719 2.42C12.6019 1.86 11.4019 1.86 10.4219 2.42L4.49187 5.85C3.52187 6.41 2.92188 7.45 2.92188 8.58V15.42C2.92188 16.54 3.52187 17.58 4.49187 18.15L10.4319 21.58C11.4019 22.14 12.6019 22.14 13.5819 21.58L19.5219 18.15C20.4919 17.59 21.0919 16.55 21.0919 15.42V8.58C21.0819 7.45 20.4819 6.42 19.5119 5.85ZM12.0019 7.34C13.2919 7.34 14.3319 8.38 14.3319 9.67C14.3319 10.96 13.2919 12 12.0019 12C10.7119 12 9.67188 10.96 9.67188 9.67C9.67188 8.39 10.7119 7.34 12.0019 7.34ZM14.6819 16.66H9.32187C8.51187 16.66 8.04187 15.76 8.49187 15.09C9.17187 14.08 10.4919 13.4 12.0019 13.4C13.5119 13.4 14.8319 14.08 15.5119 15.09C15.9619 15.75 15.4819 16.66 14.6819 16.66Z" fill="currentColor"/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const AlbumIcon = `export const AlbumIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M18 5.25H6C5.59 5.25 5.25 4.91 5.25 4.5C5.25 4.09 5.59 3.75 6 3.75H18C18.41 3.75 18.75 4.09 18.75 4.5C18.75 4.91 18.41 5.25 18 5.25Z" fill="currentColor"/>
|
||||
<path d="M15 2.75H9C8.59 2.75 8.25 2.41 8.25 2C8.25 1.59 8.59 1.25 9 1.25H15C15.41 1.25 15.75 1.59 15.75 2C15.75 2.41 15.41 2.75 15 2.75Z" fill="currentColor"/>
|
||||
<path d="M13.9091 16.2109C13.5691 16.2109 13.2891 16.4909 13.2891 16.8309C13.2891 17.1709 13.5691 17.4509 13.9091 17.4509C14.2491 17.4509 14.5291 17.1709 14.5291 16.8309C14.5291 16.4909 14.2491 16.2109 13.9091 16.2109Z" fill="currentColor"/>
|
||||
<path d="M9.50953 17.7389C9.50953 17.3989 9.22953 17.1289 8.88953 17.1289C8.54953 17.1289 8.26953 17.4089 8.26953 17.7489C8.26953 18.0889 8.54953 18.3689 8.88953 18.3689C9.22953 18.3589 9.50953 18.0789 9.50953 17.7389Z" fill="currentColor"/>
|
||||
<path d="M18 7H6C3.8 7 2 8.8 2 11V18C2 20.2 3.8 22 6 22H18C20.2 22 22 20.2 22 18V11C22 8.8 20.2 7 18 7ZM16.03 12.46V16.83C16.03 16.85 16.02 16.86 16.02 16.88C15.99 18.02 15.06 18.95 13.91 18.95C12.74 18.95 11.79 18 11.79 16.83C11.79 15.66 12.74 14.71 13.91 14.71C14.13 14.71 14.33 14.75 14.53 14.81V13.44L11.01 14.4V17.73V17.74C11.01 18.91 10.06 19.86 8.89 19.86C7.72 19.86 6.77 18.91 6.77 17.74C6.77 16.57 7.72 15.62 8.89 15.62C9.11 15.62 9.31 15.66 9.51 15.72V13.82V12.22C9.51 11.33 10.06 10.61 10.91 10.39L13.64 9.64C14.52 9.41 15.06 9.64 15.37 9.88C15.67 10.11 16.03 10.58 16.03 11.47V12.46Z" fill="currentColor"/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const SongIcon = `export const SongIcon = (props) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path d="M21.7188 6.27879V7.44879C21.7188 8.42879 21.3288 9.26879 20.6388 9.75879C20.2087 10.0788 19.6787 10.2288 19.1287 10.2288C18.7887 10.2288 18.4488 10.1788 18.0988 10.0588L12.7188 8.26879V17.9988C12.7188 20.6188 10.5887 22.7488 7.96875 22.7488C5.34875 22.7488 3.21875 20.6188 3.21875 17.9988C3.21875 15.3788 5.34875 13.2488 7.96875 13.2488C9.22875 13.2488 10.3688 13.7488 11.2188 14.5488V3.99879C11.2188 3.02879 11.6188 2.18879 12.3088 1.68879C12.9987 1.19879 13.9187 1.08879 14.8387 1.38879L19.2588 2.86879C20.6188 3.31879 21.7188 4.84879 21.7188 6.27879Z" fill="currentColor"/>
|
||||
</svg>
|
||||
);`;
|
||||
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
import {HomeIcon} from "./HomeIcon";
|
||||
import {MusicIcon} from "./MusicIcon";
|
||||
import {ArtistIcon} from "./ArtistIcon";
|
||||
import {AlbumIcon} from "./AlbumIcon";
|
||||
import {SongIcon} from "./SongIcon";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Breadcrumbs>
|
||||
<BreadcrumbItem startContent={<HomeIcon />}>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem startContent={<MusicIcon />}>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem startContent={<ArtistIcon />}>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem startContent={<AlbumIcon />}>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem startContent={<SongIcon />}>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
"/HomeIcon.jsx": HomeIcon,
|
||||
"/MusicIcon.jsx": MusicIcon,
|
||||
"/ArtistIcon.jsx": ArtistIcon,
|
||||
"/AlbumIcon.jsx": AlbumIcon,
|
||||
"/SongIcon.jsx": SongIcon,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
const App = `import {Breadcrumbs, BreadcrumbItem} from "@nextui-org/react";
|
||||
|
||||
export default function App() {
|
||||
const underlines = ["none", "hover", "always", "active", "focus"];
|
||||
const descriptions = {
|
||||
none: "No underline",
|
||||
hover: "Underline on hover",
|
||||
always: "Always underline",
|
||||
active: "Underline on active",
|
||||
focus: "Underline on focus",
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-wrap gap-4">
|
||||
{underlines.map((u) => (
|
||||
<div key={u}>
|
||||
<p className="mb-1 text-small text-default-600 capitalize">
|
||||
{descriptions[u]} ({u})
|
||||
</p>
|
||||
<Breadcrumbs underline={u}>
|
||||
<BreadcrumbItem>Home</BreadcrumbItem>
|
||||
<BreadcrumbItem>Music</BreadcrumbItem>
|
||||
<BreadcrumbItem>Artist</BreadcrumbItem>
|
||||
<BreadcrumbItem>Album</BreadcrumbItem>
|
||||
<BreadcrumbItem>Song</BreadcrumbItem>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
|
||||
const react = {
|
||||
"/App.jsx": App,
|
||||
};
|
||||
|
||||
export default {
|
||||
...react,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user