Skip to content

Commit 45aff62

Browse files
committed
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
1 parent 65571be commit 45aff62

11 files changed

Lines changed: 352 additions & 300 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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`. Update both together.
7+
*/
8+
9+
export type ButtonTheme =
10+
| 'primary'
11+
| 'secondary'
12+
| 'danger'
13+
| 'yellow'
14+
| 'ghost'
15+
| 'icon'
16+
17+
export type ButtonSize = 'sm' | 'md'
18+
19+
const buttonBaseClass =
20+
'whitespace-nowrap truncate inline-flex items-center justify-center gap-x-2 text-sm font-medium rounded-md cursor-pointer disabled:cursor-not-allowed'
21+
22+
const buttonSizes: Record<ButtonSize, string> = {
23+
sm: 'px-3 py-2',
24+
md: 'px-3.5 py-2.5'
25+
}
26+
27+
const buttonThemes: Record<ButtonTheme, string> = {
28+
primary:
29+
'border border-indigo-600 bg-indigo-600 text-white hover:bg-indigo-700 focus-visible:outline-indigo-600 disabled:border-transparent disabled:bg-indigo-400/60 disabled:dark:bg-indigo-600/30 disabled:dark:text-white/35',
30+
secondary:
31+
'border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-50 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:border-gray-600 dark:hover:text-white disabled:text-gray-700/40 dark:disabled:text-gray-500 dark:disabled:bg-gray-800 dark:disabled:border-gray-800',
32+
yellow:
33+
'border border-yellow-600/90 bg-yellow-600/90 text-white hover:bg-yellow-600 focus-visible:outline-yellow-600 disabled:border-yellow-400/60 disabled:bg-yellow-400/60 disabled:dark:border-yellow-600/30 disabled:dark:bg-yellow-600/30 disabled:dark:text-white/35',
34+
danger:
35+
'border border-gray-300 dark:border-gray-800 text-red-600 bg-white dark:bg-gray-800 hover:text-red-700 dark:hover:text-red-400 dark:text-red-500 active:text-red-800 disabled:text-red-700/40 disabled:hover:shadow-none dark:disabled:text-red-500/35 dark:disabled:bg-gray-800',
36+
ghost:
37+
'border border-transparent text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 hover:border-gray-100 dark:hover:border-gray-800 disabled:text-gray-500 disabled:dark:text-gray-600 disabled:hover:bg-transparent disabled:hover:border-transparent',
38+
icon: 'border border-transparent text-gray-400 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'
39+
}
40+
41+
export const buttonClassName = ({
42+
theme = 'primary',
43+
size = 'md',
44+
className
45+
}: {
46+
theme?: ButtonTheme
47+
size?: ButtonSize
48+
className?: string
49+
} = {}): string =>
50+
classNames(buttonBaseClass, buttonSizes[size], buttonThemes[theme], className)
51+
52+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
53+
theme?: ButtonTheme
54+
size?: ButtonSize
55+
}
56+
57+
export const Button = ({
58+
theme = 'primary',
59+
size = 'md',
60+
type = 'button',
61+
className,
62+
...rest
63+
}: ButtonProps) => (
64+
<button
65+
type={type}
66+
className={buttonClassName({ theme, size, className })}
67+
{...rest}
68+
/>
69+
)
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 py-2">
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)