-
-
Notifications
You must be signed in to change notification settings - Fork 1k
docs: Add banner rotator #4000
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
docs: Add banner rotator #4000
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||||||||||||||||||||
| import clsx from 'clsx'; | ||||||||||||||||||||||||||||
| import React, { type ReactNode, useEffect, useMemo, useState } from 'react'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import HandIcon from '../HandIcon'; | ||||||||||||||||||||||||||||
| import styles from './styles.module.css'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| type Promo = { | ||||||||||||||||||||||||||||
| key: string; | ||||||||||||||||||||||||||||
| href: string; | ||||||||||||||||||||||||||||
| bg: string; | ||||||||||||||||||||||||||||
| buttonLabel: string; | ||||||||||||||||||||||||||||
| label: ReactNode; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const PROMOS: readonly Promo[] = [ | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| key: 'appjs', | ||||||||||||||||||||||||||||
| href: 'https://appjs.co?origin=swmansion_bar', | ||||||||||||||||||||||||||||
| bg: '#C7CEF5', | ||||||||||||||||||||||||||||
| buttonLabel: 'Get your tickets', | ||||||||||||||||||||||||||||
| label: ( | ||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||
| <strong>App.js Conf 2026</strong> | ||||||||||||||||||||||||||||
| <span className={styles.hiddenOnMobile}> | ||||||||||||||||||||||||||||
| {' '} | ||||||||||||||||||||||||||||
| is just around the corner! | ||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| key: 'paradise', | ||||||||||||||||||||||||||||
| href: 'https://paradise.swmansion.com?origin=swmansion_bar', | ||||||||||||||||||||||||||||
| bg: '#FFF4C0', | ||||||||||||||||||||||||||||
| buttonLabel: 'Learn more', | ||||||||||||||||||||||||||||
| label: ( | ||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||
| <strong>React Native Paradise</strong> | ||||||||||||||||||||||||||||
| <span className={styles.hiddenOnMobile}> | ||||||||||||||||||||||||||||
| {' '} | ||||||||||||||||||||||||||||
| - a week of advanced RN workshops in Croatia! | ||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export default function TopPromoRotator() { | ||||||||||||||||||||||||||||
| const promos = useMemo(() => PROMOS, []); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const [index, setIndex] = useState(0); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||
| const id = window.setInterval(() => { | ||||||||||||||||||||||||||||
| setIndex(i => (i + 1) % promos.length); | ||||||||||||||||||||||||||||
| }, 5_000); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return () => window.clearInterval(id); | ||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+58
|
||||||||||||||||||||||||||||
| }, [promos.length]); | ||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+59
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const active = promos[index]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const barHeight = 50; | ||||||||||||||||||||||||||||
| const translateY = `translateY(-${index * barHeight}px)`; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||
| className={clsx(styles.wrapper)} | ||||||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||||||
| backgroundColor: active.bg, | ||||||||||||||||||||||||||||
| transition: 'background-color 600ms ease', | ||||||||||||||||||||||||||||
| }}> | ||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+72
|
||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||
| className={styles.slider} | ||||||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||||||
| transform: translateY, | ||||||||||||||||||||||||||||
| transition: 'transform 700ms cubic-bezier(0.22, 1, 0.36, 1)', | ||||||||||||||||||||||||||||
| }}> | ||||||||||||||||||||||||||||
|
Comment on lines
+73
to
+78
|
||||||||||||||||||||||||||||
| {promos.map(p => ( | ||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||
| key={p.key} | ||||||||||||||||||||||||||||
| href={p.href} | ||||||||||||||||||||||||||||
| target="_blank" | ||||||||||||||||||||||||||||
| rel="noopener noreferrer" | ||||||||||||||||||||||||||||
| className={styles.banner}> | ||||||||||||||||||||||||||||
| <span>{p.label}</span> | ||||||||||||||||||||||||||||
| <HandIcon aria-hidden="true" className={styles.icon} /> | ||||||||||||||||||||||||||||
| <span className={styles.underline}>{p.buttonLabel}</span> | ||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| <span className="sr-only"> | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| <span className="sr-only"> | |
| <span | |
| style={{ | |
| border: 0, | |
| clip: 'rect(0 0 0 0)', | |
| height: '1px', | |
| margin: '-1px', | |
| overflow: 'hidden', | |
| padding: 0, | |
| position: 'absolute', | |
| width: '1px', | |
| whiteSpace: 'nowrap', | |
| }}> |
Copilot
AI
Feb 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The screen reader fallback text only displays content when active.label is a string, but in this implementation, both promos use ReactNode (JSX elements) for labels. This means screen readers will never receive alternative text for the rotating banners. Consider extracting plain text versions of the labels or implementing a function to convert the ReactNode content to text for screen readers.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||||||||||||||||||
| .wrapper { | ||||||||||||||||||||||
| position: relative; | ||||||||||||||||||||||
| height: 50px; | ||||||||||||||||||||||
| min-height: 50px; | ||||||||||||||||||||||
| max-height: 50px; | ||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||
| overflow: hidden; | ||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||
| justify-content: center; | ||||||||||||||||||||||
| font-size: 1rem; | ||||||||||||||||||||||
| font-weight: 500; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .slider { | ||||||||||||||||||||||
| position: absolute; | ||||||||||||||||||||||
| inset: 0; | ||||||||||||||||||||||
| will-change: transform; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .banner { | ||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||
| height: 50px; | ||||||||||||||||||||||
| min-height: 50px; | ||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||
| justify-content: center; | ||||||||||||||||||||||
| gap: 0.5rem; | ||||||||||||||||||||||
| padding: 0 0.75rem; | ||||||||||||||||||||||
| font-size: 1rem; | ||||||||||||||||||||||
| font-weight: 500; | ||||||||||||||||||||||
| line-height: 1; | ||||||||||||||||||||||
| color: #001a72; | ||||||||||||||||||||||
| text-align: center; | ||||||||||||||||||||||
| text-decoration: none; | ||||||||||||||||||||||
| transition: background-color 300ms ease-out; | ||||||||||||||||||||||
| white-space: nowrap; | ||||||||||||||||||||||
| box-sizing: border-box; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .banner:hover { | ||||||||||||||||||||||
| color: #001a72 !important; | ||||||||||||||||||||||
|
Comment on lines
+33
to
+42
|
||||||||||||||||||||||
| text-decoration: none !important; | ||||||||||||||||||||||
| background-color: rgba(0, 0, 0, 0.05); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
||||||||||||||||||||||
| .banner:focus, | |
| .banner:focus-visible { | |
| color: #001a72 !important; | |
| text-decoration: none !important; | |
| background-color: rgba(0, 0, 0, 0.05); | |
| outline: 2px solid #001a72; | |
| outline-offset: 2px; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import React from 'react'; | ||
| import useBaseUrl from '@docusaurus/useBaseUrl'; | ||
| import { Navbar } from '@swmansion/t-rex-ui'; | ||
| import TopPromoRotator from '@site/src/components/TopPromoRotator'; | ||
|
|
||
| export default function NavbarWrapper(props) { | ||
| const titleImages = { | ||
|
|
@@ -12,6 +13,9 @@ export default function NavbarWrapper(props) { | |
| logo: useBaseUrl('/img/logo-hero.svg'), | ||
| }; | ||
| return ( | ||
| <Navbar heroImages={heroImages} titleImages={titleImages} {...props} /> | ||
| <div style={{ display: 'flex', flexDirection: 'column', flexShrink: 0 }}> | ||
|
||
| <TopPromoRotator /> | ||
| <Navbar heroImages={heroImages} titleImages={titleImages} {...props} /> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
useMemohook is being used to memoize a constant arrayPROMOS, but sincePROMOSis already defined as a constant outside the component, usinguseMemohere is unnecessary and adds overhead. You can directly usePROMOSin the component or simply assign it withconst promos = PROMOS.