feat(admin,admin-ui,medusa): Add Medusa Admin plugin (#3334)

This commit is contained in:
Kasper Fabricius Kristensen
2023-03-03 10:09:16 +01:00
committed by GitHub
parent d6b1ad1ccd
commit 40de54b010
928 changed files with 85441 additions and 384 deletions

View File

@@ -0,0 +1,96 @@
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import clsx from "clsx"
import React, { useState } from "react"
import CheckIcon from "../../fundamentals/icons/check-icon"
import ChevronDownIcon from "../../fundamentals/icons/chevron-down"
export type FilteringOptionProps = {
title: string
options: {
title: string
count?: number
onClick: () => void
}[]
} & React.HTMLAttributes<HTMLDivElement>
const FilteringOptions: React.FC<FilteringOptionProps> = ({
title,
options,
className,
...props
}) => {
const [selected, setSelected] = useState(options?.[0]?.title || "All")
const [open, setOpen] = useState(false)
return (
<div
className={clsx(
"inter-small-regular flex text-grey-50 mr-6 last:mr-0",
className
)}
{...props}
>
<span className="">{title}:</span>
<DropdownMenu.Root onOpenChange={setOpen}>
<DropdownMenu.Trigger
asChild
className={clsx(
"inter-small-regular text-grey-50 flex items-center pl-1.5 pr-0.5 rounded active:bg-grey-5 hover:bg-grey-5",
{ "bg-grey-5": open }
)}
>
<div className="flex align-center">
{selected}
<div className="text-grey-40 h-min">
<ChevronDownIcon size={16} />
</div>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Content
sideOffset={8}
className="bg-grey-0 p-2 border border-grey-20 rounded-rounded shadow-dropdown"
>
{options.map((opt, idx) => (
<DropdownMenu.DropdownMenuItem
key={`${idx}-${opt.title}`}
onSelect={() => {
opt.onClick()
setSelected(opt.title)
}}
disabled={typeof opt.count !== "undefined" && opt.count < 1}
className={clsx(
"py-1.5 my-1 w-48 px-3 flex items-center rounded text-grey-90 hover:border-0 hover:outline-none inter-small-semibold",
{
"cursor-pointer hover:bg-grey-10":
typeof opt.count === "undefined" || opt.count > 0,
}
)}
>
{selected === opt.title && (
<span className="w-4">
<CheckIcon size={16} />
</span>
)}
<div
className={clsx("ml-3 w-full flex justify-between", {
"ml-7": selected !== opt.title,
"text-grey-30": (opt.count || 0) < 1,
})}
>
{opt.title}
<span
className={clsx("inter-small-regular text-grey-40 ml-3", {
"text-grey-30": (opt.count || 0) < 1,
})}
>
{opt.count}
</span>
</div>
</DropdownMenu.DropdownMenuItem>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
)
}
export default FilteringOptions

View File

@@ -0,0 +1,250 @@
import clsx from "clsx"
import React from "react"
import { useNavigate } from "react-router-dom"
import Spinner from "../../atoms/spinner"
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
import ArrowRightIcon from "../../fundamentals/icons/arrow-right-icon"
import SortingIcon from "../../fundamentals/icons/sorting-icon"
import Actionables, { ActionType } from "../../molecules/actionables"
import FilteringOptions, { FilteringOptionProps } from "./filtering-option"
import TableSearch from "./table-search"
type TableRowProps = React.HTMLAttributes<HTMLTableRowElement> & {
forceDropdown?: boolean
actions?: ActionType[]
linkTo?: string
}
type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement> & {
linkTo?: string
name?: string
}
type SortingHeadCellProps = {
onSortClicked: () => void
sortDirection?: "ASC" | "DESC"
setSortDirection: (string) => void
} & React.HTMLAttributes<HTMLTableCellElement>
export type TableProps = {
filteringOptions?: FilteringOptionProps[] | React.ReactNode
tableActions?: React.ReactNode
enableSearch?: boolean
immediateSearchFocus?: boolean
searchPlaceholder?: string
searchValue?: string
containerClassName?: string
handleSearch?: (searchTerm: string) => void
} & React.HTMLAttributes<HTMLTableElement>
type TableElement<T> = React.ForwardRefExoticComponent<T> &
React.RefAttributes<unknown>
type TableType = {
Head: TableElement<React.HTMLAttributes<HTMLTableSectionElement>>
HeadRow: TableElement<React.HTMLAttributes<HTMLTableRowElement>>
HeadCell: TableElement<React.ThHTMLAttributes<HTMLTableCellElement>>
SortingHeadCell: TableElement<SortingHeadCellProps>
Body: TableElement<React.HTMLAttributes<HTMLTableSectionElement>>
Row: TableElement<TableRowProps>
Cell: TableElement<TableCellProps>
} & TableElement<TableProps>
const Table = React.forwardRef<HTMLTableElement, TableProps>(
(
{
className,
children,
tableActions,
enableSearch,
immediateSearchFocus,
searchPlaceholder,
searchValue,
handleSearch,
filteringOptions,
containerClassName,
...props
},
ref
) => {
if (enableSearch && !handleSearch) {
throw new Error("Table cannot enable search without a search handler")
}
return (
<div className={`flex flex-col ${containerClassName}`}>
<div className="w-full flex justify-between mb-2">
{filteringOptions ? (
<div className="flex mb-2 self-end">
{Array.isArray(filteringOptions)
? filteringOptions.map((fo) => <FilteringOptions {...fo} />)
: filteringOptions}
</div>
) : (
<span aria-hidden />
)}
<div className="flex items-center">
{tableActions && <div>{tableActions}</div>}
{enableSearch && (
<TableSearch
autoFocus={immediateSearchFocus}
placeholder={searchPlaceholder}
searchValue={searchValue}
onSearch={handleSearch!}
/>
)}
</div>
</div>
<div className="relative">
<table
ref={ref}
className={clsx("w-full table-auto", className)}
{...props}
>
{children}
</table>
</div>
</div>
)
}
) as TableType
Table.Head = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, children, ...props }, ref) => (
<thead
ref={ref}
className={clsx(
"whitespace-nowrap inter-small-semibold text-grey-50 border-t border-b border-grey-20",
className
)}
{...props}
>
{children}
</thead>
))
Table.HeadRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, children, ...props }, ref) => (
<tr ref={ref} className={clsx(className)} {...props}>
{children}
</tr>
))
Table.HeadCell = React.forwardRef<
HTMLTableCellElement,
React.HTMLAttributes<HTMLTableCellElement>
>(({ className, children, ...props }, ref) => (
<th ref={ref} className={clsx("text-left h-[40px]", className)} {...props}>
{children}
</th>
))
Table.SortingHeadCell = React.forwardRef<
HTMLTableCellElement,
SortingHeadCellProps
>(
(
{
onSortClicked,
sortDirection,
setSortDirection,
className,
children,
...props
}: SortingHeadCellProps,
ref
) => {
return (
<th ref={ref} className={clsx("text-left py-2.5", className)} {...props}>
<div
className="flex items-center cursor-pointer select-none"
onClick={(e) => {
e.preventDefault()
if (!sortDirection) {
setSortDirection("ASC")
} else {
if (sortDirection === "ASC") {
setSortDirection("DESC")
} else {
setSortDirection(undefined)
}
}
onSortClicked()
}}
>
{children}
<SortingIcon
size={16}
ascendingColor={sortDirection === "ASC" ? "#111827" : undefined}
descendingColor={sortDirection === "DESC" ? "#111827" : undefined}
/>
</div>
</th>
)
}
)
Table.Body = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, children, ...props }, ref) => (
<tbody ref={ref} className={clsx(className)} {...props}>
{children}
</tbody>
))
Table.Cell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
({ className, linkTo, children, ...props }, ref) => {
const navigate = useNavigate()
return (
<td
ref={ref}
className={clsx("inter-small-regular h-[40px]", className)}
{...props}
{...(linkTo && {
onClick: (e) => {
navigate(linkTo)
e.stopPropagation()
},
})}
>
{children}
</td>
)
}
)
Table.Row = React.forwardRef<HTMLTableRowElement, TableRowProps>(
({ className, actions, children, linkTo, forceDropdown, ...props }, ref) => {
const navigate = useNavigate()
return (
<tr
ref={ref}
className={clsx(
"inter-small-regular border-t border-b border-grey-20 text-grey-90",
className,
{ "cursor-pointer hover:bg-grey-5": linkTo !== undefined }
)}
{...props}
{...(linkTo && {
onClick: () => {
navigate(linkTo)
},
})}
>
{children}
{actions && (
<Table.Cell onClick={(e) => e.stopPropagation()} className="w-[32px]">
<Actionables forceDropdown={forceDropdown} actions={actions} />
</Table.Cell>
)}
</tr>
)
}
)
export default Table

View File

@@ -0,0 +1,47 @@
import clsx from "clsx"
import React from "react"
import SearchIcon from "../../fundamentals/icons/search-icon"
type TableSearchProps = {
autoFocus?: boolean
onSearch: (term: string) => void
placeholder?: string
searchValue?: string
} & React.HTMLAttributes<HTMLDivElement>
const TableSearch: React.FC<TableSearchProps> = ({
autoFocus,
onSearch,
placeholder = "Search",
searchValue,
className,
...props
}) => {
return (
<div
className={clsx(
"inter-small-regular mt-1 transition-color flex text-grey-50 items-center mb-1 pl-1 py-1.5 rounded-rounded border border-grey-20 min-w-content w-60 focus-within:shadow-input focus-within:border-violet-60 bg-grey-5",
className
)}
{...props}
>
<span className="px-2.5 py-0.5">
<SearchIcon size={16} />
</span>
<input
autoFocus={autoFocus}
type="text"
value={searchValue}
className={clsx(
"focus:outline-none focus:border-none inter-small-regular w-full bg-transparent focus:text-grey-90 caret-violet-60 placeholder:inter-small-regular placeholder-grey-40"
)}
placeholder={placeholder}
onChange={(e) => {
onSearch(e.target.value)
}}
/>
</div>
)
}
export default TableSearch