feat(dashboard): support RTL in dashboard (#11252)

* fix: add direction attribute to components and adjust styles for RTL support

* fix(data-grid): comment it out

* Added useDocumentDirection hook

* refactor: Integrate useDocumentDirection hook

* refactor: Standardize direction prop usage across components

* resolve

* fix: resolve build errors

* fix : remove unused useDocument

* Apply RTL styles for some components

* Create smooth-gorillas-hide.md

* refactor: update some styles for RTL support

---------

Co-authored-by: William Bouchard <46496014+willbouch@users.noreply.github.com>
This commit is contained in:
Ayman Mustafa
2025-09-23 16:11:30 +01:00
committed by GitHub
parent a501364b2d
commit a75cf7fb36
70 changed files with 407 additions and 142 deletions
@@ -29,6 +29,7 @@ import { queryClient } from "../../../lib/query-client"
import { useExtension } from "../../../providers/extension-provider"
import { useSearch } from "../../../providers/search-provider"
import { UserMenu } from "../user-menu"
import { useDocumentDirection } from "../../../hooks/use-document-direction"
export const MainLayout = () => {
return (
@@ -94,7 +95,7 @@ const Logout = () => {
const Header = () => {
const { t } = useTranslation()
const { store, isPending, isError, error } = useStore()
const direction = useDocumentDirection()
const name = store?.name
const fallback = store?.name?.slice(0, 1).toUpperCase()
@@ -106,11 +107,12 @@ const Header = () => {
return (
<div className="w-full p-3">
<DropdownMenu>
<DropdownMenu
dir={direction}>
<DropdownMenu.Trigger
disabled={!isLoaded}
className={clx(
"bg-ui-bg-subtle transition-fg grid w-full grid-cols-[24px_1fr_15px] items-center gap-x-3 rounded-md p-0.5 pr-2 outline-none",
"bg-ui-bg-subtle transition-fg grid w-full grid-cols-[24px_1fr_15px] items-center gap-x-3 rounded-md p-0.5 pe-2 outline-none",
"hover:bg-ui-bg-subtle-hover",
"data-[state=open]:bg-ui-bg-subtle-hover",
"focus-visible:shadow-borders-focus"
@@ -121,7 +123,7 @@ const Header = () => {
) : (
<Skeleton className="h-6 w-6 rounded-md" />
)}
<div className="block overflow-hidden text-left">
<div className="block overflow-hidden text-start">
{name ? (
<Text
size="small"
@@ -267,7 +269,7 @@ const Searchbar = () => {
)}
>
<MagnifyingGlass />
<div className="flex-1 text-left">
<div className="flex-1 text-start">
<Text size="small" leading="compact" weight="plus">
{t("app.search.label")}
</Text>
@@ -156,7 +156,7 @@ const Breadcrumbs = () => {
)}
{!isLast && (
<span className="mx-2">
<TriangleRightMini />
<TriangleRightMini className="rtl:rotate-180" />
</span>
)}
</li>
@@ -177,7 +177,7 @@ const ToggleSidebar = () => {
onClick={() => toggle("desktop")}
size="small"
>
<SidebarLeft className="text-ui-fg-muted" />
<SidebarLeft className="text-ui-fg-muted rtl:rotate-180" />
</IconButton>
<IconButton
className="hidden max-lg:flex"
@@ -185,7 +185,7 @@ const ToggleSidebar = () => {
onClick={() => toggle("mobile")}
size="small"
>
<SidebarLeft className="text-ui-fg-muted" />
<SidebarLeft className="text-ui-fg-muted rtl:rotate-180" />
</IconButton>
</div>
)
@@ -210,7 +210,7 @@ const DesktopSidebarContainer = ({ children }: PropsWithChildren) => {
return (
<div
className={clx("hidden h-screen w-[220px] border-r", {
className={clx("hidden h-screen w-[220px] border-e", {
"lg:flex": desktop,
})}
>
@@ -234,8 +234,8 @@ const MobileSidebarContainer = ({ children }: PropsWithChildren) => {
/>
<RadixDialog.Content
className={clx(
"bg-ui-bg-subtle shadow-elevation-modal fixed inset-y-2 left-2 flex w-full max-w-[304px] flex-col overflow-hidden rounded-lg border-r",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2 duration-200"
"bg-ui-bg-subtle shadow-elevation-modal fixed inset-y-2 start-2 flex w-full max-w-[304px] flex-col overflow-hidden rounded-lg border-r",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-start-1/2 data-[state=open]:slide-in-from-start-1/2 duration-200"
)}
>
<div className="p-3">
@@ -29,10 +29,12 @@ import { useLogout, useMe } from "../../../hooks/api"
import { queryClient } from "../../../lib/query-client"
import { useGlobalShortcuts } from "../../../providers/keybind-provider/hooks"
import { useTheme } from "../../../providers/theme-provider"
import { useDocumentDirection } from "../../../hooks/use-document-direction"
export const UserMenu = () => {
const { t } = useTranslation()
const location = useLocation()
const direction = useDocumentDirection()
const [openMenu, setOpenMenu] = useState(false)
const [openModal, setOpenModal] = useState(false)
@@ -44,33 +46,33 @@ export const UserMenu = () => {
return (
<div>
<DropdownMenu open={openMenu} onOpenChange={setOpenMenu}>
<DropdownMenu dir={direction} open={openMenu} onOpenChange={setOpenMenu}>
<UserBadge />
<DropdownMenu.Content className="min-w-[var(--radix-dropdown-menu-trigger-width)] max-w-[var(--radix-dropdown-menu-trigger-width)]">
<UserItem />
<DropdownMenu.Separator />
<DropdownMenu.Item asChild>
<Link to="/settings/profile" state={{ from: location.pathname }}>
<UserIcon className="text-ui-fg-subtle mr-2" />
<UserIcon className="text-ui-fg-subtle me-2" />
{t("app.menus.user.profileSettings")}
</Link>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item asChild>
<Link to="https://docs.medusajs.com" target="_blank">
<BookOpen className="text-ui-fg-subtle mr-2" />
<BookOpen className="text-ui-fg-subtle me-2" />
{t("app.menus.user.documentation")}
</Link>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<Link to="https://medusajs.com/changelog/" target="_blank">
<TimelineVertical className="text-ui-fg-subtle mr-2" />
<TimelineVertical className="text-ui-fg-subtle me-2" />
{t("app.menus.user.changelog")}
</Link>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onClick={toggleModal}>
<Keyboard className="text-ui-fg-subtle mr-2" />
<Keyboard className="text-ui-fg-subtle me-2" />
{t("app.menus.user.shortcuts")}
</DropdownMenu.Item>
<ThemeToggle />
@@ -93,7 +95,7 @@ const UserBadge = () => {
if (isPending) {
return (
<button className="shadow-borders-base flex max-w-[192px] select-none items-center gap-x-2 overflow-hidden text-ellipsis whitespace-nowrap rounded-full py-1 pl-1 pr-2.5">
<button className="shadow-borders-base flex max-w-[192px] select-none items-center gap-x-2 overflow-hidden text-ellipsis whitespace-nowrap rounded-full py-1 ps-1 pe-2.5">
<Skeleton className="h-5 w-5 rounded-full" />
<Skeleton className="h-[9px] w-[70px]" />
</button>
@@ -109,7 +111,7 @@ const UserBadge = () => {
<DropdownMenu.Trigger
disabled={!user}
className={clx(
"bg-ui-bg-subtle grid w-full cursor-pointer grid-cols-[24px_1fr_15px] items-center gap-2 rounded-md py-1 pl-0.5 pr-2 outline-none",
"bg-ui-bg-subtle grid w-full cursor-pointer grid-cols-[24px_1fr_15px] items-center gap-2 rounded-md py-1 ps-0.5 pe-2 outline-none",
"hover:bg-ui-bg-subtle-hover",
"data-[state=open]:bg-ui-bg-subtle-hover",
"focus-visible:shadow-borders-focus"
@@ -148,9 +150,9 @@ const ThemeToggle = () => {
return (
<DropdownMenu.SubMenu>
<DropdownMenu.SubMenuTrigger className="rounded-md">
<CircleHalfSolid className="text-ui-fg-subtle mr-2" />
{t("app.menus.user.theme.label")}
<DropdownMenu.SubMenuTrigger dir="ltr" className="rounded-md rtl:rotate-180">
<CircleHalfSolid className="text-ui-fg-subtle me-2" />
<span className="rtl:rotate-180">{t("app.menus.user.theme.label")}</span>
</DropdownMenu.SubMenuTrigger>
<DropdownMenu.SubMenuContent>
<DropdownMenu.RadioGroup value={theme}>