Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/docs-gesture-handler/src/components/HandIcon/index.tsx

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, []);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useMemo hook is being used to memoize a constant array PROMOS, but since PROMOS is already defined as a constant outside the component, using useMemo here is unnecessary and adds overhead. You can directly use PROMOS in the component or simply assign it with const promos = PROMOS.

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct usage of window.setInterval and window.clearInterval could cause issues during server-side rendering (SSR) in Docusaurus. Other components in the codebase (like RadonBanner, Wave, FooterBackground) wrap browser-specific code with BrowserOnly or check ExecutionEnvironment.canUseViewport. Consider wrapping this component or its interval logic to prevent SSR errors.

Copilot uses AI. Check for mistakes.
}, [promos.length]);
Comment on lines +53 to +59
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The automatically rotating banner doesn't provide users with controls to pause, stop, or manually navigate the rotation. According to WCAG 2.1 Success Criterion 2.2.2 (Pause, Stop, Hide), content that auto-updates or rotates for more than 5 seconds should provide a mechanism for users to pause, stop, or hide it. Consider adding pause/play controls or allowing users to navigate between banners manually.

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rotating banner doesn't announce changes to screen readers when the content rotates. Consider adding an ARIA live region with appropriate politeness level (e.g., aria-live="polite") to the wrapper div so screen reader users are notified when the banner content changes.

Copilot uses AI. Check for mistakes.
<div
className={styles.slider}
style={{
transform: translateY,
transition: 'transform 700ms cubic-bezier(0.22, 1, 0.36, 1)',
}}>
Comment on lines +73 to +78
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users with reduced motion preferences will still experience the automatic sliding animation. Consider respecting the prefers-reduced-motion media query to disable or reduce animations for users who have indicated they prefer less motion, which is important for accessibility and can help prevent motion sickness.

Copilot uses AI. Check for mistakes.
{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">
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sr-only class is used but doesn't appear to be defined in the codebase's CSS files. This class is critical for screen reader accessibility. Either this class needs to be defined in a global stylesheet, or you should use a CSS module class from styles.module.css. Without a proper definition, screen reader users won't have access to the alternative text for the rotating banners.

Suggested change
<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 uses AI. Check for mistakes.
{typeof active.label === 'string' ? active.label : ''}
Copy link

Copilot AI Feb 26, 2026

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.

Copilot uses AI. Check for mistakes.
</span>
</div>
);
}
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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color #001a72 is hardcoded in multiple places (lines 33, 42) in the CSS. This dark blue color doesn't appear to use CSS variables and may not respect the theme's color mode (light/dark). Consider using CSS variables for colors to maintain consistency with the rest of the site and support dark mode properly.

Copilot uses AI. Check for mistakes.
text-decoration: none !important;
background-color: rgba(0, 0, 0, 0.05);
}

Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The banner links don't have visible focus styles defined. Users navigating with keyboard will not see a clear indicator when the banner link is focused. Consider adding a :focus or :focus-visible style to provide clear visual feedback for keyboard navigation, which is important for accessibility.

Suggested change
.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;
}

Copilot uses AI. Check for mistakes.
.banner:hover * {
text-decoration: none !important;
}

.banner:hover .underline {
text-decoration: underline !important;
text-underline-offset: 2px;
}

.hiddenOnMobile {
display: inline;
}

@media (max-width: 768px) {
.hiddenOnMobile {
display: none;
}
}

.icon {
flex-shrink: 0;
width: 28px;
height: 28px;
transform: rotate(-90deg);
display: block;
}

.underline {
text-decoration: underline;
text-underline-offset: 2px;
}
6 changes: 5 additions & 1 deletion packages/docs-gesture-handler/src/theme/Navbar/index.js
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 = {
Expand All @@ -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 }}>
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wrapper div uses inline styles for flexbox layout. While this works, it would be more maintainable to define these styles in a CSS module file (e.g., in the Navbar's styles.module.css or a separate file) to keep styling concerns separated from the component logic and make it easier to adjust if needed.

Copilot uses AI. Check for mistakes.
<TopPromoRotator />
<Navbar heroImages={heroImages} titleImages={titleImages} {...props} />
</div>
);
}
Loading