feat(dashboard): metadata component (#7117)

**What**
- add new metadata component

**Note**
- _example of usage on customer edit form_
- we are not handling update metadata case in the internal module service so for now delete case doesn't work properly

---


https://github.com/medusajs/medusa/assets/16856471/b588752d-9cf5-4d96-9cf8-760a764ab03e
This commit is contained in:
Frane Polić
2024-05-02 09:51:39 +02:00
committed by GitHub
parent ea1d926dfa
commit 155e276b90
11 changed files with 278 additions and 5 deletions

View File

@@ -0,0 +1 @@
export * from "./metadata"

View File

@@ -0,0 +1,157 @@
import { useEffect } from "react"
import { useTranslation } from "react-i18next"
import { Alert, Button, Input, Text } from "@medusajs/ui"
import { Trash } from "@medusajs/icons"
import { UseFormReturn } from "react-hook-form"
import { MetadataField } from "../../../lib/metadata"
type MetadataProps = {
form: UseFormReturn<MetadataField[]>
}
type FieldProps = {
field: MetadataField
isLast: boolean
onDelete: () => void
updateKey: (key: string) => void
updateValue: (value: string) => void
}
function Field({
field,
updateKey,
updateValue,
onDelete,
isLast,
}: FieldProps) {
const { t } = useTranslation()
/**
* value on the index of deleted field will be undefined,
* but we need to keep it to preserve list ordering
* so React could correctly render elements when adding/deleting
*/
if (field.isDeleted || field.isIgnored) {
return null
}
return (
<tr className="group divide-x [&:not(:last-child)]:border-b">
<td>
<Input
className="rounded-none border-none bg-transparent !shadow-none"
placeholder={t("fields.key")}
defaultValue={field.key}
onChange={(e) => {
updateKey(e.currentTarget.value)
}}
/>
</td>
<td className="relative">
<Input
className="rounded-none border-none bg-transparent pr-[40px] !shadow-none"
placeholder={t("fields.value")}
defaultValue={field.value}
onChange={(e) => {
updateValue(e.currentTarget.value)
}}
/>
{!isLast && (
<Button
size="small"
variant="transparent"
className="text-ui-fg-subtle invisible absolute right-0 top-0 h-[32px] w-[32px] p-0 hover:bg-transparent group-hover:visible"
type="button"
onClick={onDelete}
>
<Trash />
</Button>
)}
</td>
</tr>
)
}
export function Metadata({ form }: MetadataProps) {
const { t } = useTranslation()
const metadataWatch = form.watch("metadata") as MetadataField[]
const ignoredKeys = metadataWatch.filter((k) => k.isIgnored)
const addKeyPair = () => {
form.setValue(
`metadata.${metadataWatch.length ? metadataWatch.length : 0}`,
{ key: "", value: "" }
)
}
const onKeyChange = (index: number) => {
return (key: string) => {
form.setValue(`metadata.${index}.key`, key, { shouldDirty: true })
if (index === metadataWatch.length - 1) {
addKeyPair()
}
}
}
const onValueChange = (index: number) => {
return (value: any) => {
form.setValue(`metadata.${index}.value`, value, { shouldDirty: true })
if (index === metadataWatch.length - 1) {
addKeyPair()
}
}
}
const deleteKeyPair = (index: number) => {
return () => {
form.setValue(`metadata.${index}.isDeleted`, true, { shouldDirty: true })
}
}
return (
<div>
<Text weight="plus" size="small">
{t("fields.metadata")}
</Text>
<table className="shadow-elevation-card-rest mt-2 w-full table-fixed overflow-hidden rounded">
<thead>
<tr className="bg-ui-bg-field divide-x border-b">
<th>
<Text className="px-2 py-1 text-left" weight="plus" size="small">
{t("fields.key")}
</Text>
</th>
<th>
<Text className="px-2 py-1 text-left" weight="plus" size="small">
{t("fields.value")}
</Text>
</th>
</tr>
</thead>
<tbody>
{metadataWatch.map((field, index) => {
return (
<Field
key={index}
field={field}
updateKey={onKeyChange(index)}
updateValue={onValueChange(index)}
onDelete={deleteKeyPair(index)}
isLast={index === metadataWatch.length - 1}
/>
)
})}
</tbody>
</table>
{!!ignoredKeys.length && (
<Alert variant="warning" className="mt-2" dismissible>
{t("metadata.warnings.ignoredKeys", { keys: ignoredKeys.join(",") })}
</Alert>
)}
</div>
)
}