feat(floating-actions): add FloatingActions component#745
feat(floating-actions): add FloatingActions component#745paanSinghCoder wants to merge 6 commits intomainfrom
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis change introduces a new Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
apps/www/src/app/examples/page.tsxapps/www/src/components/playground/floating-actions-examples.tsxapps/www/src/components/playground/index.tsapps/www/src/content/docs/components/floating-actions/demo.tsapps/www/src/content/docs/components/floating-actions/index.mdxapps/www/src/content/docs/components/floating-actions/props.tspackages/raystack/components/floating-actions/__tests__/floating-actions.test.tsxpackages/raystack/components/floating-actions/floating-actions.module.csspackages/raystack/components/floating-actions/floating-actions.tsxpackages/raystack/components/floating-actions/index.tsxpackages/raystack/index.tsx
| 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>` |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
🧩 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-actionsRepository: 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 -5Repository: 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 -10Repository: 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.tsRepository: 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 -3Repository: 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 -3Repository: 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 -20Repository: 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 -40Repository: 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 -3Repository: 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 -30Repository: 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.tsRepository: 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 -30Repository: 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.
| 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')); | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| <div | ||
| aria-hidden='true' | ||
| className={cx(styles.separator, className)} | ||
| {...props} | ||
| /> |
There was a problem hiding this comment.
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.
| <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.
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>
…ute for vertical orientation
Summary
FloatingActionscomponent — a floating bar for surfacing contextual actions (bulk-action toolbar, row-hover actions)Chip,Button,IconButtonFloatingActions.Separatorand Vitest suite; docs + playground + examples page wired upUsage
Test plan
pnpm --filter @raystack/apsara test components/floating-actions🤖 Generated with Claude Code