From b70b8ab2c5d6bd5261766c04b25a596a574734e1 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Tue, 14 Jul 2026 12:12:17 -0700 Subject: [PATCH 1/5] 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/5] 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/5] 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; From f6adfc735c57ac44409905c274204e5058b89d78 Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Wed, 15 Jul 2026 12:47:40 -0700 Subject: [PATCH 4/5] fixup! --- apps/site/components/withBanner.tsx | 12 ++++----- packages/i18n/src/locales/en.json | 3 ++- .../src/Common/Banner/index.module.css | 19 +++++++++----- .../ui-components/src/Common/Banner/index.tsx | 26 +++++++++++-------- .../ui-components/src/styles/animations.css | 16 ++++++++++++ 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/site/components/withBanner.tsx b/apps/site/components/withBanner.tsx index 077eda597e07e..b8a186d01b23a 100644 --- a/apps/site/components/withBanner.tsx +++ b/apps/site/components/withBanner.tsx @@ -17,28 +17,28 @@ const WithBanner: FC<{ section: string }> = ({ section }) => { const banner = siteConfig.websiteBanners[section]; const t = useTranslations(); - const [dismissed, setDismissed] = useState(false); + const [open, setOpen] = 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); + // eslint-disable-next-line @eslint-react/set-state-in-effect + setOpen(localStorage.getItem(STORAGE_KEY) !== banner.text); } }, [banner]); - if (banner && !dismissed && dateIsBetween(banner.startDate, banner.endDate)) { + if (banner && open && dateIsBetween(banner.startDate, banner.endDate)) { const bannerType = banner.type || 'default'; const onClose = () => { localStorage.setItem(STORAGE_KEY, banner.text); - setDismissed(true); + setOpen(false); }; return ( {banner.link ? ( diff --git a/packages/i18n/src/locales/en.json b/packages/i18n/src/locales/en.json index 4fbbeb0c19b46..6c8466e119bf2 100644 --- a/packages/i18n/src/locales/en.json +++ b/packages/i18n/src/locales/en.json @@ -191,7 +191,8 @@ "banner": { "default": "Announcement", "warning": "Warning notification", - "error": "Error notification" + "error": "Error notification", + "close": "Close banner" }, "search": { "searchPlaceholder": "Start typing...", diff --git a/packages/ui-components/src/Common/Banner/index.module.css b/packages/ui-components/src/Common/Banner/index.module.css index 0f7f382e972d5..d9bf2cc94b981 100644 --- a/packages/ui-components/src/Common/Banner/index.module.css +++ b/packages/ui-components/src/Common/Banner/index.module.css @@ -1,13 +1,11 @@ @reference "../../styles/index.css"; .banner { - @apply relative - flex + @apply animate-fade-and-slide-down + relative + grid w-full - flex-row - items-center - justify-center - gap-2 + grid-rows-1 px-8 py-3 text-sm; @@ -30,6 +28,15 @@ } } +.bannerContent { + @apply flex + min-h-0 + flex-row + items-center + justify-center + gap-2; +} + .default { @apply bg-brand-600; } diff --git a/packages/ui-components/src/Common/Banner/index.tsx b/packages/ui-components/src/Common/Banner/index.tsx index 6d0fd5a165dfe..b3cba18be84ec 100644 --- a/packages/ui-components/src/Common/Banner/index.tsx +++ b/packages/ui-components/src/Common/Banner/index.tsx @@ -11,11 +11,13 @@ import styles from './index.module.css'; export type BannerProps = { type?: 'default' | 'warning' | 'error'; + closeButtonAriaLabel?: string; onClose?: MouseEventHandler; } & HTMLAttributes; const Banner: FC> = ({ type = 'default', + closeButtonAriaLabel = 'Close banner', onClose, children, ...props @@ -24,17 +26,19 @@ const Banner: FC> = ({ className={classNames(styles.banner, styles[type] || styles.default)} {...props} > - {children} - {onClose && ( - - )} +
+ {children} + {onClose && ( + + )} +
); diff --git a/packages/ui-components/src/styles/animations.css b/packages/ui-components/src/styles/animations.css index 8be5edac4e807..6d71df062bad3 100644 --- a/packages/ui-components/src/styles/animations.css +++ b/packages/ui-components/src/styles/animations.css @@ -7,6 +7,8 @@ --animate-dot-move-delay-400: dot-move 1.2s infinite ease-in-out 400ms; --animate-slide-to-left: slide-to-left 300ms ease-in-out; --animate-slide-to-right: slide-to-right 300ms ease-in-out; + --animate-fade-and-slide-down: fade-and-slide-down 300ms ease-out 400ms + backwards; @keyframes surf { 0% { @@ -86,4 +88,18 @@ transform: translateX(0); } } + + @keyframes fade-and-slide-down { + from { + grid-template-rows: 0fr; + opacity: 0; + transform: translateY(-100%); + } + + to { + grid-template-rows: 1fr; + opacity: 1; + transform: translateY(0); + } + } } From 64f758c4b57a84bcb75c9e322f74d29148f7511e Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Wed, 15 Jul 2026 12:50:24 -0700 Subject: [PATCH 5/5] fixup! --- packages/ui-components/src/styles/animations.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ui-components/src/styles/animations.css b/packages/ui-components/src/styles/animations.css index 6d71df062bad3..c16c21240ad67 100644 --- a/packages/ui-components/src/styles/animations.css +++ b/packages/ui-components/src/styles/animations.css @@ -93,12 +93,14 @@ from { grid-template-rows: 0fr; opacity: 0; + padding-block: 0; transform: translateY(-100%); } to { grid-template-rows: 1fr; opacity: 1; + padding-block: calc(var(--spacing, 0.25rem) * 3); transform: translateY(0); } }