feat: extend mobile layout support
This commit is contained in:
@@ -61,6 +61,10 @@ export default function Dashboard({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeSideNav = () => {
|
||||||
|
setIsSideNavOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleRightPanel = () => {
|
const toggleRightPanel = () => {
|
||||||
setIsRightPanelOpen(!isRightPanelOpen);
|
setIsRightPanelOpen(!isRightPanelOpen);
|
||||||
if (!isRightPanelOpen) {
|
if (!isRightPanelOpen) {
|
||||||
@@ -98,9 +102,9 @@ export default function Dashboard({
|
|||||||
background-color: ${!isDarkMode ? "hsl(0deg 0% 97.3%)" : "#1c1c1c"};
|
background-color: ${!isDarkMode ? "hsl(0deg 0% 97.3%)" : "#1c1c1c"};
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<div className="relative flex h-screen flex-col bg-light-100 px-3 py-3 dark:bg-dark-100">
|
<div className="relative flex h-screen flex-col bg-light-50 dark:bg-dark-50 md:bg-light-100 md:p-3 md:dark:bg-dark-100">
|
||||||
{/* Mobile Navigation Controls - positioned absolutely over content */}
|
{/* Mobile Header */}
|
||||||
<div className="absolute left-8 top-8 z-50 flex items-center gap-2 md:hidden">
|
<div className="flex h-12 items-center justify-between border-b border-light-300 bg-light-50 px-3 dark:border-dark-300 dark:bg-dark-50 md:hidden">
|
||||||
<button
|
<button
|
||||||
ref={sideNavButtonRef}
|
ref={sideNavButtonRef}
|
||||||
onClick={toggleSideNav}
|
onClick={toggleSideNav}
|
||||||
@@ -118,10 +122,8 @@ export default function Dashboard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* {hasRightPanel && (
|
{hasRightPanel && (
|
||||||
<div className="absolute right-8 top-8 z-50 md:hidden">
|
|
||||||
<button
|
<button
|
||||||
ref={rightPanelButtonRef}
|
ref={rightPanelButtonRef}
|
||||||
onClick={toggleRightPanel}
|
onClick={toggleRightPanel}
|
||||||
@@ -139,21 +141,22 @@ export default function Dashboard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
)} */}
|
</div>
|
||||||
|
|
||||||
<div className="flex h-[calc(100dvh-1.5rem)] min-h-0 w-full">
|
<div className="flex h-[calc(100dvh-4.5rem)] min-h-0 w-full md:h-[calc(100dvh-1.5rem)]">
|
||||||
<div
|
<div
|
||||||
ref={sideNavRef}
|
ref={sideNavRef}
|
||||||
className={`fixed left-3 top-3 z-40 h-[calc(100dvh-1.5rem)] transform transition-transform duration-300 ease-in-out md:relative md:left-0 md:top-0 md:h-full md:translate-x-0 ${isSideNavOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"} `}
|
className={`fixed top-12 z-40 h-[calc(100dvh-3rem)] w-[calc(100vw-1.5rem)] transform transition-transform duration-300 ease-in-out md:relative md:top-0 md:h-full md:w-auto md:translate-x-0 ${isSideNavOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"} `}
|
||||||
>
|
>
|
||||||
<SideNavigation
|
<SideNavigation
|
||||||
user={{ email: session?.user.email, image: session?.user.image }}
|
user={{ email: session?.user.email, image: session?.user.image }}
|
||||||
isLoading={sessionLoading}
|
isLoading={sessionLoading}
|
||||||
|
onCloseSideNav={closeSideNav}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative h-full min-h-0 w-full overflow-hidden rounded-lg border border-light-300 bg-light-50 dark:border-dark-300 dark:bg-dark-50">
|
<div className="relative h-full min-h-0 w-full overflow-hidden md:rounded-lg md:border md:border-light-300 md:bg-light-50 md:dark:border-dark-300 md:dark:bg-dark-50">
|
||||||
<div className="relative flex h-full min-h-0 w-full overflow-hidden">
|
<div className="relative flex h-full min-h-0 w-full overflow-hidden">
|
||||||
<div className="h-full w-full overflow-y-auto">{children}</div>
|
<div className="h-full w-full overflow-y-auto">{children}</div>
|
||||||
|
|
||||||
@@ -161,13 +164,11 @@ export default function Dashboard({
|
|||||||
{hasRightPanel && rightPanel && (
|
{hasRightPanel && rightPanel && (
|
||||||
<div
|
<div
|
||||||
ref={rightPanelRef}
|
ref={rightPanelRef}
|
||||||
className={`fixed right-3 top-3 z-40 h-[calc(100dvh-1.5rem)] w-80 transform bg-light-200 transition-transform duration-300 ease-in-out dark:bg-dark-100 md:hidden ${
|
className={`fixed right-0 top-12 z-40 h-[calc(100dvh-3rem)] w-80 transform border-l border-light-300 bg-light-200 transition-transform duration-300 ease-in-out dark:border-dark-300 dark:bg-dark-100 md:hidden ${
|
||||||
isRightPanelOpen ? "translate-x-0" : "translate-x-full"
|
isRightPanelOpen ? "translate-x-0" : "translate-x-full"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="h-full overflow-y-auto rounded-md border dark:border-dark-200">
|
<div className="h-full">{rightPanel}</div>
|
||||||
{rightPanel}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import LottieIcon from "~/components/LottieIcon";
|
import LottieIcon from "~/components/LottieIcon";
|
||||||
|
import { useIsMobile } from "~/hooks/useMediaQuery";
|
||||||
|
|
||||||
function classNames(...classes: string[]): string {
|
function classNames(...classes: string[]): string {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
@@ -13,30 +15,41 @@ const Button: React.FC<{
|
|||||||
name: string;
|
name: string;
|
||||||
json: object;
|
json: object;
|
||||||
isCollapsed?: boolean;
|
isCollapsed?: boolean;
|
||||||
}> = ({ href, current, name, json, isCollapsed = false }) => {
|
onCloseSideNav?: () => void;
|
||||||
|
}> = ({ href, current, name, json, isCollapsed = false, onCloseSideNav }) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [index, setIndex] = useState(0);
|
const [index, setIndex] = useState(0);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
setIndex((index) => index + 1);
|
setIndex((index) => index + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (onCloseSideNav && isMobile) {
|
||||||
|
onCloseSideNav();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onClick={handleClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"group flex h-[34px] items-center rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 hover:bg-light-200 dark:hover:bg-dark-200 dark:hover:text-dark-1000",
|
"group flex h-[34px] items-center rounded-md p-1.5 text-sm font-normal leading-6 text-neutral-900 hover:bg-light-200 dark:hover:bg-dark-200 dark:hover:text-dark-1000",
|
||||||
current
|
current
|
||||||
? "bg-light-200 dark:bg-dark-200 dark:text-dark-1000"
|
? "bg-light-200 dark:bg-dark-200 dark:text-dark-1000"
|
||||||
: "dark:bg-dark-100 dark:text-dark-900",
|
: "dark:bg-dark-100 dark:text-dark-900",
|
||||||
isCollapsed ? "justify-center" : "gap-x-3",
|
isCollapsed
|
||||||
|
? "justify-start gap-x-3 md:justify-center md:gap-x-0"
|
||||||
|
: "gap-x-3",
|
||||||
)}
|
)}
|
||||||
title={isCollapsed ? name : undefined}
|
title={isCollapsed ? name : undefined}
|
||||||
>
|
>
|
||||||
<LottieIcon index={index} json={json} isPlaying={isHovered} />
|
<LottieIcon index={index} json={json} isPlaying={isHovered} />
|
||||||
{!isCollapsed && name}
|
<span className={twMerge(isCollapsed && "md:hidden")}>{name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { useTheme } from "~/providers/theme";
|
|||||||
interface SideNavigationProps {
|
interface SideNavigationProps {
|
||||||
user: UserType;
|
user: UserType;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
onCloseSideNav?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserType {
|
interface UserType {
|
||||||
@@ -33,6 +34,7 @@ interface UserType {
|
|||||||
export default function SideNavigation({
|
export default function SideNavigation({
|
||||||
user,
|
user,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
onCloseSideNav,
|
||||||
}: SideNavigationProps) {
|
}: SideNavigationProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
@@ -87,12 +89,12 @@ export default function SideNavigation({
|
|||||||
<>
|
<>
|
||||||
<nav
|
<nav
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex h-full flex-col justify-between bg-light-100 pr-3 dark:bg-dark-100",
|
"flex h-full w-64 flex-col justify-between border-r border-light-300 bg-light-100 p-3 dark:border-dark-300 dark:bg-dark-100 md:py-0 md:pl-0",
|
||||||
isCollapsed ? "w-auto" : "w-64",
|
isCollapsed && "md:w-auto",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex h-14 items-center justify-between pb-[18px] pt-1.5">
|
<div className="hidden h-14 items-center justify-between pb-[18px] pt-1.5 md:flex">
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<Link href="/" className="block">
|
<Link href="/" className="block">
|
||||||
<h1 className="pl-2 text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000">
|
<h1 className="pl-2 text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000">
|
||||||
@@ -120,7 +122,7 @@ export default function SideNavigation({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-1 mb-4 w-auto border-b border-light-300 dark:border-dark-400" />
|
<div className="mx-1 mb-4 hidden w-auto border-b border-light-300 dark:border-dark-400 md:block" />
|
||||||
|
|
||||||
<WorkspaceMenu isCollapsed={isCollapsed} />
|
<WorkspaceMenu isCollapsed={isCollapsed} />
|
||||||
<ul role="list" className="space-y-1">
|
<ul role="list" className="space-y-1">
|
||||||
@@ -132,6 +134,7 @@ export default function SideNavigation({
|
|||||||
name={item.name}
|
name={item.name}
|
||||||
json={item.icon}
|
json={item.icon}
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
|
onCloseSideNav={onCloseSideNav}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -144,6 +147,7 @@ export default function SideNavigation({
|
|||||||
imageUrl={user.image ?? undefined}
|
imageUrl={user.image ?? undefined}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
|
onCloseSideNav={onCloseSideNav}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import { useRouter } from "next/navigation";
|
|||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { authClient } from "@kan/auth/client";
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
|
import { useIsMobile } from "~/hooks/useMediaQuery";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "~/providers/theme";
|
||||||
import { getAvatarUrl } from "~/utils/helpers";
|
import { getAvatarUrl } from "~/utils/helpers";
|
||||||
@@ -16,10 +18,7 @@ interface UserMenuProps {
|
|||||||
email: string;
|
email: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isCollapsed?: boolean;
|
isCollapsed?: boolean;
|
||||||
}
|
onCloseSideNav?: () => void;
|
||||||
|
|
||||||
function classNames(...classes: string[]): string {
|
|
||||||
return classes.filter(Boolean).join(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserMenu({
|
export default function UserMenu({
|
||||||
@@ -27,27 +26,48 @@ export default function UserMenu({
|
|||||||
email,
|
email,
|
||||||
isLoading,
|
isLoading,
|
||||||
isCollapsed = false,
|
isCollapsed = false,
|
||||||
|
onCloseSideNav,
|
||||||
}: UserMenuProps) {
|
}: UserMenuProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { themePreference, switchTheme } = useTheme();
|
const { themePreference, switchTheme } = useTheme();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
|
if (onCloseSideNav && isMobile) {
|
||||||
|
onCloseSideNav();
|
||||||
|
}
|
||||||
await authClient.signOut();
|
await authClient.signOut();
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
if (onCloseSideNav && isMobile) {
|
||||||
|
onCloseSideNav();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOpen = (modalType: string) => {
|
||||||
|
if (onCloseSideNav && isMobile) {
|
||||||
|
onCloseSideNav();
|
||||||
|
}
|
||||||
|
openModal(modalType);
|
||||||
|
};
|
||||||
|
|
||||||
const avatarUrl = imageUrl ? getAvatarUrl(imageUrl) : null;
|
const avatarUrl = imageUrl ? getAvatarUrl(imageUrl) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative inline-block w-full text-left">
|
<Menu as="div" className="relative inline-block w-full text-left">
|
||||||
<div>
|
<div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className={classNames(isCollapsed ? "" : "flex")}>
|
<div className={twMerge(!isCollapsed && "flex")}>
|
||||||
<div className="h-[30px] w-[30px] animate-pulse rounded-full bg-light-200 dark:bg-dark-200" />
|
<div className="h-[30px] w-[30px] animate-pulse rounded-full bg-light-200 dark:bg-dark-200" />
|
||||||
{!isCollapsed && (
|
<div
|
||||||
<div className="mx-2 h-[30px] w-[175px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
|
className={twMerge(
|
||||||
)}
|
"mx-2 h-[30px] w-[175px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200",
|
||||||
|
isCollapsed && "md:hidden",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
@@ -73,9 +93,14 @@ export default function UserMenu({
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!isCollapsed && (
|
<span
|
||||||
<span className="mx-2 truncate text-sm">{email}</span>
|
className={twMerge(
|
||||||
)}
|
"mx-2 truncate text-sm",
|
||||||
|
isCollapsed && "md:hidden",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{email}
|
||||||
|
</span>
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +115,7 @@ export default function UserMenu({
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items
|
<Menu.Items
|
||||||
className={classNames(
|
className={twMerge(
|
||||||
"absolute bottom-[40px] z-10 mt-2 origin-top-left rounded-md border border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300",
|
"absolute bottom-[40px] z-10 mt-2 origin-top-left rounded-md border border-light-600 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-600 dark:bg-dark-300",
|
||||||
isCollapsed ? "left-0 w-48" : "left-0 w-full",
|
isCollapsed ? "left-0 w-48" : "left-0 w-full",
|
||||||
)}
|
)}
|
||||||
@@ -106,7 +131,7 @@ export default function UserMenu({
|
|||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={twMerge(
|
||||||
themePreference === "system" ? "visible" : "invisible",
|
themePreference === "system" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
@@ -120,7 +145,7 @@ export default function UserMenu({
|
|||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={twMerge(
|
||||||
themePreference === "dark" ? "visible" : "invisible",
|
themePreference === "dark" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
@@ -134,7 +159,7 @@ export default function UserMenu({
|
|||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={twMerge(
|
||||||
themePreference === "light" ? "visible" : "invisible",
|
themePreference === "light" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
@@ -149,6 +174,7 @@ export default function UserMenu({
|
|||||||
href="mailto:support@kan.bn"
|
href="mailto:support@kan.bn"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
onClick={handleLinkClick}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
{t`Support`}
|
{t`Support`}
|
||||||
@@ -159,6 +185,7 @@ export default function UserMenu({
|
|||||||
href="https://docs.kan.bn"
|
href="https://docs.kan.bn"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
onClick={handleLinkClick}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
{t`Documentation`}
|
{t`Documentation`}
|
||||||
@@ -166,7 +193,7 @@ export default function UserMenu({
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => openModal("NEW_FEEDBACK")}
|
onClick={() => handleModalOpen("NEW_FEEDBACK")}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
{t`Feedback`}
|
{t`Feedback`}
|
||||||
|
|||||||
@@ -20,17 +20,20 @@ export default function WorkspaceMenu({
|
|||||||
<Menu as="div" className="relative inline-block w-full pb-3 text-left">
|
<Menu as="div" className="relative inline-block w-full pb-3 text-left">
|
||||||
<div>
|
<div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className={twMerge("mb-1", isCollapsed && "flex p-1.5")}>
|
<div className={twMerge("mb-1", isCollapsed && "md:flex md:p-1.5")}>
|
||||||
<div className="h-6 w-6 animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
|
<div className="h-6 w-6 animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
|
||||||
{!isCollapsed && (
|
<div
|
||||||
<div className="ml-2 h-6 w-[150px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200" />
|
className={twMerge(
|
||||||
)}
|
"ml-2 h-6 w-[150px] animate-pulse rounded-md bg-light-200 dark:bg-dark-200",
|
||||||
|
isCollapsed && "hidden md:block",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Menu.Button
|
<Menu.Button
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"mb-1 flex h-[34px] w-full items-center rounded-md p-1.5 hover:bg-light-200 dark:hover:bg-dark-200",
|
"mb-1 flex h-[34px] w-full items-center rounded-md p-1.5 hover:bg-light-200 dark:hover:bg-dark-200",
|
||||||
isCollapsed && "mb-1.5 h-9 p-1",
|
isCollapsed && "md:mb-1.5 md:h-9 md:p-1",
|
||||||
)}
|
)}
|
||||||
title={isCollapsed ? workspace.name : undefined}
|
title={isCollapsed ? workspace.name : undefined}
|
||||||
>
|
>
|
||||||
@@ -39,17 +42,23 @@ export default function WorkspaceMenu({
|
|||||||
{workspace.name.charAt(0).toUpperCase()}
|
{workspace.name.charAt(0).toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{!isCollapsed && (
|
<span
|
||||||
<>
|
className={twMerge(
|
||||||
<span className="ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
"ml-2 text-sm font-bold text-neutral-900 dark:text-dark-1000",
|
||||||
{workspace.name}
|
isCollapsed && "md:hidden",
|
||||||
</span>
|
)}
|
||||||
{workspace.plan === "pro" && (
|
>
|
||||||
<span className="ml-2 inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-[10px] font-medium text-indigo-700">
|
{workspace.name}
|
||||||
Pro
|
</span>
|
||||||
</span>
|
{workspace.plan === "pro" && (
|
||||||
|
<span
|
||||||
|
className={twMerge(
|
||||||
|
"ml-2 inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-[10px] font-medium text-indigo-700",
|
||||||
|
isCollapsed && "hidden md:inline-flex",
|
||||||
)}
|
)}
|
||||||
</>
|
>
|
||||||
|
Pro
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
23
apps/web/src/hooks/useMediaQuery.ts
Normal file
23
apps/web/src/hooks/useMediaQuery.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useMediaQuery(query: string): boolean {
|
||||||
|
const [matches, setMatches] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const media = window.matchMedia(query);
|
||||||
|
setMatches(media.matches);
|
||||||
|
|
||||||
|
const listener = (event: MediaQueryListEvent) => {
|
||||||
|
setMatches(event.matches);
|
||||||
|
};
|
||||||
|
|
||||||
|
media.addEventListener("change", listener);
|
||||||
|
return () => media.removeEventListener("change", listener);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsMobile(): boolean {
|
||||||
|
return useMediaQuery("(max-width: 767px)");
|
||||||
|
}
|
||||||
@@ -191,7 +191,7 @@ export default function CardPage() {
|
|||||||
<div className="flex h-full flex-1 flex-row">
|
<div className="flex h-full flex-1 flex-row">
|
||||||
<div className="m-auto flex h-full w-full max-w-[800px] flex-col overflow-hidden">
|
<div className="m-auto flex h-full w-full max-w-[800px] flex-col overflow-hidden">
|
||||||
<div className="h-full max-h-[calc(100dvh-3rem)] overflow-y-auto p-6 md:max-h-[calc(100dvh-4rem)] md:p-8">
|
<div className="h-full max-h-[calc(100dvh-3rem)] overflow-y-auto p-6 md:max-h-[calc(100dvh-4rem)] md:p-8">
|
||||||
<div className="mb-8 mt-6 flex w-full items-center justify-between">
|
<div className="mb-8 flex w-full items-center justify-between md:mt-6">
|
||||||
{!card && isLoading && (
|
{!card && isLoading && (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
||||||
|
|||||||
Reference in New Issue
Block a user