Skip to content

Commit 96c8ed3

Browse files
authored
feat(components/Button): migrate to react-aria onPress event
1 parent 501474c commit 96c8ed3

20 files changed

+236
-296
lines changed

packages/components/src/components/Button/Button.mdx

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ import { Button } from '@koobiq/react-components';
2525

2626
<Props />
2727

28+
## Events
29+
30+
Button supports user interactions via mouse, keyboard, and touch.
31+
You can handle all of these via the `onPress` prop. This is similar to the standard `onClick` event,
32+
but normalized to support all interaction methods equally.
33+
34+
In addition, the `onPressStart`, `onPressEnd`, and `onPressChange` events are fired as the user interacts with the button.
35+
2836
## Variant
2937

3038
To change the visual state of the button, use the `variant` prop.

packages/components/src/components/Button/Button.stories.tsx

+9-48
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1+
import { useBoolean } from '@koobiq/react-core';
12
import {
2-
type ComponentProps,
3-
type CSSProperties,
4-
type FC,
5-
type MouseEvent,
6-
useState,
7-
} from 'react';
8-
9-
import {
10-
IconArrowUpFromBracket16,
113
IconArrowUpRightFromSquare16,
124
IconChevronDown16,
135
IconPlus16,
@@ -56,7 +48,7 @@ type Story = StoryObj<typeof meta>;
5648
export const Base: Story = {
5749
render: (args: ButtonBaseProps) => (
5850
// eslint-disable-next-line no-alert
59-
<Button onClick={() => alert('Click')} {...args}>
51+
<Button onPress={() => alert('Press')} {...args}>
6052
Button
6153
</Button>
6254
),
@@ -118,14 +110,14 @@ export const Variant: Story = {
118110

119111
export const Disabled: Story = {
120112
render: function Render(args: ButtonBaseProps) {
121-
const [checked, setChecked] = useState(false);
113+
const [checked, { set }] = useBoolean(false);
122114

123115
return (
124116
<FlexBox gap="l" direction="column">
125117
<Button progress={checked} disabled {...args}>
126118
Button
127119
</Button>
128-
<Checkbox checked={checked} onChange={setChecked}>
120+
<Checkbox checked={checked} onChange={set}>
129121
Progress
130122
</Checkbox>
131123
</FlexBox>
@@ -154,40 +146,9 @@ export const FullWidth: Story = {
154146
};
155147

156148
export const RootTag: Story = {
157-
render: () => {
158-
const VisuallyHiddenInput: FC<ComponentProps<'input'>> = (props) => {
159-
const inputStyle = {
160-
inset: 0,
161-
blockSize: 1,
162-
inlineSize: 1,
163-
overflow: 'hidden',
164-
position: 'absolute',
165-
whiteSpace: 'nowrap',
166-
clipPath: 'inset(50%)',
167-
} as CSSProperties;
168-
169-
return <input style={inputStyle} {...props} />;
170-
};
171-
172-
return (
173-
<FlexBox gap="l">
174-
<Button as="a" href="#" endIcon={<IconArrowUpRightFromSquare16 />}>
175-
Link
176-
</Button>
177-
<Button
178-
as="label"
179-
tabIndex={-1}
180-
startIcon={<IconArrowUpFromBracket16 />}
181-
onMouseDown={(e: MouseEvent<HTMLLabelElement>) => e.preventDefault()}
182-
>
183-
Upload files
184-
<VisuallyHiddenInput
185-
type="file"
186-
onChange={(event) => console.log(event.target.files)}
187-
multiple
188-
/>
189-
</Button>
190-
</FlexBox>
191-
);
192-
},
149+
render: () => (
150+
<Button as="a" href="#" endIcon={<IconArrowUpRightFromSquare16 />}>
151+
Link
152+
</Button>
153+
),
193154
};

packages/components/src/components/Button/types.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export const buttonPropVariant = [
1313

1414
export type ButtonPropVariant = (typeof buttonPropVariant)[number];
1515

16-
export type ButtonPropOnClick = ButtonOptions['onPress'];
17-
1816
export type ButtonBaseProps = {
1917
/** The content of the component. */
2018
children?: ReactNode;
@@ -55,5 +53,4 @@ export type ButtonBaseProps = {
5553
onHoverStart?: (e: HoverEvent) => void;
5654
/** Handler that is called when a hover interaction ends. */
5755
onHoverEnd?: (e: HoverEvent) => void;
58-
onClick?: ButtonPropOnClick;
59-
};
56+
} & ButtonOptions;

packages/components/src/components/IconButton/IconButton.mdx

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ import { IconButton } from '@koobiq/react-components';
2525

2626
<Props />
2727

28+
## Events
29+
30+
IconButton supports user interactions via mouse, keyboard, and touch.
31+
You can handle all of these via the `onPress` prop. This is similar to the standard `onClick` event,
32+
but normalized to support all interaction methods equally.
33+
34+
In addition, the `onPressStart`, `onPressEnd`, and `onPressChange` events are fired as the user interacts with the button.
35+
2836
## Size
2937

3038
For `l` or `xl` buttons, use the `size` prop.

packages/components/src/components/IconButton/IconButton.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Story = StoryObj<typeof meta>;
4444
export const Base: Story = {
4545
render: (args: IconButtonBaseProps) => (
4646
// eslint-disable-next-line no-alert
47-
<IconButton onClick={() => alert('Click')} {...args}>
47+
<IconButton onPress={() => alert('Click')} {...args}>
4848
{args.children || <IconMagnifyingGlass24 />}
4949
</IconButton>
5050
),

packages/components/src/components/IconButton/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ export type IconButtonBaseProps = {
4545
/** Handler that is called when a hover interaction starts. */
4646
onHoverStart?: (e: HoverEvent) => void;
4747
/** Handler that is called when a hover interaction ends. */
48-
onClick?: ButtonOptions['onPress'];
49-
};
48+
onHoverEnd?: (e: HoverEvent) => void;
49+
} & ButtonOptions;

packages/components/src/components/Modal/Modal.stories.tsx

+21-34
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useState } from 'react';
2-
31
import { useBoolean } from '@koobiq/react-core';
42
import type { StoryObj } from '@storybook/react';
53

64
import { Button } from '../Button';
5+
import { FlexBox } from '../FlexBox';
76
import { Input } from '../Input';
8-
import { flex, spacing } from '../layout';
7+
import { spacing } from '../layout';
98
import { Textarea } from '../Textarea';
109
import { Toggle } from '../Toggle';
1110

@@ -14,9 +13,7 @@ import {
1413
ModalContent,
1514
ModalFooter,
1615
ModalHeader,
17-
type ModalPropControl,
1816
type ModalProps,
19-
type ModalPropSize,
2017
} from './index';
2118
import { modalPropSize } from './index.js';
2219

@@ -72,37 +69,27 @@ export const Base: Story = {
7269

7370
export const Size: Story = {
7471
render: function Render(args: ModalProps) {
75-
const [size, setSize] = useState<ModalPropSize>();
76-
77-
const control: ModalPropControl = ({ onClick, ...other }) => (
78-
<div className={flex({ gap: 'l' })}>
72+
return (
73+
<FlexBox gap="l">
7974
{modalPropSize.map((size) => (
80-
<Button
81-
onClick={(e) => {
82-
onClick?.(e);
83-
setSize(size);
84-
}}
75+
<Modal
8576
key={size}
86-
{...other}
77+
size={size}
78+
control={(props) => <Button {...props}>size = {size}</Button>}
79+
{...args}
8780
>
88-
size = {size}
89-
</Button>
81+
{({ close }) => (
82+
<>
83+
<ModalHeader>I have a {size} size</ModalHeader>
84+
<ModalContent>But there&#39;s nothing to say…</ModalContent>
85+
<ModalFooter>
86+
<Button onPress={close}>Ok</Button>
87+
</ModalFooter>
88+
</>
89+
)}
90+
</Modal>
9091
))}
91-
</div>
92-
);
93-
94-
return (
95-
<Modal size={size} control={control} {...args}>
96-
{({ close }) => (
97-
<>
98-
<ModalHeader>I have a {size} size</ModalHeader>
99-
<ModalContent>But there&#39;s nothing to say…</ModalContent>
100-
<ModalFooter>
101-
<Button onClick={close}>Ok</Button>
102-
</ModalFooter>
103-
</>
104-
)}
105-
</Modal>
92+
</FlexBox>
10693
);
10794
},
10895
};
@@ -167,7 +154,7 @@ export const Settings: Story = {
167154
<>
168155
<ModalHeader>Adjust me</ModalHeader>
169156
<ModalContent>
170-
<div className={flex({ direction: 'column', gap: 'l' })}>
157+
<FlexBox gap="l" direction="column">
171158
<Toggle
172159
checked={hideBackdrop}
173160
onChange={setHideBackdrop.toggle}
@@ -192,7 +179,7 @@ export const Settings: Story = {
192179
>
193180
Disable the exit by pressing ESC key
194181
</Toggle>
195-
</div>
182+
</FlexBox>
196183
</ModalContent>
197184
<ModalFooter>
198185
<Button onClick={close}>Ok</Button>

packages/components/src/components/Modal/Modal.test.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
44
import { userEvent } from '@testing-library/user-event';
55
import { describe, expect, it, vi } from 'vitest';
66

7-
import { Link } from '../Link';
7+
import { Button } from '../Button';
88

99
import { Modal } from './Modal';
1010
import { type ModalProps, modalPropSize } from './types';
@@ -251,9 +251,7 @@ describe('Modal', () => {
251251
<Modal
252252
{...baseProps}
253253
onOpenChange={onOpenChange}
254-
control={(props) => (
255-
<Link as="button" data-testid="control" {...props} />
256-
)}
254+
control={(props) => <Button data-testid="control" {...props} />}
257255
/>
258256
);
259257

packages/components/src/components/Modal/Modal.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,11 @@ export const Modal = forwardRef<ModalRef, ModalProps>((props, ref) => {
109109
slotProps?.modal
110110
);
111111

112-
const { isDisabled, onPress, ...otherTriggerProps } = triggerProps;
112+
const { isDisabled, ...otherTriggerProps } = triggerProps;
113113

114114
return (
115115
<>
116116
{control?.({
117-
onClick: onPress,
118117
disabled: isDisabled,
119118
...otherTriggerProps,
120119
})}

0 commit comments

Comments
 (0)