chore(ui): move toast to top right (#13092)

* chore(ui): display toast in top right corner

* fix: error as well

* refactor: feedback

* chore: move the rest of the modal controls to footer

---------

Co-authored-by: William Bouchard <46496014+willbouch@users.noreply.github.com>
This commit is contained in:
Frane Polić
2025-08-20 08:39:45 +02:00
committed by GitHub
parent 2f594291ad
commit 5b7a041246
10 changed files with 140 additions and 110 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/ui": patch
---
chore(ui): display toast in top right corner

View File

@@ -148,14 +148,6 @@ export const EditCategoryProductsForm = ({
{form.formState.errors.product_ids.message} {form.formState.errors.product_ids.message}
</Hint> </Hint>
)} )}
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</div> </div>
</RouteFocusModal.Header> </RouteFocusModal.Header>
<RouteFocusModal.Body className="size-full overflow-hidden"> <RouteFocusModal.Body className="size-full overflow-hidden">
@@ -178,6 +170,16 @@ export const EditCategoryProductsForm = ({
search="autofocus" search="autofocus"
/> />
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -157,14 +157,6 @@ export const AddProductsToCollectionForm = ({
{form.formState.errors.add && ( {form.formState.errors.add && (
<Hint variant="error">{form.formState.errors.add.message}</Hint> <Hint variant="error">{form.formState.errors.add.message}</Hint>
)} )}
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</div> </div>
</RouteFocusModal.Header> </RouteFocusModal.Header>
<RouteFocusModal.Body className="size-full overflow-hidden"> <RouteFocusModal.Body className="size-full overflow-hidden">
@@ -187,6 +179,16 @@ export const AddProductsToCollectionForm = ({
search="autofocus" search="autofocus"
/> />
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -46,25 +46,13 @@ export const CreateCollectionForm = () => {
return ( return (
<RouteFocusModal.Form form={form}> <RouteFocusModal.Form form={form}>
<KeyboundForm onSubmit={handleSubmit}> <KeyboundForm
<RouteFocusModal.Header> onSubmit={handleSubmit}
<div className="flex items-center justify-end gap-x-2"> className="flex h-full flex-col overflow-hidden"
<RouteFocusModal.Close asChild> >
<Button size="small" variant="secondary"> <RouteFocusModal.Header />
{t("actions.cancel")}
</Button> <RouteFocusModal.Body className="flex size-full flex-col items-center p-16">
</RouteFocusModal.Close>
<Button
size="small"
variant="primary"
type="submit"
isLoading={isPending}
>
{t("actions.create")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex flex-col items-center p-16">
<div className="flex w-full max-w-[720px] flex-col gap-y-8"> <div className="flex w-full max-w-[720px] flex-col gap-y-8">
<div> <div>
<Heading>{t("collections.createCollection")}</Heading> <Heading>{t("collections.createCollection")}</Heading>
@@ -111,6 +99,21 @@ export const CreateCollectionForm = () => {
</div> </div>
</div> </div>
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
size="small"
variant="primary"
type="submit"
isLoading={isPending}
>
{t("actions.create")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -137,19 +137,6 @@ export const AddCustomersForm = ({
{form.formState.errors.customer_ids.message} {form.formState.errors.customer_ids.message}
</Hint> </Hint>
)} )}
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
type="submit"
variant="primary"
size="small"
isLoading={isPending}
>
{t("actions.save")}
</Button>
</div> </div>
</RouteFocusModal.Header> </RouteFocusModal.Header>
<RouteFocusModal.Body className="size-full overflow-hidden"> <RouteFocusModal.Body className="size-full overflow-hidden">
@@ -176,6 +163,21 @@ export const AddCustomersForm = ({
}} }}
/> />
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
type="submit"
variant="primary"
size="small"
isLoading={isPending}
>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -44,12 +44,12 @@ export function OrderCreateShipmentForm({
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
const addedLabels = data.labels const addedLabels = data.labels
.filter((l) => !!l.tracking_number) .filter((l) => !!l.tracking_number)
.map((l) => ({ .map((l) => ({
tracking_number: l.tracking_number, tracking_number: l.tracking_number,
tracking_url: "#", tracking_url: "#",
label_url: "#", label_url: "#",
})) }))
await createShipment( await createShipment(
{ {
@@ -78,18 +78,8 @@ export function OrderCreateShipmentForm({
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden" className="flex h-full flex-col overflow-hidden"
> >
<RouteFocusModal.Header> <RouteFocusModal.Header />
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto"> <RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
<div className="flex size-full flex-col items-center overflow-auto p-16"> <div className="flex size-full flex-col items-center overflow-auto p-16">
<div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2"> <div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2">
@@ -166,6 +156,16 @@ export function OrderCreateShipmentForm({
</div> </div>
</div> </div>
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isMutating}>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -310,18 +310,7 @@ export function ManageVariantInventoryItemsForm({
return ( return (
<RouteFocusModal.Form form={form}> <RouteFocusModal.Form form={form}>
<KeyboundForm className="flex h-full flex-col" onSubmit={handleSubmit}> <KeyboundForm className="flex h-full flex-col" onSubmit={handleSubmit}>
<RouteFocusModal.Header> <RouteFocusModal.Header />
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isPending}>
{t("actions.save")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex justify-center overflow-auto"> <RouteFocusModal.Body className="flex justify-center overflow-auto">
<div className="flex w-full flex-col gap-y-8 px-6 pt-12 md:w-[720px] md:pt-24"> <div className="flex w-full flex-col gap-y-8 px-6 pt-12 md:w-[720px] md:pt-24">
<Heading> <Heading>
@@ -371,6 +360,16 @@ export function ManageVariantInventoryItemsForm({
</div> </div>
</div> </div>
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isPending}>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -129,18 +129,8 @@ export const AddCountriesForm = ({ region }: AddCountriesFormProps) => {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden" className="flex h-full flex-col overflow-hidden"
> >
<RouteFocusModal.Header> <RouteFocusModal.Header />
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" isLoading={isLoading} type="submit">
{t("actions.add")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="overflow-hidden"> <RouteFocusModal.Body className="overflow-hidden">
<_DataTable <_DataTable
table={table} table={table}
@@ -158,6 +148,16 @@ export const AddCountriesForm = ({ region }: AddCountriesFormProps) => {
prefix={PREFIX} prefix={PREFIX}
/> />
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" isLoading={isLoading} type="submit">
{t("actions.add")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -186,18 +186,7 @@ export const CreateRegionForm = ({
className="flex h-full flex-col overflow-hidden" className="flex h-full flex-col overflow-hidden"
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<RouteFocusModal.Header> <RouteFocusModal.Header />
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isPendingRegion}>
{t("actions.save")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex overflow-hidden"> <RouteFocusModal.Body className="flex overflow-hidden">
<div <div
className={clx( className={clx(
@@ -434,6 +423,16 @@ export const CreateRegionForm = ({
</div> </div>
</div> </div>
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button size="small" type="submit" isLoading={isPendingRegion}>
{t("actions.save")}
</Button>
</RouteFocusModal.Footer>
</KeyboundForm> </KeyboundForm>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )

View File

@@ -3,6 +3,8 @@ import { ToastAction, ToastVariant, ToasterPosition } from "@/types"
import * as React from "react" import * as React from "react"
import { ExternalToast, toast as toastFn } from "sonner" import { ExternalToast, toast as toastFn } from "sonner"
const DEFAULT_TOAST_POSITION = "top-right"
interface BaseToastProps { interface BaseToastProps {
id?: string | number id?: string | number
position?: ToasterPosition position?: ToasterPosition
@@ -16,7 +18,11 @@ interface ToastProps extends BaseToastProps {
action?: ToastAction action?: ToastAction
} }
function create(variant: ToastVariant, title: React.ReactNode, props: ToastProps = {}) { function create(
variant: ToastVariant,
title: React.ReactNode,
props: ToastProps = {}
) {
const external: ExternalToast = { const external: ExternalToast = {
position: props.position, position: props.position,
duration: props.duration, duration: props.duration,
@@ -50,13 +56,15 @@ function message(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: ToastProps = {} props: ToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("message", title, props) return create("message", title, props)
} }
function custom() { function custom() {
return create("message", "Custom",) return create("message", "Custom")
} }
interface VariantToastProps extends Omit<ToastProps, "icon"> {} interface VariantToastProps extends Omit<ToastProps, "icon"> {}
@@ -68,7 +76,9 @@ function info(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: VariantToastProps = {} props: VariantToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("info", title, props) return create("info", title, props)
} }
@@ -80,7 +90,9 @@ function error(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: VariantToastProps = {} props: VariantToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("error", title, props) return create("error", title, props)
} }
@@ -92,7 +104,9 @@ function success(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: VariantToastProps = { } props: VariantToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("success", title, props) return create("success", title, props)
} }
@@ -104,7 +118,9 @@ function warning(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: VariantToastProps = {} props: VariantToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("warning", title, props) return create("warning", title, props)
} }
@@ -116,7 +132,9 @@ function loading(
/** /**
* The props of the toast. * The props of the toast.
*/ */
props: VariantToastProps = {} props: VariantToastProps = {
position: DEFAULT_TOAST_POSITION,
}
) { ) {
return create("loading", title, { ...props, dismissable: false }) return create("loading", title, { ...props, dismissable: false })
} }