-
-
Notifications
You must be signed in to change notification settings - Fork 52
Docs Improvements - UX - 13 Sep #1562
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds Accordion API docs for Header and Content; normalizes many docs default values and tailwind color classes; removes layout-image and background animation; introduces accessibility/global UX styles; adds multiple client-side utility components (navigation, search, TOC, shortcuts, accessibility, lazy loader, progress); switches NavBar to ModernNavigation; hardens PostHogProvider; updates landing/home composition; adds lucide-react. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant App
participant PostHogProvider
participant PostHog
User->>App: Load page
App->>PostHogProvider: Render
alt SSR
PostHogProvider-->>App: Skip init (no window)
else Browser
PostHogProvider->>PostHog: init(apiKey, options)
alt Invalid/placeholder key
PostHogProvider-->>App: Do not render provider
else Init errors or request failures (>=3)
PostHogProvider->>PostHog: on_request_error()
PostHogProvider-->>App: Disable analytics
else Success
PostHogProvider-->>App: Render PHProvider(children)
end
end
sequenceDiagram
autonumber
actor User
participant Router as Next Router
participant ModernNav as ModernNavigation
participant Widgets as {SearchBar,QuickActions,Accessibility,Shortcuts}
participant Sections as {docsSections|default}
Router-->>ModernNav: pathname change
ModernNav->>ModernNav: choose Sections (includes "/docs/" ? docsSections : default)
ModernNav->>Widgets: render utilities (search/actions/a11y/shortcuts)
ModernNav->>Sections: render categories list
User->>SearchBar: type query
SearchBar->>SearchBar: filter mock data, show results
User->>QuickActions: click action (e.g., copy)
QuickActions-->>User: feedback (Copied!)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
CoverageThis report compares the PR with the base branch. “Δ” shows how the PR affects each metric.
Coverage improved or stayed the same. Great job! Run |
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.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
docs/app/docs/components/callout/examples/CalloutVariants.tsx (1)
12-14
: Fix grammar in user-facing copy.Pluralize “callout” and clarify wording.
Apply:
- soft: 'Soft callout have a soft background color and a border.', - outline: 'Outline callout have a border and a background color.', + soft: 'Soft callouts have a soft background color and a border.', + outline: 'Outline callouts have a border and a background color.',docs/app/landingComponents/MusicAppPlayerDemo.js (2)
69-69
: Remove stray semicolon after component; it renders a literal “;” in the UI.This is user-visible.
- <MusicBars />; + <MusicBars />
37-37
: Avoid conflicting height utilities (h-4 vs h-[...]).Both set height; outcome can be unpredictable in Tailwind. Keep only one.
- return <div key={index} className={`h-4 rounded-md ${bar} ${index === 12 ? 'bg-gray-950 w-1.5' : 'bg-gray-700 w-1'}`} ></div> + return <div key={index} className={`rounded-md ${bar} ${index === 12 ? 'bg-gray-950 w-1.5' : 'bg-gray-700 w-1'}`}></div>
♻️ Duplicate comments (4)
docs/app/playground/components/SeparatorPlayground.js (1)
15-15
: text-gray-1000 token check (duplicate).Same note as in TopArtists.tsx: ensure gray-1000 exists in your Tailwind theme; otherwise this won’t render a custom color.
Refer to the tailwind token verification script in TopArtists.tsx.
docs/app/landingComponents/AddToCartDemo.js (1)
30-32
: text-gray-1000 token check (duplicate).Both updated text classes rely on gray-1000. Verify the token exists in Tailwind.
Use the previously provided script to confirm the theme token.
docs/app/docs/components/em/docs/EmExample.tsx (1)
7-7
: text-gray-1000 token check (duplicate).Ensure gray-1000 exists in Tailwind to avoid silent fallback.
Use the previously provided verification script.
docs/app/playground/page.tsx (1)
18-18
: text-gray-1000 token check (duplicate).Confirm gray-1000 exists in Tailwind.
Use the earlier verification script.
🧹 Nitpick comments (39)
docs/app/landingComponents/ToolbarDemo.js (1)
99-101
: Optional: make the field non-controlled demo with placeholder.Avoids a non-editable controlled input in the demo and removes the no-op onChange.
- <input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950' /> + <input placeholder="Search..." className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950' />docs/app/showcase/music-app/helpers/sections/TopArtists.tsx (2)
63-63
: Improve image a11y and perf.Use descriptive alt text and enable lazy loading.
Apply:
- <img style={{ width: DIMENSIONS, height: DIMENSIONS }} className='rounded-md shadow-sm border-2 border-gray-200' src={artist.image} alt='Album Cover' /> + <img + style={{ width: DIMENSIONS, height: DIMENSIONS }} + className='rounded-md shadow-sm border-2 border-gray-200' + src={artist.image} + alt={`${artist.name} — ${artist.album} cover`} + loading="lazy" + decoding="async" + />
59-60
: Avoid any; add lightweight props typing.Give Artist strong types without importing React types.
Apply:
-const Artist: any = ({ artist }: any) => { - const DIMENSIONS = 160 +interface ArtistData { name: string; album: string; image: string } +type ArtistProps = { artist: ArtistData } +const Artist = ({ artist }: ArtistProps) => { + const DIMENSIONS = 160 as constdocs/app/docs/components/callout/examples/CalloutVariants.tsx (1)
20-20
: Remove stray console.log in examples.Avoid noisy logs in docs.
Apply:
- console.log(variant); + // console.log(variant);docs/app/docs/components/badge/examples/BadgeVariants.tsx (1)
38-38
: Remove stray space in JSX.Minor cleanup.
- <span >badge</span> + <span>badge</span>docs/app/docs/components/accordion/docs/example_1.tsx (1)
31-31
: Avoid leaking non-standard props to DOM; prefer data-attr or remove.Unless Accordion.Content explicitly supports index, this will become a non-standard DOM attribute.
- <Accordion.Content index={index}>{item.content}</Accordion.Content> + <Accordion.Content data-index={index}>{item.content}</Accordion.Content>If the prop is unused, omit it entirely.
docs/app/docs/components/card/docs/component_api/card.tsx (1)
25-25
: Empty-string defaults are consistent, but ‘children’ isn’t a string—confirm intended representation.If docs aim to show “no default,” consider a neutral symbol (e.g., —) for non-string props to avoid implying string defaults.
- default: "''", + default: "—",If uniformity across the site requires "''", keep as-is and ignore.
Also applies to: 41-41, 49-49
docs/app/landingComponents/MusicAppPlayerDemo.js (1)
25-25
: Remove unused prop and index shadowing in MusicBars.The function param isn’t used and is shadowed by the map callback.
-const MusicBars = ({ index }) => { +const MusicBars = () => {And adjust the map callback to avoid confusion:
- {randomHeightBars.map((bar, index) => { - return <div key={index} className={`rounded-md ${bar} ${index === 12 ? 'bg-gray-950 w-1.5' : 'bg-gray-700 w-1'}`} ></div> + {randomHeightBars.map((bar, i) => { + return <div key={i} className={`rounded-md ${bar} ${i === 12 ? 'bg-gray-950 w-1.5' : 'bg-gray-700 w-1'}`}></div> })}docs/app/showcase/music-app/helpers/MusicSidebar.js (2)
14-14
: Remove redundant hover color.Base text-gray-1000 already matches hover color.
- <Text className={`${active?'!font-medium text-gray-1000':'font-light text-gray-1000 hover:text-gray-1000 !hover:font-medium'}`}>{label}</Text> + <Text className={`${active ? '!font-medium text-gray-1000' : 'font-light text-gray-1000 !hover:font-medium'}`}>{label}</Text>
11-15
: Optional: use semantic controls for clickable items.MenuItem is a div with click semantics; consider a button/anchor (or add role="button" and keyboard handlers) for a11y.
docs/app/docs/components/accordion/docs/component_api/content.tsx (2)
24-26
: Align default display with the rest of Accordion docsUse "''" (empty string display) for className to match Item/Root/Trigger.
Apply this diff:
- default: "--", + default: "''",
1-36
: Add a small TS shape to prevent drift across API tablesDefining a lightweight type for columns/rows reduces typos and keeps tables consistent.
You can add a type above and annotate the object:
type ColumnId = 'prop' | 'type' | 'default' interface Column { name: string; id: ColumnId } interface Row { prop: { name: string; info_tooltips: string }; type: string; default: string } interface ApiTable { name: string; description: string; columns: Column[]; data: Row[] } const data: ApiTable = { /* existing object */ }docs/components/TableOfContents.tsx (1)
69-84
: Improve semantics and accessibilityUse a list for navigation structure and aria-current on the active link.
Apply this diff:
- <nav className="space-y-1"> - {tocItems.map((item) => ( - <Link - key={item.id} - href={`#${item.id}`} - className={`block text-sm py-1 px-2 rounded ${ - activeId === item.id - ? 'text-green-600 bg-green-50 font-medium' - : 'text-gray-600 hover:text-gray-1000 hover:bg-gray-50' - }`} - style={{ marginLeft: `${(item.level - 1) * 12}px` }} - > - {item.title} - </Link> - ))} - </nav> + <nav className="space-y-1" aria-label="Table of contents"> + <ul> + {tocItems.map((item) => ( + <li key={item.id} style={{ marginLeft: `${(item.level - 1) * 12}px` }}> + <Link + href={`#${item.id}`} + aria-current={activeId === item.id ? 'true' : undefined} + className={`block text-sm py-1 px-2 rounded ${ + activeId === item.id + ? 'text-green-600 bg-green-50 font-medium' + : 'text-gray-600 hover:text-gray-1000 hover:bg-gray-50' + }`} + > + {item.title} + </Link> + </li> + ))} + </ul> + </nav>docs/components/Performance/LazyComponent.tsx (3)
4-4
: Remove unused importText is not used.
Apply this diff:
-import Text from '@radui/ui/Text'
6-10
: Tighten types and allow passing props to the lazy componentEnsure the dynamic import returns a default component and forward props when needed.
Apply this diff:
-interface LazyComponentProps { - component: () => Promise<any> - fallback?: React.ReactNode - className?: string -} +interface LazyComponentProps<P = Record<string, unknown>> { + component: () => Promise<{ default: React.ComponentType<P> }> + fallback?: React.ReactNode + className?: string + props?: P +} @@ -const LazyComponent = ({ component, fallback, className }: LazyComponentProps) => { +const LazyComponent = <P,>({ component, fallback, className, props }: LazyComponentProps<P>) => { const LazyLoadedComponent = lazy(component) @@ - <div className={className}> - <LazyLoadedComponent /> + <div className={className}> + <LazyLoadedComponent {...(props as P)} /> </div>Also applies to: 22-29
12-20
: Minor a11y for skeleton fallbackMark the placeholder as a status region.
Apply this diff:
-const DefaultFallback = () => ( - <Card className="p-8 border border-gray-200"> - <div className="space-y-4"> +const DefaultFallback = () => ( + <Card className="p-8 border border-gray-200" role="status" aria-live="polite" aria-busy="true"> + <div className="space-y-4" aria-hidden="true"> <div className="h-4 bg-gray-200 rounded w-3/4"></div> <div className="h-4 bg-gray-200 rounded w-1/2"></div> <div className="h-4 bg-gray-200 rounded w-2/3"></div> </div> </Card> )docs/app/landingComponents/SponsorsSection.js (2)
11-12
: Hover state unused; add visual affordance or remove stateUse the hover state to enhance feedback on focus/hover.
Apply this diff:
- <Card + <Card key={sponsor.name} - className={`p-6 cursor-pointer`} + className={`p-6 cursor-pointer transition ${hoveredSponsor === sponsor.name ? 'ring-2 ring-green-400' : 'hover:ring-1 ring-gray-300'}`} onMouseEnter={() => setHoveredSponsor(sponsor.name)} onMouseLeave={() => setHoveredSponsor(null)} - onClick={() => handleSponsorClick(sponsor.website)} >Also applies to: 34-34, 44-52
2-9
: Use Next.js Link for internal nav instead of window.locationThis preserves client-side routing and SPA behavior.
Apply this diff:
-import Heading from "@radui/ui/Heading" +import Heading from "@radui/ui/Heading" import Text from "@radui/ui/Text" import Button from "@radui/ui/Button" import Badge from "@radui/ui/Badge" import Image from 'next/image' import Card from "@radui/ui/Card" +import Link from 'next/link' @@ - <div className="text-center"> - <Button - variant="outline" - color="green" - onClick={() => window.location.href = '/sponsors'} - className="" - > - View all sponsors - </Button> - </div> + <div className="text-center"> + <Link href="/sponsors"> + <Button variant="outline" color="green">View all sponsors</Button> + </Link> + </div>Also applies to: 72-81
docs/components/ReadingProgress.tsx (1)
21-27
: Add progressbar semantics for accessibilityExpose progress to assistive tech.
- return ( - <div className="fixed top-0 left-0 w-full h-1 bg-gray-100 z-50"> + return ( + <div + className="fixed top-0 left-0 w-full h-1 bg-gray-100 z-50" + role="progressbar" + aria-valuemin={0} + aria-valuemax={100} + aria-valuenow={Math.round(progress)} + aria-label="Reading progress" + >docs/components/QuickActions.tsx (1)
71-84
: Improve a11y on clear/copy buttonsAdd labels and prevent accidental form submit.
- <button + <button + type="button" onClick={() => { setQuery('') setResults([]) }} className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600" + aria-label="Clear search" > <X className="w-4 h-4" /> </button> @@ - <Button + <Button variant="ghost" size="small" onClick={copyInstallCommand} className="p-1" + aria-label="Copy install command" > <Copy className="w-3 h-3" /> </Button>docs/app/docs/components/accordion/docs/component_api/header.tsx (2)
1-36
: Type the data structure for safety and editor hintsDefine a type and annotate the data object.
-const data = { +type ApiColumn = { name: string; id: string } +type ApiRow = { + prop: { name: string; info_tooltips: string } + type: string + default: string +} +type ApiTable = { + name: string + description: string + columns: ApiColumn[] + data: ApiRow[] +} + +const data: ApiTable = {
22-23
: Minor copy consistencyConsider “Accordion Header” instead of “AccordionHeader” for display strings.
Also applies to: 30-31
docs/components/navigation/Breadcrumbs.tsx (2)
16-21
: Decode segments and memoize breadcrumbsHandle URL-encoded paths; avoid recomputation.
- const generateBreadcrumbs = (): BreadcrumbItem[] => { + const generateBreadcrumbs = (): BreadcrumbItem[] => { const segments = pathname.split('/').filter(Boolean) @@ - let label = segment + const decoded = (() => { + try { return decodeURIComponent(segment) } catch { return segment } + })() + let label = decoded .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ')Optionally wrap with useMemo keyed by pathname.
Also applies to: 28-33
70-79
: Mark current page for screen readersAdd aria-current on the active crumb.
- {item.isActive ? ( - <Text className="text-gray-1000 font-medium">{item.label}</Text> - ) : ( + {item.isActive ? ( + <Text className="text-gray-1000 font-medium" aria-current="page"> + {item.label} + </Text> + ) : ( <Link href={item.href} className="text-gray-600 hover:text-gray-1000" > {item.label} </Link> )}docs/components/KeyboardShortcuts.tsx (1)
51-80
: Render help panel via React state, not inline styleUse hidden/aria attributes for a11y.
- <Card className="p-4 border border-gray-200" id="keyboard-shortcuts-help" style={{ display: 'none' }}> + <Card + className="p-4 border border-gray-200" + id="keyboard-shortcuts-help" + hidden={!open} + aria-hidden={!open} + >docs/components/SearchBar.tsx (2)
70-80
: Clear button should be announced and not submit forms- {query && ( - <button + {query && ( + <button + type="button" onClick={() => { setQuery('') setResults([]) }} className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600" + aria-label="Clear search" > <X className="w-4 h-4" /> </button> )}
83-109
: Announce results with listbox/option roles- {isOpen && results.length > 0 && ( - <Card className="absolute top-full left-0 right-0 mt-2 p-4 border border-gray-200 shadow-lg z-50"> - <div className="space-y-2"> - {results.map((result, index) => ( - <Link - key={index} - href={result.href} - className="block p-2 hover:bg-gray-50 rounded" - onClick={() => { - setIsOpen(false) - setQuery('') - }} - > + {isOpen && results.length > 0 && ( + <Card className="absolute top-full left-0 right-0 mt-2 p-4 border border-gray-200 shadow-lg z-50"> + <ul id="docs-search-listbox" role="listbox" className="space-y-2"> + {results.map((result, index) => ( + <li key={index} role="option" aria-selected="false"> + <Link + href={result.href} + className="block p-2 hover:bg-gray-50 rounded" + onClick={() => { + setIsOpen(false) + setQuery('') + }} + > <div className="flex justify-between items-start"> <div> <Text className="font-medium text-gray-1000">{result.title}</Text> {result.description && ( <Text className="text-sm text-gray-600">{result.description}</Text> )} </div> <Text className="text-xs text-gray-500 ml-2">{result.category}</Text> </div> - </Link> + </Link> + </li> ))} - </div> + </ul> </Card> )}docs/components/PostHogProvider.tsx (1)
41-57
: Fully disable on repeated errorsAlso listen for transport failures and opt out to stop further sends.
- on_request_error: (failedRequest) => { + on_request_error: (_res) => { requestCount++ @@ if (requestCount >= maxFailedRequests) { - setPostHogEnabled(false) + setPostHogEnabled(false) + try { posthog.opt_out_capturing() } catch {} if (process.env.NODE_ENV === "development") { console.warn('PostHog disabled due to repeated network failures') } return } }, + on_xhr_error: () => { + requestCount++ + if (requestCount >= maxFailedRequests) { + setPostHogEnabled(false) + try { posthog.opt_out_capturing() } catch {} + } + },Docs: on_request_error exists; opting out halts capture. (posthog.com)
docs/components/AccessibilityFeatures.tsx (1)
11-12
: Honor OS motion preference and set a sensible default.Initialize reducedMotion from prefers-reduced-motion and subscribe to changes (without overriding a deliberate user toggle later if you persist settings).
@@ - const [reducedMotion, setReducedMotion] = useState(false) + const [reducedMotion, setReducedMotion] = useState(false) @@ + // Initialize from OS setting once + useEffect(() => { + const media = window.matchMedia('(prefers-reduced-motion: reduce)'); + if (media.matches) setReducedMotion(true); + const onChange = (e: MediaQueryListEvent) => setReducedMotion(e.matches); + media.addEventListener?.('change', onChange); + return () => media.removeEventListener?.('change', onChange); + }, []);If you want persistence, I can add a small localStorage hook to remember choices.
Also applies to: 36-47
docs/components/navigation/ModernNavigation.tsx (2)
15-26
: Use Next.js Link for internal routes and wire up customSections.
- Swap plain anchors for Next.js
<Link>
to avoid full page reloads.- Respect
customSections
when provided.- Prefer stable keys over index.
@@ -const ModernNavigation = ({ customSections }: { customSections?: any }) => { - const defaultSections = [ +import Link from 'next/link'; +const ModernNavigation = ({ customSections }: { customSections?: any }) => { + const defaultSections = [ @@ - const [sections, setSections] = useState(docsSections); + const [sections, setSections] = useState(customSections ?? docsSections); @@ - if (pathname.includes("/docs/")) { - setSections(docsSections) + if (customSections) { + setSections(customSections); + } else if (pathname.includes("/docs/")) { + setSections(docsSections); } else { - setSections(defaultSections) + setSections(defaultSections); } - }, [pathname]) + }, [pathname, customSections]); @@ - <a - href="/docs/first-steps/installation" - className="block text-sm text-gray-950 hover:text-gray-1000" - > + <Link href="/docs/first-steps/installation" className="block text-sm text-gray-950 hover:text-gray-1000"> Installation - </a> + </Link> @@ - <a - href="/playground" - className="block text-sm text-gray-950 hover:text-gray-1000" - > + <Link href="/playground" className="block text-sm text-gray-950 hover:text-gray-1000"> Playground - </a> + </Link> @@ - <a - href="/sponsors" - className="block text-sm text-gray-950 hover:text-gray-1000" - > + <Link href="/sponsors" className="block text-sm text-gray-950 hover:text-gray-1000"> Sponsors - </a> + </Link>Optional: when mapping sections, use
key={section.title}
instead of index for stability.Also applies to: 33-41, 68-91
8-9
: Remove unused import.
Badge
isn’t used here.-import Badge from "@radui/ui/Badge"
docs/components/Main/NavBar/index.js (1)
4-5
: Drop legacy Navigation import.
Navigation
is no longer used.-import Navigation from "@/components/navigation/Navigation" import ModernNavigation from "@/components/navigation/ModernNavigation"
docs/app/globals.scss (2)
10-12
: Correct text-rendering value casing (minor).Use
optimizeLegibility
for consistency with common usage.- text-rendering: optimizelegibility; + text-rendering: optimizeLegibility;
31-33
: Enhance cross-browser a11y defaults (optional).
- Add Firefox scrollbar colors.
- Provide a baseline respects-OS reduced motion without needing the JS toggle.
::-webkit-scrollbar-thumb:hover { background: var(--rad-ui-color-gray-400); } + +/* Firefox scrollbar */ +* { + scrollbar-width: thin; + scrollbar-color: var(--rad-ui-color-gray-300) var(--rad-ui-color-gray-100); +} @@ .reduced-motion * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } + +/* Respect OS preference by default */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +}Also applies to: 46-50
docs/components/layout/Documentation/ModernDocumentation.js (3)
15-21
: Remove unused arrow icon components or export them.They’re defined but unused.
-const LeftArrow = () => { - return <svg width="18" height="18" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.85355 3.14645C7.04882 3.34171 7.04882 3.65829 6.85355 3.85355L3.70711 7H12.5C12.7761 7 13 7.22386 13 7.5C13 7.77614 12.7761 8 12.5 8H3.70711L6.85355 11.1464C7.04882 11.3417 7.04882 11.6583 6.85355 11.8536C6.65829 12.0488 6.34171 12.0488 6.14645 11.8536L2.14645 7.85355C1.95118 7.65829 1.95118 7.34171 2.14645 7.14645L6.14645 3.14645C6.34171 2.95118 6.65829 2.95118 6.85355 3.14645Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>; -}; - -const RightArrow = () => { - return <svg width="18" height="18" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.14645 3.14645C8.34171 2.95118 8.65829 2.95118 8.85355 3.14645L12.8536 7.14645C13.0488 7.34171 13.0488 7.65829 12.8536 7.85355L8.85355 11.8536C8.65829 12.0488 8.34171 12.0488 8.14645 11.8536C7.95118 11.6583 7.95118 11.3417 8.14645 11.1464L11.2929 8H2.5C2.22386 8 2 7.77614 2 7.5C2 7.22386 2.22386 7 2.5 7H11.2929L8.14645 3.85355C7.95118 3.65829 7.95118 3.34171 8.14645 3.14645Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>; -}; +// (optional) export or remove LeftArrow/RightArrow if needed later
35-40
: Slugify heading IDs to ensure valid anchors.Passing raw titles as IDs can yield invalid/duplicate anchors. Use a simple slug.
- <BookMarkLink id={title}> + <BookMarkLink id={title.toLowerCase().replace(/\s+/g, '-')} > @@ - <BookMarkLink id={title}> + <BookMarkLink id={title.toLowerCase().replace(/\s+/g, '-')} >If you prefer, we can centralize a small
slugify(title)
util and reuse it across the docs components.Also applies to: 89-95
23-24
: Remove unused prop.
currentPage
isn’t used.-const ModernDocumentation = ({ title = '', description = '', currentPage = undefined, children }) => { +const ModernDocumentation = ({ title = '', description = '', children }) => {docs/app/landingComponents/HeroSection.js (1)
125-127
: Prefer client-side routing or safe window.open for CTA.These are internal links; use Link/router for prefetch and a11y. If you must use window.open, add noopener/noreferrer.
Option A (preferred): replace button with Link
- <button onClick={onClickHandler} className="flex items-center space-x-2 text-gray-1000 hover:space-x-4 hover:text-gray-1000"> - <span>{cta}</span> <ForwardArrowIcon/> - </button> + <Link href={ctaLink} className="flex items-center space-x-2 text-gray-1000 hover:space-x-4 hover:text-gray-1000"> + <span>{cta}</span> <ForwardArrowIcon/> + </Link>Add import at top:
+import Link from "@radui/ui/Link"
Option B: keep button, harden window.open
- const onClickHandler = () => { - // open link - > /docs/installation - window.open(ctaLink, '_blank'); - } + const onClickHandler = () => { + window.open(ctaLink, '_blank', 'noopener,noreferrer'); + }docs/app/page.tsx (1)
60-80
: Minor key choice nit.Use a stable key (e.g., stat.label) instead of array index.
- {stats.map((stat, index) => ( - <div key={index} className="text-center"> + {stats.map((stat) => ( + <div key={stat.label} className="text-center">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
docs/pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (50)
docs/app/docs/components/accordion/docs/codeUsage.js
(2 hunks)docs/app/docs/components/accordion/docs/component_api/content.tsx
(1 hunks)docs/app/docs/components/accordion/docs/component_api/header.tsx
(1 hunks)docs/app/docs/components/accordion/docs/component_api/item.tsx
(1 hunks)docs/app/docs/components/accordion/docs/component_api/root.tsx
(1 hunks)docs/app/docs/components/accordion/docs/component_api/trigger.tsx
(1 hunks)docs/app/docs/components/accordion/docs/example_1.tsx
(1 hunks)docs/app/docs/components/accordion/page.mdx
(1 hunks)docs/app/docs/components/avatar/docs/codeUsage.js
(1 hunks)docs/app/docs/components/avatar/docs/component_api/fallback.tsx
(1 hunks)docs/app/docs/components/avatar/docs/component_api/image.tsx
(1 hunks)docs/app/docs/components/avatar/docs/component_api/root.tsx
(2 hunks)docs/app/docs/components/badge/docs/codeUsage.js
(1 hunks)docs/app/docs/components/badge/examples/BadgeVariants.tsx
(1 hunks)docs/app/docs/components/button/docs/component_api/button.tsx
(1 hunks)docs/app/docs/components/button/examples/ButtonVariants.tsx
(1 hunks)docs/app/docs/components/callout/examples/CalloutVariants.tsx
(1 hunks)docs/app/docs/components/card/docs/component_api/card.tsx
(2 hunks)docs/app/docs/components/card/page.mdx
(1 hunks)docs/app/docs/components/em/docs/EmExample.tsx
(1 hunks)docs/app/docs/layout.tsx
(0 hunks)docs/app/globals.scss
(1 hunks)docs/app/landingComponents/AddToCartDemo.js
(1 hunks)docs/app/landingComponents/HeroSection.js
(4 hunks)docs/app/landingComponents/MusicAppPlayerDemo.js
(2 hunks)docs/app/landingComponents/SponsorsSection.js
(1 hunks)docs/app/landingComponents/ToolbarDemo.js
(1 hunks)docs/app/page.tsx
(3 hunks)docs/app/playground/components/EmPlayground.js
(1 hunks)docs/app/playground/components/HeadingPlayground.js
(1 hunks)docs/app/playground/components/SeparatorPlayground.js
(1 hunks)docs/app/playground/page.tsx
(1 hunks)docs/app/showcase/music-app/helpers/MusicSidebar.js
(2 hunks)docs/app/showcase/music-app/helpers/sections/PlaylistHero.tsx
(1 hunks)docs/app/showcase/music-app/helpers/sections/TopArtists.tsx
(1 hunks)docs/components/AccessibilityFeatures.tsx
(1 hunks)docs/components/KeyboardShortcuts.tsx
(1 hunks)docs/components/Main/NavBar/index.js
(2 hunks)docs/components/Performance/LazyComponent.tsx
(1 hunks)docs/components/PostHogProvider.tsx
(2 hunks)docs/components/QuickActions.tsx
(1 hunks)docs/components/ReadingProgress.tsx
(1 hunks)docs/components/SearchBar.tsx
(1 hunks)docs/components/TableOfContents.tsx
(1 hunks)docs/components/layout/Documentation/Documentation.js
(1 hunks)docs/components/layout/Documentation/ModernDocumentation.js
(1 hunks)docs/components/layout/Documentation/helpers/CodeBlock.js
(0 hunks)docs/components/navigation/Breadcrumbs.tsx
(1 hunks)docs/components/navigation/ModernNavigation.tsx
(1 hunks)docs/package.json
(1 hunks)
💤 Files with no reviewable changes (2)
- docs/components/layout/Documentation/helpers/CodeBlock.js
- docs/app/docs/layout.tsx
🧰 Additional context used
📓 Path-based instructions (3)
docs/**/*.{ts,tsx}
📄 CodeRabbit inference engine (docs/.cursor/rules/posthog-integration.mdc)
In TypeScript, store feature flag names in an enum
Files:
docs/app/showcase/music-app/helpers/sections/PlaylistHero.tsx
docs/app/showcase/music-app/helpers/sections/TopArtists.tsx
docs/app/docs/components/accordion/docs/component_api/root.tsx
docs/app/docs/components/callout/examples/CalloutVariants.tsx
docs/components/ReadingProgress.tsx
docs/components/AccessibilityFeatures.tsx
docs/components/QuickActions.tsx
docs/app/docs/components/button/docs/component_api/button.tsx
docs/app/docs/components/badge/examples/BadgeVariants.tsx
docs/app/docs/components/em/docs/EmExample.tsx
docs/app/docs/components/card/docs/component_api/card.tsx
docs/app/docs/components/button/examples/ButtonVariants.tsx
docs/app/docs/components/avatar/docs/component_api/fallback.tsx
docs/app/docs/components/accordion/docs/component_api/item.tsx
docs/app/docs/components/avatar/docs/component_api/root.tsx
docs/app/docs/components/avatar/docs/component_api/image.tsx
docs/app/playground/page.tsx
docs/app/docs/components/accordion/docs/example_1.tsx
docs/components/TableOfContents.tsx
docs/components/Performance/LazyComponent.tsx
docs/app/docs/components/accordion/docs/component_api/content.tsx
docs/app/docs/components/accordion/docs/component_api/header.tsx
docs/components/navigation/ModernNavigation.tsx
docs/components/navigation/Breadcrumbs.tsx
docs/components/KeyboardShortcuts.tsx
docs/app/docs/components/accordion/docs/component_api/trigger.tsx
docs/components/PostHogProvider.tsx
docs/components/SearchBar.tsx
docs/app/page.tsx
docs/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (docs/.cursor/rules/posthog-integration.mdc)
docs/**/*.{ts,tsx,js,jsx}
: Write enum/const object members for feature flag names in UPPERCASE_WITH_UNDERSCORE
Gate flag-dependent code with a check that verifies the flag’s values are valid and expected before use
If a custom person/event property is referenced in two or more places, define it in a central enum (TS) or const object (JS) and reuse that reference
Files:
docs/app/showcase/music-app/helpers/sections/PlaylistHero.tsx
docs/app/showcase/music-app/helpers/sections/TopArtists.tsx
docs/app/docs/components/accordion/docs/component_api/root.tsx
docs/app/docs/components/callout/examples/CalloutVariants.tsx
docs/components/ReadingProgress.tsx
docs/app/playground/components/HeadingPlayground.js
docs/components/AccessibilityFeatures.tsx
docs/components/QuickActions.tsx
docs/app/docs/components/button/docs/component_api/button.tsx
docs/app/docs/components/badge/examples/BadgeVariants.tsx
docs/app/docs/components/em/docs/EmExample.tsx
docs/app/landingComponents/AddToCartDemo.js
docs/components/layout/Documentation/Documentation.js
docs/app/docs/components/card/docs/component_api/card.tsx
docs/app/landingComponents/ToolbarDemo.js
docs/app/docs/components/button/examples/ButtonVariants.tsx
docs/app/playground/components/SeparatorPlayground.js
docs/app/showcase/music-app/helpers/MusicSidebar.js
docs/app/docs/components/avatar/docs/component_api/fallback.tsx
docs/app/landingComponents/SponsorsSection.js
docs/app/docs/components/accordion/docs/component_api/item.tsx
docs/app/docs/components/avatar/docs/component_api/root.tsx
docs/app/docs/components/avatar/docs/component_api/image.tsx
docs/app/docs/components/accordion/docs/codeUsage.js
docs/components/Main/NavBar/index.js
docs/app/playground/page.tsx
docs/app/docs/components/accordion/docs/example_1.tsx
docs/components/TableOfContents.tsx
docs/app/docs/components/avatar/docs/codeUsage.js
docs/components/Performance/LazyComponent.tsx
docs/app/landingComponents/HeroSection.js
docs/app/docs/components/accordion/docs/component_api/content.tsx
docs/app/landingComponents/MusicAppPlayerDemo.js
docs/app/docs/components/accordion/docs/component_api/header.tsx
docs/components/navigation/ModernNavigation.tsx
docs/components/navigation/Breadcrumbs.tsx
docs/app/playground/components/EmPlayground.js
docs/components/KeyboardShortcuts.tsx
docs/app/docs/components/accordion/docs/component_api/trigger.tsx
docs/components/PostHogProvider.tsx
docs/app/docs/components/badge/docs/codeUsage.js
docs/components/SearchBar.tsx
docs/app/page.tsx
docs/components/layout/Documentation/ModernDocumentation.js
docs/**/*.{js,jsx}
📄 CodeRabbit inference engine (docs/.cursor/rules/posthog-integration.mdc)
In JavaScript, store feature flag names as strings in a constant object to simulate an enum, and use a consistent naming convention
Files:
docs/app/playground/components/HeadingPlayground.js
docs/app/landingComponents/AddToCartDemo.js
docs/components/layout/Documentation/Documentation.js
docs/app/landingComponents/ToolbarDemo.js
docs/app/playground/components/SeparatorPlayground.js
docs/app/showcase/music-app/helpers/MusicSidebar.js
docs/app/landingComponents/SponsorsSection.js
docs/app/docs/components/accordion/docs/codeUsage.js
docs/components/Main/NavBar/index.js
docs/app/docs/components/avatar/docs/codeUsage.js
docs/app/landingComponents/HeroSection.js
docs/app/landingComponents/MusicAppPlayerDemo.js
docs/app/playground/components/EmPlayground.js
docs/app/docs/components/badge/docs/codeUsage.js
docs/components/layout/Documentation/ModernDocumentation.js
🧬 Code graph analysis (6)
docs/app/docs/components/accordion/docs/example_1.tsx (1)
docs/app/docs/components/accordion/docs/anatomy.tsx (1)
Accordion
(3-14)
docs/components/TableOfContents.tsx (1)
docs/components/layout/Documentation/ModernDocumentation.js (1)
TableOfContents
(102-125)
docs/components/navigation/ModernNavigation.tsx (3)
docs/components/Main/NavBar/index.js (1)
useContext
(12-12)docs/components/Main/NavBar/NavBarContext.tsx (1)
NavBarContext
(9-12)docs/components/Main/Main.js (1)
isDocsNavOpen
(17-17)
docs/components/navigation/Breadcrumbs.tsx (1)
docs/utils/seo/generateBreadcrumbData.ts (1)
BreadcrumbItem
(1-4)
docs/app/page.tsx (1)
docs/app/landingComponents/SponsorsSection.js (1)
SponsorsSection
(10-84)
docs/components/layout/Documentation/ModernDocumentation.js (4)
docs/components/layout/Documentation/helpers/CodeBlock.js (2)
CodeBlock
(35-118)code
(37-37)docs/components/layout/Documentation/helpers/ComponentHero/ComponentHero.js (1)
ComponentHero
(10-45)docs/components/layout/Documentation/helpers/ComponentFeatures/ComponentFeatures.js (1)
ComponentFeatures
(11-35)docs/components/layout/Documentation/helpers/DocsTable.js (1)
DocsTable
(85-132)
🪛 Biome (2.1.2)
docs/app/landingComponents/ToolbarDemo.js
[error] 99-101: input is a void element tag and must not have children.
Unsafe fix: Remove the children.
(lint/correctness/noVoidElementsWithChildren)
docs/components/layout/Documentation/ModernDocumentation.js
[error] 102-102: Shouldn't redeclare 'TableOfContents'. Consider to delete it or rename it.
'TableOfContents' is defined here:
(lint/suspicious/noRedeclare)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: coverage
🔇 Additional comments (38)
docs/app/playground/components/EmPlayground.js (1)
13-13
: LGTM — token update only.docs/package.json (1)
31-31
: Verify lucide-react@0.544.0 — peer deps OK; confirm package exports/ESM for Next 15 SSR
- Verified:
npm view lucide-react@0.544.0
reports peerDependencies include React (supports ^19.0.0) and license = ISC.- Action required: the npm query did not return the package "exports"/"type" fields — confirm the package.json (npm registry or packages/lucide-react/package.json on the repo) for an export map and "type" (ESM/CJS entrypoints) to ensure Next 15/SSR/Edge compatibility and CJS↔ESM interop.
docs/app/docs/components/button/examples/ButtonVariants.tsx (1)
45-45
: LGTM — visual text color tweak is consistent with palette changes.docs/app/showcase/music-app/helpers/sections/PlaylistHero.tsx (1)
62-62
: LGTM — input text color aligns with system-wide token updates.docs/components/layout/Documentation/Documentation.js (1)
28-28
: LGTM — typography token normalized to text-gray-1000.docs/app/docs/components/button/docs/component_api/button.tsx (1)
25-25
: LGTM — default value standardized to empty string for docs table.docs/app/docs/components/accordion/docs/component_api/root.tsx (1)
25-25
: Normalize empty-string default for className across Accordion API docs
- Already consistent: docs/app/docs/components/accordion/docs/component_api/item.tsx, trigger.tsx, root.tsx use
default : "''"
.- header.tsx and content.tsx were not matched by the search — confirm whether those files exist and, if so, align their className default to
default : "''"
.docs/app/playground/components/HeadingPlayground.js (1)
14-17
: Verify palette tokens: gray-1000 vs gray-950.
- gray-950 is standard in Tailwind; no action needed.
- gray-1000 is non-standard; confirm it’s defined in your theme.
Use the earlier tailwind token script to confirm gray-1000.
docs/app/docs/components/callout/examples/CalloutVariants.tsx (1)
32-32
: LGTM on color tweak.text-gray-950 is a valid Tailwind token; change is consistent with palette shifts elsewhere.
docs/app/docs/components/badge/examples/BadgeVariants.tsx (2)
42-44
: Color token tweak LGTM.The switch to text-gray-950 aligns with the palette updates.
28-30
: Fix grammar in tooltip copy (pluralize "badge") and confirm 'outline' backgroundFile: docs/app/docs/components/badge/examples/BadgeVariants.tsx — update badgestyleDescription:
- soft: 'Soft badge have a soft background color and a border.', - outline: 'Outline badge have a border and a background color.', + soft: 'Soft badges have a soft background color and a border.', + outline: 'Outline badges have a border and a background color.',Confirm whether "outline" intentionally has a background; if not, adjust the outline string to reflect "no background."
docs/app/docs/components/avatar/docs/codeUsage.js (1)
36-38
: Approve — Avatar docs defaults standardized to empty string ('')Search of docs/app/docs/components/avatar shows defaults set to "''" (codeUsage.js and component_api/*); no null or "--" defaults found.
docs/app/docs/components/accordion/docs/example_1.tsx (1)
24-35
: Sample structure LGTM.Clear mapping with stable keys and values.
docs/app/docs/components/card/page.mdx (1)
20-20
: Color token tweak LGTM; confirm contrast on bg-gray-50.text-gray-950 on gray-50 should pass AA for small text; double-check with your token contrast matrix.
docs/app/docs/components/avatar/docs/component_api/image.tsx (1)
25-25
: Defaults standardized—looks good.Consistent with other Avatar API tables.
Also applies to: 33-33
docs/app/landingComponents/MusicAppPlayerDemo.js (2)
48-50
: Color token tweak LGTM.
65-65
: Color token tweak LGTM.docs/app/showcase/music-app/helpers/MusicSidebar.js (1)
31-31
: Color token tweak LGTM.docs/app/docs/components/accordion/docs/component_api/item.tsx (1)
25-25
: Consistency with Accordion docs defaultsDefault set to "''" looks fine. Please ensure Content/Header/Root/Trigger use the same "''" for className to avoid mixed render states in the API tables.
docs/app/docs/components/avatar/docs/component_api/fallback.tsx (2)
25-25
: LGTM: default display standardizedchildren default set to "''" matches the new convention across Avatar docs.
33-33
: LGTM: className default standardizedConsistent with related Root/Image updates.
docs/app/docs/components/avatar/docs/component_api/root.tsx (1)
25-25
: LGTM: defaults normalized across AvatarRootAll defaults moved to "''" are consistent. No further action.
Also applies to: 33-33, 49-49, 57-57, 65-65, 73-73
docs/app/docs/components/badge/docs/codeUsage.js (1)
26-37
: LGTM: defaults normalized and formatting cleanedBadgeTable defaults set to "''" and minor formatting fixes look good.
docs/components/PostHogProvider.tsx (1)
34-36
: Confirm installed posthog-js version supports capture_exceptions / capture_pageviewSandbox couldn't locate package.json or any posthog.init usage; confirm package.json declares the posthog-js version and that that version's PostHogConfig includes capture_exceptions and capture_pageview. Location: docs/components/PostHogProvider.tsx lines 34–36 (also 58–62).
docs/app/docs/components/accordion/docs/component_api/trigger.tsx (1)
25-25
: LGTM on default value formatting.Using "''" for empty string aligns with other Accordion docs entries.
docs/app/docs/components/accordion/page.mdx (2)
33-33
: Header API table wiring looks correct.Assuming api_documentation.header matches {description, columns, data}.
35-35
: Content API table wiring looks correct.Assuming api_documentation.content matches {description, columns, data}.
docs/app/docs/components/accordion/docs/codeUsage.js (2)
15-16
: New API imports are properly added.Imports for header/content match the existing pattern.
34-37
: api_documentation surface extended cleanly.No breaking changes for existing consumers; new keys are additive.
docs/app/landingComponents/HeroSection.js (4)
137-137
: Layout tweak is fine.Top-level spacing change is non-breaking.
142-145
: Copy and color token usage look good.No functional impact.
215-231
: Section restructure reads well and remains responsive.No issues spotted in layout/semantics.
192-200
: ```shell
#!/bin/bash
set -euo pipefailFILES=("tailwind.config.js" "docs/tailwind.config.ts")
for f in "${FILES[@]}"; do
if [ -f "$f" ]; then
echo "== $f =="
sed -n '1,240p' "$f" || true
echo
echo "---- rg matches (1000|gray|colors|extend|theme) in $f ----"
rg -n --hidden -S "1000|gray\b|colors\b|extend\b|theme\b" "$f" || true
echo
else
echo "== $f NOT FOUND =="
fi
doneecho "== Searching CSS for @theme / @config / CSS color variables =="
rg -n --hidden -S "@theme|@config|--color|--rad-ui|--rad|--color-gray" || trueecho "== Occurrences of 'text-gray-1000' with context =="
rg -n --hidden -S -C2 "text-gray-1000" || trueecho "== Search for explicit 'gray-1000' definitions or numeric '1000' color keys =="
rg -n --hidden -S "gray-1000|gray_1000|\b1000\b|"1000"|'1000'" || trueecho "== Show common global CSS entrypoints if present =="
for css in "app/globals.css" "src/styles.css" "styles/globals.css" "src/globals.css" "styles.css" "globals.css" "docs/styles.css"; do
if [ -f "$css" ]; then
echo "== $css =="
sed -n '1,240p' "$css" || true
echo
fi
done</blockquote></details> <details> <summary>docs/app/page.tsx (5)</summary><blockquote> `6-10`: **New UI imports and SponsorsSection integration look correct.** Composition aligns with the new sections below. --- `55-55`: **Good hardening for external links.** target="_blank" with rel="noopener noreferrer" avoids reverse tabnabbing. --- `84-118`: **JoinCommunity block looks solid.** Clear hierarchy and safe external links. --- `123-123`: **Confirm desired link behavior for “Designed by”.** Links now open in the same tab (no target/_blank). Confirm this is intentional for external sites. --- `134-138`: **Page composition updates LGTM.** New sections grouped under a padded container; no SSR/client boundary issues expected. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
const handleSponsorClick = (website) => { | ||
window.open(website, '_blank', 'noopener,noreferrer') | ||
} |
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.
🛠️ Refactor suggestion
Prefer semantic links for external navigation
Use an anchor with rel/target inside the Card for accessibility and security; drop window.open.
Apply this diff:
- const handleSponsorClick = (website) => {
- window.open(website, '_blank', 'noopener,noreferrer')
- }
+ const handleSponsorClick = undefined
@@
- <div className="text-center space-y-4">
+ <a
+ href={sponsor.website}
+ target="_blank"
+ rel="noopener noreferrer"
+ aria-label={`Open ${sponsor.name} website in a new tab`}
+ className="block focus:outline-none focus:ring-2 focus:ring-green-400 rounded"
+ >
+ <div className="text-center space-y-4">
@@
- </div>
+ </div>
+ </a>
Also applies to: 53-68
🤖 Prompt for AI Agents
In docs/app/landingComponents/SponsorsSection.js around lines 30-32 (and
similarly lines 53-68), replace the programmatic window.open call with semantic
anchor links: remove handleSponsorClick and instead render each sponsor Card
content wrapped in an <a> element (or use an anchor inside the Card) with
target="_blank" and rel="noopener noreferrer"; ensure the anchor has appropriate
link text or an aria-label for accessibility and remove any onClick that calls
window.open so the browser native link behavior is used.
<input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950'> | ||
|
||
</input> |
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.
Fix invalid input markup (void element must be self-closing).
Current markup renders an invalid <input>
with children. Self-close to satisfy React/HTML and Biome.
- <input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950'>
-
- </input>
+ <input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950' />
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950'> | |
</input> | |
<input value="Search..." onChange={() => { }} className='flex flex-1 items-center bg-gray-100 px-2 rounded-md border border-gray-400 text-gray-950' /> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 99-101: input is a void element tag and must not have children.
Unsafe fix: Remove the children.
(lint/correctness/noVoidElementsWithChildren)
🤖 Prompt for AI Agents
In docs/app/landingComponents/ToolbarDemo.js around lines 99 to 101, the <input>
element is written with an explicit closing tag and empty children which is
invalid for void elements; replace the paired tag with a self-closing input
element (remove children and the separate closing tag) and keep the attributes
(value, onChange, className) intact so the element is valid JSX/HTML.
<div> | ||
<Text className="!text-sm font-bold">{artist.name}</Text> | ||
<Text className="text-gray-900 !text-xs">{artist.album}</Text> | ||
<Text className="text-gray-1000 !text-xs">{artist.album}</Text> |
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.
💡 Verification agent
🧩 Analysis chain
Confirm Tailwind token: text-gray-1000.
If gray-1000 isn’t defined in your Tailwind theme, this class won’t output CSS and text will fall back to default color. Please verify the token exists (and its contrast vs. bg-gray-50/200).
Run this to check presence of a gray.1000 token and all usages:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tailwind config files:"
fd -a -g 'tailwind.config.*' || true
echo -e "\nSearch for gray 1000 in Tailwind config:"
rg -nPi --glob 'tailwind.config.*' '(gray[^:]*:\s*{[^}]*\b1000\b)|\bgray-1000\b|1000\s*:' -C2 || true
echo -e "\nAll occurrences of text-gray-1000 in repo:"
rg -n 'text-gray-1000' -S || true
Length of output: 14378
Add gray.1000 to Tailwind config or replace usages — token missing
Verification: tailwind.config.ts and tailwind.config.js exist but contain no gray.1000; repo contains many uses of "text-gray-1000" (e.g. docs/app/showcase/music-app/helpers/sections/TopArtists.tsx:66, src/components/ui/Text/tests/Text.test.js:17).
Fix: add colors.gray.1000 to your Tailwind theme in the config(s) or replace all "text-gray-1000" usages with an existing token and update tests/stories accordingly.
🤖 Prompt for AI Agents
In docs/app/showcase/music-app/helpers/sections/TopArtists.tsx around line 66,
the class "text-gray-1000" references a Tailwind color token that doesn't exist
in the project's Tailwind configs; either add colors.gray.1000 to the Tailwind
theme (tailwind.config.ts and/or tailwind.config.js) with the desired hex/value
and rebuild, or replace all occurrences of "text-gray-1000" across the repo with
an existing token (e.g., text-gray-900 or another approved token) and update
related tests and stories to expect the new class/value.
<Button | ||
variant="ghost" | ||
size="small" | ||
onClick={() => setFontSize(Math.max(12, fontSize - 2))} | ||
disabled={fontSize <= 12} | ||
> | ||
A- | ||
</Button> | ||
<Text className="text-xs text-gray-500 w-8 text-center">{fontSize}px</Text> | ||
<Button | ||
variant="ghost" | ||
size="small" | ||
onClick={() => setFontSize(Math.min(24, fontSize + 2))} | ||
disabled={fontSize >= 24} | ||
> | ||
A+ | ||
</Button> | ||
</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.
🛠️ Refactor suggestion
Add accessible toggle semantics (aria-pressed/labels) to controls.
Buttons that act as toggles should expose state via aria-pressed and have descriptive aria-labels. Font-size controls also benefit from explicit labels.
@@
- <Button
+ <Button
variant="ghost"
size="small"
onClick={() => setFontSize(Math.max(12, fontSize - 2))}
disabled={fontSize <= 12}
+ aria-label="Decrease font size"
>
@@
- <Button
+ <Button
variant="ghost"
size="small"
onClick={() => setFontSize(Math.min(24, fontSize + 2))}
disabled={fontSize >= 24}
+ aria-label="Increase font size"
>
@@
- <Button
+ <Button
variant={highContrast ? "solid" : "outline"}
size="small"
onClick={() => setHighContrast(!highContrast)}
+ aria-pressed={highContrast}
+ aria-label="Toggle high contrast mode"
>
@@
- <Button
+ <Button
variant={reducedMotion ? "solid" : "outline"}
size="small"
onClick={() => setReducedMotion(!reducedMotion)}
+ aria-pressed={reducedMotion}
+ aria-label="Toggle reduced motion"
>
@@
- <Button
+ <Button
variant={screenReader ? "solid" : "outline"}
size="small"
onClick={toggleScreenReader}
+ aria-pressed={screenReader}
+ aria-label="Toggle screen reader helper"
>
Also applies to: 92-99, 106-113, 121-127
🤖 Prompt for AI Agents
In docs/components/AccessibilityFeatures.tsx around lines 66-83 (and similarly
at 92-99, 106-113, 121-127), the font-size and other toggle-like buttons lack
accessible semantics and labels; update each Button to include an explicit
aria-label describing its action (e.g., "Decrease font size", "Increase font
size") and, for buttons that represent toggle state, add
aria-pressed={isToggled} bound to the appropriate state variable so assistive
tech can read the current state; ensure the visible text remains unchanged and
keep disabled logic intact.
const KeyboardShortcuts = () => { | ||
useEffect(() => { | ||
const handleKeyDown = (e: KeyboardEvent) => { | ||
// Ctrl/Cmd + K for search | ||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') { | ||
e.preventDefault() | ||
const searchInput = document.querySelector('input[placeholder*="Search"]') as HTMLInputElement | ||
if (searchInput) { | ||
searchInput.focus() | ||
} | ||
} | ||
|
||
// Ctrl/Cmd + / for help | ||
if ((e.ctrlKey || e.metaKey) && e.key === '/') { | ||
e.preventDefault() | ||
// Toggle keyboard shortcuts help | ||
const helpElement = document.getElementById('keyboard-shortcuts-help') | ||
if (helpElement) { | ||
helpElement.style.display = helpElement.style.display === 'none' ? 'block' : 'none' | ||
} | ||
} | ||
|
||
// Escape to close modals/dropdowns | ||
if (e.key === 'Escape') { | ||
const activeElement = document.activeElement as HTMLElement | ||
if (activeElement && activeElement.blur) { | ||
activeElement.blur() | ||
} | ||
} | ||
} | ||
|
||
document.addEventListener('keydown', handleKeyDown) | ||
return () => document.removeEventListener('keydown', handleKeyDown) | ||
}, []) |
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.
🛠️ Refactor suggestion
Scope shortcuts to avoid interfering with user input; use state instead of DOM style toggling
Don’t blur globally on Escape; ignore keybinds when typing; manage open state in React.
-const KeyboardShortcuts = () => {
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
+const KeyboardShortcuts = () => {
+ const [open, setOpen] = useState(false)
+ useEffect(() => {
+ const isTypingTarget = (el: EventTarget | null) => {
+ const t = el as HTMLElement | null
+ return !!t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)
+ }
+ const handleKeyDown = (e: KeyboardEvent) => {
// Ctrl/Cmd + K for search
- if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
e.preventDefault()
- const searchInput = document.querySelector('input[placeholder*="Search"]') as HTMLInputElement
+ const searchInput = document.getElementById('docs-search-input') as HTMLInputElement | null
if (searchInput) {
searchInput.focus()
}
}
// Ctrl/Cmd + / for help
- if ((e.ctrlKey || e.metaKey) && e.key === '/') {
+ if ((e.ctrlKey || e.metaKey) && e.key === '/' && !isTypingTarget(e.target)) {
e.preventDefault()
- // Toggle keyboard shortcuts help
- const helpElement = document.getElementById('keyboard-shortcuts-help')
- if (helpElement) {
- helpElement.style.display = helpElement.style.display === 'none' ? 'block' : 'none'
- }
+ setOpen((v) => !v)
}
// Escape to close modals/dropdowns
- if (e.key === 'Escape') {
- const activeElement = document.activeElement as HTMLElement
- if (activeElement && activeElement.blur) {
- activeElement.blur()
- }
- }
+ if (e.key === 'Escape') setOpen(false)
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const KeyboardShortcuts = () => { | |
useEffect(() => { | |
const handleKeyDown = (e: KeyboardEvent) => { | |
// Ctrl/Cmd + K for search | |
if ((e.ctrlKey || e.metaKey) && e.key === 'k') { | |
e.preventDefault() | |
const searchInput = document.querySelector('input[placeholder*="Search"]') as HTMLInputElement | |
if (searchInput) { | |
searchInput.focus() | |
} | |
} | |
// Ctrl/Cmd + / for help | |
if ((e.ctrlKey || e.metaKey) && e.key === '/') { | |
e.preventDefault() | |
// Toggle keyboard shortcuts help | |
const helpElement = document.getElementById('keyboard-shortcuts-help') | |
if (helpElement) { | |
helpElement.style.display = helpElement.style.display === 'none' ? 'block' : 'none' | |
} | |
} | |
// Escape to close modals/dropdowns | |
if (e.key === 'Escape') { | |
const activeElement = document.activeElement as HTMLElement | |
if (activeElement && activeElement.blur) { | |
activeElement.blur() | |
} | |
} | |
} | |
document.addEventListener('keydown', handleKeyDown) | |
return () => document.removeEventListener('keydown', handleKeyDown) | |
}, []) | |
const KeyboardShortcuts = () => { | |
const [open, setOpen] = useState(false) | |
useEffect(() => { | |
const isTypingTarget = (el: EventTarget | null) => { | |
const t = el as HTMLElement | null | |
return !!t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable) | |
} | |
const handleKeyDown = (e: KeyboardEvent) => { | |
// Ctrl/Cmd + K for search | |
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') { | |
e.preventDefault() | |
const searchInput = document.getElementById('docs-search-input') as HTMLInputElement | null | |
if (searchInput) { | |
searchInput.focus() | |
} | |
} | |
// Ctrl/Cmd + / for help | |
if ((e.ctrlKey || e.metaKey) && e.key === '/' && !isTypingTarget(e.target)) { | |
e.preventDefault() | |
setOpen((v) => !v) | |
} | |
// Escape to close modals/dropdowns | |
if (e.key === 'Escape') setOpen(false) | |
} | |
document.addEventListener('keydown', handleKeyDown) | |
return () => document.removeEventListener('keydown', handleKeyDown) | |
}, []) |
const copyInstallCommand = () => { | ||
navigator.clipboard.writeText('npm install @radui/ui') | ||
setCopied(true) | ||
setTimeout(() => setCopied(false), 2000) | ||
} |
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.
🛠️ Refactor suggestion
Harden clipboard copy with capability check and error handling
Clipboard API can fail (permissions/HTTP). Provide fallback and surface errors gracefully.
- const copyInstallCommand = () => {
- navigator.clipboard.writeText('npm install @radui/ui')
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
- }
+ const copyInstallCommand = async () => {
+ const text = 'npm install @radui/ui'
+ try {
+ if (navigator.clipboard?.writeText) {
+ await navigator.clipboard.writeText(text)
+ } else {
+ const ta = document.createElement('textarea')
+ ta.value = text
+ ta.setAttribute('readonly', '')
+ ta.style.position = 'absolute'
+ ta.style.left = '-9999px'
+ document.body.appendChild(ta)
+ ta.select()
+ document.execCommand('copy')
+ document.body.removeChild(ta)
+ }
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch {
+ // no-op: optionally show a toast
+ }
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const copyInstallCommand = () => { | |
navigator.clipboard.writeText('npm install @radui/ui') | |
setCopied(true) | |
setTimeout(() => setCopied(false), 2000) | |
} | |
const copyInstallCommand = async () => { | |
const text = 'npm install @radui/ui' | |
try { | |
if (navigator.clipboard?.writeText) { | |
await navigator.clipboard.writeText(text) | |
} else { | |
const ta = document.createElement('textarea') | |
ta.value = text | |
ta.setAttribute('readonly', '') | |
ta.style.position = 'absolute' | |
ta.style.left = '-9999px' | |
document.body.appendChild(ta) | |
ta.select() | |
document.execCommand('copy') | |
document.body.removeChild(ta) | |
} | |
setCopied(true) | |
setTimeout(() => setCopied(false), 2000) | |
} catch { | |
// no-op: optionally show a toast | |
} | |
} |
🤖 Prompt for AI Agents
In docs/components/QuickActions.tsx around lines 11-15, the clipboard write
operation is unguarded and can fail; update copyInstallCommand to be async,
check for navigator.clipboard and navigator.clipboard.writeText before calling
it, await the promise and wrap in try/catch to handle errors, setCopied(true)
only on success, and in the catch branch perform a fallback copy using a
temporary textarea + document.execCommand('copy') (also wrapped in try/catch) or
surface the error to the user (console.error or a toast). Ensure timeouts to
reset copied still run only after a successful copy and do not swallow
exceptions.
icon: <ExternalLink className="w-4 h-4" />, | ||
label: 'Open Playground', | ||
action: () => window.open('/playground', '_blank'), | ||
variant: 'outline' as const | ||
}, | ||
{ | ||
icon: <Github className="w-4 h-4" />, | ||
label: 'View on GitHub', | ||
action: () => window.open('https://github.com/rad-ui/ui', '_blank'), | ||
variant: 'outline' as const | ||
}, | ||
{ | ||
icon: <MessageCircle className="w-4 h-4" />, | ||
label: 'Join Discord', | ||
action: () => window.open('https://discord.gg/nMaQfeEPNp', '_blank'), | ||
variant: 'outline' as const | ||
}, | ||
{ | ||
icon: <Star className="w-4 h-4" />, | ||
label: 'Star on GitHub', | ||
action: () => window.open('https://github.com/rad-ui/ui', '_blank'), | ||
variant: 'solid' as const, |
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.
Fix reverse tabnabbing in window.open calls
Always use noopener/noreferrer. Prefer helper.
const actions = [
@@
- action: () => window.open('/playground', '_blank'),
+ action: () => {
+ const w = window.open('/playground', '_blank', 'noopener,noreferrer')
+ if (w) w.opener = null
+ },
@@
- action: () => window.open('https://github.com/rad-ui/ui', '_blank'),
+ action: () => {
+ const w = window.open('https://github.com/rad-ui/ui', '_blank', 'noopener,noreferrer')
+ if (w) w.opener = null
+ },
@@
- action: () => window.open('https://discord.gg/nMaQfeEPNp', '_blank'),
+ action: () => {
+ const w = window.open('https://discord.gg/nMaQfeEPNp', '_blank', 'noopener,noreferrer')
+ if (w) w.opener = null
+ },
@@
- action: () => window.open('https://github.com/rad-ui/ui', '_blank'),
+ action: () => {
+ const w = window.open('https://github.com/rad-ui/ui', '_blank', 'noopener,noreferrer')
+ if (w) w.opener = null
+ },
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
icon: <ExternalLink className="w-4 h-4" />, | |
label: 'Open Playground', | |
action: () => window.open('/playground', '_blank'), | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <Github className="w-4 h-4" />, | |
label: 'View on GitHub', | |
action: () => window.open('https://github.com/rad-ui/ui', '_blank'), | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <MessageCircle className="w-4 h-4" />, | |
label: 'Join Discord', | |
action: () => window.open('https://discord.gg/nMaQfeEPNp', '_blank'), | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <Star className="w-4 h-4" />, | |
label: 'Star on GitHub', | |
action: () => window.open('https://github.com/rad-ui/ui', '_blank'), | |
variant: 'solid' as const, | |
icon: <ExternalLink className="w-4 h-4" />, | |
label: 'Open Playground', | |
action: () => { | |
const w = window.open('/playground', '_blank', 'noopener,noreferrer') | |
if (w) w.opener = null | |
}, | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <Github className="w-4 h-4" />, | |
label: 'View on GitHub', | |
action: () => { | |
const w = window.open('https://github.com/rad-ui/ui', '_blank', 'noopener,noreferrer') | |
if (w) w.opener = null | |
}, | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <MessageCircle className="w-4 h-4" />, | |
label: 'Join Discord', | |
action: () => { | |
const w = window.open('https://discord.gg/nMaQfeEPNp', '_blank', 'noopener,noreferrer') | |
if (w) w.opener = null | |
}, | |
variant: 'outline' as const | |
}, | |
{ | |
icon: <Star className="w-4 h-4" />, | |
label: 'Star on GitHub', | |
action: () => { | |
const w = window.open('https://github.com/rad-ui/ui', '_blank', 'noopener,noreferrer') | |
if (w) w.opener = null | |
}, | |
variant: 'solid' as const, |
🤖 Prompt for AI Agents
In docs/components/QuickActions.tsx around lines 25 to 46 the window.open calls
open links in a new tab without noopener/noreferrer which allows reverse
tabnabbing; replace direct window.open usages with a small helper (e.g.
openExternal) that opens the URL in a new tab safely by calling window.open(url,
'_blank', 'noopener,noreferrer') and, if possible, nulling the returned
window.opener for extra safety; update all actions to call that helper instead
of window.open.
useEffect(() => { | ||
const updateProgress = () => { | ||
const scrollTop = window.scrollY | ||
const docHeight = document.documentElement.scrollHeight - window.innerHeight | ||
const scrollPercent = (scrollTop / docHeight) * 100 | ||
setProgress(scrollPercent) | ||
} | ||
|
||
window.addEventListener('scroll', updateProgress) | ||
updateProgress() // Initial calculation | ||
|
||
return () => window.removeEventListener('scroll', updateProgress) | ||
}, []) |
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.
🛠️ Refactor suggestion
Clamp progress and guard zero-height; throttle with rAF + passive listeners
Avoid NaN/Infinity on short pages and reduce scroll jank.
Apply:
useEffect(() => {
- const updateProgress = () => {
- const scrollTop = window.scrollY
- const docHeight = document.documentElement.scrollHeight - window.innerHeight
- const scrollPercent = (scrollTop / docHeight) * 100
- setProgress(scrollPercent)
- }
-
- window.addEventListener('scroll', updateProgress)
- updateProgress() // Initial calculation
-
- return () => window.removeEventListener('scroll', updateProgress)
+ let ticking = false
+ const updateProgress = () => {
+ const scrollTop = window.scrollY || document.documentElement.scrollTop || 0
+ const docHeight = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1) // guard
+ const raw = (scrollTop / docHeight) * 100
+ const clamped = Math.min(100, Math.max(0, raw))
+ setProgress(clamped)
+ }
+ const onScroll = () => {
+ if (ticking) return
+ ticking = true
+ requestAnimationFrame(() => {
+ updateProgress()
+ ticking = false
+ })
+ }
+ window.addEventListener('scroll', onScroll, { passive: true })
+ window.addEventListener('resize', updateProgress)
+ updateProgress()
+ return () => {
+ window.removeEventListener('scroll', onScroll as EventListener)
+ window.removeEventListener('resize', updateProgress)
+ }
}, [])
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
const updateProgress = () => { | |
const scrollTop = window.scrollY | |
const docHeight = document.documentElement.scrollHeight - window.innerHeight | |
const scrollPercent = (scrollTop / docHeight) * 100 | |
setProgress(scrollPercent) | |
} | |
window.addEventListener('scroll', updateProgress) | |
updateProgress() // Initial calculation | |
return () => window.removeEventListener('scroll', updateProgress) | |
}, []) | |
useEffect(() => { | |
let ticking = false | |
const updateProgress = () => { | |
const scrollTop = window.scrollY || document.documentElement.scrollTop || 0 | |
const docHeight = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1) // guard | |
const raw = (scrollTop / docHeight) * 100 | |
const clamped = Math.min(100, Math.max(0, raw)) | |
setProgress(clamped) | |
} | |
const onScroll = () => { | |
if (ticking) return | |
ticking = true | |
requestAnimationFrame(() => { | |
updateProgress() | |
ticking = false | |
}) | |
} | |
window.addEventListener('scroll', onScroll, { passive: true }) | |
window.addEventListener('resize', updateProgress) | |
updateProgress() | |
return () => { | |
window.removeEventListener('scroll', onScroll as EventListener) | |
window.removeEventListener('resize', updateProgress) | |
} | |
}, []) |
🤖 Prompt for AI Agents
In docs/components/ReadingProgress.tsx around lines 7 to 19, the scroll progress
calculation can produce NaN/Infinity on short pages and cause jank from frequent
scroll events; change updateProgress to compute docHeight = Math.max(1,
document.documentElement.scrollHeight - window.innerHeight) to guard division,
clamp the resulting scrollPercent to 0–100 before calling setProgress, and
throttle updates by scheduling the DOM reads and setState inside
requestAnimationFrame (track a pending rAF id to avoid multiple frames); also
register the scroll listener with { passive: true } and ensure the rAF is
canceled in the cleanup return along with removing the listener.
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" /> | ||
<input | ||
type="text" | ||
placeholder="Search documentation..." | ||
value={query} | ||
onChange={(e) => setQuery(e.target.value)} | ||
onFocus={() => setIsOpen(true)} | ||
onKeyDown={handleKeyDown} | ||
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent" | ||
/> |
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.
🛠️ Refactor suggestion
Add combobox ARIA and a stable input id for shortcuts
Improves accessibility and lets KeyboardShortcuts focus reliably.
- <input
+ <input
type="text"
+ id="docs-search-input"
placeholder="Search documentation..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setIsOpen(true)}
onKeyDown={handleKeyDown}
- className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
+ className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
+ role="combobox"
+ aria-label="Search documentation"
+ aria-autocomplete="list"
+ aria-expanded={isOpen && results.length > 0}
+ aria-controls="docs-search-listbox"
/>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
<input | |
type="text" | |
placeholder="Search documentation..." | |
value={query} | |
onChange={(e) => setQuery(e.target.value)} | |
onFocus={() => setIsOpen(true)} | |
onKeyDown={handleKeyDown} | |
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent" | |
/> | |
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" /> | |
<input | |
type="text" | |
id="docs-search-input" | |
placeholder="Search documentation..." | |
value={query} | |
onChange={(e) => setQuery(e.target.value)} | |
onFocus={() => setIsOpen(true)} | |
onKeyDown={handleKeyDown} | |
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent" | |
role="combobox" | |
aria-label="Search documentation" | |
aria-autocomplete="list" | |
aria-expanded={isOpen && results.length > 0} | |
aria-controls="docs-search-listbox" | |
/> |
🤖 Prompt for AI Agents
In docs/components/SearchBar.tsx around lines 60-69, the input lacks combobox
ARIA attributes and a stable id so keyboard shortcuts and AT can't reliably
target it; add a stable id (e.g., "search-input" or a prop-driven id) to the
input and add combobox-related ARIA attributes: role="combobox" and
aria-expanded tied to isOpen, aria-controls pointing to the suggestions list id,
and aria-activedescendant set when a suggestion is highlighted; ensure the
suggestions container renders with the matching id and role="listbox" and that
aria- attributes update as state changes so KeyboardShortcuts can focus the
input by id and assistive tech receives correct semantics.
interface TocItem { | ||
id: string | ||
title: string | ||
level: number | ||
element: HTMLElement | ||
} |
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.
🛠️ Refactor suggestion
Duplicate heading IDs risk; avoid storing DOM nodes in state
Slugging from text can create duplicate ids and invalid HTML, breaking navigation and React keys. Also, keeping HTMLElements in state is unnecessary.
Apply this diff to ensure unique ids and keep DOM outside state:
interface TocItem {
id: string
title: string
level: number
- element: HTMLElement
}
const TableOfContents = () => {
const [tocItems, setTocItems] = useState<TocItem[]>([])
const [activeId, setActiveId] = useState<string>('')
useEffect(() => {
- const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
- const items: TocItem[] = []
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')) as HTMLElement[]
+ const used = new Set<string>()
+ const slugify = (text: string) =>
+ text.toLowerCase().trim().replace(/\s+/g, '-').replace(/[^\w-]/g, '')
+ const ensureUnique = (base: string) => {
+ let i = 1
+ let candidate = base
+ while (used.has(candidate)) candidate = `${base}-${i++}`
+ used.add(candidate)
+ return candidate
+ }
+ const items: TocItem[] = []
- headings.forEach((heading) => {
- const element = heading as HTMLElement
- const id = element.id || element.textContent?.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '') || ''
-
- if (id) {
- element.id = id
- items.push({
- id,
- title: element.textContent || '',
- level: parseInt(element.tagName.charAt(1)),
- element
- })
- }
- })
+ headings.forEach((el) => {
+ const base = el.id || slugify(el.textContent || '')
+ if (!base) return
+ const id = ensureUnique(base)
+ el.id = id
+ items.push({
+ id,
+ title: el.textContent || '',
+ level: parseInt(el.tagName.charAt(1)),
+ })
+ })
setTocItems(items)
// Intersection Observer for active section
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id)
}
})
},
{
rootMargin: '-20% 0% -80% 0%',
threshold: 0
}
)
- items.forEach((item) => observer.observe(item.element))
+ items.forEach((item) => {
+ const el = document.getElementById(item.id)
+ if (el) observer.observe(el)
+ })
return () => observer.disconnect()
}, [])
Also applies to: 19-39, 41-58
🤖 Prompt for AI Agents
In docs/components/TableOfContents.tsx around lines 8-13 (and also affecting
19-39 and 41-58), the component currently stores HTMLElements in state and
generates slugs that can collide; change TocItem.element from HTMLElement to
string (the element id), stop storing DOM nodes in React state, and instead
query the DOM (document.getElementById or refs) when you need the node. Ensure
slug generation produces unique, valid HTML ids by normalizing the heading text
and appending a numeric suffix for duplicates (track counts in a map while
building the TOC), set those ids on the heading elements, update state items to
{id, title, level} only, and update all keys/usages to use the unique id.
Summary by CodeRabbit
New Features
Documentation
Style
Chores