Skip to content

Refactor ToggleGroup to use RovingFocusGroup implementation#938

Merged
kotAPI merged 6 commits intomainfrom
kotapi/refactor-toggle-group-to-use-roving-focus
Mar 20, 2025
Merged

Refactor ToggleGroup to use RovingFocusGroup implementation#938
kotAPI merged 6 commits intomainfrom
kotapi/refactor-toggle-group-to-use-roving-focus

Conversation

@kotAPI
Copy link
Copy Markdown
Collaborator

@kotAPI kotAPI commented Mar 20, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced the Toggle component with flexible state handling, now supporting both controlled and uncontrolled modes.
    • Expanded Toggle documentation and interactive examples to showcase various configurations including disabled states, color variants, form integration, and multiple synchronized toggles.
    • Improved the Toggle Group with customizable horizontal or vertical orientation and enhanced focus management for a better user experience.
    • Introduced a new hook, useControllableState, for managing controlled and uncontrolled component states effectively.
  • Bug Fixes

    • Standardized formatting across various components for improved readability and maintainability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2025

Walkthrough

The pull request standardizes formatting across several UI components and tests, such as using single quotes for strings and consistent spacing in destructuring assignments. It refines the Toggle component by making its pressed prop optional and integrating a new useControllableState hook for controlled/uncontrolled state management, along with corresponding test updates. Additionally, it enhances focus management for toggle groups by removing manual handlers and incorporating RovingFocusGroup. New comprehensive stories for the Toggle component and tests for the new hook have also been added.

Changes

File(s) Summary
src/components/ui/Button/Button.tsx
src/components/ui/Code/Code.tsx
src/components/ui/Code/stories/Code...
src/core/hooks/createDataAttribute/index.ts
src/core/hooks/createDataAttribute/createDataAttribute.test.tsx
Formatting improvements including standardized import spacing and consistent string quotations; adjustments to array declarations and JSX indentation to enhance readability.
src/components/ui/Toggle/Toggle.tsx
src/components/ui/Toggle/stories/Toggle.stories.tsx
src/components/ui/Toggle/tests/Toggle.test.js
Updated Toggle component to support both controlled and uncontrolled modes using the new useControllableState hook; removed redundant manual state management and adjusted tests to focus on controlled state via onChange and defaultPressed.
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx
src/components/ui/ToggleGroup/fragments/ToggleItem.tsx
Removed manual focus management and keyboard navigation; added a new direction prop and integrated RovingFocusGroup for improved focus handling in ToggleGroup components.
src/core/hooks/useControllableState/index.tsx
src/core/hooks/useControllableState/useControllableState.test.tsx
Introduced a new React hook for managing controlled/uncontrolled state with appropriate warnings and callbacks; added tests to verify both modes and functional updates.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Toggle as Toggle Component
    participant Hook as useControllableState
    participant Parent as Parent Component

    User->>Toggle: Click or toggle action
    Toggle->>Hook: Invoke setValue(newValue)
    alt Controlled Mode
        Hook-->>Toggle: Return controlled value
        Toggle->>Parent: Trigger onChange(newValue)
    else Uncontrolled Mode
        Hook-->>Toggle: Update internal state
        Toggle->>Parent: Trigger onChange(newValue) (if provided)
    end
Loading
sequenceDiagram
    participant User
    participant TG as ToggleGroup Component
    participant RFG as RovingFocusGroup

    User->>TG: Focus toggle item / Use arrow key
    TG->>RFG: Delegate focus management
    RFG-->>TG: Return updated focused item
Loading

Possibly related PRs

Suggested labels

automerge

Suggested reviewers

  • GoldGroove06

Poem

I'm a rabbit hopping through the code,
Skipping past bugs in every node.
With hooks and focus now in tune,
My toggle leaps beneath the moon.
A joyful hop in every line—code’s a real delight!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (1)

17-21: ⚠️ Potential issue

Fix missing context properties causing build failure.

The sendValues object is missing the required nextItem and previousItem properties that are defined in the ToggleContext type, causing a TypeScript error.

const sendValues = {
    activeToggles,
    setActiveToggles,
    type
+   nextItem: () => {},
+   previousItem: () => {}
};

Consider whether these functions are still needed in the context, as you're now using RovingFocusGroup for focus management. If they're no longer needed, update the context type definition instead.

🧹 Nitpick comments (3)
src/components/ui/Toggle/Toggle.tsx (1)

38-39: Helpful transitional comments

These comments explain why validation was removed, which is helpful for understanding the changes. Consider whether these comments will remain valuable long-term or if they should be removed after the PR is merged.

src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (1)

54-61: Improved focus management with RovingFocusGroup

Wrapping TogglePrimitive with RovingFocusGroup.Item is a good refactoring that removes the need for manual focus and keyboard event handling. This likely improves accessibility and reduces code complexity.

Consider improving the JSX formatting for consistency by adding proper indentation:

-    return <RovingFocusGroup.Item>
-        <TogglePrimitive
-            onClick={handleToggleSelect}
-            {...ariaProps}
-            {...dataProps}
-            {...props}
-        >{children}</TogglePrimitive>
-    </RovingFocusGroup.Item>;
+    return (
+        <RovingFocusGroup.Item>
+            <TogglePrimitive
+                onClick={handleToggleSelect}
+                {...ariaProps}
+                {...dataProps}
+                {...props}
+            >
+                {children}
+            </TogglePrimitive>
+        </RovingFocusGroup.Item>
+    );
src/components/ui/Toggle/stories/Toggle.stories.tsx (1)

238-242: Consider adding aria-label for icon-only toggles.

For better accessibility, icon-only toggles should include an aria-label to describe their purpose to screen reader users.

<Toggle
    defaultPressed={false}
    onChange={() => {}}
+   aria-label="Toggle move function"
>
    <MoveIcon />
</Toggle>

Also applies to: 248-252

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26eff58 and a4ba5c6.

📒 Files selected for processing (12)
  • src/components/ui/Button/Button.tsx (2 hunks)
  • src/components/ui/Code/Code.tsx (1 hunks)
  • src/components/ui/Code/stories/Code.stories.tsx (3 hunks)
  • src/components/ui/Toggle/Toggle.tsx (2 hunks)
  • src/components/ui/Toggle/stories/Toggle.stories.tsx (1 hunks)
  • src/components/ui/Toggle/tests/Toggle.test.js (1 hunks)
  • src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (2 hunks)
  • src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (3 hunks)
  • src/core/hooks/createDataAttribute/createDataAttribute.test.tsx (1 hunks)
  • src/core/hooks/createDataAttribute/index.ts (1 hunks)
  • src/core/hooks/useControllableState/index.tsx (1 hunks)
  • src/core/hooks/useControllableState/useControllableState.test.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/components/ui/Toggle/Toggle.tsx (1)
Learnt from: kotAPI
PR: rad-ui/ui#576
File: src/core/primitives/Toggle/index.tsx:15-22
Timestamp: 2025-03-13T06:35:04.614Z
Learning: In the `TogglePrimitive` component (`src/core/primitives/Toggle/index.tsx`), when the component becomes controlled, it's acceptable to not sync the internal `isPressed` state with the external `pressed` prop.
🧬 Code Definitions (6)
src/core/hooks/createDataAttribute/createDataAttribute.test.tsx (1)
src/core/hooks/createDataAttribute/index.ts (2) (2)
  • useCreateDataAttribute (10-25)
  • useComposeAttributes (33-40)
src/core/hooks/useControllableState/useControllableState.test.tsx (1)
src/core/hooks/useControllableState/index.tsx (1) (1)
  • useControllableState (15-78)
src/components/ui/Button/Button.tsx (1)
src/core/hooks/createDataAttribute/index.ts (1) (1)
  • useCreateDataAttribute (10-25)
src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (1)
src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1) (1)
  • ToggleContext (11-17)
src/components/ui/Toggle/Toggle.tsx (1)
src/core/hooks/useControllableState/index.tsx (1) (1)
  • useControllableState (15-78)
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (1)
src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1) (1)
  • ToggleContext (11-17)
🪛 GitHub Check: lint
src/components/ui/Toggle/stories/Toggle.stories.tsx

[warning] 201-201:
A form label must be associated with a control


[warning] 192-192:
A form label must be associated with a control


[warning] 183-183:
A form label must be associated with a control

🪛 GitHub Actions: Build RAD UI
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx

[error] 30-33: TS2739: Type '{ activeToggles: any; setActiveToggles: Dispatch; type: any; }' is missing the following properties from type 'ToggleContextType': nextItem, previousItem

🔇 Additional comments (39)
src/components/ui/Code/Code.tsx (1)

16-16: Consistent formatting looks good!

The spacing around default parameter assignments has been standardized, which enhances code readability.

src/components/ui/Code/stories/Code.stories.tsx (3)

6-7: Formatting improvement for better readability!

Adding spaces after commas in arrays improves readability and follows standard formatting practices.


56-58: Consistent indentation improves code readability!

The proper indentation of JSX content helps maintain a clear hierarchical structure.


76-80: Well-structured JSX formatting!

The consistent indentation in the mapping function's return statement improves readability.

src/core/hooks/createDataAttribute/index.ts (5)

1-1: Standardized quote usage!

Using single quotes for string literals is more consistent with the project's style.


5-5: Improved JSDoc formatting!

Adding a line break after the first sentence in JSDoc comments improves readability.


10-13: Better function parameter indentation!

The improved indentation of function parameters enhances code readability.


14-24: Consistent function body indentation!

The function body is now properly indented, making the code structure clearer.


33-40: Consistent indentation for useComposeAttributes!

The improved indentation for parameters and function body enhances readability.

src/core/hooks/createDataAttribute/createDataAttribute.test.tsx (2)

1-2: Standardized import formatting!

Using single quotes for imports maintains consistency with the project's style.


7-53: Consistent string quotation style!

Converting all string literals to use single quotes improves code style consistency throughout the test file.

src/core/hooks/useControllableState/useControllableState.test.tsx (1)

1-74: Great comprehensive test coverage for the useControllableState hook!

The tests thoroughly cover both uncontrolled and controlled modes of the hook, including:

  • Default value initialization
  • Value updates
  • Callback handling
  • Functional updates

This provides robust validation for a hook that will be critical for managing state in the refactored Toggle component.

src/components/ui/Toggle/Toggle.tsx (4)

4-4: Implements controlled and uncontrolled state patterns with new hook

The addition of useControllableState hook enhances the component by enabling proper controlled and uncontrolled behavior patterns.


12-12: LGTM, makes Toggle more flexible

Making the pressed prop optional is a good enhancement that allows the component to operate in both controlled and uncontrolled modes. The comment clearly explains the rationale.


32-36: Clean implementation of state management

The implementation using useControllableState is clean and follows best practices for React components that need to support both controlled and uncontrolled modes. This approach appropriately manages the internal state based on prop presence.


53-53: Simplified event handling

Directly passing setIsPressed to onPressedChange simplifies the code by eliminating the need for an intermediate handler function. This is a good refactoring that makes the code more maintainable.

src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (2)

5-5: Good upgrade to use RovingFocusGroup

Importing and utilizing RovingFocusGroup is a solid improvement that delegates focus management to a specialized utility rather than implementing it manually.


14-14: Simplified context usage

The context usage has been simplified to only extract what's needed after removing focus management code. This follows good practices by minimizing dependencies.

src/components/ui/Button/Button.tsx (2)

6-6: Improved formatting consistency

Removing extra spaces in the import statement improves code formatting consistency.


22-23: Standardized string quotes

Changing from double quotes to single quotes standardizes the string literals format in the codebase, improving consistency.

src/components/ui/Toggle/tests/Toggle.test.js (7)

7-7: Simplified component initialization

Removing the pressed prop appropriately tests the component in its uncontrolled state, which aligns with the component changes.


12-12: Updated component initialization for uncontrolled mode

Properly updated test cases to use the component in uncontrolled mode, aligning with the main component changes.

Also applies to: 17-17


21-41: Comprehensive test for controlled mode

This test case thoroughly verifies the behavior of the controlled mode:

  1. Confirms initial state matches the prop
  2. Verifies callback is called with the expected value when clicked
  3. Checks that the internal state doesn't change until props are updated
  4. Confirms the state updates correctly after props change

This is a good pattern for testing controlled components.


44-68: Well-structured test for uncontrolled mode

The new test case for uncontrolled mode appropriately verifies:

  1. Initial state based on defaultPressed
  2. Internal state updates when the toggle is clicked
  3. Callback is called with the correct values
  4. Toggle behavior works in both directions

This thoroughly covers the uncontrolled behavior of the component.


70-73: Simplified disabled state test

The test for disabled state has been appropriately simplified while still verifying the essential behavior.


75-80: Added test for defaultPressed

Good addition of a specific test for the defaultPressed prop in uncontrolled mode, ensuring this essential functionality is verified.


83-83: Updated component initialization in color test

Properly updated the color test to use the component in uncontrolled mode.

src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (3)

1-5: Good refactoring to use RovingFocusGroup for standardized accessibility.

The integration of RovingFocusGroup from core utils is a good decision as it provides standardized keyboard navigation and focus management, improving the component's accessibility.


9-9: New direction prop enhances component flexibility.

Adding the direction prop with default 'horizontal' improves the component's versatility by allowing both horizontal and vertical toggle group orientations.


29-36: Well-structured integration of RovingFocusGroup.

The implementation correctly wraps the toggle group with RovingFocusGroup components and passes appropriate props. Good job moving data-attributes to the Root component and maintaining className handling on the Group component.

🧰 Tools
🪛 GitHub Actions: Build RAD UI

[error] 30-33: TS2739: Type '{ activeToggles: any; setActiveToggles: Dispatch; type: any; }' is missing the following properties from type 'ToggleContextType': nextItem, previousItem

src/core/hooks/useControllableState/index.tsx (5)

3-14: Well-documented hook with clear purpose and parameters.

The JSDoc comments are thorough and clearly explain the hook's purpose, parameters, and return values.


15-24: Type-safe implementation with proper generics usage.

The hook correctly uses TypeScript generics to ensure type safety while remaining flexible for any value type. The implementation properly initializes state and tracks whether the component is operating in controlled or uncontrolled mode.


26-38: Helpful development-only warnings for mode changes.

The warning logic for detecting switches between controlled and uncontrolled modes will help developers identify potential issues during development.


40-54: Good practice to warn about controlled value changes without handlers.

This warning helps prevent the common React anti-pattern of changing a controlled value without providing an onChange handler.


56-75: Robust setValue implementation with proper memoization.

The setValue function correctly handles both direct values and updater functions, while properly managing state based on the component mode. The useCallback memoization prevents unnecessary re-renders.

src/components/ui/Toggle/stories/Toggle.stories.tsx (4)

8-25: Excellent component documentation with clear usage patterns.

The storybook parameters include comprehensive documentation that clearly explains the controlled vs. uncontrolled usage modes of the Toggle component.


32-70: Great controlled mode example with interactive state control.

This story effectively demonstrates the controlled usage pattern with React state and includes interactive buttons to manipulate the state from outside the component.


72-98: Clear uncontrolled example showing internal state management.

The implementation clearly demonstrates how to use the Toggle component in uncontrolled mode with the defaultPressed prop.


318-323: Good demonstration of disabled toggle states.

The stories effectively demonstrate disabled toggles in both on and off states, which is helpful for understanding the component's visual states.

Also applies to: 343-348

Comment on lines +155 to +224
export const FormIntegration = () => {
const [formData, setFormData] = React.useState({
notifications: false,
darkMode: true,
autoSave: false
});

const handleToggle = (field: keyof typeof formData, value: boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
alert(`Form submitted with: ${JSON.stringify(formData, null, 2)}`);
};

return (
<SandboxEditor className="space-y-4 pt-4">
<div className="mb-2 text-sm font-medium">Form Integration (Controlled)</div>
<div className="text-xs text-gray-600 mb-4">
Example showing toggles integrated into a form with controlled state.
</div>

<form onSubmit={handleSubmit} className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-sm">Enable Notifications</label>
<Toggle
pressed={formData.notifications}
onChange={(value) => handleToggle('notifications', value)}
color="blue"
/>
</div>

<div className="flex items-center justify-between">
<label className="text-sm">Dark Mode</label>
<Toggle
pressed={formData.darkMode}
onChange={(value) => handleToggle('darkMode', value)}
color="purple"
/>
</div>

<div className="flex items-center justify-between">
<label className="text-sm">Auto-Save</label>
<Toggle
pressed={formData.autoSave}
onChange={(value) => handleToggle('autoSave', value)}
color="green"
/>
</div>

<div className="pt-4">
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Save Preferences
</button>
</div>
</form>

<div className="text-sm mt-4 p-2 bg-gray-100 rounded">
<pre className="text-xs">{JSON.stringify(formData, null, 2)}</pre>
</div>
</SandboxEditor>
);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Form labels need proper associations with controls.

The form implementation is good, but there are accessibility issues with the labels not being properly associated with their controls.

Update the form labels to use proper associations with their corresponding Toggle components:

- <label className="text-sm">Enable Notifications</label>
+ <label className="text-sm" id="notifications-label">Enable Notifications</label>
  <Toggle
      pressed={formData.notifications}
      onChange={(value) => handleToggle('notifications', value)}
      color="blue"
+     aria-labelledby="notifications-label"
  />

Apply similar changes to the other form labels and Toggle components for proper accessibility.

📝 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.

Suggested change
export const FormIntegration = () => {
const [formData, setFormData] = React.useState({
notifications: false,
darkMode: true,
autoSave: false
});
const handleToggle = (field: keyof typeof formData, value: boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
alert(`Form submitted with: ${JSON.stringify(formData, null, 2)}`);
};
return (
<SandboxEditor className="space-y-4 pt-4">
<div className="mb-2 text-sm font-medium">Form Integration (Controlled)</div>
<div className="text-xs text-gray-600 mb-4">
Example showing toggles integrated into a form with controlled state.
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-sm">Enable Notifications</label>
<Toggle
pressed={formData.notifications}
onChange={(value) => handleToggle('notifications', value)}
color="blue"
/>
</div>
<div className="flex items-center justify-between">
<label className="text-sm">Dark Mode</label>
<Toggle
pressed={formData.darkMode}
onChange={(value) => handleToggle('darkMode', value)}
color="purple"
/>
</div>
<div className="flex items-center justify-between">
<label className="text-sm">Auto-Save</label>
<Toggle
pressed={formData.autoSave}
onChange={(value) => handleToggle('autoSave', value)}
color="green"
/>
</div>
<div className="pt-4">
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Save Preferences
</button>
</div>
</form>
<div className="text-sm mt-4 p-2 bg-gray-100 rounded">
<pre className="text-xs">{JSON.stringify(formData, null, 2)}</pre>
</div>
</SandboxEditor>
);
};
export const FormIntegration = () => {
const [formData, setFormData] = React.useState({
notifications: false,
darkMode: true,
autoSave: false
});
const handleToggle = (field: keyof typeof formData, value: boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
alert(`Form submitted with: ${JSON.stringify(formData, null, 2)}`);
};
return (
<SandboxEditor className="space-y-4 pt-4">
<div className="mb-2 text-sm font-medium">Form Integration (Controlled)</div>
<div className="text-xs text-gray-600 mb-4">
Example showing toggles integrated into a form with controlled state.
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-sm" id="notifications-label">Enable Notifications</label>
<Toggle
pressed={formData.notifications}
onChange={(value) => handleToggle('notifications', value)}
color="blue"
aria-labelledby="notifications-label"
/>
</div>
<div className="flex items-center justify-between">
<label className="text-sm" id="darkMode-label">Dark Mode</label>
<Toggle
pressed={formData.darkMode}
onChange={(value) => handleToggle('darkMode', value)}
color="purple"
aria-labelledby="darkMode-label"
/>
</div>
<div className="flex items-center justify-between">
<label className="text-sm" id="autoSave-label">Auto-Save</label>
<Toggle
pressed={formData.autoSave}
onChange={(value) => handleToggle('autoSave', value)}
color="green"
aria-labelledby="autoSave-label"
/>
</div>
<div className="pt-4">
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Save Preferences
</button>
</div>
</form>
<div className="text-sm mt-4 p-2 bg-gray-100 rounded">
<pre className="text-xs">{JSON.stringify(formData, null, 2)}</pre>
</div>
</SandboxEditor>
);
};
🧰 Tools
🪛 GitHub Check: lint

[warning] 201-201:
A form label must be associated with a control


[warning] 192-192:
A form label must be associated with a control


[warning] 183-183:
A form label must be associated with a control

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/components/ui/ToggleGroup/tests/ToggleGroup.test.js (1)

27-27: Consider adding onChange callback verifications.

These lines clearly test multiple selection states by clicking items and verifying the pressed attributes. To bolster coverage, you may want to confirm that any onChange callback (if present) is triggered with the correct toggles.

Also applies to: 29-31, 33-35, 37-38, 40-41

src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (1)

9-9: New ‘direction’ prop expands usage flexibility.

The introduction of a direction prop enhances orientation control. Consider validating its possible values (e.g., restricting to 'horizontal' or 'vertical') to prevent unexpected usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2894c and ee7f84f.

📒 Files selected for processing (2)
  • src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (2 hunks)
  • src/components/ui/ToggleGroup/tests/ToggleGroup.test.js (2 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (1)
src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1) (1)
  • ToggleContext (9-13)
🔇 Additional comments (8)
src/components/ui/ToggleGroup/tests/ToggleGroup.test.js (5)

13-13: Great usage of getAllByText.

The test correctly verifies that all expected items are rendered and matches them by label text.

Also applies to: 16-18


45-45: Multiple selection toggles are thoroughly tested.

This sequence of clicks and un-toggles is correctly validated, ensuring reliable behavior when toggling items repeatedly.

Also applies to: 47-51, 53-56, 57-57, 59-60


64-65: Single selection logic looks robust.

Verifying that only the clicked item is active aligns with the expected single selection.

Also applies to: 66-68, 69-71, 73-75


79-80: Sequential clicks in single selection scenario are handled well.

The final check correctly ensures that only the last clicked item remains active.

Also applies to: 81-85, 87-91, 93-93


97-98: Color attribute test is valid.

Confirming that the data-accent-color attribute is set to "blue" verifies the stylistic prop usage.

src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (3)

1-1: Importing React and useState is appropriate.

No concerns here; importing useState is essential for local state management.


5-5: RovingFocusGroup import is properly introduced.

Replacing manual focus control with a dedicated utility is a beneficial step for maintainability and accessibility.


29-36: Focus management refactor using RovingFocusGroup is well-structured.

Wrapping children with RovingFocusGroup.Root and RovingFocusGroup.Group cleanly replaces manual handling. It simplifies the component implementation and should help ensure consistent focus behavior.

@kotAPI kotAPI merged commit f4a44ad into main Mar 20, 2025
6 checks passed
@kotAPI kotAPI deleted the kotapi/refactor-toggle-group-to-use-roving-focus branch March 20, 2025 01:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee7f84f and cda4c3d.

📒 Files selected for processing (5)
  • src/components/ui/Toggle/Toggle.tsx (1 hunks)
  • src/components/ui/ToggleGroup/ToggleGroup.tsx (2 hunks)
  • src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1 hunks)
  • src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (2 hunks)
  • src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ui/ToggleGroup/contexts/toggleContext.tsx
🧰 Additional context used
🧠 Learnings (1)
src/components/ui/Toggle/Toggle.tsx (1)
Learnt from: kotAPI
PR: rad-ui/ui#576
File: src/core/primitives/Toggle/index.tsx:15-22
Timestamp: 2025-03-13T06:35:04.614Z
Learning: In the `TogglePrimitive` component (`src/core/primitives/Toggle/index.tsx`), when the component becomes controlled, it's acceptable to not sync the internal `isPressed` state with the external `pressed` prop.
🧬 Code Definitions (3)
src/components/ui/Toggle/Toggle.tsx (1)
src/core/hooks/useControllableState/index.tsx (1) (1)
  • useControllableState (15-78)
src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (1)
src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1) (1)
  • ToggleContext (20-24)
src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (1)
src/components/ui/ToggleGroup/contexts/toggleContext.tsx (1) (1)
  • ToggleContext (20-24)
🪛 Biome (1.9.4)
src/components/ui/ToggleGroup/ToggleGroup.tsx

[error] 12-12: Shouldn't redeclare 'ToggleItem'. Consider to delete it or rename it.

'ToggleItem' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (23)
src/components/ui/Toggle/Toggle.tsx (6)

4-4: Good addition of useControllableState hook

Adding the useControllableState hook is a great improvement to support both controlled and uncontrolled component patterns in a standardized way.


10-30: Well-documented props with improved type definitions

The updated JSDoc comments and type definitions provide clear documentation for both controlled and uncontrolled usage patterns. Making pressed optional while adding defaultPressed follows React's established pattern for controllable components.


33-47: Great component documentation with usage examples

The documentation now clearly illustrates both controlled and uncontrolled usage patterns with concrete examples, making it much easier for developers to understand how to use this component correctly.


58-63: Excellent implementation of useControllableState

The implementation correctly uses the useControllableState hook to handle the component's state management in both controlled and uncontrolled modes. This simplifies the component logic and makes it more robust.


65-67: Appropriately removed redundant validation

Good removal of the validation logic since the hook now handles the validation and provides appropriate warnings in development mode.


80-80: Simplified event handling

Directly passing setIsPressed to onPressedChange is cleaner than having an intermediate handler function. This change reduces code complexity while maintaining the same functionality.

src/components/ui/ToggleGroup/fragments/ToggleItem.tsx (7)

5-5: Good use of RovingFocusGroup for accessibility

Adding RovingFocusGroup significantly improves keyboard navigation and accessibility without requiring manual focus management code.


7-18: Improved prop type definitions and documentation

The enhanced type definitions with descriptive JSDoc comments make the API more self-documenting and easier to use correctly.


20-33: Comprehensive component documentation

Adding detailed JSDoc comments with usage examples significantly improves the developer experience by making it clear how to use this component within the ToggleGroup context.


35-35: Simplified context usage

The code is now more focused on component logic by removing type assertions and directly destructuring the context values.


38-40: Good typing for attribute objects

Using Record<string, string> for the attribute objects provides proper typing while maintaining flexibility.


70-77: Improved code comments

Adding a descriptive comment before setting ARIA attributes helps explain the purpose of this code block.


79-86: Simplified component rendering with RovingFocusGroup

The component structure is now cleaner and more focused on its core functionality by delegating focus management to RovingFocusGroup.Item. This also removes a significant amount of manual event handling code.

src/components/ui/ToggleGroup/ToggleGroup.tsx (5)

19-31: Good prop interface definition

The ToggleGroupProps type clearly defines the available props with descriptive comments for each property.


32-40: Well-structured component type definition

The ToggleGroupComponent type properly defines the component with its static properties, making TypeScript aware of the compound component pattern.


41-68: Excellent component documentation with examples

The comprehensive JSDoc comments with usage examples for both single and multiple selection modes make it clear how to use the component correctly.


69-83: Implementation aligned with type definitions

The component implementation correctly uses the newly defined types and provides default values for optional props.


87-95: Good documentation for compound components

Adding descriptive comments for the Root and Item static properties helps developers understand the purpose of each sub-component.

src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx (5)

5-5: Good use of RovingFocusGroup for accessibility

Importing and utilizing RovingFocusGroup improves keyboard navigation and accessibility without requiring complex manual focus management code.


9-33: Well-structured interface with comprehensive documentation

The ToggleGroupRootProps interface clearly defines all the component's props with descriptive JSDoc comments, improving type safety and developer experience.


34-47: Excellent component documentation with example

The JSDoc comments provide a clear explanation of the component's purpose and usage, including a concrete example that demonstrates proper implementation.


48-58: Props destructuring with proper typing

The component now correctly specifies its prop type and provides sensible defaults for optional props, including the new direction prop.


78-84: Improved component structure with RovingFocusGroup

The component now properly uses RovingFocusGroup to handle keyboard navigation, simplifying the code by removing manual focus management. The nested structure with Root and Group components follows the RovingFocusGroup API correctly.

Comment on lines +8 to +18
/**
* ToggleGroup Item interface
* @typedef ToggleItem
*/
type ToggleItem = {
/** Unique value for the toggle item */
value: any;
/** Display label for the toggle item */
label: any;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type name conflict with imported component

The ToggleItem type is named the same as the imported component, which could lead to confusion and is flagged by static analysis.

Rename the type to avoid confusion:

-type ToggleItem = {
+type ToggleItemType = {
     /** Unique value for the toggle item */
     value: any;
     /** Display label for the toggle item */
     label: any;
 };

Then update the items prop type in ToggleGroupProps:

-    items?: ToggleItem[];
+    items?: ToggleItemType[];
📝 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.

Suggested change
/**
* ToggleGroup Item interface
* @typedef ToggleItem
*/
type ToggleItem = {
/** Unique value for the toggle item */
value: any;
/** Display label for the toggle item */
label: any;
};
/**
* ToggleGroup Item interface
* @typedef ToggleItemType
*/
-type ToggleItem = {
+type ToggleItemType = {
/** Unique value for the toggle item */
value: any;
/** Display label for the toggle item */
label: any;
};
// ... other parts of the file ...
type ToggleGroupProps = {
// ... other properties ...
- items?: ToggleItem[];
+ items?: ToggleItemType[];
// ... other properties ...
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 12-12: Shouldn't redeclare 'ToggleItem'. Consider to delete it or rename it.

'ToggleItem' is defined here:

(lint/suspicious/noRedeclare)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant