Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alert banner styling updates #1021

Merged
merged 5 commits into from May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tiny-waves-dance.md
@@ -0,0 +1,5 @@
---
'@hashicorp/react-alert-banner': minor
---

alert banner styling updates
21 changes: 1 addition & 20 deletions packages/alert-banner/index.test.js
Expand Up @@ -3,9 +3,7 @@
* SPDX-License-Identifier: MPL-2.0
*/

import { fireEvent, render, screen } from '@testing-library/react'
import cookie from 'js-cookie'
import slugify from 'slugify'
import { render, screen } from '@testing-library/react'
import AlertBanner from './'

/**
Expand All @@ -30,23 +28,6 @@ describe('<AlertBanner />', () => {
expect(linkElem.href).toBe(url)
})

it('should allow closing the banner, and set a cookie to remember', () => {
const tag = 'Newish'
const text = 'This is a cool banner'
const linkText = 'Check it out'
const { container } = render(
<AlertBanner tag={tag} text={text} linkText={linkText} />
)
const rootElem = container.firstChild
const closeButton = screen.getByText('Dismiss alert')
expect(rootElem).toHaveClass('isShown')
fireEvent.click(closeButton)
expect(rootElem).not.toHaveClass('isShown')

const dismissalCookieId = `banner_${slugify(text, { lower: true })}`
expect(cookie.get(dismissalCookieId)).toBe('1')
})

describe('with an expiration date set', () => {
beforeAll(() => {
// Lock Date.now() with value of Oct. 20, 2020
Expand Down
63 changes: 9 additions & 54 deletions packages/alert-banner/index.tsx
Expand Up @@ -4,18 +4,14 @@
*/

import { useEffect, useState } from 'react'
import cookie from 'js-cookie'
import slugify from 'slugify'
import classNames from 'classnames'
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
import useProductMeta, {
Products as HashiCorpProduct,
} from '@hashicorp/platform-product-meta'
import Image from 'next/image'
import CloseIcon from './img/close-icon.svg'
import fragment from './fragment.graphql'
import s from './style.module.css'
import analytics from './analytics'
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'

interface AlertBannerProps {
tag: string
Expand Down Expand Up @@ -43,41 +39,20 @@ function AlertBanner({
text,
url,
}: AlertBannerProps): React.ReactElement {
const dismissalCookieId = `banner_${name || slugify(text, { lower: true })}`

const [isShown, setIsShown] = useState(() => {
const hasExpired = expirationDate && Date.now() > Date.parse(expirationDate)
const hasBeenDismissed =
typeof window === 'undefined'
? false
: cookie.get(dismissalCookieId) === '1'

return !hasExpired && !hasBeenDismissed
return !hasExpired
})
const { themeClass } = useProductMeta(product)

/**
* On mount, hide the banner if it is expired or
* if a cookie indicates it was previously closed
* On mount, hide the banner if it is expired
*/
useEffect(() => {
const hasBeenDismissed = cookie.get(dismissalCookieId)
const hasExpired = expirationDate && Date.now() > Date.parse(expirationDate)
const shouldBeShown = !hasBeenDismissed && !hasExpired
setIsShown((current) =>
current !== shouldBeShown ? shouldBeShown : current
)
}, [dismissalCookieId, expirationDate])

/**
* Dismiss the banner, and set a cookie
* to remember the dismissal of this banner
*/
function closeBanner() {
cookie.set(dismissalCookieId, '1')
setIsShown(false)
analytics.trackClose({ linkText, product, tag, text })
}
setIsShown((current) => (current !== !hasExpired ? !hasExpired : current))
}, [expirationDate])

return (
<>
Expand All @@ -100,35 +75,15 @@ function AlertBanner({
<span className={s.text}>
{text}
{linkText ? (
<span className={s.linkText}> {linkText}</span>
<span className={s.link}>
<span className={s.linkText}> {linkText}</span>
<IconArrowRight16 />
</span>
) : null}
</span>
</span>
</a>
<button className={s.closeButton} onClick={closeBanner}>
<Image alt="Close" {...CloseIcon} />
<VisuallyHidden>Dismiss alert</VisuallyHidden>
</button>
</div>
{/* This inline script checks if the alert banner has been previously dismissed, and if so it removes the isShown class.
The render-blocking inline script ensures the class is removed before the first paint, preventing layout shift. */}
<script
dangerouslySetInnerHTML={{
__html: `
(function checkDismissAlertBanner() {
try {
if (document.cookie.includes('${dismissalCookieId}=1')) {
const element = document.querySelector('.${s.root}')
element.classList.remove('${s.isShown}')
}
} catch (_) {
// do nothing
}
})()
`,
}}
type="text/javascript"
/>
</>
)
}
Expand Down
47 changes: 16 additions & 31 deletions packages/alert-banner/style.module.css
Expand Up @@ -25,7 +25,7 @@

.linkElem {
background: var(--brand-secondary);
color: var(--black);
color: var(--token-color-foreground-faint, #656a76);
display: block;
position: relative;
}
Expand All @@ -35,9 +35,9 @@
align-items: flex-start;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 15px;
padding-top: 15px;
min-height: 32px;
margin: 0;
padding: 4px 0 4px 10px;

@media (min-width: 576px) {
flex-direction: row;
Expand All @@ -48,18 +48,18 @@
align-items: center;
composes: g-type-body-small-x-strong from global;
display: flex;
margin-right: 11px;
margin-right: 8px;
position: relative;
white-space: nowrap;

/* Vertical divider using ::after pseudo class */
&::after {
content: '';
height: 16px;
background: black;
background: var(--token-color-foreground-faint, #656a76);
width: 1px;
display: block;
margin-left: 11px;
margin-left: 8px;
}
}

Expand All @@ -77,39 +77,24 @@
}
}

.linkText {
.link {
color: var(--token-color-foreground-primary, #3b3d45);
composes: g-type-body-small-x-strong from global;
display: none;

@media (min-width: 992px) {
display: block;
display: flex;
flex-direction: row;
align-items: center;
margin-left: 8px;
text-decoration: underline;
white-space: nowrap;
}
}

.closeButton {
background: none;
border: none;
margin: 0;
opacity: 0.75;
padding: 0;
position: absolute;
right: 24px;
top: 16px;
transition: opacity 0.15s;
z-index: 2;

& > svg {
display: block;
& [stroke] {
stroke: var(--black);
}
}

.linkText {
&:hover {
cursor: pointer;
opacity: 1;
text-decoration: underline;
}

margin-right: 6px;
}