-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(explorer): filterable tables selector (#3203)
Co-authored-by: Kevin Ingersoll <kingersoll@gmail.com>
- Loading branch information
Showing
8 changed files
with
749 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@latticexyz/explorer": patch | ||
--- | ||
|
||
Table selector of the Explore tab now has an input for searching/filtering tables by name. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 58 additions & 28 deletions
86
...s/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/explore/TableSelector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,72 @@ | ||
import { Lock } from "lucide-react"; | ||
import { Check, ChevronsUpDown, Lock } from "lucide-react"; | ||
import { useParams } from "next/navigation"; | ||
import { useState } from "react"; | ||
import { internalTableNames } from "@latticexyz/store-sync/sqlite"; | ||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../../../components/ui/Select"; | ||
import { Button } from "../../../../../../components/ui/Button"; | ||
import { | ||
Command, | ||
CommandEmpty, | ||
CommandGroup, | ||
CommandInput, | ||
CommandItem, | ||
CommandList, | ||
} from "../../../../../../components/ui/Command"; | ||
import { Popover, PopoverContent, PopoverTrigger } from "../../../../../../components/ui/Popover"; | ||
import { cn } from "../../../../../../lib/utils"; | ||
|
||
type Props = { | ||
value: string | undefined; | ||
options: string[]; | ||
}; | ||
|
||
export function TableSelector({ value, options }: Props) { | ||
const [open, setOpen] = useState(false); | ||
const { worldAddress } = useParams(); | ||
|
||
return ( | ||
<div className="py-4"> | ||
<Select | ||
value={value} | ||
onValueChange={(value: string) => { | ||
const url = new URL(window.location.href); | ||
const searchParams = new URLSearchParams(url.search); | ||
searchParams.set("table", value); | ||
window.history.pushState({}, "", `${window.location.pathname}?${searchParams}`); | ||
}} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select a table ..." /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
{options?.map((option) => { | ||
return ( | ||
<SelectItem key={option} value={option} className="font-mono"> | ||
{(internalTableNames as string[]).includes(option) && ( | ||
<Lock className="mr-2 inline-block opacity-70" size={14} /> | ||
)} | ||
{option.replace(`${worldAddress}__`, "")} | ||
</SelectItem> | ||
); | ||
})} | ||
</SelectContent> | ||
</Select> | ||
<div className="w-full py-4"> | ||
<Popover open={open} onOpenChange={setOpen}> | ||
<PopoverTrigger asChild> | ||
<Button variant="outline" role="combobox" aria-expanded={open} className="w-full justify-between"> | ||
{value ? options.find((option) => option === value)?.replace(`${worldAddress}__`, "") : "Select table..."} | ||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> | ||
</Button> | ||
</PopoverTrigger> | ||
|
||
<PopoverContent className="w-[--radix-popover-trigger-width] p-0"> | ||
<Command> | ||
<CommandInput placeholder="Search tables..." className="font-mono" /> | ||
<CommandList> | ||
<CommandEmpty>No framework found.</CommandEmpty> | ||
<CommandGroup> | ||
{options.map((option) => { | ||
return ( | ||
<CommandItem | ||
key={option} | ||
value={option} | ||
onSelect={(currentValue) => { | ||
const url = new URL(window.location.href); | ||
const searchParams = new URLSearchParams(url.search); | ||
searchParams.set("tableId", currentValue); | ||
window.history.pushState({}, "", `${window.location.pathname}?${searchParams}`); | ||
|
||
setOpen(false); | ||
}} | ||
className="font-mono" | ||
> | ||
<Check className={cn("mr-2 h-4 w-4", value === option ? "opacity-100" : "opacity-0")} /> | ||
{(internalTableNames as string[]).includes(option) && ( | ||
<Lock className="mr-2 inline-block opacity-70" size={14} /> | ||
)} | ||
{option.replace(`${worldAddress}__`, "")} | ||
</CommandItem> | ||
); | ||
})} | ||
</CommandGroup> | ||
</CommandList> | ||
</Command> | ||
</PopoverContent> | ||
</Popover> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
"use client"; | ||
|
||
import { Search } from "lucide-react"; | ||
import { Command as CommandPrimitive } from "cmdk"; | ||
import * as React from "react"; | ||
import { type DialogProps } from "@radix-ui/react-dialog"; | ||
import { cn } from "../../lib/utils"; | ||
import { Dialog, DialogContent } from "./Dialog"; | ||
|
||
const Command = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive> | ||
>(({ className, ...props }, ref) => ( | ||
<CommandPrimitive | ||
ref={ref} | ||
className={cn( | ||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
Command.displayName = CommandPrimitive.displayName; | ||
|
||
interface CommandDialogProps extends DialogProps {} | ||
|
||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => { | ||
return ( | ||
<Dialog {...props}> | ||
<DialogContent className="overflow-hidden p-0 shadow-lg"> | ||
<Command | ||
className={cn( | ||
"[&_[cmdk-group-heading]]:px-2", | ||
"[&_[cmdk-group-heading]]:font-medium", | ||
"[&_[cmdk-group-heading]]:text-muted-foreground", | ||
"[&_[cmdk-group]]:px-2", | ||
"[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0", | ||
"[&_[cmdk-input-wrapper]_svg]:h-5", | ||
"[&_[cmdk-input-wrapper]_svg]:w-5", | ||
"[&_[cmdk-input]]:h-12", | ||
"[&_[cmdk-item]]:px-2", | ||
"[&_[cmdk-item]]:py-3", | ||
"[&_[cmdk-item]_svg]:h-5", | ||
"[&_[cmdk-item]_svg]:w-5", | ||
)} | ||
> | ||
{children} | ||
</Command> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
const CommandInput = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.Input>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> | ||
>(({ className, ...props }, ref) => ( | ||
<div className="flex items-center border-b px-3" cmdk-input-wrapper=""> | ||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> | ||
<CommandPrimitive.Input | ||
ref={ref} | ||
className={cn( | ||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
</div> | ||
)); | ||
|
||
CommandInput.displayName = CommandPrimitive.Input.displayName; | ||
|
||
const CommandList = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.List>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> | ||
>(({ className, ...props }, ref) => ( | ||
<CommandPrimitive.List | ||
ref={ref} | ||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)} | ||
{...props} | ||
/> | ||
)); | ||
|
||
CommandList.displayName = CommandPrimitive.List.displayName; | ||
|
||
const CommandEmpty = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.Empty>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> | ||
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />); | ||
|
||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName; | ||
|
||
const CommandGroup = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.Group>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> | ||
>(({ className, ...props }, ref) => ( | ||
<CommandPrimitive.Group | ||
ref={ref} | ||
className={cn( | ||
"overflow-hidden", | ||
"p-1", | ||
"text-foreground", | ||
"[&_[cmdk-group-heading]]:px-2", | ||
"[&_[cmdk-group-heading]]:py-1.5", | ||
"[&_[cmdk-group-heading]]:text-xs", | ||
"[&_[cmdk-group-heading]]:font-medium", | ||
"[&_[cmdk-group-heading]]:text-muted-foreground", | ||
|
||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
|
||
CommandGroup.displayName = CommandPrimitive.Group.displayName; | ||
|
||
const CommandSeparator = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.Separator>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator> | ||
>(({ className, ...props }, ref) => ( | ||
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} /> | ||
)); | ||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName; | ||
|
||
const CommandItem = React.forwardRef< | ||
React.ElementRef<typeof CommandPrimitive.Item>, | ||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> | ||
>(({ className, ...props }, ref) => ( | ||
<CommandPrimitive.Item | ||
ref={ref} | ||
className={cn( | ||
"relative flex", | ||
"px-2 py-1.5", | ||
"text-sm", | ||
"cursor-default select-none", | ||
"outline-none", | ||
"rounded-sm", | ||
"data-[disabled=true]:pointer-events-none", | ||
"data-[disabled=true]:opacity-50", | ||
"data-[selected='true']:bg-accent", | ||
"data-[selected=true]:text-accent-foreground", | ||
|
||
className, | ||
)} | ||
{...props} | ||
/> | ||
)); | ||
|
||
CommandItem.displayName = CommandPrimitive.Item.displayName; | ||
|
||
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => { | ||
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />; | ||
}; | ||
CommandShortcut.displayName = "CommandShortcut"; | ||
|
||
export { | ||
Command, | ||
CommandDialog, | ||
CommandInput, | ||
CommandList, | ||
CommandEmpty, | ||
CommandGroup, | ||
CommandItem, | ||
CommandShortcut, | ||
CommandSeparator, | ||
}; |
Oops, something went wrong.