From b70b8ab2c5d6bd5261766c04b25a596a574734e1 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Tue, 14 Jul 2026 12:12:17 -0700 Subject: [PATCH 1/3] feat(banner): add `x` for closing --- packages/ui-components/package.json | 2 +- .../src/Common/Banner/index.module.css | 15 ++++++- .../ui-components/src/Common/Banner/index.tsx | 44 +++++++++++++++---- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index b05e7e5119a64..76a3b62338c4a 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@node-core/ui-components", - "version": "1.7.1", + "version": "1.7.2", "type": "module", "exports": { "./*": { diff --git a/packages/ui-components/src/Common/Banner/index.module.css b/packages/ui-components/src/Common/Banner/index.module.css index d55d5359cf8c8..0f7f382e972d5 100644 --- a/packages/ui-components/src/Common/Banner/index.module.css +++ b/packages/ui-components/src/Common/Banner/index.module.css @@ -1,7 +1,8 @@ @reference "../../styles/index.css"; .banner { - @apply flex + @apply relative + flex w-full flex-row items-center @@ -40,3 +41,15 @@ .warning { @apply bg-warning-600; } + +.closeButton { + @apply absolute + right-4 + text-lg; + + span { + @apply text-white/60 + transition-colors + hover:text-white; + } +} diff --git a/packages/ui-components/src/Common/Banner/index.tsx b/packages/ui-components/src/Common/Banner/index.tsx index b28ceade24816..1d28e84f807ab 100644 --- a/packages/ui-components/src/Common/Banner/index.tsx +++ b/packages/ui-components/src/Common/Banner/index.tsx @@ -1,22 +1,48 @@ -import type { FC, HTMLAttributes, PropsWithChildren } from 'react'; +'use client'; + +import classNames from 'classnames'; +import { + useState, + type FC, + type HTMLAttributes, + type PropsWithChildren, +} from 'react'; import styles from './index.module.css'; export type BannerProps = { type?: 'default' | 'warning' | 'error'; + onClose?: (e: React.MouseEvent) => void; } & HTMLAttributes; const Banner: FC> = ({ type = 'default', + onClose, children, ...props -}) => ( -
- {children} -
-); +}) => { + const [dismissed, setDismissed] = useState(false); + if (dismissed) {return null;} + + return ( +
+ {children} + +
+ ); +}; export default Banner; From bd140ea4ccbef9793d42a06f1759ca84c0481c61 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Tue, 14 Jul 2026 12:16:34 -0700 Subject: [PATCH 2/3] fixup! --- packages/ui-components/src/Common/Banner/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui-components/src/Common/Banner/index.tsx b/packages/ui-components/src/Common/Banner/index.tsx index 1d28e84f807ab..310e871ac1f81 100644 --- a/packages/ui-components/src/Common/Banner/index.tsx +++ b/packages/ui-components/src/Common/Banner/index.tsx @@ -22,7 +22,9 @@ const Banner: FC> = ({ ...props }) => { const [dismissed, setDismissed] = useState(false); - if (dismissed) {return null;} + if (dismissed) { + return null; + } return (
Date: Tue, 14 Jul 2026 12:54:39 -0700 Subject: [PATCH 3/3] fixup! --- apps/site/components/withBanner.tsx | 23 +++++++++- .../src/Common/Banner/index.stories.tsx | 8 ++++ .../ui-components/src/Common/Banner/index.tsx | 45 ++++++++----------- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/apps/site/components/withBanner.tsx b/apps/site/components/withBanner.tsx index 1023051e1d0d5..077eda597e07e 100644 --- a/apps/site/components/withBanner.tsx +++ b/apps/site/components/withBanner.tsx @@ -1,6 +1,9 @@ +'use client'; + import { ArrowUpRightIcon } from '@heroicons/react/24/outline'; import Banner from '@node-core/ui-components/Common/Banner'; import { useTranslations } from 'next-intl'; +import { useEffect, useState } from 'react'; import Link from '#site/components/Link'; import { siteConfig } from '#site/next.json.mjs'; @@ -8,17 +11,35 @@ import { dateIsBetween } from '#site/util/date'; import type { FC } from 'react'; +const STORAGE_KEY = 'banner-dismissal'; + const WithBanner: FC<{ section: string }> = ({ section }) => { const banner = siteConfig.websiteBanners[section]; const t = useTranslations(); - if (banner && dateIsBetween(banner.startDate, banner.endDate)) { + const [dismissed, setDismissed] = useState(false); + + // localStorage is only available on the client, so the banner is always + // rendered on the server and hidden after hydration if previously dismissed + useEffect(() => { + if (banner) { + setDismissed(localStorage.getItem(STORAGE_KEY) === banner.text); + } + }, [banner]); + + if (banner && !dismissed && dateIsBetween(banner.startDate, banner.endDate)) { const bannerType = banner.type || 'default'; + const onClose = () => { + localStorage.setItem(STORAGE_KEY, banner.text); + setDismissed(true); + }; + return ( {banner.link ? ( {banner.text} diff --git a/packages/ui-components/src/Common/Banner/index.stories.tsx b/packages/ui-components/src/Common/Banner/index.stories.tsx index 60fc4c4d2e336..1ca61a6312971 100644 --- a/packages/ui-components/src/Common/Banner/index.stories.tsx +++ b/packages/ui-components/src/Common/Banner/index.stories.tsx @@ -26,4 +26,12 @@ export const Warning: Story = { }, }; +export const Dismissible: Story = { + args: { + children: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + type: 'default', + onClose: () => {}, + }, +}; + export default { component: Banner } as Meta; diff --git a/packages/ui-components/src/Common/Banner/index.tsx b/packages/ui-components/src/Common/Banner/index.tsx index 310e871ac1f81..6d0fd5a165dfe 100644 --- a/packages/ui-components/src/Common/Banner/index.tsx +++ b/packages/ui-components/src/Common/Banner/index.tsx @@ -1,18 +1,17 @@ -'use client'; - import classNames from 'classnames'; -import { - useState, - type FC, - type HTMLAttributes, - type PropsWithChildren, + +import type { + FC, + HTMLAttributes, + MouseEventHandler, + PropsWithChildren, } from 'react'; import styles from './index.module.css'; export type BannerProps = { type?: 'default' | 'warning' | 'error'; - onClose?: (e: React.MouseEvent) => void; + onClose?: MouseEventHandler; } & HTMLAttributes; const Banner: FC> = ({ @@ -20,31 +19,23 @@ const Banner: FC> = ({ onClose, children, ...props -}) => { - const [dismissed, setDismissed] = useState(false); - if (dismissed) { - return null; - } - - return ( -
- {children} +}) => ( +
+ {children} + {onClose && ( -
- ); -}; + )} +
+); export default Banner;