Skip to content

Commit 2d434db

Browse files
RAC: Allow form attributes in Button (#4452)
* expand Button prop types * cleanup * pass additional props and fix types * revert types * revert extra props merge * remove | undefined * Add test to verify we can pass through props --------- Co-authored-by: Robert Snow <[email protected]>
1 parent 273e828 commit 2d434db

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

packages/react-aria-components/src/Button.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212
import {AriaButtonProps, mergeProps, useButton, useFocusRing, useHover} from 'react-aria';
1313
import {ContextValue, RenderProps, SlotProps, useContextProps, useRenderProps} from './utils';
14+
import {filterDOMProps} from '@react-aria/utils';
1415
import React, {createContext, ForwardedRef, forwardRef} from 'react';
1516

1617
export interface ButtonRenderProps {
@@ -41,11 +42,37 @@ export interface ButtonRenderProps {
4142
isDisabled: boolean
4243
}
4344

44-
export interface ButtonProps extends Omit<AriaButtonProps, 'children' | 'href' | 'target' | 'rel' | 'elementType'>, SlotProps, RenderProps<ButtonRenderProps> {}
45+
export interface ButtonProps extends Omit<AriaButtonProps, 'children' | 'href' | 'target' | 'rel' | 'elementType'>, SlotProps, RenderProps<ButtonRenderProps> {
46+
/**
47+
* The <form> element to associate the button with.
48+
* The value of this attribute must be the id of a <form> in the same document.
49+
*/
50+
form?: string,
51+
/**
52+
* The URL that processes the information submitted by the button.
53+
* Overrides the action attribute of the button's form owner.
54+
*/
55+
formAction?: string,
56+
/** Indicates how to encode the form data that is submitted. */
57+
formEncType?: string,
58+
/** Indicates the HTTP method used to submit the form. */
59+
formMethod?: string,
60+
/** Indicates that the form is not to be validated when it is submitted. */
61+
formNoValidate?: boolean,
62+
/** Overrides the target attribute of the button's form owner. */
63+
formTarget?: string,
64+
/** Submitted as a pair with the button's value as part of the form data. */
65+
name?: string,
66+
/** The value associated with the button's name when it's submitted with the form data. */
67+
value?: string
68+
}
69+
4570
interface ButtonContextValue extends ButtonProps {
4671
isPressed?: boolean
4772
}
4873

74+
const additionalButtonHTMLAttributes = new Set(['form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', 'name', 'value']);
75+
4976
export const ButtonContext = createContext<ContextValue<ButtonContextValue, HTMLButtonElement>>({});
5077

5178
function Button(props: ButtonProps, ref: ForwardedRef<HTMLButtonElement>) {
@@ -62,6 +89,7 @@ function Button(props: ButtonProps, ref: ForwardedRef<HTMLButtonElement>) {
6289

6390
return (
6491
<button
92+
{...filterDOMProps(props, {propNames: additionalButtonHTMLAttributes})}
6593
{...mergeProps(buttonProps, focusProps, hoverProps)}
6694
{...renderProps}
6795
ref={ref}

packages/react-aria-components/test/Button.test.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ describe('Button', () => {
3434
expect(button).toHaveAttribute('data-foo', 'bar');
3535
});
3636

37+
it('should support form props', () => {
38+
let {getByRole} = render(<form id="foo"><Button form="foo" formMethod="post">Test</Button></form>);
39+
let button = getByRole('button');
40+
expect(button).toHaveAttribute('form', 'foo');
41+
expect(button).toHaveAttribute('formMethod', 'post');
42+
});
43+
3744
it('should support slot', () => {
3845
let {getByRole} = render(
3946
<ButtonContext.Provider value={{slots: {test: {'aria-label': 'test'}}}}>
@@ -65,7 +72,7 @@ describe('Button', () => {
6572
it('should support focus ring', () => {
6673
let {getByRole} = render(<Button className={({isFocusVisible}) => isFocusVisible ? 'focus' : ''}>Test</Button>);
6774
let button = getByRole('button');
68-
75+
6976
expect(button).not.toHaveAttribute('data-focus-visible');
7077
expect(button).not.toHaveClass('focus');
7178

@@ -109,12 +116,12 @@ describe('Button', () => {
109116
it('should support render props', () => {
110117
let {getByRole} = render(<Button>{({isPressed}) => isPressed ? 'Pressed' : 'Test'}</Button>);
111118
let button = getByRole('button');
112-
119+
113120
expect(button).toHaveTextContent('Test');
114-
121+
115122
fireEvent.mouseDown(button);
116123
expect(button).toHaveTextContent('Pressed');
117-
124+
118125
fireEvent.mouseUp(button);
119126
expect(button).toHaveTextContent('Test');
120127
});

0 commit comments

Comments
 (0)