fix(dashboard): replace native select Element in CountrySelect & ProvinceSelect with Select(Medusa UI). (#13521)

* fix: replace CountrySelect fallback with Medusa Select

* Country Select Component Fix to use Medusa UI Select Component

* added Change set

* using the props supported by the Select

* value should be lowercased if passed from the pareant component

* fix province Select with medusa UI select and added change set

* bug fix the province with providing

---------

Co-authored-by: Aryan Patel <21cs038@charusat.edu.in>
Co-authored-by: William Bouchard <46496014+willbouch@users.noreply.github.com>
This commit is contained in:
Patel Aryan Saurabhkumar
2025-10-10 10:22:51 -04:00
committed by GitHub
co-authored by Aryan Patel William Bouchard
parent c54c5ed6de
commit a1c56d29d0
4 changed files with 85 additions and 126 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard):replace native select Element in CountrySelect & ProvinceSelect with Select(Medusa UI).
@@ -4,75 +4,50 @@ import {
useImperativeHandle,
useRef,
} from "react"
import { TrianglesMini } from "@medusajs/icons"
import { clx } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { countries } from "../../../lib/data/countries"
import { Select } from "@medusajs/ui"
export const CountrySelect = forwardRef<
HTMLSelectElement,
ComponentPropsWithoutRef<"select"> & {
HTMLButtonElement,
ComponentPropsWithoutRef<typeof Select> & {
placeholder?: string
value?: string
defaultValue?: string
onChange?: (value: string) => void
}
>(
(
{ className, disabled, placeholder, value, defaultValue, ...props },
ref
) => {
const { t } = useTranslation()
const innerRef = useRef<HTMLSelectElement>(null)
>(({ disabled, placeholder, defaultValue, onChange, ...field }, ref) => {
const { t } = useTranslation()
const innerRef = useRef<HTMLButtonElement>(null)
useImperativeHandle(ref, () => innerRef.current as HTMLSelectElement)
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement)
return (
<div className="relative">
<Select
{...field}
value={field.value ? field.value?.toLowerCase() : undefined}
onValueChange={onChange}
defaultValue={defaultValue ? defaultValue.toLowerCase() : undefined}
disabled={disabled}
>
<Select.Trigger ref={innerRef} className="w-full">
<Select.Value
placeholder={placeholder || t("fields.selectCountry")}
/>
</Select.Trigger>
<Select.Content>
{countries.map((country) => (
<Select.Item
key={country.iso_2}
value={country.iso_2.toLowerCase()}
>
{country.display_name}
</Select.Item>
))}
</Select.Content>
</Select>
</div>
)
})
const isPlaceholder = innerRef.current?.value === ""
return (
<div className="relative">
<TrianglesMini
className={clx(
"text-ui-fg-muted transition-fg pointer-events-none absolute end-2 top-1/2 -translate-y-1/2",
{
"text-ui-fg-disabled": disabled,
}
)}
/>
<select
value={value !== undefined ? value.toLowerCase() : undefined}
defaultValue={defaultValue ? defaultValue.toLowerCase() : undefined}
disabled={disabled}
className={clx(
"bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 outline-none",
"placeholder:text-ui-fg-muted text-ui-fg-base",
"hover:bg-ui-bg-field-hover",
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
"aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error",
"invalid::border-ui-border-error invalid:shadow-borders-error",
"disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled",
{
"text-ui-fg-muted": isPlaceholder,
},
className
)}
{...props}
ref={innerRef}
>
{/* Add an empty option so the first option is preselected */}
<option value="" disabled className="text-ui-fg-muted">
{placeholder || t("fields.selectCountry")}
</option>
{countries.map((country) => {
return (
<option key={country.iso_2} value={country.iso_2.toLowerCase()}>
{country.display_name}
</option>
)
})}
</select>
</div>
)
}
)
CountrySelect.displayName = "CountrySelect"
@@ -4,47 +4,36 @@ import {
useImperativeHandle,
useRef,
} from "react"
import { TrianglesMini } from "@medusajs/icons"
import { clx } from "@medusajs/ui"
import { Select } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { getCountryProvinceObjectByIso2 } from "../../../lib/data/country-states"
interface ProvinceSelectProps extends ComponentPropsWithoutRef<"select"> {
/**
* ISO 3166-1 alpha-2 country code
*/
country_code: string
/**
* Whether to use the ISO 3166-1 alpha-2 code or the name of the province as the value
*
* @default "iso_2"
*/
valueAs?: "iso_2" | "name"
placeholder?: string
}
export const ProvinceSelect = forwardRef<
HTMLSelectElement,
ProvinceSelectProps
HTMLButtonElement,
ComponentPropsWithoutRef<typeof Select> & {
placeholder?: string
defaultValue?: string
country_code: string
valueAs?: "iso_2" | "name"
onChange?: (value: string) => void
}
>(
(
{
className,
disabled,
placeholder,
defaultValue,
country_code,
valueAs = "iso_2",
...props
onChange,
...field
},
ref
) => {
const { t } = useTranslation()
const innerRef = useRef<HTMLSelectElement>(null)
const innerRef = useRef<HTMLButtonElement>(null)
useImperativeHandle(ref, () => innerRef.current as HTMLSelectElement)
const isPlaceholder = innerRef.current?.value === ""
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement)
const provinceObject = getCountryProvinceObjectByIso2(country_code)
@@ -55,9 +44,12 @@ export const ProvinceSelect = forwardRef<
const options = Object.entries(provinceObject?.options ?? {}).map(
([iso2, name]) => {
return (
<option key={iso2} value={valueAs === "iso_2" ? iso2 : name}>
<Select.Item
key={iso2}
value={valueAs === "iso_2" ? iso2.toLowerCase() : name}
>
{name}
</option>
</Select.Item>
)
}
)
@@ -66,46 +58,34 @@ export const ProvinceSelect = forwardRef<
? t(`taxRegions.fields.sublevels.placeholders.${provinceObject.type}`)
: ""
const placeholderOption = provinceObject ? (
<option value="" disabled className="text-ui-fg-muted">
{placeholder || placeholderText}
</option>
) : null
return (
<div className="relative">
<TrianglesMini
className={clx(
"text-ui-fg-muted transition-fg pointer-events-none absolute end-2 top-1/2 -translate-y-1/2",
{
"text-ui-fg-disabled": disabled,
}
)}
/>
<select
<Select
{...field}
value={
field.value
? valueAs === "iso_2"
? field.value.toLowerCase()
: field.value
: undefined
}
defaultValue={
defaultValue
? valueAs === "iso_2"
? defaultValue.toLowerCase()
: defaultValue
: undefined
}
onValueChange={onChange}
disabled={disabled}
className={clx(
"bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 outline-none",
"placeholder:text-ui-fg-muted text-ui-fg-base",
"hover:bg-ui-bg-field-hover",
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
"aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error",
"invalid::border-ui-border-error invalid:shadow-borders-error",
"disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled",
{
"text-ui-fg-muted": isPlaceholder,
},
className
)}
{...props}
ref={innerRef}
>
{/* Add an empty option so the first option is preselected */}
{placeholderOption}
{options}
</select>
<Select.Trigger ref={innerRef} className="w-full">
<Select.Value placeholder={placeholder || placeholderText} />
</Select.Trigger>
<Select.Content>{options}</Select.Content>
</Select>
</div>
)
}
)
ProvinceSelect.displayName = "CountrySelect"
ProvinceSelect.displayName = "ProvinceSelect"
@@ -4,12 +4,11 @@ import { Button, Input, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
import { Form } from "../../../../../components/common/form"
import { CountrySelect } from "../../../../../components/inputs/country-select"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useUpdateStockLocation } from "../../../../../hooks/api/stock-locations"
import { CountrySelect } from "../../../../../components/inputs/country-select/country-select"
type EditLocationFormProps = {
location: HttpTypes.AdminStockLocation