Skip to content

Commit 53569d7

Browse files
authored
Standardize all filter and segment modals styles (#6298)
* Standardize all filter and segment modals styles - Add a shared ModalLayout/ModalFooter to give every modal the same header, body spacing, and button alignment - Add a shared Button component (matching the Phoenix variant) and switch all modal buttons to it for consistent sizing and theming - Align footer buttons to the right in all modal footers - Restructure the segment details modal: fixed "Segment details" title, with the segment name, type, and authorship shown inline in the body - Update the delete segment modal with a clearer title and a confirmation message - Make filter pills better visible inside modals with a gray background - Add a Cancel button to the filter modal when no filters are applied - Refine button theme borders so ghost, yellow, and icon variants line up at the same height as bordered buttons, and fix the visible border on disabled primary buttons * Dark mode and responsive fixes * Implement feedback - Move shared button styles into `.btn-*` Tailwind component classes used by both the React Button and the Phoenix `button` component - Tweak filter pill styling inside modals for dark mode
1 parent 2d42d5f commit 53569d7

13 files changed

Lines changed: 419 additions & 310 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
2424
- "Top referrers" and "Search terms" breakdowns are rendered side by side with other "Sources" tabs instead of replacing them
2525
- Improved top bar and top stats UI/styling
2626
- Moved graph interval picker, export button, imported data toggle and notices out of the graph and into a new options menu in the top bar
27+
- Standardised and improved segment and filter modals styling
2728

2829
### Fixed
2930

assets/css/app.css

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,71 @@
5151
}
5252
}
5353

54+
@layer components {
55+
/*
56+
* Button component classes shared between the React `Button` component
57+
* (assets/js/dashboard/components/button.tsx) and the Phoenix `button`
58+
* component (lib/plausible_web/components/generic.ex). Update here only.
59+
*/
60+
.btn-base {
61+
@apply whitespace-nowrap truncate inline-flex items-center justify-center
62+
gap-x-2 text-sm font-medium rounded-md cursor-pointer
63+
disabled:cursor-not-allowed;
64+
}
65+
66+
.btn-sm {
67+
@apply px-3 py-2;
68+
}
69+
70+
.btn-md {
71+
@apply px-3.5 py-2.5;
72+
}
73+
74+
.btn-theme-primary {
75+
@apply border border-indigo-600 bg-indigo-600 text-white
76+
hover:bg-indigo-700 focus-visible:outline-indigo-600
77+
disabled:border-transparent disabled:bg-indigo-400/60
78+
disabled:dark:bg-indigo-600/30 disabled:dark:text-white/35;
79+
}
80+
81+
.btn-theme-secondary {
82+
@apply border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700
83+
text-gray-800 dark:text-gray-100 hover:bg-gray-50 hover:text-gray-900
84+
dark:hover:bg-gray-600 dark:hover:border-gray-600 dark:hover:text-white
85+
disabled:text-gray-700/40 dark:disabled:text-gray-500
86+
dark:disabled:bg-gray-800 dark:disabled:border-gray-800;
87+
}
88+
89+
.btn-theme-yellow {
90+
@apply border border-yellow-600/90 bg-yellow-600/90 text-white
91+
hover:bg-yellow-600 focus-visible:outline-yellow-600
92+
disabled:border-yellow-400/60 disabled:bg-yellow-400/60
93+
disabled:dark:border-yellow-600/30 disabled:dark:bg-yellow-600/30
94+
disabled:dark:text-white/35;
95+
}
96+
97+
.btn-theme-danger {
98+
@apply border border-gray-300 dark:border-gray-800 text-red-600 bg-white
99+
dark:bg-gray-800 hover:text-red-700 dark:hover:text-red-400
100+
dark:text-red-500 active:text-red-800 disabled:text-red-700/40
101+
disabled:hover:shadow-none dark:disabled:text-red-500/35
102+
dark:disabled:bg-gray-800;
103+
}
104+
105+
.btn-theme-ghost {
106+
@apply border border-transparent text-gray-700 dark:text-gray-300
107+
hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100
108+
dark:hover:bg-gray-800 hover:border-gray-100 dark:hover:border-gray-800
109+
disabled:text-gray-500 disabled:dark:text-gray-600
110+
disabled:hover:bg-transparent disabled:hover:border-transparent;
111+
}
112+
113+
.btn-theme-icon {
114+
@apply border border-transparent text-gray-400 dark:text-gray-400
115+
hover:text-gray-900 dark:hover:text-gray-100;
116+
}
117+
}
118+
54119
@theme {
55120
/* Color aliases from tailwind.config.js */
56121

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react'
2+
import classNames from 'classnames'
3+
4+
/**
5+
* Themes and sizes are kept in sync with the Phoenix `button` component in
6+
* `lib/plausible_web/components/generic.ex`. The actual Tailwind classes live
7+
* in `assets/css/app.css` (.btn-base, .btn-{sm,md}, .btn-theme-*).
8+
*/
9+
10+
export type ButtonTheme =
11+
| 'primary'
12+
| 'secondary'
13+
| 'danger'
14+
| 'yellow'
15+
| 'ghost'
16+
| 'icon'
17+
18+
export type ButtonSize = 'sm' | 'md'
19+
20+
const buttonBaseClass = 'btn-base'
21+
22+
const buttonSizes: Record<ButtonSize, string> = {
23+
sm: 'btn-sm',
24+
md: 'btn-md'
25+
}
26+
27+
const buttonThemes: Record<ButtonTheme, string> = {
28+
primary: 'btn-theme-primary',
29+
secondary: 'btn-theme-secondary',
30+
yellow: 'btn-theme-yellow',
31+
danger: 'btn-theme-danger',
32+
ghost: 'btn-theme-ghost',
33+
icon: 'btn-theme-icon'
34+
}
35+
36+
export const buttonClassName = ({
37+
theme = 'primary',
38+
size = 'md',
39+
className
40+
}: {
41+
theme?: ButtonTheme
42+
size?: ButtonSize
43+
className?: string
44+
} = {}): string =>
45+
classNames(buttonBaseClass, buttonSizes[size], buttonThemes[theme], className)
46+
47+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
48+
theme?: ButtonTheme
49+
size?: ButtonSize
50+
}
51+
52+
export const Button = ({
53+
theme = 'primary',
54+
size = 'md',
55+
type = 'button',
56+
className,
57+
...rest
58+
}: ButtonProps) => (
59+
<button
60+
type={type}
61+
className={buttonClassName({ theme, size, className })}
62+
{...rest}
63+
/>
64+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { ReactNode } from 'react'
2+
import { XMarkIcon } from '@heroicons/react/20/solid'
3+
import ModalWithRouting from '../stats/modals/modal'
4+
5+
export function ModalLayout({
6+
title,
7+
onClose,
8+
children,
9+
maxWidth = '460px',
10+
allowScroll = true
11+
}: {
12+
title: ReactNode
13+
onClose: () => void
14+
children: ReactNode
15+
maxWidth?: string
16+
allowScroll?: boolean
17+
}) {
18+
return (
19+
<ModalWithRouting
20+
maxWidth={maxWidth}
21+
allowScroll={allowScroll}
22+
onClose={onClose}
23+
>
24+
<div className="flex flex-col gap-6 p-1 md:py-2 md:px-0">
25+
<div className="flex items-center justify-between gap-3">
26+
<h1 className="text-base font-bold leading-tight dark:text-gray-100">
27+
{title}
28+
</h1>
29+
<button
30+
type="button"
31+
onClick={onClose}
32+
aria-label="Close modal"
33+
className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
34+
>
35+
<XMarkIcon className="size-4.5" />
36+
</button>
37+
</div>
38+
{children}
39+
</div>
40+
</ModalWithRouting>
41+
)
42+
}
43+
44+
export function ModalFooter({ children }: { children: ReactNode }) {
45+
return <div className="flex gap-x-3 items-center justify-end">{children}</div>
46+
}

assets/js/dashboard/components/remove-filter-button.tsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

assets/js/dashboard/nav-menu/filter-pill.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function FilterPill({
3737
return (
3838
<div
3939
className={classNames(
40-
'flex h-8 shadow rounded-md bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm items-center',
40+
'flex h-8 shadow-sm rounded-md bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm items-center',
4141
className
4242
)}
4343
>

assets/js/dashboard/segments/segment-authorship.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ export function SegmentAuthorship({
2424
const showUpdatedAt = updated_at !== inserted_at
2525

2626
return (
27-
<div className={className}>
28-
<div>
27+
<span className={className}>
28+
<span>
2929
{`Created at ${formatDayShort(dateForSite(inserted_at, site))}`}
3030
{!showUpdatedAt && !!authorLabel && ` by ${authorLabel}`}
31-
</div>
31+
</span>
3232
{showUpdatedAt && (
33-
<div>
34-
{`Last updated at ${formatDayShort(dateForSite(updated_at, site))}`}
35-
{!!authorLabel && ` by ${authorLabel}`}
36-
</div>
33+
<>
34+
{' • '}
35+
<span>
36+
{`Last updated at ${formatDayShort(dateForSite(updated_at, site))}`}
37+
{!!authorLabel && ` by ${authorLabel}`}
38+
</span>
39+
</>
3740
)}
38-
</div>
41+
</span>
3942
)
4043
}

0 commit comments

Comments
 (0)