feat(ui): LanguageSelector component with search + RadioGroup + Tabs#84
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| type="button" | ||
| role="switch" | ||
| aria-checked={checked} | ||
| aria-label={ariaLabel ?? label} |
There was a problem hiding this comment.
Suggestion: Ensure the switch always has an accessible name by enforcing that at least one of label or ariaLabel is required, so the rendered control is never unnamed. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The switch can be rendered with both label and ariaLabel missing, because both props are optional. In that case aria-label becomes undefined and aria-labelledby is also omitted, leaving the role="switch" control unnamed. This is a real accessibility issue, so the suggestion is verified.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/settings/SettingsShared.tsx
**Line:** 22:22
**Comment:**
*Custom Rule: Ensure the switch always has an accessible name by enforcing that at least one of `label` or `ariaLabel` is required, so the rendered control is never unnamed.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| return ( | ||
| <div | ||
| role="radiogroup" | ||
| aria-label={name} |
There was a problem hiding this comment.
Suggestion: Ensure the radiogroup always has a guaranteed accessible name by requiring a non-optional labeling prop (or a required labelled-by reference) instead of relying on an optional value that can be undefined. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The component passes an optional name prop directly to aria-label, so the radiogroup can render without an accessible name when name is undefined. That is a real accessibility issue, so the suggestion correctly identifies a violation.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/RadioGroup.tsx
**Line:** 33:33
**Comment:**
*Custom Rule: Ensure the radiogroup always has a guaranteed accessible name by requiring a non-optional labeling prop (or a required labelled-by reference) instead of relying on an optional value that can be undefined.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| aria-controls={`${groupId}-${tab.id}-panel`} | ||
| id={`${groupId}-${tab.id}`} |
There was a problem hiding this comment.
Suggestion: Make the tab-to-panel ARIA linkage consistent so each tab id matches the corresponding panel aria-labelledby and each tab aria-controls points to the actual panel id. [custom_rule]
Severity Level: Minor
Why it matters? 🤔
The tab's aria-controls references ${groupId}-${tab.id}-panel, but the corresponding TabPanel in this file uses id={${tabId}-panel} without the groupId prefix. That means the tab is pointing at a non-matching panel id, so the accessibility linkage is inconsistent and the violation is real.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/Tabs.tsx
**Line:** 48:49
**Comment:**
*Custom Rule: Make the tab-to-panel ARIA linkage consistent so each tab `id` matches the corresponding panel `aria-labelledby` and each tab `aria-controls` points to the actual panel `id`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| <input | ||
| type="radio" | ||
| id={itemId} | ||
| name={groupId} |
There was a problem hiding this comment.
Suggestion: The component accepts a name prop but ignores it and forces every input to use groupId as the radio name. This breaks caller expectations for form field naming and standard form submission contracts. Pass through the provided name (with a fallback) instead of always using groupId. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Forms using RadioGroup submit unexpected field names.
- ⚠️ Server-side validation based on field names may fail.Steps of Reproduction ✅
1. Open `components/ui/RadioGroup.tsx` and note the `RadioGroupProps` definition with
optional `name` prop at lines 10–16 and the component implementation at lines 19–91.
2. Observe that the `name` prop is destructured at line 24 but never used when rendering
the `<input type="radio">` elements at lines 42–60.
3. Specifically, each radio input uses `name={groupId}` at line 45, where `groupId` is
generated via `useId()` at line 28, instead of using the caller-provided `name` prop.
4. To reproduce in a real UI, create any form component that imports `RadioGroup` from
`components/ui/RadioGroup.tsx` and renders it inside a `<form>` with
`name="preferredLanguage"`, then inspect the DOM: all `<input type="radio">` elements will
have the auto-generated `groupId` as their `name` attribute, not `"preferredLanguage"`, so
form submission will not contain the expected field key.
5. A `Grep` search for `RadioGroup` across `*.tsx` files shows no current callers outside
`components/ui/RadioGroup.tsx`, meaning this is a latent API bug that will affect the
first consumer that relies on HTML form naming semantics.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/RadioGroup.tsx
**Line:** 45:45
**Comment:**
*Api Mismatch: The component accepts a `name` prop but ignores it and forces every input to use `groupId` as the radio name. This breaks caller expectations for form field naming and standard form submission contracts. Pass through the provided `name` (with a fallback) instead of always using `groupId`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| @@ -0,0 +1,88 @@ | |||
| import type React from 'react'; | |||
There was a problem hiding this comment.
Suggestion: React is imported as a type-only import but then used as a runtime value via React.memo. Type-only imports are erased at runtime, so this can crash when evaluating the module. Import React as a normal value (or import memo directly) before using it in executable code. [import error]
Severity Level: Critical 🚨
- ❌ Any route importing Tabs crashes during rendering.
- ⚠️ Blocks adoption of new Tabs component across app.Steps of Reproduction ✅
1. Open `components/ui/Tabs.tsx` and note the type-only import `import type React from
'react';` at line 1 and the component definition using `React.memo` at lines 18–62 and
72–87.
2. Because `React` is imported with `import type`, TypeScript erases this binding in the
emitted JavaScript when `importsNotUsedAsValues` is configured (the common default), so
there is no runtime `React` variable.
3. Create any component (e.g., `SomePage.tsx`) that imports `{ Tabs }` from
`components/ui/Tabs.tsx` and renders `<Tabs>`; this forces the `Tabs` module to be
evaluated at runtime.
4. When the module executes, `React.memo(...)` at line 18 is evaluated, but `React` is
`undefined` because the import was erased, causing a `ReferenceError: React is not
defined` and crashing the route where `<Tabs>` is used.
5. A `Grep` search for `Tabs` shows the only `Tabs` symbol defined in
`components/ui/Tabs.tsx` and no external callers yet, so the crash will occur immediately
once any consumer starts using this new component.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/Tabs.tsx
**Line:** 1:1
**Comment:**
*Import Error: `React` is imported as a type-only import but then used as a runtime value via `React.memo`. Type-only imports are erased at runtime, so this can crash when evaluating the module. Import `React` as a normal value (or import `memo` directly) before using it in executable code.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| type="button" | ||
| role="tab" | ||
| aria-selected={isActive} | ||
| aria-controls={`${groupId}-${tab.id}-panel`} |
There was a problem hiding this comment.
Suggestion: The tab button aria-controls points to an ID prefixed with groupId, but TabPanel IDs are generated without that prefix, so the control relationship is broken. Align both components to generate the same panel ID format. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Screen readers cannot connect tabs to their panels.
- ⚠️ Accessibility of new Tabs component significantly reduced.Steps of Reproduction ✅
1. Open `components/ui/Tabs.tsx` and inspect the `Tabs` component at lines 18–62: each
`<button role="tab">` is rendered inside the `tabs.map` loop at lines 40–57.
2. At line 48, each tab button sets `aria-controls={`${groupId}-${tab.id}-panel`}`, where
`groupId` comes from `useId()` at line 20 and `tab.id` is the logical tab identifier.
3. In the same file, inspect the `TabPanel` component at lines 72–87: it renders a
`<section role="tabpanel">` with `id={`${tabId}-panel`}` at line 78, without any `groupId`
prefix.
4. Use these components together in any page (e.g., import `{ Tabs, TabPanel }` from
`components/ui/Tabs.tsx` and render `<Tabs tabs={[{ id: 'one', label: 'One' }]} ... />`
plus `<TabPanel tabId="one" activeTab="one">…</TabPanel>`); when you inspect the DOM, each
tab button will have `aria-controls="react-<id>-one-panel"` while the corresponding panel
has `id="one-panel"`, so there is no element with the ID referenced by `aria-controls`.
5. This mismatch means assistive technologies cannot follow the tab-to-panel relationship;
`Grep` confirms `TabPanel` is only defined in `components/ui/Tabs.tsx` currently, so this
is a latent accessibility bug that will appear for the first consumer of these components.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/Tabs.tsx
**Line:** 48:48
**Comment:**
*Api Mismatch: The tab button `aria-controls` points to an ID prefixed with `groupId`, but `TabPanel` IDs are generated without that prefix, so the control relationship is broken. Align both components to generate the same panel ID format.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| <section | ||
| role="tabpanel" | ||
| id={`${tabId}-panel`} | ||
| aria-labelledby={tabId} |
There was a problem hiding this comment.
Suggestion: The panel uses aria-labelledby={tabId}, but the corresponding tab button ID is generated as ${groupId}-${tab.id}, so the panel label reference never resolves. Use the exact tab button ID format when setting aria-labelledby. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Panels lack accessible labels for screen readers.
- ⚠️ Violates WAI-ARIA expectations for tabpanel labelling.Steps of Reproduction ✅
1. In `components/ui/Tabs.tsx`, inspect the tab button markup inside `Tabs` at lines
40–57: each button sets `id={`${groupId}-${tab.id}`}` at line 49, combining the internal
`groupId` with the logical `tab.id`.
2. In the same file, inspect the `TabPanel` component at lines 72–87: the `<section
role="tabpanel">` sets `aria-labelledby={tabId}` at line 79, where `tabId` is the
`TabPanelProps.tabId` value passed by the caller.
3. Use `Tabs` and `TabPanel` together (e.g., `<Tabs tabs={[{ id: 'one', label: 'One' }]}
... />` and `<TabPanel tabId="one" activeTab="one">…</TabPanel>`): the tab button will
have `id="react-<id>-one"` while the panel will have `aria-labelledby="one"`.
4. Because no element has `id="one"`—the actual tab's ID includes the `groupId`
prefix—`aria-labelledby` on the panel points to a non-existent label element, so screen
readers do not correctly associate the panel with its tab label.
5. `Grep` confirms `TabPanel` has no external callers yet, so this is an accessibility
wiring bug that will manifest as soon as any feature adopts the new tab components.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/ui/Tabs.tsx
**Line:** 79:79
**Comment:**
*Api Mismatch: The panel uses `aria-labelledby={tabId}`, but the corresponding tab button ID is generated as `${groupId}-${tab.id}`, so the panel label reference never resolves. Use the exact tab button ID format when setting `aria-labelledby`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
3363916 to
947c5f0
Compare
- Add LanguageSelector.tsx: Modern combobox with search, flag icons, RTL support - Add RadioGroup.tsx: Accessible radio group component with proper ARIA - Add Tabs.tsx: WAI-ARIA compliant tabs component - Update WelcomePortal.tsx: Use new LanguageSelector component - Update SettingsShared.tsx: RTL-aware ToggleSwitch with reduced-motion support - Add translation key 'portal.language.searchPlaceholder' to all 11 locales
947c5f0 to
d5284a8
Compare
…RadioGroup, Tabs - ToggleSwitch: Ensure accessible name fallback to 'Toggle' when both label and ariaLabel are missing - RadioGroup: Make 'name' prop required for guaranteed accessible name on radiogroup - Tabs: Make 'ariaLabel' prop required instead of hardcoded 'Tabs' string QNBS-v3: WCAG 2.2 AA compliance fixes
- RadioGroup: Pass through 'name' prop to input elements (was ignored) - Tabs: Add groupId prop to TabPanel for consistent ARIA linkage - Tabs: aria-controls and aria-labelledby now use matching IDs QNBS-v3: WCAG 2.2 AA compliance - API mismatch fixes
- LanguageSelector, RadioGroup, Tabs components completed - All 6 CodeAnt AI accessibility issues fixed - Ready for CI verification and merge
f490f4c to
907dbd7
Compare
User description
UI Modernization - Phase 1
Added
LanguageSelector.tsx: Modern combobox with search functionality, flag emojis, RTL support, and reduced-motion awarenessRadioGroup.tsx: Accessible radio group component with proper ARIA attributesTabs.tsx: WAI-ARIA compliant tabs component with multiple variants (default, pills, underline)Changed
WelcomePortal.tsx: Now uses the new LanguageSelector component instead of inline language buttonsSettingsShared.tsx: ToggleSwitch optimized for RTL with reduced-motion supporti18n
portal.language.searchPlaceholdertranslation key to all 11 locales (de, en, fr, es, it, ar, he, ja, zh, pt, el)Design Principles
Testing
CodeAnt-AI Description
Add searchable language pickers and reusable tab/radio controls
What Changed
Impact
✅ Faster language switching✅ Easier language finding for multilingual users✅ Fewer right-to-left layout issues💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.