chore: move next admin packages to core repo (#5983)

**What**
- Move packages for `next` version of admin to core repo

**Other**
- Since this PR introduces packages that depend on Vite 5, it also introduces @types/node@^20. We have never had a direct dependency on the types package for Node, and as far as I can see that has resulted in us using the types from Node.js@8, as those are a dependency of one of our dependencies. With the introduction of @types/node@^20, two of our packages had TS errors because they were using the NodeJS.Timer type, which was deprecated in Node.js@14. We should add specific @types/node packages to all our packages, but I haven't done so in this PR to keep it as clean as possible.
- Q: @olivermrbl I've added the new packages to the ignore list for changeset, is this enough to prevent them from being published?
This commit is contained in:
Kasper Fabricius Kristensen
2024-01-08 10:26:46 +01:00
committed by GitHub
parent 479a8b82a9
commit f868775861
491 changed files with 11332 additions and 428 deletions
@@ -0,0 +1,45 @@
import { Input } from "@medusajs/ui"
import { ComponentProps, useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
type DebouncedSearchProps = Omit<
ComponentProps<typeof Input>,
"value" | "defaultValue" | "onChange" | "type"
> & {
debounce?: number
value: string
onChange: (value: string) => void
}
export const DebouncedSearch = ({
value: initialValue,
onChange,
debounce = 500,
placeholder,
...props
}: DebouncedSearchProps) => {
const [value, setValue] = useState<string>(initialValue)
const { t } = useTranslation()
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => {
onChange?.(value)
}, debounce)
return () => clearTimeout(timeout)
}, [value])
return (
<Input
{...props}
placeholder={placeholder || t("general.search")}
type="search"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
)
}
@@ -0,0 +1 @@
export * from "./debounced-search"
@@ -0,0 +1,199 @@
import {
Hint as HintComponent,
Label as LabelComponent,
Text,
clx,
} from "@medusajs/ui"
import * as LabelPrimitives from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import { createContext, forwardRef, useContext, useId } from "react"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
useFormState,
} from "react-hook-form"
import { useTranslation } from "react-i18next"
const Provider = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const Field = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
type FormItemContextValue = {
id: string
}
const FormItemContext = createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const useFormField = () => {
const fieldContext = useContext(FormFieldContext)
const itemContext = useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within a FormField")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formErrorMessageId: `${id}-form-item-message`,
...fieldState,
}
}
const Item = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = useId()
return (
<FormItemContext.Provider value={{ id }}>
<div
ref={ref}
className={clx("flex flex-col space-y-2", className)}
{...props}
/>
</FormItemContext.Provider>
)
}
)
Item.displayName = "Form.Item"
const Label = forwardRef<
React.ElementRef<typeof LabelPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitives.Root> & {
optional?: boolean
}
>(({ className, optional = false, ...props }, ref) => {
const { formItemId } = useFormField()
const { t } = useTranslation()
return (
<div className="flex items-center gap-x-1">
<LabelComponent
ref={ref}
className={clx(className)}
htmlFor={formItemId}
size="small"
weight="plus"
{...props}
/>
{optional && (
<Text size="small" leading="compact" className="text-ui-fg-muted">
({t("fields.optional")})
</Text>
)}
</div>
)
})
Label.displayName = "Form.Label"
const Control = forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formErrorMessageId } =
useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formErrorMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
Control.displayName = "Form.Control"
const Hint = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<HintComponent
ref={ref}
id={formDescriptionId}
className={className}
{...props}
/>
)
})
Hint.displayName = "Form.Hint"
const ErrorMessage = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formErrorMessageId } = useFormField()
const msg = error ? String(error?.message) : children
if (!msg) {
return null
}
return (
<HintComponent
ref={ref}
id={formErrorMessageId}
className={className}
variant={error ? "error" : "info"}
{...props}
>
{msg}
</HintComponent>
)
})
ErrorMessage.displayName = "Form.ErrorMessage"
const Form = Object.assign(Provider, {
Item,
Label,
Control,
Hint,
ErrorMessage,
Field,
})
export { Form }
@@ -0,0 +1 @@
export * from "./form";
@@ -0,0 +1 @@
export * from "./json-view";
@@ -0,0 +1,116 @@
import {
ArrowsPointingOut,
CheckCircleMiniSolid,
SquareTwoStackMini,
XMarkMini,
} from "@medusajs/icons"
import {
Badge,
Container,
Drawer,
Heading,
IconButton,
Kbd,
} from "@medusajs/ui"
import Primitive from "@uiw/react-json-view"
import { CSSProperties, Suspense } from "react"
type JsonViewProps = {
data: object
root?: string
}
// TODO: Fix the positioning of the copy btn
export const JsonView = ({ data, root }: JsonViewProps) => {
const numberOfKeys = Object.keys(data).length
return (
<Container className="flex items-center justify-between py-6">
<div className="flex items-center gap-x-4">
<Heading level="h2">JSON</Heading>
<Badge>{numberOfKeys} keys</Badge>
</div>
<Drawer>
<Drawer.Trigger asChild>
<IconButton variant="transparent" className="text-ui-fg-subtle">
<ArrowsPointingOut />
</IconButton>
</Drawer.Trigger>
<Drawer.Content className="border-ui-code-border bg-ui-code-bg-base text-ui-code-text-base dark overflow-hidden border shadow-none max-md:inset-x-2 max-md:max-w-[calc(100%-16px)]">
<div className="bg-ui-code-bg-header border-ui-code-border flex items-center justify-between border-b px-8 py-6">
<div className="flex items-center gap-x-4">
<Heading>JSON</Heading>
<Badge>{numberOfKeys} keys</Badge>
</div>
<div className="flex items-center gap-x-2">
<Kbd>esc</Kbd>
<Drawer.Close asChild>
<IconButton variant="transparent" className="text-ui-fg-subtle">
<XMarkMini />
</IconButton>
</Drawer.Close>
</div>
</div>
<Drawer.Body className="overflow-auto p-4">
<Suspense fallback={<div>Loading...</div>}>
<Primitive
value={data}
displayDataTypes={false}
keyName={root}
style={
{
"--w-rjv-font-family": "Roboto Mono, monospace",
"--w-rjv-line-color": "#2E3035",
"--w-rjv-curlybraces-color": "#ADB1B8",
"--w-rjv-key-string": "#A78BFA",
"--w-rjv-info-color": "#FBBF24",
"--w-rjv-type-string-color": "#34D399",
"--w-rjv-quotes-string-color": "#34D399",
"--w-rjv-type-boolean-color": "#FBBF24",
"--w-rjv-type-int-color": "#60A5FA",
"--w-rjv-type-float-color": "#60A5FA",
"--w-rjv-type-bigint-color": "#60A5FA",
"--w-rjv-key-number": "#60A5FA",
} as CSSProperties
}
collapsed={1}
>
<Primitive.Copied
// @ts-expect-error - types are missing the 'data-copied' prop
render={({ "data-copied": copied, onClick }) => {
if (copied) {
return (
<CheckCircleMiniSolid className="text-ui-fg-subtle cursor-pointer align-middle" />
)
}
return (
<SquareTwoStackMini
className="text-ui-fg-subtle cursor-pointer align-middle"
onClick={onClick}
/>
)
}}
/>
<Primitive.Quote render={() => " "} />
<Primitive.Null
render={() => (
<span className="text-ui-tag-red-text">null</span>
)}
/>
<Primitive.CountInfo
render={(_props, { value }) => {
return (
<span className="text-ui-tag-neutral-text ml-2">
{Object.keys(value as object).length} items
</span>
)
}}
/>
</Primitive>
</Suspense>
</Drawer.Body>
</Drawer.Content>
</Drawer>
</Container>
)
}
@@ -0,0 +1 @@
export * from "./product-table-cells"
@@ -0,0 +1,133 @@
import {
Product,
ProductCollection,
ProductVariant,
SalesChannel,
} from "@medusajs/medusa"
import { StatusBadge, Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { Thumbnail } from "../thumbnail"
export const ProductInventoryCell = ({
variants,
}: {
variants: ProductVariant[] | null
}) => {
const { t } = useTranslation()
if (!variants || !variants.length) {
return (
<Text size="small" className="text-ui-fg-subtle">
-
</Text>
)
}
const inventory = variants.reduce((acc, v) => acc + v.inventory_quantity, 0)
return (
<Text size="small" className="text-ui-fg-base">
{t("products.inStockVariants", {
count: variants.length,
inventory: inventory,
})}
</Text>
)
}
export const ProductStatusCell = ({
status,
}: {
status: Product["status"]
}) => {
const { t } = useTranslation()
const color = {
draft: "grey",
published: "green",
rejected: "red",
proposed: "blue",
}[status] as "grey" | "green" | "red" | "blue"
return (
<StatusBadge color={color}>
{t(`products.productStatus.${status}`)}
</StatusBadge>
)
}
export const ProductAvailabilityCell = ({
salesChannels,
}: {
salesChannels: SalesChannel[] | null
}) => {
const { t } = useTranslation()
if (!salesChannels || salesChannels.length === 0) {
return (
<Text size="small" className="text-ui-fg-subtle">
-
</Text>
)
}
if (salesChannels.length < 3) {
return (
<Text size="small" className="text-ui-fg-base">
{salesChannels.map((sc) => sc.name).join(", ")}
</Text>
)
}
return (
<div className="flex items-center gap-x-2">
<Text size="small" className="text-ui-fg-base">
<span>
{salesChannels
.slice(0, 2)
.map((sc) => sc.name)
.join(", ")}
</span>{" "}
<span>
{t("general.plusCountMore", {
count: salesChannels.length - 2,
})}
</span>
</Text>
</div>
)
}
export const ProductTitleCell = ({ product }: { product: Product }) => {
const thumbnail = product.thumbnail
const title = product.title
return (
<div className="flex items-center gap-x-3">
<Thumbnail src={thumbnail} alt={`Thumbnail image of ${title}`} />
<Text size="small" className="text-ui-fg-base">
{title}
</Text>
</div>
)
}
export const ProductCollectionCell = ({
collection,
}: {
collection: ProductCollection | null
}) => {
if (!collection) {
return (
<Text size="small" className="text-ui-fg-subtle">
-
</Text>
)
}
return (
<Text size="small" className="text-ui-fg-base">
{collection.title}
</Text>
)
}
@@ -0,0 +1 @@
export * from "./thumbnail";
@@ -0,0 +1,22 @@
import { Photo } from "@medusajs/icons";
type ThumbnailProps = {
src?: string | null;
alt?: string;
};
export const Thumbnail = ({ src, alt }: ThumbnailProps) => {
return (
<div className="bg-ui-bg-component flex h-8 w-6 items-center justify-center overflow-hidden rounded-[4px]">
{src ? (
<img
src={src}
alt={alt}
className="h-full w-full object-cover object-center"
/>
) : (
<Photo />
)}
</div>
);
};