fix: mobile responsiveness across all pages

- SiteNav: add horizontal scroll for 8 tabs on mobile
- NotificationCenter: full-width dropdown on mobile
- ContentStats/Locations/TechSpecs: scrollable tab bars
- FrustrationTable: fix selector text overflow
- FrustrationByPageTable: horizontal scroll on mobile
- CDN: better stat card grid breakpoints
- Home: reduce stat card height, prevent button wrap
- Billing: shorter invoice labels on mobile
- Bump @ciphera-net/ui to 0.2.6 (AppLauncher mobile fix)
This commit is contained in:
Usman Baig
2026-03-15 18:15:06 +01:00
parent 7103a39273
commit 24c71f7991
13 changed files with 30 additions and 21 deletions

View File

@@ -400,7 +400,7 @@ export default function HomePage() {
) : null
})() ?? (
<Link href="/sites/new">
<Button variant="primary" className="text-sm">
<Button variant="primary" className="text-sm whitespace-nowrap">
Add New Site
</Button>
</Link>
@@ -409,11 +409,11 @@ export default function HomePage() {
{/* * Global Overview - min-h ensures no layout shift when Plan & usage loads */}
<div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="flex min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<p className="text-sm text-neutral-500 dark:text-neutral-400">Total Sites</p>
<p className="text-2xl font-bold text-neutral-900 dark:text-white">{sites.length}</p>
</div>
<div className="flex min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<div className="flex min-h-[100px] sm:min-h-[160px] flex-col rounded-2xl border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900">
<p className="text-sm text-neutral-500 dark:text-neutral-400">Total Visitors (24h)</p>
<p className="text-2xl font-bold text-neutral-900 dark:text-white">
{sites.length === 0 || Object.keys(siteStats).length < sites.length

View File

@@ -143,7 +143,7 @@ export default function CDNPage() {
</div>
<SkeletonLine className="h-9 w-36 rounded-lg" />
</div>
<div className="grid grid-cols-2 sm:grid-cols-5 gap-4 mb-8">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
<StatCardSkeleton />
<StatCardSkeleton />
<StatCardSkeleton />
@@ -249,7 +249,7 @@ export default function CDNPage() {
</div>
{/* Overview cards */}
<div className="grid grid-cols-2 sm:grid-cols-5 gap-4 mb-8">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
<OverviewCard
label="Bandwidth"
value={overview ? formatBytes(overview.total_bandwidth) : '-'}

View File

@@ -45,7 +45,7 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy
{loading ? (
<SkeletonRows />
) : hasData ? (
<div>
<div className="overflow-x-auto -mx-6 px-6">
{/* Header */}
<div className="flex items-center justify-between px-2 -mx-2 mb-2 text-xs font-medium text-neutral-400 dark:text-neutral-500 uppercase tracking-wider">
<span>Page</span>
@@ -72,7 +72,7 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy
style={{ width: `${barWidth}%` }}
/>
<span
className="relative text-sm text-neutral-900 dark:text-white truncate max-w-[300px]"
className="relative text-sm text-neutral-900 dark:text-white truncate max-w-[200px] sm:max-w-[300px]"
title={page.page_path}
>
{page.page_path}

View File

@@ -81,7 +81,7 @@ function Row({
return (
<div className="flex items-center justify-between h-9 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 min-w-0 overflow-hidden">
<SelectorCell selector={item.selector} />
<span
className="text-xs text-neutral-400 dark:text-neutral-500 truncate shrink-0"

View File

@@ -115,7 +115,7 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
</button>
)}
</div>
<div className="flex gap-1" role="tablist" aria-label="Pages view tabs" onKeyDown={handleTabKeyDown}>
<div className="flex gap-1 overflow-x-auto scrollbar-hide" role="tablist" aria-label="Pages view tabs" onKeyDown={handleTabKeyDown}>
{(['top_pages', 'entry_pages', 'exit_pages'] as Tab[]).map((tab) => (
<button
key={tab}

View File

@@ -233,7 +233,7 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
</button>
)}
</div>
<div className="flex gap-1" role="tablist" aria-label="Location view tabs" onKeyDown={handleTabKeyDown}>
<div className="flex gap-1 overflow-x-auto scrollbar-hide" role="tablist" aria-label="Location view tabs" onKeyDown={handleTabKeyDown}>
{(['map', 'globe', 'countries', 'regions', 'cities'] as Tab[]).map((tab) => (
<button
key={tab}

View File

@@ -36,7 +36,7 @@ export default function SiteNav({ siteId }: SiteNavProps) {
return (
<div className="border-b border-neutral-200 dark:border-neutral-800 mb-6">
<nav className="flex gap-1" role="tablist" aria-label="Site navigation" onKeyDown={handleTabKeyDown}>
<nav className="flex gap-1 overflow-x-auto scrollbar-hide" role="tablist" aria-label="Site navigation" onKeyDown={handleTabKeyDown}>
{tabs.map((tab) => (
<Link
key={tab.href}
@@ -44,7 +44,7 @@ export default function SiteNav({ siteId }: SiteNavProps) {
role="tab"
aria-selected={isActive(tab.href)}
tabIndex={isActive(tab.href) ? 0 : -1}
className={`relative px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange rounded-t cursor-pointer -mb-px ${
className={`relative shrink-0 whitespace-nowrap px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange rounded-t cursor-pointer -mb-px ${
isActive(tab.href)
? 'text-neutral-900 dark:text-white'
: 'text-neutral-400 dark:text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300'

View File

@@ -145,7 +145,7 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
</button>
)}
</div>
<div className="flex gap-1" role="tablist" aria-label="Technology view tabs" onKeyDown={handleTabKeyDown}>
<div className="flex gap-1 overflow-x-auto scrollbar-hide" role="tablist" aria-label="Technology view tabs" onKeyDown={handleTabKeyDown}>
{(['browsers', 'os', 'devices', 'screens'] as Tab[]).map((tab) => (
<button
key={tab}

View File

@@ -152,7 +152,7 @@ export default function NotificationCenter() {
id="notification-dropdown"
role="dialog"
aria-label="Notifications"
className="absolute right-0 top-full mt-2 w-80 sm:w-96 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-xl overflow-hidden z-[100]"
className="fixed left-4 right-4 top-16 sm:absolute sm:left-auto sm:right-0 sm:top-full sm:mt-2 sm:w-96 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-xl overflow-hidden z-[100]"
>
<div className="flex items-center justify-between px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
<h3 className="font-semibold text-neutral-900 dark:text-white">Notifications</h3>

View File

@@ -1098,7 +1098,7 @@ export default function OrganizationSettings() {
<a href={invoice.invoice_pdf} target="_blank" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-2.5 py-1.5 text-xs font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange" title="Download PDF">
<DownloadIcon className="w-3.5 h-3.5" />
Download PDF
<span className="hidden sm:inline">Download</span> PDF
</a>
)}
{invoice.hosted_invoice_url && (
@@ -1110,7 +1110,7 @@ export default function OrganizationSettings() {
}`}
title={invoice.status === 'open' ? 'Pay now' : 'View invoice'}>
<ExternalLinkIcon className="w-3.5 h-3.5" />
{invoice.status === 'open' ? 'Pay now' : 'View invoice'}
{invoice.status === 'open' ? 'Pay now' : <><span className="hidden sm:inline">View </span>Invoice</>}
</a>
)}
</div>

8
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "pulse-frontend",
"version": "0.15.0-alpha",
"dependencies": {
"@ciphera-net/ui": "^0.2.5",
"@ciphera-net/ui": "^0.2.6",
"@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10",
"@simplewebauthn/browser": "^13.2.2",
@@ -1667,9 +1667,9 @@
}
},
"node_modules/@ciphera-net/ui": {
"version": "0.2.5",
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.5/01371025a6706621b7a2c353cb1b07a239961fc3",
"integrity": "sha512-Ybd3zZLqpdv/dktNylT/jOm9OTMVST35+19QY+DTvDeluF3B4bN2YA7S85V7PpXGmZBmnPQX3U8qP4t2HwyyMw==",
"version": "0.2.6",
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.2.6/85b0deed2ec86461502209b098f64e028b49e63e",
"integrity": "sha512-mNlK4FNwWAYiScMcyTP7Y5EEZjSUf8H63EdQUlcTEWoFo4km5ZPrlJcfPsbUsN65YB9OtT+iAiu/XRG4dI0/Gg==",
"dependencies": {
"@phosphor-icons/react": "^2.1.10",
"class-variance-authority": "^0.7.1",

View File

@@ -12,7 +12,7 @@
"test:watch": "vitest"
},
"dependencies": {
"@ciphera-net/ui": "^0.2.5",
"@ciphera-net/ui": "^0.2.6",
"@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10",
"@simplewebauthn/browser": "^13.2.2",

View File

@@ -82,6 +82,15 @@
transform-style: preserve-3d;
}
/* * Scrollbar hide - for horizontal scroll navs */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* * Animations */
@keyframes float {
0%, 100% { transform: translateY(0px); }