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:
@@ -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
|
||||
|
||||
@@ -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) : '-'}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user