feat(dashboard): Add global commands (#7782)
* add global commands * update lock * shorten keybinds --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
co-authored by
Oli Juhl
parent
27bb93c5b5
commit
aee75f6ba0
@@ -1,34 +1,74 @@
|
||||
import { MagnifyingGlass } from "@medusajs/icons"
|
||||
import { Kbd, Text, clx } from "@medusajs/ui"
|
||||
import { Badge, Kbd, Text, clx } from "@medusajs/ui"
|
||||
import * as Dialog from "@radix-ui/react-dialog"
|
||||
import { Command } from "cmdk"
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
ElementRef,
|
||||
HTMLAttributes,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { Shortcut, ShortcutType } from "../../providers/keybind-provider"
|
||||
import { useGlobalShortcuts } from "../../providers/keybind-provider/hooks"
|
||||
import { useSearch } from "../../providers/search-provider"
|
||||
|
||||
export const Search = () => {
|
||||
const { open, onOpenChange } = useSearch()
|
||||
const links = useLinks()
|
||||
const globalCommands = useGlobalShortcuts()
|
||||
const location = useLocation()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
onOpenChange(false)
|
||||
}, [location.pathname, onOpenChange])
|
||||
|
||||
const links = useMemo(() => {
|
||||
const groups = new Map<ShortcutType, Shortcut[]>()
|
||||
|
||||
globalCommands.forEach((command) => {
|
||||
const group = groups.get(command.type) || []
|
||||
group.push(command)
|
||||
groups.set(command.type, group)
|
||||
})
|
||||
|
||||
return Array.from(groups).map(([title, items]) => ({
|
||||
title,
|
||||
items,
|
||||
}))
|
||||
}, [globalCommands])
|
||||
|
||||
const handleSelect = (callback: () => void) => {
|
||||
callback()
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={onOpenChange}>
|
||||
<CommandInput placeholder="Search" />
|
||||
<CommandInput placeholder={t("app.search.placeholder")} />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandEmpty>{t("general.noResultsTitle")}</CommandEmpty>
|
||||
{links.map((group) => {
|
||||
return (
|
||||
<CommandGroup key={group.title} heading={group.title}>
|
||||
<CommandGroup
|
||||
key={group.title}
|
||||
heading={t(`app.keyboardShortcuts.${group.title}`)}
|
||||
>
|
||||
{group.items.map((item) => {
|
||||
return (
|
||||
<CommandItem key={item.label}>
|
||||
<CommandItem
|
||||
key={item.label}
|
||||
onSelect={() => handleSelect(item.callback)}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
{item.keys.Mac?.map((key, index) => {
|
||||
return <Kbd key={index}>{key}</Kbd>
|
||||
})}
|
||||
</div>
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
@@ -40,86 +80,6 @@ export const Search = () => {
|
||||
)
|
||||
}
|
||||
|
||||
type CommandItemProps = {
|
||||
label: string
|
||||
}
|
||||
|
||||
type CommandGroupProps = {
|
||||
title: string
|
||||
items: CommandItemProps[]
|
||||
}
|
||||
|
||||
const useLinks = (): CommandGroupProps[] => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
title: "Pages",
|
||||
items: [
|
||||
{
|
||||
label: t("products.domain"),
|
||||
},
|
||||
{
|
||||
label: t("categories.domain"),
|
||||
},
|
||||
{
|
||||
label: t("collections.domain"),
|
||||
},
|
||||
{
|
||||
label: t("giftCards.domain"),
|
||||
},
|
||||
{
|
||||
label: t("orders.domain"),
|
||||
},
|
||||
{
|
||||
label: t("draftOrders.domain"),
|
||||
},
|
||||
{
|
||||
label: t("customers.domain"),
|
||||
},
|
||||
{
|
||||
label: t("customerGroups.domain"),
|
||||
},
|
||||
{
|
||||
label: t("discounts.domain"),
|
||||
},
|
||||
{
|
||||
label: t("pricing.domain"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
items: [
|
||||
{
|
||||
label: t("profile.domain"),
|
||||
},
|
||||
{
|
||||
label: t("store.domain"),
|
||||
},
|
||||
{
|
||||
label: t("users.domain"),
|
||||
},
|
||||
{
|
||||
label: t("regions.domain"),
|
||||
},
|
||||
{
|
||||
label: t("locations.domain"),
|
||||
},
|
||||
{
|
||||
label: t("salesChannels.domain"),
|
||||
},
|
||||
{
|
||||
label: t("apiKeyManagement.domainTitle"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
|
||||
const CommandPalette = forwardRef<
|
||||
ElementRef<typeof Command>,
|
||||
ComponentPropsWithoutRef<typeof Command>
|
||||
@@ -133,36 +93,38 @@ const CommandPalette = forwardRef<
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Command.displayName = Command.displayName
|
||||
CommandPalette.displayName = Command.displayName
|
||||
|
||||
interface CommandDialogProps extends Dialog.DialogProps {}
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Dialog.Root {...props}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="bg-ui-bg-overlay fixed inset-0" />
|
||||
<Dialog.Content className="bg-ui-bg-base shadow-elevation-modal fixed left-[50%] top-[50%] w-full max-w-2xl translate-x-[-50%] translate-y-[-50%] overflow-hidden rounded-xl p-0">
|
||||
<CommandPalette className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[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">
|
||||
<CommandPalette className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input]]:h-[52px]">
|
||||
{children}
|
||||
</CommandPalette>
|
||||
<div className="flex items-center justify-between border-t px-4 pb-4 pt-3">
|
||||
<div></div>
|
||||
<div className="bg-ui-bg-field text-ui-fg-subtle flex items-center justify-end border-t px-4 py-3">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Text size="xsmall" leading="compact">
|
||||
Navigation
|
||||
{t("app.search.navigation")}
|
||||
</Text>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<Kbd>↓</Kbd>
|
||||
<Kbd>↑</Kbd>
|
||||
<Kbd className="bg-ui-bg-field-component">↓</Kbd>
|
||||
<Kbd className="bg-ui-bg-field-component">↑</Kbd>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-ui-border-strong h-3 w-px" />
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Text size="xsmall" leading="compact">
|
||||
Open Result
|
||||
{t("app.search.openResult")}
|
||||
</Text>
|
||||
<Kbd>↵</Kbd>
|
||||
<Kbd className="bg-ui-bg-field-component">↵</Kbd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,19 +137,26 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
const CommandInput = forwardRef<
|
||||
ElementRef<typeof Command.Input>,
|
||||
ComponentPropsWithoutRef<typeof Command.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<MagnifyingGlass className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<Command.Input
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"placeholder:text-ui-fg-muted flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col border-b">
|
||||
<div className="px-4 pt-4">
|
||||
{/* TODO: Add filter once we have search engine */}
|
||||
<Badge size="2xsmall">{t("app.search.allAreas")}</Badge>
|
||||
</div>
|
||||
<Command.Input
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"placeholder:text-ui-fg-muted flex h-10 w-full rounded-md bg-transparent p-4 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
CommandInput.displayName = Command.Input.displayName
|
||||
|
||||
@@ -198,7 +167,7 @@ const CommandList = forwardRef<
|
||||
<Command.List
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"max-h-[300px] overflow-y-auto overflow-x-hidden pb-4",
|
||||
"max-h-[300px] overflow-y-auto overflow-x-hidden px-2 pb-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -223,7 +192,7 @@ const CommandGroup = forwardRef<
|
||||
<Command.Group
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"text-ui-fg-base [&_[cmdk-group-heading]]:text-ui-fg-muted [&_[cmdk-group-heading]]:txt-compact-xsmall-plus overflow-hidden px-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:pt-4 [&_[cmdk-item]]:py-2",
|
||||
"text-ui-fg-base [&_[cmdk-group-heading]]:text-ui-fg-muted [&_[cmdk-group-heading]]:txt-compact-xsmall-plus overflow-hidden [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:pt-4 [&_[cmdk-item]]:py-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -251,7 +220,7 @@ const CommandItem = forwardRef<
|
||||
<Command.Item
|
||||
ref={ref}
|
||||
className={clx(
|
||||
"aria-selected:bg-ui-bg-base-hover hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-hover txt-compact-small relative flex cursor-default select-none items-center rounded-md p-2 outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"aria-selected:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-hover txt-compact-small [&>svg]:text-ui-fg-subtle relative flex cursor-pointer select-none items-center gap-x-3 rounded-md p-2 outline-none data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -259,19 +228,3 @@ const CommandItem = forwardRef<
|
||||
))
|
||||
|
||||
CommandItem.displayName = Command.Item.displayName
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={clx(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
CommandShortcut.displayName = "CommandShortcut"
|
||||
|
||||
Reference in New Issue
Block a user