feat(dashboard,ui): Streamline spacing and sizing (#6061)

This commit is contained in:
Kasper Fabricius Kristensen
2024-01-15 11:43:16 +01:00
committed by GitHub
parent 5dacd4ac9f
commit a2c149e7e5
266 changed files with 10738 additions and 4646 deletions

View File

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

View File

@@ -0,0 +1,378 @@
import {
ArrowRightOnRectangle,
BellAlert,
BookOpen,
Calendar,
CircleHalfSolid,
CogSixTooth,
MagnifyingGlass,
Sidebar,
User as UserIcon,
} from "@medusajs/icons"
import { Avatar, DropdownMenu, IconButton, Kbd, Text, clx } from "@medusajs/ui"
import * as Dialog from "@radix-ui/react-dialog"
import { useAdminDeleteSession, useAdminGetSession } from "medusa-react"
import { PropsWithChildren } from "react"
import {
Link,
Outlet,
UIMatch,
useLocation,
useMatches,
useNavigate,
} from "react-router-dom"
import { Skeleton } from "../../common/skeleton"
import { useSearch } from "../../../providers/search-provider"
import { useSidebar } from "../../../providers/sidebar-provider"
import { useTheme } from "../../../providers/theme-provider"
export const Shell = ({ children }: PropsWithChildren) => {
return (
<div className="flex h-screen flex-col items-start overflow-hidden lg:flex-row">
<div>
<MobileSidebarContainer>{children}</MobileSidebarContainer>
<DesktopSidebarContainer>{children}</DesktopSidebarContainer>
</div>
<div className="flex flex-col h-screen w-full">
<Topbar />
<div className="flex h-full w-full flex-col items-center overflow-y-auto">
<Gutter>
<Outlet />
</Gutter>
</div>
</div>
</div>
)
}
const Gutter = ({ children }: PropsWithChildren) => {
return (
<div className="flex w-full max-w-[1600px] flex-col gap-y-2 p-3">
{children}
</div>
)
}
const Breadcrumbs = () => {
const matches = useMatches() as unknown as UIMatch<
unknown,
{ crumb?: (data?: unknown) => string }
>[]
const crumbs = matches
.filter((match) => Boolean(match.handle?.crumb))
.map((match) => {
const handle = match.handle
return {
label: handle.crumb!(match.data),
path: match.pathname,
}
})
return (
<ol className={clx("text-ui-fg-muted flex items-center select-none")}>
{crumbs.map((crumb, index) => {
const isLast = index === crumbs.length - 1
return (
<li
key={index}
className={clx("txt-compact-small-plus flex items-center", {
"text-ui-fg-subtle": isLast,
})}
>
{!isLast ? (
<Link
className="transition-fg hover:text-ui-fg-subtle"
to={crumb.path}
>
{crumb.label}
</Link>
) : (
<div>
<span className="block md:hidden">...</span>
<span key={index} className="hidden md:block">
{crumb.label}
</span>
</div>
)}
{/* {!isLast && <TriangleRightMini className="-mt-0.5 mx-2" />} */}
{!isLast && <span className="-mt-0.5 mx-2"></span>}
</li>
)
})}
</ol>
)
}
const UserBadge = () => {
const { user, isError, error } = useAdminGetSession()
const displayName = user
? user.first_name && user.last_name
? `${user.first_name} ${user.last_name}`
: user.first_name
? user.first_name
: user.email
: null
const fallback = displayName ? displayName[0].toUpperCase() : null
if (isError) {
throw error
}
return (
<DropdownMenu.Trigger asChild>
<button
disabled={!user}
className={clx(
"shadow-borders-base flex max-w-[192px] items-center gap-x-2 overflow-hidden text-ellipsis whitespace-nowrap rounded-full py-1 pl-1 pr-2.5 select-none"
)}
>
{fallback ? (
<Avatar size="xsmall" fallback={fallback} />
) : (
<Skeleton className="w-5 h-5 rounded-full" />
)}
{displayName ? (
<Text
size="xsmall"
weight="plus"
leading="compact"
className="truncate"
>
{displayName}
</Text>
) : (
<Skeleton className="w-[70px] h-[9px]" />
)}
</button>
</DropdownMenu.Trigger>
)
}
const ThemeToggle = () => {
const { theme, setTheme } = useTheme()
return (
<DropdownMenu.SubMenu>
<DropdownMenu.SubMenuTrigger className="rounded-md">
<CircleHalfSolid className="text-ui-fg-subtle mr-2" />
Theme
</DropdownMenu.SubMenuTrigger>
<DropdownMenu.SubMenuContent>
<DropdownMenu.RadioGroup value={theme}>
<DropdownMenu.RadioItem
value="light"
onClick={(e) => {
e.preventDefault()
setTheme("light")
}}
>
Light
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem
value="dark"
onClick={(e) => {
e.preventDefault()
setTheme("dark")
}}
>
Dark
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.SubMenuContent>
</DropdownMenu.SubMenu>
)
}
const Logout = () => {
const navigate = useNavigate()
const { mutateAsync: logoutMutation } = useAdminDeleteSession()
const handleLayout = async () => {
await logoutMutation(undefined, {
onSuccess: () => {
navigate("/login")
},
})
}
return (
<DropdownMenu.Item onClick={handleLayout}>
<div className="flex items-center gap-x-2">
<ArrowRightOnRectangle className="text-ui-fg-subtle" />
<span>Logout</span>
</div>
</DropdownMenu.Item>
)
}
const Profile = () => {
const location = useLocation()
return (
<Link to="/settings/profile" state={{ from: location.pathname }}>
<DropdownMenu.Item>
<UserIcon className="text-ui-fg-subtle mr-2" />
Profile
</DropdownMenu.Item>
</Link>
)
}
const LoggedInUser = () => {
return (
<DropdownMenu>
<UserBadge />
<DropdownMenu.Content align="center">
<Profile />
<DropdownMenu.Separator />
<Link to="https://docs.medusajs.com/user-guide" target="_blank">
<DropdownMenu.Item>
<BookOpen className="text-ui-fg-subtle mr-2" />
Documentation
</DropdownMenu.Item>
</Link>
<Link to="https://medusajs.com/changelog/" target="_blank">
<DropdownMenu.Item>
<Calendar className="text-ui-fg-subtle mr-2" />
Changelog
</DropdownMenu.Item>
</Link>
<DropdownMenu.Separator />
<ThemeToggle />
<DropdownMenu.Separator />
<Logout />
</DropdownMenu.Content>
</DropdownMenu>
)
}
const SettingsLink = () => {
const location = useLocation()
return (
<Link
to="/settings"
className="flex items-center justify-center"
state={{ from: location.pathname }}
>
<IconButton
size="small"
variant="transparent"
className="text-ui-fg-muted transition-fg hover:text-ui-fg-subtle"
>
<CogSixTooth />
</IconButton>
</Link>
)
}
const ToggleNotifications = () => {
return (
<IconButton
size="small"
variant="transparent"
className="text-ui-fg-muted transition-fg hover:text-ui-fg-subtle"
>
<BellAlert />
</IconButton>
)
}
const Searchbar = () => {
const { toggleSearch } = useSearch()
return (
<button
onClick={toggleSearch}
className="shadow-borders-base bg-ui-bg-subtle hover:bg-ui-bg-subtle-hover transition-fg focus-visible:shadow-borders-focus text-ui-fg-muted flex w-full max-w-[280px] items-center gap-x-2 rounded-full py-1.5 pl-2 pr-1.5 outline-none select-none"
>
<MagnifyingGlass />
<div className="flex-1 text-left">
<Text size="small" leading="compact">
Jump to or search
</Text>
</div>
<Kbd className="rounded-full">K</Kbd>
</button>
)
}
const ToggleSidebar = () => {
const { toggle } = useSidebar()
return (
<div>
<IconButton
className="hidden lg:flex"
variant="transparent"
onClick={() => toggle("desktop")}
>
<Sidebar className="text-ui-fg-muted" />
</IconButton>
<IconButton
className="hidden max-lg:flex"
variant="transparent"
onClick={() => toggle("mobile")}
>
<Sidebar className="text-ui-fg-muted" />
</IconButton>
</div>
)
}
const Topbar = () => {
return (
<div className="w-full grid-cols-3 border-b p-3 grid">
<div className="flex items-center gap-x-1.5">
<ToggleSidebar />
<Breadcrumbs />
</div>
<div className="flex items-center justify-center">
<Searchbar />
</div>
<div className="flex items-center justify-end gap-x-3">
<div className="text-ui-fg-muted flex items-center gap-x-1">
<ToggleNotifications />
<SettingsLink />
</div>
<LoggedInUser />
</div>
</div>
)
}
const DesktopSidebarContainer = ({ children }: PropsWithChildren) => {
const { desktop } = useSidebar()
return (
<div
className={clx("hidden h-screen w-[220px] border-r", {
"lg:flex": desktop,
})}
>
{children}
</div>
)
}
const MobileSidebarContainer = ({ children }: PropsWithChildren) => {
const { mobile, toggle } = useSidebar()
return (
<Dialog.Root open={mobile} onOpenChange={() => toggle("mobile")}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-ui-bg-overlay" />
<Dialog.Content className="h-screen fixed left-0 inset-y-0 w-[220px] border-r bg-ui-bg-subtle">
{children}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}