Skip to content

Commit

Permalink
refactor(docs): autocomplete dx (#3934)
Browse files Browse the repository at this point in the history
* feat(docs): add *.js?raw module

* feat(docs): change to react-jsx and add **/*.js

* chore(root): include js and jsx

* refactor(docs): autocomplete dx

* chore(docs): rollback overrides

* chore(autocomplete): lint

* fix(autocomplete): incorrect import path

* fix(docs): autocomplete dx

* chore(docs): remove highlightedLines
  • Loading branch information
wingkwong authored Nov 6, 2024
1 parent 93f274b commit e056c0b
Show file tree
Hide file tree
Showing 68 changed files with 2,454 additions and 2,368 deletions.
6 changes: 1 addition & 5 deletions apps/docs/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"files": ["*.ts", "*.tsx"],
"parserOptions": {
"project": ["apps/docs/tsconfig(.*)?.json"],
"ecmaFeatures": {
Expand All @@ -22,10 +22,6 @@
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
34 changes: 34 additions & 0 deletions apps/docs/content/components/autocomplete/async-filtering.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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>
);
}
41 changes: 41 additions & 0 deletions apps/docs/content/components/autocomplete/async-filtering.raw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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>
);
}
78 changes: 2 additions & 76 deletions apps/docs/content/components/autocomplete/async-filtering.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,5 @@
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>
);
}`;
import App from "./async-filtering.raw.jsx?raw";
import AppTs from "./async-filtering.raw.tsx?raw";

const react = {
"/App.jsx": App,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from "react";
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";

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));
}

const 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") {
// eslint-disable-next-line no-console
console.log("Fetch aborted");
} else {
// eslint-disable-next-line no-console
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 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"
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>
);
}
107 changes: 107 additions & 0 deletions apps/docs/content/components/autocomplete/async-loading-items.raw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from "react";
import {Autocomplete, AutocompleteItem} from "@nextui-org/react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";

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,
};
}

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"
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>
);
}
Loading

0 comments on commit e056c0b

Please sign in to comment.