Skip to content

feat(floating-actions): add FloatingActions component#745

Open
paanSinghCoder wants to merge 6 commits intomainfrom
feat/floating-actions
Open

feat(floating-actions): add FloatingActions component#745
paanSinghCoder wants to merge 6 commits intomainfrom
feat/floating-actions

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Apr 20, 2026

Summary

  • New FloatingActions component — a floating bar for surfacing contextual actions (bulk-action toolbar, row-hover actions)
  • Position-agnostic visual primitive with shadow + rounded pill; composes with existing Chip, Button, IconButton
  • Ships FloatingActions.Separator and Vitest suite; docs + playground + examples page wired up

Usage

<FloatingActions aria-label="Selection actions">
  <Chip isDismissible leadingIcon={<TransformIcon />}>2 selected</Chip>
  <FloatingActions.Separator />
  <Button variant="outline" color="neutral" size="small">Move to</Button>
</FloatingActions>

Test plan

  • pnpm --filter @raystack/apsara test components/floating-actions
  • Docs site renders the demos

🤖 Generated with Claude Code

A floating bar for surfacing contextual actions (bulk-action toolbar,
row hover actions, etc.). Position-agnostic visual primitive with a
matching vertical separator; composes freely with existing Chip, Button,
and IconButton.

- Component source, styles (--rs-shadow-lifted), and tests
- Docs page with preview, bulk-actions, and icon-only demos
- Playground example and examples/page.tsx section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

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

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 20, 2026 11:06am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@paanSinghCoder has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 31 minutes and 54 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 31 minutes and 54 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a85a0bf5-d156-4e77-9e1b-d6e2ce8109b1

📥 Commits

Reviewing files that changed from the base of the PR and between cbbd2c8 and b614f17.

📒 Files selected for processing (4)
  • apps/www/src/content/docs/components/floating-actions/index.mdx
  • packages/raystack/components/floating-actions/__tests__/floating-actions.test.tsx
  • packages/raystack/components/floating-actions/floating-actions.module.css
  • packages/raystack/components/floating-actions/floating-actions.tsx
📝 Walkthrough

Walkthrough

This change introduces a new FloatingActions component to the Raystack component library. The implementation includes a root container component with a configurable ARIA role (defaulting to "toolbar") and a nested Separator subcomponent for visual dividers. The component is accompanied by CSS styling, comprehensive tests, TypeScript prop interfaces, documentation, and usage examples across the website. The component integrates with existing Raystack patterns and is exported through the public API surface.

Suggested reviewers

  • rsbh
  • rohanchkrabrty
  • ravisuhag
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding the FloatingActions component.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly describes the new FloatingActions component, its purpose (contextual actions bar), key features (separator, composition), and provides a concrete usage example.

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


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
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: 5

🤖 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/app/examples/page.tsx`:
- Around line 2818-2831: The IconButton components inside the FloatingActions
(the icon-only row actions using IconButton with GearIcon, FileTextIcon, and
DotsHorizontalIcon) lack accessible names; update each IconButton to provide an
accessible label (e.g., add an aria-label or aria-labelledby with a clear short
name like "Settings", "View file", "More actions") so screen readers can
identify them, or alternatively include visually hidden text inside the button
and keep the icons for visual users; ensure you update all three IconButton
instances (the ones wrapping GearIcon, FileTextIcon, and DotsHorizontalIcon).

In `@apps/www/src/content/docs/components/floating-actions/demo.ts`:
- Around line 42-47: The IconButton instances inside the FloatingActions demo
lack accessible names; update each icon-only IconButton (the IconButton
components wrapping Pencil2Icon, UploadIcon, and InfoCircledIcon) to provide
accessible labels—e.g., add an aria-label prop or include visually hidden text
describing the action like "Edit", "Upload", and "More info"—so screen readers
can announce their purpose; leave FloatingActions.Separator as-is.

In `@apps/www/src/content/docs/components/floating-actions/props.ts`:
- Around line 1-18: Update the docs types to match the implementation by
importing React types and extending the div passthrough API: add an import for
React (so React.ComponentProps and React.ReactNode are available) and change
FloatingActionsProps to extend React.ComponentProps<'div'> (keeping or removing
explicit props like children if desired) and change
FloatingActionsSeparatorProps to extend React.ComponentProps<'div'> so all
native div attributes (aria-*, data-*, event handlers, style, ref, etc.) are
supported; update references to FloatingActionsProps and
FloatingActionsSeparatorProps accordingly.

In
`@packages/raystack/components/floating-actions/__tests__/floating-actions.test.tsx`:
- Around line 43-75: The Separator component currently allows callers to
override aria-hidden because props are spread after the hardcoded attribute;
update the FloatingActions.Separator implementation in floating-actions.tsx to
spread {...props} first and then set aria-hidden="true" (so the hardcoded value
wins), and add/keep a regression test in floating-actions.test.tsx that renders
<FloatingActions.Separator aria-hidden="false" data-testid="sep"> and asserts
the element has aria-hidden="true"; reference the FloatingActions.Separator
component and ensure ref forwarding and className tests still pass.

In `@packages/raystack/components/floating-actions/floating-actions.tsx`:
- Around line 24-28: The separator div in floating-actions.tsx currently spreads
{...props} after setting aria-hidden='true', allowing callers to override it;
fix by ensuring aria-hidden stays true when props are forwarded—either move
{...props} before aria-hidden or (preferable) keep {...props} but explicitly set
aria-hidden={true} after the spread on the same div (the element using
className={cx(styles.separator, className)}), so aria-hidden cannot be
overridden by incoming props.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e00c87d3-cd54-40ca-bfcf-9b26aff31d29

📥 Commits

Reviewing files that changed from the base of the PR and between 4ccb30c and cbbd2c8.

📒 Files selected for processing (11)
  • apps/www/src/app/examples/page.tsx
  • apps/www/src/components/playground/floating-actions-examples.tsx
  • apps/www/src/components/playground/index.ts
  • apps/www/src/content/docs/components/floating-actions/demo.ts
  • apps/www/src/content/docs/components/floating-actions/index.mdx
  • apps/www/src/content/docs/components/floating-actions/props.ts
  • packages/raystack/components/floating-actions/__tests__/floating-actions.test.tsx
  • packages/raystack/components/floating-actions/floating-actions.module.css
  • packages/raystack/components/floating-actions/floating-actions.tsx
  • packages/raystack/components/floating-actions/index.tsx
  • packages/raystack/index.tsx

Comment thread apps/www/src/app/examples/page.tsx Outdated
Comment on lines +42 to +47
code: `<FloatingActions>
<IconButton variant="text" color="neutral" size="small"><Pencil2Icon /></IconButton>
<IconButton variant="text" color="neutral" size="small"><UploadIcon /></IconButton>
<FloatingActions.Separator />
<IconButton variant="text" color="neutral" size="small"><InfoCircledIcon /></IconButton>
</FloatingActions>`
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 | 🟠 Major

Add accessible names to the icon-only buttons.

These IconButtons have only SVG children, so the actions are likely unnamed for screen readers. The docs should model accessible usage.

♿ Proposed fix
-<FloatingActions>
-  <IconButton variant="text" color="neutral" size="small"><Pencil2Icon /></IconButton>
-  <IconButton variant="text" color="neutral" size="small"><UploadIcon /></IconButton>
+<FloatingActions aria-label="Row actions">
+  <IconButton aria-label="Edit" variant="text" color="neutral" size="small"><Pencil2Icon /></IconButton>
+  <IconButton aria-label="Upload" variant="text" color="neutral" size="small"><UploadIcon /></IconButton>
   <FloatingActions.Separator />
-  <IconButton variant="text" color="neutral" size="small"><InfoCircledIcon /></IconButton>
+  <IconButton aria-label="View details" variant="text" color="neutral" size="small"><InfoCircledIcon /></IconButton>
 </FloatingActions>`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/floating-actions/demo.ts` around lines
42 - 47, The IconButton instances inside the FloatingActions demo lack
accessible names; update each icon-only IconButton (the IconButton components
wrapping Pencil2Icon, UploadIcon, and InfoCircledIcon) to provide accessible
labels—e.g., add an aria-label prop or include visually hidden text describing
the action like "Edit", "Upload", and "More info"—so screen readers can announce
their purpose; leave FloatingActions.Separator as-is.

Comment on lines +1 to +18
export interface FloatingActionsProps {
/**
* The ARIA role of the container.
* @defaultValue "toolbar"
*/
role?: string;

/** Additional CSS class names. */
className?: string;

/** The contents of the floating bar. */
children?: React.ReactNode;
}

export interface FloatingActionsSeparatorProps {
/** Additional CSS class names. */
className?: string;
}
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Compare FloatingActions docs props against implementation/docs prop typing patterns.
# Expected: implementation uses div passthrough props; docs should either mirror that or intentionally document native props elsewhere.

rg -n -C3 "FloatingActionsProps|FloatingActionsSeparatorProps|ComponentProps<'div'>|React\\.ReactNode" \
  packages/raystack/components/floating-actions \
  apps/www/src/content/docs/components/floating-actions

Repository: raystack/apsara

Length of output: 4739


🏁 Script executed:

# Check for docs generator behavior with inherited props
find apps/www -type f -name "*.ts" -o -name "*.tsx" -o -name "*.js" | xargs grep -l "auto-type-table\|type-table" | head -5

Repository: raystack/apsara

Length of output: 105


🏁 Script executed:

# Find other components using ComponentProps<'div'> in docs to see if there's a pattern
fd -e "props.ts" "apps/www/src/content/docs/components" | head -10

Repository: raystack/apsara

Length of output: 508


🏁 Script executed:

# Check if React is imported in the docs props file
head -20 apps/www/src/content/docs/components/floating-actions/props.ts

Repository: raystack/apsara

Length of output: 440


🏁 Script executed:

# Look for an implementation pattern in other components that use ComponentProps<'div'>
find packages/raystack/components -type f -name "*.tsx" -exec grep -l "extends ComponentProps<'div'>" {} \; | head -3

Repository: raystack/apsara

Length of output: 453


🏁 Script executed:

# Check other component docs props files for import patterns
ls apps/www/src/content/docs/components/*/props.ts | head -5 | xargs head -3

Repository: raystack/apsara

Length of output: 832


🏁 Script executed:

# Check if any docs props files import React or ComponentProps
grep -r "^import\|^export interface" apps/www/src/content/docs/components/*/props.ts | head -20

Repository: raystack/apsara

Length of output: 1991


🏁 Script executed:

# Check the auto-type-table implementation in mdx-components
grep -A 20 "auto-type-table" apps/www/src/components/mdx/mdx-components.tsx | head -40

Repository: raystack/apsara

Length of output: 41


🏁 Script executed:

# Check tsconfig for global React types
find . -name "tsconfig.json" -o -name "tsconfig.*.json" | xargs grep -l "React\|types" | head -3

Repository: raystack/apsara

Length of output: 156


🏁 Script executed:

# Check tsconfig for global React types
cat apps/www/tsconfig.json | grep -A 5 -B 5 "React\|types\|skipLibCheck"

Repository: raystack/apsara

Length of output: 530


🏁 Script executed:

# Check the actual implementation interfaces more carefully
cat -n packages/raystack/components/floating-actions/floating-actions.tsx | head -30

Repository: raystack/apsara

Length of output: 1083


🏁 Script executed:

# Check if floating-actions docs props.ts actually compiles (check for any import)
cat -n apps/www/src/content/docs/components/floating-actions/props.ts

Repository: raystack/apsara

Length of output: 566


🏁 Script executed:

# Look at breadcrumb props.ts (which has imports) for comparison
cat -n apps/www/src/content/docs/components/breadcrumb/props.ts | head -30

Repository: raystack/apsara

Length of output: 1050


Align the docs props with the implementation's div passthrough API.

The implementation extends ComponentProps<'div'> and spreads all props, but the docs interface is a manual subset listing only role, className, and children. This hides the supported native div attributes (aria-, data-, event handlers, style, ref, etc.). Also, the docs file uses React.ReactNode without importing React, which is inconsistent with other component docs files like breadcrumb.

Import React types explicitly and extend ComponentProps<'div'> in both interfaces to match the implementation:

📝 Proposed docs type update
+import type { ComponentProps } from 'react';
+
-export interface FloatingActionsProps {
+export interface FloatingActionsProps extends ComponentProps<'div'> {
   /**
    * The ARIA role of the container.
    * `@defaultValue` "toolbar"
    */
-  role?: string;

   /** Additional CSS class names. */
-  className?: string;

   /** The contents of the floating bar. */
-  children?: React.ReactNode;
 }

-export interface FloatingActionsSeparatorProps {
+export interface FloatingActionsSeparatorProps extends ComponentProps<'div'> {
   /** Additional CSS class names. */
-  className?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/floating-actions/props.ts` around lines
1 - 18, Update the docs types to match the implementation by importing React
types and extending the div passthrough API: add an import for React (so
React.ComponentProps and React.ReactNode are available) and change
FloatingActionsProps to extend React.ComponentProps<'div'> (keeping or removing
explicit props like children if desired) and change
FloatingActionsSeparatorProps to extend React.ComponentProps<'div'> so all
native div attributes (aria-*, data-*, event handlers, style, ref, etc.) are
supported; update references to FloatingActionsProps and
FloatingActionsSeparatorProps accordingly.

Comment on lines +43 to +75
it('renders a separator with the separator class', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep).toBeInTheDocument();
expect(sep.className).toContain(styles.separator);
expect(sep).toHaveAttribute('aria-hidden', 'true');
});

it('applies custom className', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' className='custom-sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep.className).toContain(styles.separator);
expect(sep.className).toContain('custom-sep');
});

it('forwards ref', () => {
const ref = createRef<HTMLDivElement>();
render(
<FloatingActions>
<FloatingActions.Separator ref={ref} data-testid='sep' />
</FloatingActions>
);
expect(ref.current).toBeInstanceOf(HTMLDivElement);
expect(ref.current).toBe(screen.getByTestId('sep'));
});
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 | 🟡 Minor

Lock the separator’s decorative accessibility contract.

The current implementation can let callers override aria-hidden because props are spread after the hardcoded value. Add a regression test here and move aria-hidden='true' after {...props} in floating-actions.tsx.

♿ Proposed regression test and implementation adjustment
     it('renders a separator with the separator class', () => {
       render(
         <FloatingActions>
           <FloatingActions.Separator data-testid='sep' />
         </FloatingActions>
@@
       expect(sep.className).toContain(styles.separator);
       expect(sep).toHaveAttribute('aria-hidden', 'true');
     });
+
+    it('keeps the separator hidden from assistive technology', () => {
+      render(
+        <FloatingActions>
+          <FloatingActions.Separator data-testid='sep' aria-hidden={false} />
+        </FloatingActions>
+      );
+
+      expect(screen.getByTestId('sep')).toHaveAttribute('aria-hidden', 'true');
+    });

Apply this in packages/raystack/components/floating-actions/floating-actions.tsx:

   <div
-    aria-hidden='true'
     className={cx(styles.separator, className)}
     {...props}
+    aria-hidden='true'
   />
📝 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
it('renders a separator with the separator class', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep).toBeInTheDocument();
expect(sep.className).toContain(styles.separator);
expect(sep).toHaveAttribute('aria-hidden', 'true');
});
it('applies custom className', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' className='custom-sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep.className).toContain(styles.separator);
expect(sep.className).toContain('custom-sep');
});
it('forwards ref', () => {
const ref = createRef<HTMLDivElement>();
render(
<FloatingActions>
<FloatingActions.Separator ref={ref} data-testid='sep' />
</FloatingActions>
);
expect(ref.current).toBeInstanceOf(HTMLDivElement);
expect(ref.current).toBe(screen.getByTestId('sep'));
});
it('renders a separator with the separator class', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep).toBeInTheDocument();
expect(sep.className).toContain(styles.separator);
expect(sep).toHaveAttribute('aria-hidden', 'true');
});
it('keeps the separator hidden from assistive technology', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' aria-hidden={false} />
</FloatingActions>
);
expect(screen.getByTestId('sep')).toHaveAttribute('aria-hidden', 'true');
});
it('applies custom className', () => {
render(
<FloatingActions>
<FloatingActions.Separator data-testid='sep' className='custom-sep' />
</FloatingActions>
);
const sep = screen.getByTestId('sep');
expect(sep.className).toContain(styles.separator);
expect(sep.className).toContain('custom-sep');
});
it('forwards ref', () => {
const ref = createRef<HTMLDivElement>();
render(
<FloatingActions>
<FloatingActions.Separator ref={ref} data-testid='sep' />
</FloatingActions>
);
expect(ref.current).toBeInstanceOf(HTMLDivElement);
expect(ref.current).toBe(screen.getByTestId('sep'));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/raystack/components/floating-actions/__tests__/floating-actions.test.tsx`
around lines 43 - 75, The Separator component currently allows callers to
override aria-hidden because props are spread after the hardcoded attribute;
update the FloatingActions.Separator implementation in floating-actions.tsx to
spread {...props} first and then set aria-hidden="true" (so the hardcoded value
wins), and add/keep a regression test in floating-actions.test.tsx that renders
<FloatingActions.Separator aria-hidden="false" data-testid="sep"> and asserts
the element has aria-hidden="true"; reference the FloatingActions.Separator
component and ensure ref forwarding and className tests still pass.

Comment on lines +24 to +28
<div
aria-hidden='true'
className={cx(styles.separator, className)}
{...props}
/>
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 | 🟡 Minor

Keep the separator hidden even when props are forwarded.

Because {...props} is applied after aria-hidden, a caller can override it with aria-hidden={false}, which breaks the documented separator semantics.

🛡️ Proposed fix
   <div
-    aria-hidden='true'
-    className={cx(styles.separator, className)}
     {...props}
+    aria-hidden='true'
+    className={cx(styles.separator, className)}
   />
📝 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
<div
aria-hidden='true'
className={cx(styles.separator, className)}
{...props}
/>
<div
{...props}
aria-hidden='true'
className={cx(styles.separator, className)}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/floating-actions/floating-actions.tsx` around
lines 24 - 28, The separator div in floating-actions.tsx currently spreads
{...props} after setting aria-hidden='true', allowing callers to override it;
fix by ensuring aria-hidden stays true when props are forwarded—either move
{...props} before aria-hidden or (preferable) keep {...props} but explicitly set
aria-hidden={true} after the spread on the same div (the element using
className={cx(styles.separator, className)}), so aria-hidden cannot be
overridden by incoming props.

@paanSinghCoder paanSinghCoder requested a review from rsbh April 20, 2026 10:44
Keep the component, docs, and playground — the examples/page.tsx
scratch page is out of scope for this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FloatingActions.Separator now wraps `Separator` from the design system
with `orientation="vertical"` and `size="full"` defaults, instead of
re-implementing a local 1px×16px div. Drops the local `.separator`
CSS class and picks up proper `role="separator"` semantics, size/color
variants, and Base UI behavior for free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The floating bar is horizontal-only, so the separator is always
vertical. `Omit<..., 'orientation'>` from the prop type and drop the
unused horizontal CSS rule. Scoped `.separator` class overrides the
Separator primitive's size variant to --rs-space-5 per the Figma spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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