Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { QuickNavPopover } from "@/components/navbar-popover";
import { NavbarActionButton } from "@/components/navigation/action-button";
import { CopyableIDButton } from "@/components/navigation/copyable-id-button";
import { Navbar } from "@/components/navigation/navbar";
import { useIsMobile } from "@/hooks/use-mobile";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { trpc } from "@/lib/trpc/client";
import type { Workspace } from "@unkey/db";
import { ChevronExpandY, Gear, Nodes, Plus, TaskUnchecked } from "@unkey/icons";
import { useIsMobile } from "@unkey/ui";
import { CreateKeyDialog } from "./_components/create-key";
import { KeySettingsDialog } from "./_components/key-settings-dialog";

Expand Down Expand Up @@ -101,7 +101,7 @@ const NavbarContent = ({
keyId,
activePage,
workspace,
isMobile,
isMobile = false,
layoutData,
}: NavbarContentProps) => {
const shouldFetchKey = Boolean(keyspaceId && keyId);
Expand Down Expand Up @@ -226,7 +226,8 @@ const NavbarContent = ({
export const ApisNavbar = ({ apiId, keyspaceId, keyId, activePage }: ApisNavbarProps) => {
const workspace = useWorkspaceNavigation();

const isMobile = useIsMobile();
// Default to false (desktop) to prevent hydration mismatches
const isMobile = useIsMobile({ defaultValue: false });

// Only make the query if we have a valid apiId
const {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";
import { VirtualTable } from "@/components/virtual-table/index";
import type { Column } from "@/components/virtual-table/types";
import { useIsMobile } from "@/hooks/use-mobile";
import type { Deployment, Environment } from "@/lib/collections";
import { shortenId } from "@/lib/shorten-id";
import { BookBookmark, CodeBranch, Cube } from "@unkey/icons";
import { useIsMobile } from "@unkey/ui";
import { Button, Empty, TimestampInfo } from "@unkey/ui";
import { cn } from "@unkey/ui/src/lib/utils";
import dynamic from "next/dynamic";
Expand Down Expand Up @@ -46,7 +46,8 @@ export const DeploymentsList = () => {
deployment: Deployment;
environment?: Environment;
} | null>(null);
const isCompactView = useIsMobile({ breakpoint: COMPACT_BREAKPOINT });
// Default to false (wide view) to prevent hydration mismatches
const isCompactView = useIsMobile({ breakpoint: COMPACT_BREAKPOINT, defaultValue: false });

const { liveDeployment, deployments, project } = useDeployments();

Expand Down
3 changes: 1 addition & 2 deletions apps/dashboard/components/logs/checkbox/filter-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Drover } from "@/components/ui/drover";
import { CaretRight } from "@unkey/icons";
import { Button, KeyboardButton } from "@unkey/ui";
import { Button, Drover, KeyboardButton } from "@unkey/ui";
import { cn } from "@unkey/ui/src/lib/utils";
import type React from "react";
import { type KeyboardEvent, useCallback, useEffect, useRef, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Drover } from "@/components/ui/drover";
import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
import { KeyboardButton } from "@unkey/ui";
import { Drover, KeyboardButton } from "@unkey/ui";
import React, {
type KeyboardEvent,
type PropsWithChildren,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import { Drawer } from "@/components/ui/drawer";
import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn, processTimeFilters } from "@/lib/utils";
import { ChevronDown } from "@unkey/icons";
import { useIsMobile } from "@unkey/ui";
import {
Button,
DateTime,
Drawer,
KeyboardButton,
Popover,
PopoverContent,
Expand Down Expand Up @@ -51,7 +51,8 @@ export const DatetimePopover = ({
minDate,
maxDate,
}: DatetimePopoverProps) => {
const isMobile = useIsMobile();
// Default to false (desktop) to prevent hydration mismatches
const isMobile = useIsMobile({ defaultValue: false });
const [timeRangeOpen, setTimeRangeOpen] = useState(false);
const [open, setOpen] = useState(false);
useKeyboardShortcut("t", () => setOpen((prev) => !prev));
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { useIsMobile } from "@unkey/ui";
import { Separator } from "@unkey/ui";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@unkey/ui";

Expand Down Expand Up @@ -64,7 +64,8 @@ const SidebarProvider = React.forwardRef<
},
ref,
) => {
const isMobile = useIsMobile();
// Default to false (desktop) to prevent hydration mismatches
const isMobile = useIsMobile({ defaultValue: false });
const [openMobile, setOpenMobile] = React.useState(false);

// This is the internal state of the sidebar.
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/components/virtual-table/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { CaretDown, CaretExpandY, CaretUp, CircleCaretRight } from "@unkey/icons";
import { useIsMobile } from "@unkey/ui";
import { Fragment, type Ref, forwardRef, useImperativeHandle, useMemo, useRef } from "react";
import { EmptyState } from "./components/empty-state";
import { LoadMoreFooter } from "./components/loading-indicator";
Expand Down Expand Up @@ -65,7 +65,8 @@ export const VirtualTable = forwardRef<VirtualTableRef, VirtualTableProps<any>>(
const isGridLayout = config.layoutMode === "grid";
const parentRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile();
// Default to false (desktop) to prevent hydration mismatches
const isMobile = useIsMobile({ defaultValue: false });

const hasPadding = config.containerPadding !== "px-0";

Expand Down
22 changes: 0 additions & 22 deletions apps/dashboard/hooks/use-mobile.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@radix-ui/react-switch": "1.0.3",
"@radix-ui/react-tabs": "1.1.0",
"@radix-ui/react-tooltip": "1.0.7",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@tailwindcss/container-queries": "0.1.1",
"@tailwindcss/typography": "0.5.12",
"@tanstack/query-core": "5.87.1",
Expand Down Expand Up @@ -100,7 +99,6 @@
"trpc-tools": "0.12.0",
"typescript": "5.7.3",
"usehooks-ts": "3.1.0",
"vaul": "0.9.0",
"zod": "3.23.5"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions internal/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@radix-ui/react-separator": "1.0.3",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-tooltip": "1.0.7",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@unkey/icons": "workspace:^",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
Expand All @@ -36,6 +37,7 @@
"react-day-picker": "8.10.1",
"sonner": "2.0.5",
"tailwind-merge": "2.5.4",
"vaul": "0.9.0",
"zod": "3.23.5"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* Imports
* ----------------------------------------------------------------------------*/

import { cn } from "@/lib/utils";
import * as React from "react";
import { Drawer as Vaul } from "vaul";
import { cn } from "../lib/utils";

/* -----------------------------------------------------------------------------
* Extend Drawer
Expand Down Expand Up @@ -55,14 +55,11 @@ DrawerContent.displayName = CONTENT_NAME;
* Exports
* ---------------------------------------------------------------------------*/

export const Drawer = Object.assign(
{},
{
Root: DrawerRoot,
Trigger: DrawerTrigger,
Content: DrawerContent,
Title: DrawerTitle,
Description: DrawerDescription,
Nested: DrawerNested,
},
);
export const Drawer = Object.assign({
Root: DrawerRoot,
Trigger: DrawerTrigger,
Content: DrawerContent,
Title: DrawerTitle,
Description: DrawerDescription,
Nested: DrawerNested,
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { useIsMobile } from "@/hooks/use-mobile";
import { createContext } from "@/lib/create-context";
import { cn } from "@/lib/utils";
import { Slot } from "@radix-ui/react-slot";
import { useControllableState } from "@radix-ui/react-use-controllable-state";
import { Popover, PopoverContent, PopoverTrigger } from "@unkey/ui";
import React from "react";
import { useIsMobile } from "../hooks/use-mobile";
import { createContext } from "../lib/create-context";
import { cn } from "../lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "./dialog/popover";
import { Drawer } from "./drawer";

type PrimitiveDivProps = React.ComponentPropsWithoutRef<"div">;
Expand Down Expand Up @@ -35,7 +35,8 @@ const [DroverProvider, useDroverContext] = createContext<DroverContextValue>(ROO

const Root: React.FC<DroverProps> = (props) => {
const { open: openProp, defaultOpen, onOpenChange, children } = props;
const isMobile = useIsMobile();
// Default to false (desktop) to prevent hydration mismatches and layout shifts
const isMobile = useIsMobile({ defaultValue: false });
const [open, setOpen] = useControllableState({
prop: openProp,
defaultProp: defaultOpen ?? false,
Expand Down
26 changes: 26 additions & 0 deletions internal/ui/src/hooks/use-mobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react";

type UseIsMobileOptions = {
breakpoint?: number;
defaultValue?: boolean;
};

export function useIsMobile(options: UseIsMobileOptions = {}): boolean {
const { breakpoint = 768, defaultValue = false } = options;
const [isMobile, setIsMobile] = React.useState<boolean>(defaultValue);

React.useEffect(() => {
if (typeof window === "undefined") {
return;
}
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
const onChange = (event: MediaQueryListEvent) => {
setIsMobile(event.matches);
};
mql.addEventListener("change", onChange);
setIsMobile(mql.matches);
return () => mql.removeEventListener("change", onChange);
}, [breakpoint]);

return isMobile;
}
3 changes: 3 additions & 0 deletions internal/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export * from "./components/dialog/dialog";
export * from "./components/dialog/dialog-container";
export * from "./components/dialog/confirmation-popover";
export * from "./components/dialog/navigable-dialog";
export * from "./components/drawer";
export * from "./components/drover";
export * from "./components/empty";
export * from "./components/form";
export * from "./components/id";
Expand All @@ -29,3 +31,4 @@ export * from "./components/timestamp-info";
export * from "./components/tooltip";
export * from "./components/separator";
export * from "./components/toaster";
export * from "./hooks/use-mobile";
39 changes: 39 additions & 0 deletions internal/ui/src/lib/create-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";

export function createContext<ContextValueType extends object | null>(
rootComponentName: string,
defaultContext?: ContextValueType,
) {
const Context = React.createContext<ContextValueType | undefined>(defaultContext);

const Provider = (props: ContextValueType & { children: React.ReactNode }) => {
const { children } = props;
// Only re-memoize when actual prop values change, not the object reference
// biome-ignore lint/correctness/useExhaustiveDependencies: props object reference changes every render; we track individual prop values instead
const value = React.useMemo(
() => {
const { children: _, ...contextValue } = props;
return contextValue as ContextValueType;
},
Object.keys(props)
.filter((k) => k !== "children")
.map((k) => (props as Record<string, unknown>)[k]),
);
return <Context.Provider value={value}>{children}</Context.Provider>;
};

function useContext(consumerName: string) {
const context = React.useContext(Context);
if (context !== undefined) {
return context;
}
if (defaultContext !== undefined) {
return defaultContext;
}
// if a defaultContext wasn't specified, it's a required context.
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
}

Provider.displayName = `${rootComponentName}Provider`;
return [Provider, useContext] as const;
}
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.