Skip to content

Commit beb869f

Browse files
committed
Improve reports layout
- Create new `ReportLayout` and `ReportHeader` components to ensure consistent layout across reports - Remove title from report header, and place tabs in the top left corner - Move the details button to the top right corner, rather than the bottom of the report, to reduce vertical space and improve readability. Reduce it to a simple icon with a 'View details' tooltip. - Update the Search terms report to use the new `ReportLayout` and `ReportHeader` components. - Update dashboard to use a simple grid layout, simplifying the html markup and css. - Change 'Screen sizes' title to 'Devices' in the devices report. - Change 'Top pages' title to 'Conversion pages' in the pages report, whenever a conversion goal filter is applied. - Add 'last 30min' pill to the behaviour report when on realtime dashboard, rather than displaying this in the title. - Remove the Combobox input from the custom properties report, and use the same tab dropdown pattern as used for funnels and UTM campaigns.
1 parent 10b1169 commit beb869f

30 files changed

+846
-474
lines changed

assets/css/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
*:focus-visible {
3232
@apply ring-2 ring-indigo-500 ring-offset-2 dark:ring-offset-gray-900 outline-none;
3333
}
34+
35+
:focus:not(:focus-visible) {
36+
@apply outline-none;
37+
}
3438
}
3539

3640
@layer components {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { ReactNode } from 'react'
2+
import classNames from 'classnames'
3+
4+
export type PillProps = {
5+
className?: string
6+
children: ReactNode
7+
}
8+
9+
export function Pill({ className, children }: PillProps) {
10+
return (
11+
<div
12+
className={classNames(
13+
'flex items-center shrink-0 h-fit rounded-md bg-green-50 dark:bg-green-900/60 text-green-700 dark:text-green-300 text-xs font-medium px-2.5 py-1',
14+
className
15+
)}
16+
>
17+
{children}
18+
</div>
19+
)
20+
}

assets/js/dashboard/components/popover.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ const items = {
5555
'data-[selected=true]:bg-gray-100',
5656
'data-[selected=true]:dark:bg-gray-700',
5757
'data-[selected=true]:text-gray-900',
58-
'data-[selected=true]:dark:text-gray-100',
59-
'data-[selected=true]:font-semibold'
58+
'data-[selected=true]:dark:text-gray-100'
6059
),
6160
hoverLink: classNames(
6261
'hover:bg-gray-100',

assets/js/dashboard/components/tabs.tsx

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Popover, Transition } from '@headlessui/react'
22
import classNames from 'classnames'
3-
import React, { ReactNode, useRef } from 'react'
3+
import React, { ReactNode, useRef, useEffect } from 'react'
44
import { ChevronDownIcon } from '@heroicons/react/20/solid'
55
import { popover, BlurMenuButtonOnEscape } from './popover'
66
import { useSearchableItems } from '../hooks/use-searchable-items'
@@ -16,7 +16,7 @@ export const TabWrapper = ({
1616
}) => (
1717
<div
1818
className={classNames(
19-
'flex text-xs font-medium text-gray-500 dark:text-gray-400 space-x-2 items-baseline',
19+
'flex items-baseline gap-x-3 text-xs font-medium text-gray-500 dark:text-gray-400',
2020
className
2121
)}
2222
>
@@ -32,12 +32,15 @@ const TabButtonText = ({
3232
active: boolean
3333
}) => (
3434
<span
35-
className={classNames('truncate text-left transition-colors duration-150', {
36-
'hover:text-indigo-700 dark:hover:text-indigo-400 cursor-pointer':
37-
!active,
38-
'text-indigo-600 dark:text-indigo-500 font-bold underline decoration-2 decoration-indigo-600 dark:decoration-indigo-500':
39-
active
40-
})}
35+
className={classNames(
36+
'-mb-px pb-4 truncate text-left text-xs font-semibold uppercase transition-colors duration-150',
37+
{
38+
'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 cursor-pointer':
39+
!active,
40+
'border-b-2 border-gray-900 dark:border-gray-100 text-gray-900 dark:text-gray-100':
41+
active
42+
}
43+
)}
4144
>
4245
{children}
4346
</span>
@@ -54,7 +57,10 @@ export const TabButton = ({
5457
onClick: () => void
5558
active: boolean
5659
}) => (
57-
<button className={classNames('rounded-sm', className)} onClick={onClick}>
60+
<button
61+
className={classNames('flex rounded-sm', className)}
62+
onClick={onClick}
63+
>
5864
<TabButtonText active={active}>{children}</TabButtonText>
5965
</button>
6066
)
@@ -84,19 +90,16 @@ export const DropdownTabButton = ({
8490
>
8591
<TabButtonText active={active}>{children}</TabButtonText>
8692

87-
<div
88-
className="flex self-stretch -mr-1 ml-1 items-center"
89-
aria-hidden="true"
90-
>
91-
<ChevronDownIcon className="h-4 w-4" />
93+
<div className="ml-1 pb-4" aria-hidden="true">
94+
<ChevronDownIcon className="size-4" />
9295
</div>
9396
</Popover.Button>
9497

9598
<Transition
9699
as="div"
97100
{...popover.transition.props}
98101
className={classNames(
99-
popover.transition.classNames.fullwidth,
102+
popover.transition.classNames.left,
100103
'mt-2',
101104
transitionClassName
102105
)}
@@ -115,15 +118,9 @@ type ItemsProps = {
115118
closeDropdown: () => void
116119
options: Array<{ selected: boolean; onClick: () => void; label: string }>
117120
searchable?: boolean
118-
collectionTitle?: string
119121
}
120122

121-
const Items = ({
122-
options,
123-
searchable,
124-
collectionTitle,
125-
closeDropdown
126-
}: ItemsProps) => {
123+
const Items = ({ options, searchable, closeDropdown }: ItemsProps) => {
127124
const {
128125
filteredData,
129126
showableData,
@@ -136,7 +133,7 @@ const Items = ({
136133
countOfMoreToShow
137134
} = useSearchableItems({
138135
data: options,
139-
maxItemsInitially: searchable ? 5 : options.length,
136+
maxItemsInitially: searchable ? 10 : options.length,
140137
itemMatchesSearchValue: (option, trimmedSearchString) =>
141138
option.label.toLowerCase().includes(trimmedSearchString.toLowerCase())
142139
})
@@ -148,24 +145,32 @@ const Items = ({
148145
popover.items.classNames.hoverLink
149146
)
150147

148+
useEffect(() => {
149+
if (searchable && showSearch && searchRef.current) {
150+
const timeoutId = setTimeout(() => {
151+
searchRef.current?.focus()
152+
}, 100)
153+
return () => clearTimeout(timeoutId)
154+
}
155+
}, [searchable, showSearch, searchRef])
156+
151157
return (
152158
<>
153159
{searchable && showSearch && (
154-
<div className="flex items-center py-2 px-4">
155-
{collectionTitle && (
156-
<div className="text-sm font-bold uppercase text-indigo-500 dark:text-indigo-400 mr-4">
157-
{collectionTitle}
158-
</div>
159-
)}
160+
<div className="flex items-center p-1">
160161
<SearchInput
161162
searchRef={searchRef}
162163
placeholderUnfocused="Press / to search"
163-
className="ml-auto w-full py-1"
164+
className="w-full !max-w-none"
164165
onSearch={handleSearchInput}
165166
/>
166167
</div>
167168
)}
168-
<div className={'max-h-[210px] overflow-y-scroll'}>
169+
<div
170+
className={
171+
'max-h-[224px] overflow-y-scroll flex flex-col gap-y-0.5 p-1'
172+
}
173+
>
169174
{showableData.map(({ selected, label, onClick }, index) => {
170175
return (
171176
<button
@@ -177,7 +182,7 @@ const Items = ({
177182
data-selected={selected}
178183
className={itemClassName}
179184
>
180-
{label}
185+
<span className="line-clamp-1">{label}</span>
181186
</button>
182187
)
183188
})}
@@ -186,7 +191,7 @@ const Items = ({
186191
onClick={handleShowAll}
187192
className={classNames(
188193
itemClassName,
189-
'w-full text-left font-bold hover:text-indigo-700 dark:hover:text-indigo-500'
194+
'w-full text-left text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
190195
)}
191196
>
192197
{`Show ${countOfMoreToShow} more`}
@@ -197,11 +202,14 @@ const Items = ({
197202
<button
198203
className={classNames(
199204
itemClassName,
200-
'w-full text-left font-bold hover:text-indigo-700 dark:hover:text-indigo-500'
205+
'w-full text-left !justify-start'
201206
)}
202207
onClick={handleClearSearch}
203208
>
204-
No items found. Clear search to show all.
209+
No items found.{' '}
210+
<span className="ml-1 text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-500">
211+
Click to clear search.
212+
</span>
205213
</button>
206214
)}
207215
</div>

assets/js/dashboard/extra/funnel.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ export default function Funnel({ funnelName, tabs }) {
294294
const header = () => {
295295
return (
296296
<div className="flex justify-between w-full">
297-
<h4 className="mt-2 text-sm dark:text-gray-100">{funnelName}</h4>
297+
<h4 className="mt-2 text-base font-semibold dark:text-gray-100">
298+
{funnelName}
299+
</h4>
298300
{tabs}
299301
</div>
300302
)
@@ -346,7 +348,7 @@ export default function Funnel({ funnelName, tabs }) {
346348
return (
347349
<div className="mb-8">
348350
{header()}
349-
<p className="mt-1 text-gray-500 text-sm">
351+
<p className="mt-0.5 text-gray-500 text-sm">
350352
{funnel.steps.length}-step funnel • {conversionRate}% conversion
351353
rate
352354
</p>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useCallback, useState } from 'react'
2+
import { AppNavigationLinkProps } from '../navigation/use-app-navigate'
3+
4+
export function useMoreLinkData() {
5+
const [listData, setListData] = useState<unknown[] | null>(null)
6+
const [linkProps, setLinkProps] = useState<AppNavigationLinkProps | null>(
7+
null
8+
)
9+
const [listLoading, setListLoading] = useState(true)
10+
11+
const onListUpdate = useCallback(
12+
(
13+
list: unknown[] | null,
14+
linkProps: AppNavigationLinkProps | undefined,
15+
loading: boolean
16+
) => {
17+
setListData(list)
18+
setLinkProps(linkProps ?? null)
19+
setListLoading(loading)
20+
},
21+
[]
22+
)
23+
24+
const reset = useCallback(() => {
25+
setListData(null)
26+
setLinkProps(null)
27+
setListLoading(true)
28+
}, [])
29+
30+
return { onListUpdate, listData, linkProps, listLoading, reset }
31+
}

assets/js/dashboard/index.tsx

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,21 @@ function DashboardStats({
5050
}
5151
}, [onLiveNavigate])
5252

53-
const statsBoxClass =
54-
'relative min-h-[436px] w-full mt-5 p-4 flex flex-col bg-white dark:bg-gray-900 shadow-sm rounded-md md:min-h-initial md:h-27.25rem md:w-[calc(50%-10px)] md:ml-[10px] md:mr-[10px] first:ml-0 last:mr-0'
55-
5653
return (
5754
<>
5855
<VisitorGraph updateImportedDataInView={updateImportedDataInView} />
59-
<div className="w-full md:flex">
60-
<div className={statsBoxClass}>
61-
<Sources />
62-
</div>
63-
<div className={statsBoxClass}>
64-
{site.flags.live_dashboard ? (
65-
<LiveViewPortal
66-
id="pages-breakdown-live"
67-
className="w-full h-full border-0 overflow-hidden"
68-
/>
69-
) : (
70-
<Pages />
71-
)}
72-
</div>
73-
</div>
74-
75-
<div className="w-full md:flex">
76-
<div className={statsBoxClass}>
77-
<Locations />
78-
</div>
79-
<div className={statsBoxClass}>
80-
<Devices />
81-
</div>
82-
</div>
56+
<Sources />
57+
{site.flags.live_dashboard ? (
58+
<LiveViewPortal
59+
id="pages-breakdown-live"
60+
className="w-full h-full border-0 overflow-hidden"
61+
/>
62+
) : (
63+
<Pages />
64+
)}
8365

66+
<Locations />
67+
<Devices />
8468
<Behaviours importedDataInView={importedDataInView} />
8569
</>
8670
)
@@ -98,7 +82,7 @@ function Dashboard() {
9882
const [importedDataInView, setImportedDataInView] = useState(false)
9983

10084
return (
101-
<div className="mb-16">
85+
<div className="mb-16 grid grid-cols-1 md:grid-cols-2 gap-5">
10286
<TopBar showCurrentVisitors={!isRealTimeDashboard} />
10387
<DashboardStats
10488
importedDataInView={

assets/js/dashboard/nav-menu/top-bar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ function TopBarStickyWrapper({ children }: { children: ReactNode }) {
2727

2828
return (
2929
<>
30-
<div id="stats-container-top" ref={ref} />
30+
<div id="stats-container-top" className="col-span-full" ref={ref} />
3131
<div
3232
className={classNames(
33-
'relative top-0 py-2 sm:py-3 z-10',
33+
'col-span-full relative top-0 py-2 sm:py-3 -my-3 sm:-my-4 z-10',
3434
!site.embedded &&
3535
!inView &&
3636
'sticky fullwidth-shadow bg-gray-50 dark:bg-gray-950'
@@ -47,7 +47,7 @@ function TopBarInner({ showCurrentVisitors }: TopBarProps) {
4747

4848
return (
4949
<div className="flex items-center w-full">
50-
<div className="flex items-center gap-x-4 shrink-0" ref={leftActionsRef}>
50+
<div className="flex items-center gap-x-5 shrink-0" ref={leftActionsRef}>
5151
<SiteSwitcher />
5252
{showCurrentVisitors && (
5353
<CurrentVisitors tooltipBoundaryRef={leftActionsRef} />

assets/js/dashboard/site-switcher.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export const SiteSwitcher = () => {
170170
<Popover.Button
171171
ref={buttonRef}
172172
className={classNames(
173-
'flex items-center rounded h-9 leading-5 font-bold dark:text-gray-100',
173+
'flex items-center rounded h-9 text-sm leading-5 font-semibold dark:text-gray-100',
174174
'hover:bg-gray-100 dark:hover:bg-gray-800'
175175
)}
176176
title={currentSite.domain}
@@ -188,7 +188,7 @@ export const SiteSwitcher = () => {
188188
? 'All sites'
189189
: currentSite.domain}
190190
</span>
191-
<ChevronDownIcon className="hidden lg:block h-5 w-5 ml-2 dark:text-gray-100" />
191+
<ChevronDownIcon className="hidden lg:block size-4 ml-2 dark:text-gray-100" />
192192
</Popover.Button>
193193
<Transition
194194
as="div"

assets/js/dashboard/stats/behaviours/conversions.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { useSiteContext } from '../../site-context'
88
import { useQueryContext } from '../../query-context'
99
import { conversionsRoute } from '../../router'
1010

11-
export default function Conversions({ afterFetchData, onGoalFilterClick }) {
11+
export default function Conversions({
12+
afterFetchData,
13+
onGoalFilterClick,
14+
onListUpdate
15+
}) {
1216
const site = useSiteContext()
1317
const { query } = useQueryContext()
1418

@@ -56,6 +60,7 @@ export default function Conversions({ afterFetchData, onGoalFilterClick }) {
5660
}}
5761
color="bg-red-50 group-hover/row:bg-red-100"
5862
colMinWidth={90}
63+
onListUpdate={onListUpdate}
5964
/>
6065
)
6166
}

0 commit comments

Comments
 (0)