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
6 changes: 3 additions & 3 deletions apps/web/src/components/dashboard/dashboard-editor-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,15 @@ export function DashboardEditorView({
}

return (
<div>
<div className="mb-6 flex flex-wrap items-center justify-between gap-3">
<div className="w-full min-w-0 max-w-[calc(100vw-1.5rem)] overflow-hidden sm:max-w-full">
<div className="mb-6 flex w-full min-w-0 max-w-full flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
<DashboardSwitcher
currentId={activeDashboardId}
dashboards={dashboards}
isAdmin={isAdmin}
onSelect={handleDashboardSelect}
/>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
{isAdmin && !editor.isEditing && isDashboardReady && (
<Button onClick={handleEdit} size="sm" variant="outline">
<PencilIcon className="mr-1 size-4" />
Expand Down
10 changes: 9 additions & 1 deletion apps/web/src/components/dashboard/dashboard-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export function DashboardGrid({
<div className="relative" key={widget.id}>
{isEditing && (
<EditOverlay
forceVisible
isStatic={isWidgetStatic(widget.config_json)}
onDelete={() => onWidgetDelete(widget.id)}
onEdit={() => onWidgetEdit(widget.id)}
Expand Down Expand Up @@ -232,18 +233,25 @@ export function DashboardGrid({
}

function EditOverlay({
forceVisible,
isStatic,
onEdit,
onDelete,
onToggleStatic
}: {
forceVisible?: boolean
isStatic?: boolean
onDelete: () => void
onEdit: () => void
onToggleStatic?: () => void
}) {
return (
<div className="absolute top-1 right-1 z-10 flex gap-1 opacity-0 transition-opacity [div:hover>&]:opacity-100">
<div
className={cn(
'absolute top-1 right-1 z-10 flex gap-1 transition-opacity [div:hover>&]:opacity-100',
forceVisible ? 'opacity-100' : 'opacity-0'
)}
>
{onToggleStatic && (
<Button
className="size-7"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/dashboard/widget-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function WidgetPicker({ onSelect, open, onOpenChange }: WidgetPickerProps
return (
<div key={category}>
<h4 className="mb-2 font-medium text-muted-foreground text-xs uppercase tracking-wide">{category}</h4>
<div className="grid grid-cols-2 gap-2">
<div className="grid gap-2 sm:grid-cols-2">
{widgets.map((widgetType) => {
const Icon = WIDGET_ICONS[widgetType.id] ?? Server
const description = WIDGET_DESCRIPTIONS[widgetType.id] ?? ''
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/components/data-table/data-table-pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ export function DataTablePagination<TData>({
return (
<div
className={cn(
'flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8',
'flex w-full min-w-0 flex-col-reverse items-center justify-between gap-4 p-1 sm:flex-row sm:gap-8',
className
)}
{...props}
>
<div className="flex-1 whitespace-nowrap text-muted-foreground text-sm">
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex flex-col-reverse items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
<div className="flex items-center space-x-2">
<div className="flex w-full flex-col-reverse items-center gap-4 sm:w-auto sm:flex-row sm:gap-6 lg:gap-8">
<div className="flex items-center gap-2">
<p className="whitespace-nowrap font-medium text-sm">Rows per page</p>
<Select
onValueChange={(value) => {
Expand All @@ -51,7 +51,7 @@ export function DataTablePagination<TData>({
<div className="flex items-center justify-center font-medium text-sm">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center gap-2">
<Button
aria-label="Go to first page"
className="hidden size-8 lg:flex"
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/data-table/data-table-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ export function DataTableSkeleton({
)

return (
<div className={cn('flex w-full flex-col gap-2.5 overflow-auto', className)} {...props}>
<div className="flex w-full items-center justify-between gap-2 overflow-auto p-1">
<div className="flex flex-1 items-center gap-2">
<div className={cn('flex w-full min-w-0 flex-col gap-2.5 overflow-hidden', className)} {...props}>
<div className="flex w-full min-w-0 flex-wrap items-center justify-between gap-2 p-1">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
{filterCount > 0
? Array.from({ length: filterCount }).map((_, i) => <Skeleton className="h-7 w-18 border-dashed" key={i} />)
: null}
</div>
{withViewOptions ? <Skeleton className="ml-auto hidden h-7 w-18 lg:flex" /> : null}
</div>
<div className="rounded-md border">
<div className="min-w-0 overflow-hidden rounded-md border">
<Table>
<TableHeader>
{Array.from({ length: 1 }).map((_, i) => (
Expand Down Expand Up @@ -77,7 +77,7 @@ export function DataTableSkeleton({
</Table>
</div>
{withPagination ? (
<div className="flex w-full items-center justify-between gap-4 overflow-auto p-1 sm:gap-8">
<div className="flex w-full min-w-0 flex-wrap items-center justify-between gap-4 p-1 sm:gap-8">
<Skeleton className="h-7 w-40 shrink-0" />
<div className="flex items-center gap-4 sm:gap-6 lg:gap-8">
<div className="flex items-center gap-2">
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/components/data-table/data-table-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export function DataTableToolbar<TData>({ table, children, className, ...props }
return (
<div
aria-orientation="horizontal"
className={cn('flex w-full items-start justify-between gap-2 p-1', className)}
className={cn('flex w-full min-w-0 flex-col gap-2 p-1 sm:flex-row sm:items-start sm:justify-between', className)}
role="toolbar"
{...props}
>
<div className="flex flex-1 flex-wrap items-center gap-2">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
{columns.map((column) => (
<DataTableToolbarFilter column={column} key={column.id} />
))}
Expand All @@ -43,7 +43,7 @@ export function DataTableToolbar<TData>({ table, children, className, ...props }
</Button>
)}
</div>
<div className="flex items-center gap-2">
<div className="flex w-full flex-wrap items-center justify-end gap-2 sm:w-auto">
{children}
<DataTableViewOptions align="end" table={table} />
</div>
Expand All @@ -67,7 +67,7 @@ function DataTableToolbarFilter<TData>({ column }: DataTableToolbarFilterProps<T
case 'text':
return (
<Input
className="h-8 w-40 lg:w-56"
className="h-8 w-full min-w-36 sm:w-40 lg:w-56"
onChange={(event) => column.setFilterValue(event.target.value)}
placeholder={columnMeta.placeholder ?? columnMeta.label}
value={(column.getFilterValue() as string) ?? ''}
Expand All @@ -76,9 +76,9 @@ function DataTableToolbarFilter<TData>({ column }: DataTableToolbarFilterProps<T

case 'number':
return (
<div className="relative">
<div className="relative min-w-32">
<Input
className={cn('h-8 w-[120px]', columnMeta.unit && 'pr-8')}
className={cn('h-8 w-full', columnMeta.unit && 'pr-8')}
inputMode="numeric"
onChange={(event) => column.setFilterValue(event.target.value)}
placeholder={columnMeta.placeholder ?? columnMeta.label}
Expand Down
36 changes: 36 additions & 0 deletions apps/web/src/components/data-table/data-table.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { DataTable } from './data-table'

interface Row {
name: string
}

const columns: ColumnDef<Row>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name
}
]

function TestTable() {
const table = useReactTable({
data: [{ name: 'server-1' }],
columns,
getCoreRowModel: getCoreRowModel()
})

return <DataTable table={table} />
}

describe('DataTable mobile layout', () => {
it('keeps horizontal overflow inside the table viewport', () => {
const { container } = render(<TestTable />)

expect(container.firstElementChild).toHaveClass('min-w-0', 'overflow-hidden')
expect(screen.getByTestId('data-table-scroll')).toHaveClass('min-w-0')
expect(screen.getByRole('table')).toHaveClass('min-w-max')
})
})
4 changes: 2 additions & 2 deletions apps/web/src/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ interface DataTableProps<TData> extends React.ComponentProps<'div'> {

export function DataTable<TData>({ table, actionBar, children, className, ...props }: DataTableProps<TData>) {
return (
<div className={cn('flex w-full flex-col gap-2.5 overflow-auto', className)} {...props}>
<div className={cn('flex w-full min-w-0 flex-col gap-2.5 overflow-hidden', className)} {...props}>
{children}
<div className="overflow-hidden rounded-md border">
<div className="min-w-0 max-w-full overflow-hidden rounded-md border" data-testid="data-table-scroll">
<Table className="table-fixed [&_td]:px-3 [&_th]:px-3">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/server/server-edit-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export function ServerEditDialog({ server, open, onClose }: ServerEditDialogProp
value={name}
/>
</Field>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-3 sm:grid-cols-2">
<Field label={t('edit_weight')}>
<Input
aria-label={t('edit_weight')}
Expand Down Expand Up @@ -282,7 +282,7 @@ export function ServerEditDialog({ server, open, onClose }: ServerEditDialogProp
<legend className="mb-1 font-medium text-muted-foreground text-xs uppercase tracking-wider">
{t('edit_billing')}
</legend>
<div className="grid grid-cols-3 gap-3">
<div className="grid gap-3 sm:grid-cols-3">
<Field label={t('edit_price')}>
<Input
aria-label={t('edit_price')}
Expand Down Expand Up @@ -336,7 +336,7 @@ export function ServerEditDialog({ server, open, onClose }: ServerEditDialogProp
<Field label={t('edit_expiration')}>
<DatePickerField ariaLabel={t('edit_expiration')} onChange={setExpiredAt} value={expiredAt} />
</Field>
<div className="grid grid-cols-2 gap-3">
<div className="grid gap-3 sm:grid-cols-2">
<Field label={t('edit_traffic_limit')}>
<Input
aria-label={t('edit_traffic_limit')}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/task/scheduled-task-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function ScheduledTaskDialog({ onClose, task }: Props) {
/>
</div>

<div className="grid grid-cols-3 gap-3">
<div className="grid gap-3 sm:grid-cols-3">
<div>
<label className="mb-1 block font-medium text-sm" htmlFor="task-timeout">
{t('tasks.timeout')}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface DataTableProps<TData> {

function DataTable<TData>({ table, noResults, className }: DataTableProps<TData>) {
return (
<div className={cn('overflow-hidden rounded-lg border', className)}>
<div className={cn('min-w-0 max-w-full overflow-hidden rounded-lg border', className)}>
<Table className="table-fixed">
<TableHeader className="sticky top-0 z-10 bg-background">
{table.getHeaderGroups().map((headerGroup) => (
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.
<ScrollAreaPrimitive.Content data-slot="scroll-area-content">{children}</ScrollAreaPrimitive.Content>
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/ui/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { cn } from '@/lib/utils'

function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<ScrollArea className="min-h-0 w-full flex-1" data-slot="table-container">
<table className={cn('w-full caption-bottom text-sm', className)} data-slot="table" {...props} />
<ScrollArea className="min-h-0 w-full min-w-0 flex-1" data-slot="table-container">
<table className={cn('w-full min-w-max caption-bottom text-sm', className)} data-slot="table" {...props} />
</ScrollArea>
)
}
Expand Down
18 changes: 9 additions & 9 deletions apps/web/src/routes/_authed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,23 @@ function AuthedLayout() {
<SidebarProvider>
<AppSidebar />
<SidebarInset className="min-h-0 overflow-hidden">
<header className="flex h-16 shrink-0 items-center justify-between gap-2 px-4">
<div className="flex items-center gap-2">
<header className="flex h-14 shrink-0 items-center justify-between gap-2 px-3 sm:h-16 sm:px-4">
<div className="flex min-w-0 items-center gap-2">
<SidebarTrigger className="-ml-1" />
<Separator
className="mr-2 data-[orientation=vertical]:h-4 data-[orientation=vertical]:self-center"
className="mr-1 data-[orientation=vertical]:h-4 data-[orientation=vertical]:self-center sm:mr-2"
orientation="vertical"
/>
<Breadcrumb>
<BreadcrumbList>
<Breadcrumb className="min-w-0">
<BreadcrumbList className="min-w-0 flex-nowrap">
{breadcrumbs.map((crumb, index) => {
const isLast = index === breadcrumbs.length - 1
const hiddenOnMobile = index === 0 && breadcrumbs.length > 1
return (
<Fragment key={crumb.label}>
<BreadcrumbItem className={hiddenOnMobile ? 'hidden md:block' : ''}>
<BreadcrumbItem className={hiddenOnMobile ? 'hidden md:block' : 'min-w-0'}>
{isLast || !crumb.to ? (
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
<BreadcrumbPage className="truncate">{crumb.label}</BreadcrumbPage>
) : (
<BreadcrumbLink render={<Link to={crumb.to} />}>{crumb.label}</BreadcrumbLink>
)}
Expand All @@ -224,13 +224,13 @@ function AuthedLayout() {
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="flex items-center gap-2">
<div className="flex shrink-0 items-center gap-1 sm:gap-2">
<LanguageSwitcher />
<ThemeToggle />
</div>
</header>
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
<main className="flex h-full flex-col p-4 pt-0">
<main className="flex min-h-full min-w-0 flex-col p-3 pt-0 sm:p-4 sm:pt-0">
<Outlet />
</main>
</ScrollArea>
Expand Down
18 changes: 9 additions & 9 deletions apps/web/src/routes/_authed/files.$serverId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,23 @@ function FilesPage() {
)

return (
<div className="flex h-full flex-col">
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
{/* Header */}
<div className="flex items-center gap-3 border-b px-4 py-2">
<div className="flex flex-wrap items-center gap-2 border-b px-3 py-2 sm:gap-3 sm:px-4">
<Link params={{ id: serverId }} search={{ range: 'realtime' }} to="/servers/$id">
<Button size="sm" variant="ghost">
<ArrowLeft aria-hidden="true" className="size-4" />
{t('back_to_server')}
</Button>
</Link>
<h1 className="font-semibold text-lg">{t('title')}</h1>
<span className="text-muted-foreground text-sm">{serverId.slice(0, 8)}...</span>
<h1 className="font-semibold text-base sm:text-lg">{t('title')}</h1>
<span className="text-muted-foreground text-xs sm:text-sm">{serverId.slice(0, 8)}...</span>
</div>

{/* Breadcrumb + Actions */}
<div className="flex items-center gap-2 border-b px-4 py-1.5">
<div className="flex flex-col gap-2 border-b px-3 py-2 sm:flex-row sm:items-center sm:px-4 sm:py-1.5">
<FileBreadcrumb onNavigate={handleNavigate} path={currentPath} />
<div className="ml-auto flex gap-1">
<div className="flex flex-wrap gap-1 sm:ml-auto">
{isAdmin && (
<Button aria-label={t('upload')} onClick={() => setUploadOpen(true)} size="sm" variant="outline">
<Upload className="size-3.5" />
Expand All @@ -169,7 +169,7 @@ function FilesPage() {
{/* Main content: file list + preview */}
<div className="flex min-h-0 flex-1">
{/* File list panel */}
<div className="w-full min-w-0 overflow-y-auto border-r md:w-[45%]">
<div className="w-full min-w-0 border-r md:w-[45%]">
<FileBrowser
entries={entries}
error={isError ? getErrorMessage(listError, t('load_error')) : undefined}
Expand All @@ -190,14 +190,14 @@ function FilesPage() {
{/* Mobile preview overlay */}
{selectedFile && (
<div className="fixed inset-0 z-40 flex flex-col bg-background md:hidden">
<div className="flex items-center gap-2 border-b px-4 py-2">
<div className="flex min-w-0 items-center gap-2 border-b px-3 py-2 sm:px-4">
<Button onClick={() => setSelectedFile(null)} size="sm" variant="ghost">
<ArrowLeft aria-hidden="true" className="size-4" />
{t('back_to_server')}
</Button>
<span className="truncate text-sm">{selectedFile.name}</span>
</div>
<div className="flex-1">
<div className="min-h-0 flex-1">
<FilePreview entry={selectedFile} readOnly={!isAdmin} serverId={serverId} />
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/routes/_authed/network/$serverId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ export function NetworkDetailPage() {
</div>

{/* Bottom stats */}
<div className="mb-6 grid grid-cols-3 gap-4">
<div className="mb-6 grid gap-4 sm:grid-cols-3">
<div className="rounded-lg border bg-card p-4 text-center">
<p className="font-mono font-semibold text-lg tabular-nums">
{stats.avgLatency != null ? `${stats.avgLatency.toFixed(1)} ms` : 'N/A'}
Expand Down
Loading
Loading