Skip to content

Commit

Permalink
search 90%
Browse files Browse the repository at this point in the history
  • Loading branch information
sphinxrave committed Nov 8, 2024
1 parent 49613f5 commit a8a794f
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommandList, Command as CommandPrimitive } from "cmdk";
import { Command, CommandGroup } from "@/shadcn/ui/command";
import { Command, CommandGroup, CommandShortcut } from "@/shadcn/ui/command";
import { cn } from "@/lib/utils";
import {
queryAtom,
Expand Down Expand Up @@ -30,28 +30,6 @@ export function SearchBar({
const { search, updateSearch, autocomplete } = useSearchboxAutocomplete();
const navigate = useNavigate();

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === "Delete" || e.key === "Backspace") {
if (input.value === "") {
setQuery((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
// This is not a default behaviour of the <input /> field
if (e.key === "Escape") {
input.blur();
}
}
},
[setQuery],
);

const handleItemSelect = useCallback(
(item: QueryItem) => {
if (item.incomplete) {
Expand Down Expand Up @@ -96,6 +74,33 @@ export function SearchBar({
}
}, [navigate, query]);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === "Delete" || e.key === "Backspace") {
if (input.value === "") {
setQuery((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
// This is not a default behaviour of the <input /> field
if (e.key === "Escape") {
input.blur();
}
if (e.key === "Enter" && e.shiftKey) {
e.preventDefault();
e.stopPropagation();
doSearch();
}
}
},
[doSearch, setQuery],
);

return (
<Command
onKeyDown={handleKeyDown}
Expand All @@ -110,7 +115,7 @@ export function SearchBar({
>
<PopoverTrigger asChild>
<div className="group rounded-md bg-base-2 p-2 text-sm ring-offset-base-2 focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 hover:bg-base-3">
<div className="flex flex-wrap gap-1">
<label className="flex flex-wrap items-center gap-1">
{queryPieces.map((queryItem, i) => {
return (
<QueryBadge
Expand All @@ -133,12 +138,21 @@ export function SearchBar({
placeholder={t("component.search.searchLabel")}
className="ml-2 flex-1 bg-transparent outline-none placeholder:text-base-8"
/>
<button
className="i-carbon-search text-base-11 opacity-0 group-focus-within:opacity-100"
tabIndex={3}
onClick={() => doSearch()}
/>
</div>
<div className="ml-auto flex flex-row opacity-0 group-focus-within:opacity-100 ">
<CommandShortcut className="pointer-events-none ">
Shift⏎
</CommandShortcut>
<button
type="submit"
className="-my-2 -mr-1 flex size-8 items-center rounded-md text-base-11 transition-all hover:bg-base-5 hover:text-primary-11"
disabled={query.length === 0}
onClick={() => doSearch()}
onSubmit={() => doSearch()}
>
<div className="i-carbon:search mx-auto"></div>
</button>
</div>
</label>
</div>
</PopoverTrigger>
<PopoverPrimitive.Portal>
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/header/searchbar/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function getQueryModelFromQuery(
}
} else return; //ignore.
});
return vqm;
return sanitizeQueryModel(vqm);
}

async function gen2array<T>(gen: AsyncIterable<T>): Promise<T[]> {
Expand Down
59 changes: 47 additions & 12 deletions packages/react/src/components/video/MainVideoListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface MainVideoListingProps {
isLoading?: boolean;
hasNextPage?: boolean;
isFetchingNextPage?: boolean;
nonVirtual?: boolean;
}

export function MainVideoListing({
Expand All @@ -23,6 +24,7 @@ export function MainVideoListing({
hasNextPage,
isFetchingNextPage,
isLoading,
nonVirtual,
}: MainVideoListingProps) {
const listClassName = useMemo(
() =>
Expand All @@ -40,6 +42,43 @@ export function MainVideoListing({
[size, className],
);

if (isLoading) {
return (
<div className={listClassName}>
{Array.from({ length: 6 }).map((_, index) => (
<SkeletonVideoCard key={`skeleton-${index}`} />
))}
</div>
);
}

// If nonVirtual is true, render a simple grid
if (nonVirtual) {
return (
<div className={listClassName}>
{videos?.map((video, idx) => (
<MemoizedVideoCard
key={`videocard-${idx}-${video.id}`}
video={video}
size={size}
/>
))}
{isFetchingNextPage && hasNextPage && (
<div className="col-span-full flex justify-center py-4">
<VirtuosoLoadingFooter
context={{
size: "sm",
isLoading: true,
hasNextPage: true,
loadMore: fetchNextPage,
autoload: !!fetchNextPage,
}}
/>
</div>
)}
</div>
);
}
// const Footer = () =>
// (isLoading || isFetchingNextPage) && (
// <div className={listClassName}>
Expand All @@ -52,19 +91,15 @@ export function MainVideoListing({
return (
<VirtuosoGrid
useWindowScroll
data={isLoading ? ([1, 2, 3, 4, 5, 6] as unknown as VideoBase[]) : videos}
data={videos ?? []}
listClassName={listClassName}
itemContent={(index, video) =>
isLoading ? (
<SkeletonVideoCard key={`placeholder-${index}`} />
) : (
<MemoizedVideoCard
key={`video-${video.id}`}
video={video}
size={size}
/>
)
}
itemContent={(idx, video) => (
<MemoizedVideoCard
key={`videocard-${idx}-${video.id}`}
video={video}
size={size}
/>
)}
endReached={async () => {
if (hasNextPage && !isFetchingNextPage && !isLoading) {
await fetchNextPage?.();
Expand Down
71 changes: 71 additions & 0 deletions packages/react/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,74 @@ export function omitNullish<T extends object>(obj: T): OmitNullish<T> {

return result;
}

/**
* Generates an array of page numbers for pagination navigation.
* The array includes the current page and surrounding pages, with ellipsis (-1)
* where pages are skipped. Always includes first and last pages.
*
* Example outputs:
* - For 3 total pages: [1, 2, 3]
* - For current page 1 of 10: [1, 2, 3, -1, 10]
* - For current page 5 of 10: [1, -1, 4, 5, 6, -1, 10]
* - For current page 10 of 10: [1, -1, 8, 9, 10]
*
* @param currentPage - The currently active page number (1-based)
* @param totalPages - The total number of pages available
* @param maxPagesToShow - Maximum number of page numbers to display (default: 5)
* @returns Array of page numbers, with -1 representing ellipsis
* @throws Error if currentPage or totalPages are less than 1
*/
export function generatePageNumbers(
currentPage: number,
totalPages: number,
maxPagesToShow: number = 5,
): number[] {
// Input validation
if (currentPage < 1 || totalPages < 1) {
throw new Error("Current page and total pages must be greater than 0");
}
if (currentPage > totalPages) {
throw new Error("Current page cannot be greater than total pages");
}

const pages: number[] = [];

// Case 1: Show all pages if total is less than or equal to max
if (totalPages <= maxPagesToShow) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
return pages;
}

// Case 2: Need to show selective pages with possible ellipsis
// Always show first page
pages.push(1);

// Add leading ellipsis if current page is far from start
if (currentPage > 3) {
pages.push(-1);
}

// Show pages around current page
const start = Math.max(2, currentPage - 1);
const end = Math.min(totalPages - 1, currentPage + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}

// Add trailing ellipsis if current page is far from end
if (currentPage < totalPages - 2) {
pages.push(-1);
}

// Always show last page
if (pages[pages.length - 1] !== totalPages) {
pages.push(totalPages);
}

return pages;
}

export default generatePageNumbers;
Loading

0 comments on commit a8a794f

Please sign in to comment.