Skip to content

feat: add Toggle component#696

Open
rohanchkrabrty wants to merge 1 commit intomainfrom
feat-toggle
Open

feat: add Toggle component#696
rohanchkrabrty wants to merge 1 commit intomainfrom
feat-toggle

Conversation

@rohanchkrabrty
Copy link
Contributor

@rohanchkrabrty rohanchkrabrty commented Mar 12, 2026

Summary

  • Add Toggle and Toggle.Group components wrapping Base UI's Toggle and ToggleGroup primitives
  • Toggle supports size prop (1–4) via CVA, matching IconButton sizing
  • Toggle.Group provides shared pressed state with single or multiple selection, with grouped styling (border-radius handled via overflow: clip)
  • Add docs page with interactive playground, code demos (group, multiple, size, controlled, disabled), and API reference
  • Add playground examples and register toggle icons in the react-live demo scope

Summary by CodeRabbit

  • New Features

    • Added Toggle component with support for multiple sizes and pressed/disabled states
    • Added Toggle.Group for single and multiple selection modes with customizable layouts
  • Documentation

    • Added comprehensive component documentation with usage examples, API reference, and interactive playground demos
  • Tests

    • Added test suite covering component rendering, state management, keyboard interactions, and accessibility

@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Mar 12, 2026 4:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new Toggle component to the raystack design system, including the React component implementation with CSS styling, size variants, a nested Toggle.Group subcomponent, comprehensive test coverage, documentation, and playground examples. The component is exported at the package level.

Changes

Cohort / File(s) Summary
Toggle Component Implementation
packages/raystack/components/toggle/toggle.tsx, packages/raystack/components/toggle/toggle.module.css
New Toggle component with forwardRef support, size variants (1-4), nested Toggle.Group subcomponent, and corresponding CSS module with interactive states (hover, pressed, disabled) and group-specific styling.
Toggle Tests
packages/raystack/components/toggle/__tests__/toggle.test.tsx
Comprehensive test suite covering rendering, size variants, pressed state, event handling, disabled state, and Toggle.Group behavior including single/multiple selection and callbacks.
Toggle Documentation & Props
apps/www/src/content/docs/components/toggle/props.ts, apps/www/src/content/docs/components/toggle/index.mdx
New TypeScript interfaces ToggleProps and ToggleGroupProps defining component API surface; MDX documentation file with usage examples, anatomy, API reference, and accessibility notes.
Toggle Demo & Playground Examples
apps/www/src/content/docs/components/toggle/demo.ts, apps/www/src/components/playground/toggle-examples.tsx
Demo module with code generation utilities and multiple demo configurations (playground, preview, grouped, controlled, size, disabled); playground component rendering Toggle examples with different states and configurations.
Package Exports
packages/raystack/components/toggle/index.ts, packages/raystack/index.tsx, apps/www/src/components/playground/index.ts
New public exports exposing Toggle at component and package levels; playground component exports expanded to include toggle-examples.
Demo Component Updates
apps/www/src/components/demo/demo.tsx
Added new icon imports (FontBoldIcon, FontItalicIcon, TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon, UnderlineIcon) and expanded scope object to expose icons for code demos.
Minor Import Reordering
packages/raystack/components/icon-button/icon-button.tsx
Reordered imports from class-variance-authority; no functional changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Do not merge

Suggested reviewers

  • rsbh
  • paanSinghCoder
  • rohilsurana

Poem

🐰 A toggle here, a toggle there,
With sizes fine and states so fair,
We hop and click through pressed and free,
Group and single, wait and see!
The docs shine bright, the tests all pass,
Now you'll toggle with such class! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: the addition of a Toggle component to the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-toggle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (4)
packages/raystack/components/toggle/toggle.tsx (1)

24-28: Redundant size type declaration.

The size?: 1 | 2 | 3 | 4 declaration on line 27 duplicates what VariantProps<typeof toggleVariants> already infers from the CVA definition. This redundancy could lead to maintenance issues if sizes change.

♻️ Suggested simplification
 export interface ToggleProps
   extends TogglePrimitive.Props,
-    VariantProps<typeof toggleVariants> {
-  size?: 1 | 2 | 3 | 4;
-}
+    VariantProps<typeof toggleVariants> {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/toggle/toggle.tsx` around lines 24 - 28, Remove
the redundant explicit size declaration from the ToggleProps interface: rely on
VariantProps<typeof toggleVariants> to provide the size type instead of the
separate "size?: 1 | 2 | 3 | 4" line; edit the ToggleProps declaration (symbol:
ToggleProps) to extend TogglePrimitive.Props and VariantProps<typeof
toggleVariants> only, remove the "size" property, and run typecheck to ensure no
other code expects the removed literal type and update those callsites if
necessary.
apps/www/src/content/docs/components/toggle/demo.ts (2)

1-9: Consider adding type annotation for props parameter.

Using any for the props parameter loses type safety. A more specific type would improve maintainability.

♻️ Suggested improvement
-const getCode = (props: any) => {
+const getCode = (props: Record<string, unknown>) => {
   return `<Toggle aria-label="Bold"${getPropsString(props)}>
   <FontBoldIcon />
 </Toggle>`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toggle/demo.ts` around lines 1 - 9, The
getCode function currently types its props parameter as any; change it to a
specific props type (e.g., React.ComponentProps<typeof Toggle> or a dedicated
ToggleProps/Partial<ToggleProps> type imported from the Toggle component) to
restore type safety, update the function signature for getCode(props: /* new
type */) and adjust any callers or the getPropsString helper signature if
necessary so types align with getPropsString(props) and the <Toggle ...> usage.

68-82: Controlled example's callback signature differs from documentation.

The controlledDemo passes setPressed directly to onPressedChange, but per the documented ToggleProps, the callback signature is (pressed: boolean, event: Event) => void. While React's useState setter will ignore the extra event argument at runtime, this example may confuse users expecting to use the event parameter.

Consider showing the full signature for educational clarity:

📝 Suggested clarification
 export const controlledDemo = {
   type: 'code',
   code: `function ControlledToggle() {
   const [pressed, setPressed] = React.useState(false);
   return (
     <Toggle
       aria-label="Star"
       pressed={pressed}
-      onPressedChange={setPressed}
+      onPressedChange={(newPressed) => setPressed(newPressed)}
     >
       {pressed ? "★" : "☆"}
     </Toggle>
   );
 }`
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toggle/demo.ts` around lines 68 - 82,
The controlled example passes setPressed directly to onPressedChange which hides
the documented callback signature (pressed: boolean, event: Event) — update the
ControlledToggle example (controlledDemo) to accept both parameters in the
handler: define an onPressedChange wrapper (e.g., (pressed, event) =>
setPressed(pressed)) and use that instead of passing setPressed directly so the
example matches ToggleProps and shows the event parameter; optionally add a
brief param name or type annotation to the handler to make the signature
explicit.
packages/raystack/components/toggle/__tests__/toggle.test.tsx (1)

48-84: Consider adding size={2} test for completeness.

The size variants tests cover sizes 1, 3, and 4 but skip size 2. While unlikely to fail given the pattern, adding it would ensure full coverage.

💚 Suggested addition
     it('applies size 1 class', () => {
       render(
         <Toggle aria-label='Bold' size={1}>
           B
         </Toggle>
       );
       const toggle = screen.getByRole('button', { name: 'Bold' });
       expect(toggle).toHaveClass(styles['size-1']);
     });

+    it('applies size 2 class', () => {
+      render(
+        <Toggle aria-label='Bold' size={2}>
+          B
+        </Toggle>
+      );
+      const toggle = screen.getByRole('button', { name: 'Bold' });
+      expect(toggle).toHaveClass(styles['size-2']);
+    });
+
     it('applies size 3 class', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/toggle/__tests__/toggle.test.tsx` around lines
48 - 84, Add a missing unit test for the size=2 variant inside the "Size
Variants" describe block by mirroring the existing size tests: render <Toggle
aria-label='Bold' size={2}>B</Toggle>, query the button with
screen.getByRole('button', { name: 'Bold' }) and assert it has the
styles['size-2'] class; place the new it('applies size 2 class', ...) alongside
the other size tests so Toggle and styles['size-2'] are exercised for full
coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/www/src/components/playground/toggle-examples.tsx`:
- Around line 37-48: The four icon-only Toggle examples (Toggle components
containing FontBoldIcon) all use the same aria-label='Bold', making the size
demo indistinguishable to screen readers; update each Toggle instance in
toggle-examples.tsx to provide a unique accessible name (e.g., aria-label='Bold
size 1', 'Bold size 2', etc.) or add per-sample visible size text or
visually-hidden labels next to the FontBoldIcon so each sample is announced
distinctly by assistive technology.

In `@apps/www/src/content/docs/components/toggle/demo.ts`:
- Around line 11-29: The playground controls define size options as strings
which leads to size being emitted as a string prop; update the
playground.controls.size definition in the demo so options are numeric (e.g.,
[1, 2, 3, 4]) and set defaultValue to a number (e.g., 3) so
getCode/getPropsString will generate size={3}; keep the rest of the controls and
getCode reference unchanged.

In `@packages/raystack/components/toggle/toggle.module.css`:
- Around line 14-15: The inner .toggleContent keeps a standalone border-radius
which makes grouped toggles render as rounded pills; update the CSS so
.toggleContent has border-radius: 0 when it lives inside a grouped toggle
container (i.e., when its parent .toggle is part of a group), and re-apply
rounded corners only for the group's first and last items using selectors like
.toggleGroup .toggle:first-child .toggleContent and .toggleGroup
.toggle:last-child .toggleContent (or the existing group modifier class you
use), and apply the same reset to the other occurrences referenced (lines ~31-33
and 75-89) so hover/pressed fills render flush across the segmented surface.
- Around line 27-40: The hover/pressed styles should be limited to enabled
toggles instead of disabling pointer events; update the selectors from
.toggle:hover and .toggle[data-pressed] (and their .toggleContent counterparts)
to guard with :not([data-disabled]) (e.g., .toggle:not([data-disabled]):hover
and .toggle:not([data-disabled])[data-pressed] and likewise for .toggleContent)
and remove pointer-events: none from .toggle[data-disabled] so cursor:
not-allowed remains effective while keeping cursor and opacity.

---

Nitpick comments:
In `@apps/www/src/content/docs/components/toggle/demo.ts`:
- Around line 1-9: The getCode function currently types its props parameter as
any; change it to a specific props type (e.g., React.ComponentProps<typeof
Toggle> or a dedicated ToggleProps/Partial<ToggleProps> type imported from the
Toggle component) to restore type safety, update the function signature for
getCode(props: /* new type */) and adjust any callers or the getPropsString
helper signature if necessary so types align with getPropsString(props) and the
<Toggle ...> usage.
- Around line 68-82: The controlled example passes setPressed directly to
onPressedChange which hides the documented callback signature (pressed: boolean,
event: Event) — update the ControlledToggle example (controlledDemo) to accept
both parameters in the handler: define an onPressedChange wrapper (e.g.,
(pressed, event) => setPressed(pressed)) and use that instead of passing
setPressed directly so the example matches ToggleProps and shows the event
parameter; optionally add a brief param name or type annotation to the handler
to make the signature explicit.

In `@packages/raystack/components/toggle/__tests__/toggle.test.tsx`:
- Around line 48-84: Add a missing unit test for the size=2 variant inside the
"Size Variants" describe block by mirroring the existing size tests: render
<Toggle aria-label='Bold' size={2}>B</Toggle>, query the button with
screen.getByRole('button', { name: 'Bold' }) and assert it has the
styles['size-2'] class; place the new it('applies size 2 class', ...) alongside
the other size tests so Toggle and styles['size-2'] are exercised for full
coverage.

In `@packages/raystack/components/toggle/toggle.tsx`:
- Around line 24-28: Remove the redundant explicit size declaration from the
ToggleProps interface: rely on VariantProps<typeof toggleVariants> to provide
the size type instead of the separate "size?: 1 | 2 | 3 | 4" line; edit the
ToggleProps declaration (symbol: ToggleProps) to extend TogglePrimitive.Props
and VariantProps<typeof toggleVariants> only, remove the "size" property, and
run typecheck to ensure no other code expects the removed literal type and
update those callsites if necessary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a75733d-55ee-4e57-a761-c95079cb1ab0

📥 Commits

Reviewing files that changed from the base of the PR and between 14de0ba and cd6975e.

📒 Files selected for processing (12)
  • apps/www/src/components/demo/demo.tsx
  • apps/www/src/components/playground/index.ts
  • apps/www/src/components/playground/toggle-examples.tsx
  • apps/www/src/content/docs/components/toggle/demo.ts
  • apps/www/src/content/docs/components/toggle/index.mdx
  • apps/www/src/content/docs/components/toggle/props.ts
  • packages/raystack/components/icon-button/icon-button.tsx
  • packages/raystack/components/toggle/__tests__/toggle.test.tsx
  • packages/raystack/components/toggle/index.ts
  • packages/raystack/components/toggle/toggle.module.css
  • packages/raystack/components/toggle/toggle.tsx
  • packages/raystack/index.tsx

@rohanchkrabrty rohanchkrabrty enabled auto-merge (squash) March 12, 2026 06:58
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