From 54b98ae44425fa6e120b02a5af76d37135b2ef51 Mon Sep 17 00:00:00 2001 From: Timur Manyanov <2737310+darkwebdev@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:11:34 +0200 Subject: [PATCH 1/4] [BREAKING] v5 (#220) * EbayMenu: Improve callbacks (#181) * Fix tests after merge * Remove global index.ts(js) * Fix EbayMenuItemProps type * Update all test/storybook imports * [#200] Update all test/storybook imports, fix lint errors * [FEAT] EbayMenuButton: new component (#156) Co-authored-by: Andrew Cho * update snapshot * EbayMenuButton: fix how labels are rendered with icons * EbayMenu: fix types, add story checks * EbayMenuButton: update callbacks to reflect original ebayui API. * EbaySplitButton (#168) Co-authored-by: Andrew Cho * add tests, WIP * [#210] update ebay-checkbox callbacks * [#210] fix types * [#210] Update fake-menu callbacks * [#210] Update icon-button callbacks * [#210] Update listbox-button callbacks * [#210] Update select callbacks * [#210] Update star-rating-select callbacks * [#210] Update tabs callbacks, remove href prop * [#210] Update video callbacks, fix report button * [#210] Update dialog callbacks * [#210] Update page-notice-section callbacks * [#210] Update pagination callbacks * [#210] Update radio callbacks, fix lint warnings * [#210] Update switch callbacks * [#210] Update textbox callbacks * [#210] Update section-notice callbacks, fix bugs * [#210] Update fake-menu-button callbacks * [#210] Update snapshots * fix storybook sample * Fixes due to PR feedback * Update video player onReport callback. * Add `baseEl` back to menu * Fix PR comments * Add baseEl on menu-button --------- Co-authored-by: Andrew Cho Co-authored-by: hlimas --- src/common/event-utils/index.ts | 31 +- src/common/event-utils/types.ts | 26 + src/common/floating-label-utils/hooks.tsx | 5 +- src/ebay-alert-dialog/README.md | 5 +- .../__tests__/index.spec.tsx | 54 +- .../__tests__/index.stories.tsx | 14 +- src/ebay-alert-dialog/alert-dialog.tsx | 2 - src/ebay-breadcrumbs/README.md | 2 +- src/ebay-breadcrumbs/__tests__/index.spec.tsx | 9 +- .../__tests__/index.stories.tsx | 2 +- src/ebay-breadcrumbs/breadcrumbs.tsx | 7 +- src/ebay-button/README.md | 8 +- src/ebay-button/__tests__/index.spec.tsx | 26 +- src/ebay-button/__tests__/index.stories.tsx | 2 +- src/ebay-carousel/carousel.tsx | 2 +- src/ebay-checkbox/README.md | 21 +- src/ebay-checkbox/__tests__/index.spec.tsx | 34 +- src/ebay-checkbox/__tests__/index.stories.tsx | 8 +- src/ebay-checkbox/checkbox.tsx | 22 +- src/ebay-confirm-dialog/README.md | 12 +- .../__snapshots__/index.spec.tsx.snap | 4 +- .../__tests__/index.spec.tsx | 74 +- .../__tests__/index.stories.tsx | 76 +- src/ebay-confirm-dialog/confirm-dialog.tsx | 2 - .../components/dialogBase.tsx | 18 +- src/ebay-drawer-dialog/README.md | 13 +- .../__snapshots__/index.spec.tsx.snap | 10 + .../__tests__/index.stories.tsx | 41 +- src/ebay-drawer-dialog/components/drawer.tsx | 3 + src/ebay-fake-menu-button/README.md | 13 +- .../__snapshots__/index.spec.tsx.snap | 4 + .../__tests__/index.spec.tsx | 91 +- .../__tests__/index.stories.tsx | 15 +- src/ebay-fake-menu-button/menu-button.tsx | 18 +- src/ebay-fake-menu/README.md | 10 +- src/ebay-fake-menu/__tests__/index.spec.tsx | 12 +- .../__tests__/index.stories.tsx | 6 +- src/ebay-fake-menu/menu-item.tsx | 2 +- src/ebay-fake-menu/menu.tsx | 14 +- .../__snapshots__/index.spec.tsx.snap | 72 + .../__snapshots__/index.spec.tsx.snap | 2 + .../__tests__/index.spec.tsx | 49 +- .../__tests__/index.stories.tsx | 13 +- .../fullscreen-dialog.tsx | 2 - src/ebay-icon-button/README.md | 9 + .../__snapshots__/index.spec.tsx.snap | 24 +- src/ebay-icon-button/__tests__/index.spec.tsx | 41 +- .../__tests__/index.stories.tsx | 21 +- src/ebay-icon-button/icon-button.tsx | 19 +- .../__snapshots__/index.spec.tsx.snap | 5 + .../__tests__/index.spec.tsx | 51 +- .../__tests__/index.stories.tsx | 21 +- src/ebay-lightbox-dialog/lightbox-dialog.tsx | 2 - src/ebay-listbox-button/README.md | 9 +- .../__snapshots__/index.spec.tsx.snap | 2 + .../__tests__/index.spec.tsx | 63 +- .../__tests__/index.stories.tsx | 12 +- src/ebay-listbox-button/listbox-button.tsx | 67 +- src/ebay-menu-button/README.md | 4 +- .../__snapshots__/index.spec.tsx.snap | 1806 +---------------- src/ebay-menu-button/__tests__/index.spec.tsx | 24 +- .../__tests__/index.stories.tsx | 11 +- src/ebay-menu-button/menu-button.tsx | 67 +- src/ebay-menu/README.md | 9 +- .../__snapshots__/index.spec.tsx.snap | 339 +++- src/ebay-menu/__tests__/index.spec.tsx | 256 ++- src/ebay-menu/__tests__/index.stories.tsx | 64 +- src/ebay-menu/index.ts | 1 - src/ebay-menu/menu-item.tsx | 2 +- src/ebay-menu/menu.tsx | 160 +- src/ebay-menu/types.ts | 35 +- src/ebay-page-notice/README.md | 6 +- src/ebay-page-notice/__tests__/index.spec.tsx | 8 +- .../__tests__/index.stories.tsx | 8 +- src/ebay-page-notice/index.ts | 2 +- src/ebay-page-notice/page-notice.tsx | 37 +- src/ebay-pagination/README.md | 10 +- src/ebay-pagination/__tests__/index.spec.tsx | 8 +- .../__tests__/index.stories.tsx | 15 +- src/ebay-pagination/pagination-item.tsx | 11 +- src/ebay-pagination/pagination.tsx | 15 +- .../__snapshots__/index.spec.tsx.snap | 5 + .../__tests__/index.spec.tsx | 52 +- .../__tests__/index.stories.tsx | 25 +- src/ebay-panel-dialog/panel-dialog.tsx | 2 - src/ebay-radio/README.md | 9 +- src/ebay-radio/__tests__/index.spec.tsx | 38 +- src/ebay-radio/__tests__/index.stories.tsx | 84 +- src/ebay-radio/radio.tsx | 31 +- src/ebay-section-notice/README.md | 5 + .../__snapshots__/index.spec.tsx.snap | 59 +- .../__tests__/index.spec.tsx | 38 + .../__tests__/index.stories.tsx | 35 +- src/ebay-section-notice/index.ts | 2 +- src/ebay-section-notice/section-notice.tsx | 38 +- src/ebay-select/README.md | 4 +- src/ebay-select/__tests__/index.spec.tsx | 12 +- src/ebay-select/__tests__/index.stories.tsx | 4 +- src/ebay-select/ebay-select.tsx | 6 +- src/ebay-select/index.ts | 2 +- src/ebay-split-button/README.md | 4 +- .../__snapshots__/index.spec.tsx.snap | 936 +-------- .../__tests__/index.spec.tsx | 49 +- .../__tests__/index.stories.tsx | 7 +- src/ebay-split-button/split-button.tsx | 2 +- src/ebay-split-button/types.ts | 6 +- src/ebay-star-rating-select/README.md | 8 + .../__tests__/index.spec.tsx | 10 +- .../star-rating-select.tsx | 28 +- src/ebay-switch/README.md | 6 +- src/ebay-switch/__tests__/index.spec.tsx | 9 +- src/ebay-switch/__tests__/index.stories.tsx | 6 +- src/ebay-switch/ebay-switch.tsx | 16 +- src/ebay-tabs/README.md | 16 +- .../__snapshots__/index.spec.tsx.snap | 52 - src/ebay-tabs/__tests__/index.spec.tsx | 12 +- src/ebay-tabs/__tests__/index.stories.tsx | 19 +- src/ebay-tabs/tab-panel.tsx | 31 +- src/ebay-tabs/tab.tsx | 55 +- src/ebay-tabs/tabs.tsx | 40 +- src/ebay-textbox/README.md | 25 +- .../__snapshots__/index.spec.tsx.snap | 124 +- src/ebay-textbox/__tests__/index.spec.tsx | 122 +- src/ebay-textbox/__tests__/index.stories.tsx | 90 +- src/ebay-textbox/postfix-icon.tsx | 8 +- src/ebay-textbox/prefix-icon.tsx | 7 +- src/ebay-textbox/textbox.tsx | 98 +- src/ebay-textbox/types.ts | 4 +- src/ebay-video/README.md | 12 +- src/ebay-video/__tests__/index.spec.tsx | 27 +- src/ebay-video/__tests__/index.stories.tsx | 9 +- src/ebay-video/controls.tsx | 14 +- src/ebay-video/reportButton.tsx | 10 +- src/ebay-video/video.tsx | 23 +- 134 files changed, 2688 insertions(+), 3748 deletions(-) diff --git a/src/common/event-utils/index.ts b/src/common/event-utils/index.ts index 7ac52816..85c08c80 100644 --- a/src/common/event-utils/index.ts +++ b/src/common/event-utils/index.ts @@ -2,6 +2,7 @@ * Based on https://github.com/eBay/ebayui-core/edit/master/src/common/event-utils/index.js */ +import React from 'react' import { Key } from './types' type Callback = () => void @@ -12,41 +13,49 @@ type Callback = () => void * @param {KeyboardEvent} e * @param {Function} callback */ -function handleKeydown(keyList: Key[], e: KeyboardEvent, callback: Callback = () => {}): void { +function handleKeydown(keyList: Key[], e: React.KeyboardEvent, callback: Callback = () => {}): void { if (keyList.includes(e.key as Key)) { callback() } } // inverse of found keys -function handleNotKeydown(keyList: Key[], e: KeyboardEvent, callback: Callback = () => {}): void { +function handleNotKeydown(keyList: Key[], e: React.KeyboardEvent, callback: Callback = () => {}): void { if (!keyList.includes(e.key as Key)) { callback() } } -export function handleEnterKeydown(e: KeyboardEvent, callback: Callback): void { - handleKeydown(['Enter'], e, callback) +export function handleEnterKeydown(e: React.KeyboardEvent, callback: Callback): void { + if (e.key === 'Enter') { + callback() + } +} + +export function handleActionKeydown(e: React.KeyboardEvent, callback: Callback): void { + if (isActionKey(e.key as Key)) { + callback() + } } -export function handleActionKeydown(e: KeyboardEvent, callback: Callback): void { - handleKeydown([' ', 'Enter'], e, callback) +export function isActionKey(key: Key): boolean { + return [' ', 'Enter'].includes(key) } -export function handleEscapeKeydown(e: KeyboardEvent, callback: Callback): void { +export function handleEscapeKeydown(e: React.KeyboardEvent, callback: Callback): void { handleKeydown(['Esc', 'Escape'], e, callback) } -export function handleUpDownArrowsKeydown(e: KeyboardEvent, callback: Callback): void { +export function handleUpDownArrowsKeydown(e: React.KeyboardEvent, callback: Callback): void { handleKeydown(['Up', 'ArrowUp', 'Down', 'ArrowDown'], e, callback) } -export function handleLeftRightArrowsKeydown(e: KeyboardEvent, callback: Callback): void { +export function handleLeftRightArrowsKeydown(e: React.KeyboardEvent, callback: Callback): void { handleKeydown(['Left', 'ArrowLeft', 'Right', 'ArrowRight'], e, callback) } // only fire for character input, not modifier/meta keys (enter, escape, backspace, tab, etc.) -export function handleTextInput(e: KeyboardEvent, callback: Callback): void { +export function handleTextInput(e: React.KeyboardEvent, callback: Callback): void { const keyList: Key[] = [ // Edge 'Esc', @@ -72,7 +81,7 @@ export function handleTextInput(e: KeyboardEvent, callback: Callback): void { handleNotKeydown(keyList, e, callback) } -export function preventDefaultIfHijax(e: KeyboardEvent, hijax: boolean): void { +export function preventDefaultIfHijax(e: React.KeyboardEvent, hijax: boolean): void { if (hijax) { e.preventDefault() } diff --git a/src/common/event-utils/types.ts b/src/common/event-utils/types.ts index f42b808f..ab9f6a93 100644 --- a/src/common/event-utils/types.ts +++ b/src/common/event-utils/types.ts @@ -1,3 +1,5 @@ +import { ChangeEvent, KeyboardEvent, MouseEvent, FocusEvent, SyntheticEvent } from 'react' + type ModifierKeys = 'Alt' | 'AltGraph' | 'Control' | 'Shift' | 'CapsLock' | 'Meta' // | 'Fn' | 'FnLock' | 'Hyper' | 'NumLock' | 'ScrollLock' | 'Super' | 'Symbol' | 'SymbolLock' type NavigationKeys = 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'Enter' | 'Tab' | ' ' | 'Escape' @@ -10,3 +12,27 @@ type NavigationKeysEdge = 'Down' | 'Left' | 'Right' | 'Up' | 'Esc' export type Key = ModifierKeys | NavigationKeys | NavigationKeysEdge +type BaseEventHandler, P> = (event: E, props?: P) => void; + +export type EbayEventHandler> = + BaseEventHandler, PropsObject>; +export type EbayMouseEventHandler> = + BaseEventHandler, PropsObject>; +export type EbayKeyboardEventHandler> = + BaseEventHandler, PropsObject>; +export type EbayChangeEventHandler> = + BaseEventHandler, PropsObject>; +export type EbayFocusEventHandler> = + BaseEventHandler, PropsObject>; +/* + type ClipboardEventHandler = EventHandler>; + type CompositionEventHandler = EventHandler>; + type DragEventHandler = EventHandler>; + type FormEventHandler = EventHandler>; + type TouchEventHandler = EventHandler>; + type PointerEventHandler = EventHandler>; + type UIEventHandler = EventHandler>; + type WheelEventHandler = EventHandler>; + type AnimationEventHandler = EventHandler>; + type TransitionEventHandler = EventHandler>; +*/ diff --git a/src/common/floating-label-utils/hooks.tsx b/src/common/floating-label-utils/hooks.tsx index d800da07..b0c1fecc 100644 --- a/src/common/floating-label-utils/hooks.tsx +++ b/src/common/floating-label-utils/hooks.tsx @@ -12,6 +12,7 @@ type FloatingLabelHookProps = { className?: string; placeholder?: string; invalid?: boolean; + onMount?: () => void; } type FloatingLabelHookReturn = { @@ -74,7 +75,8 @@ export function useFloatingLabel({ inputSize, inputValue, placeholder, - invalid + invalid, + onMount = () => {} } : FloatingLabelHookProps): FloatingLabelHookReturn { const _internalInputRef = useRef(null) const inputRef = () => ref || _internalInputRef @@ -103,6 +105,7 @@ export function useFloatingLabel({ } selectFirstOptionText.current = getPlaceholder(inputRef()?.current) setPlaceholder(inputRef()?.current, ``) + onMount() }, []) useEffect(() => { diff --git a/src/ebay-alert-dialog/README.md b/src/ebay-alert-dialog/README.md index 29d962fb..39d0894b 100644 --- a/src/ebay-alert-dialog/README.md +++ b/src/ebay-alert-dialog/README.md @@ -22,8 +22,9 @@ Name | Type | Stateful | Required | Description ## Events Event | Data | Description ---- | --- | --- -`onConfirm` | | triggered when confirm button is clicked +--- |------| --- +`onOpen` | () | triggered when dialog is opened +`onConfirm` | () | triggered when confirm button is clicked ## EbayDialogHeader Will render a header content for the dialog. Will always render the header element even if this is not present diff --git a/src/ebay-alert-dialog/__tests__/index.spec.tsx b/src/ebay-alert-dialog/__tests__/index.spec.tsx index 0c2f824a..272cc82b 100644 --- a/src/ebay-alert-dialog/__tests__/index.spec.tsx +++ b/src/ebay-alert-dialog/__tests__/index.spec.tsx @@ -1,41 +1,49 @@ import React from 'react' import requireContext from 'node-require-context' -import { fireEvent, render, RenderResult } from '@testing-library/react' +import { screen, fireEvent, render, RenderResult } from '@testing-library/react' import { EbayAlertDialog } from '../index'; import { initStoryshots } from '../../../config/jest/storyshots' import { EbayDialogHeader } from '../../ebay-dialog-base' jest.mock('../../common/random-id', () => ({ randomId: () => 'abc123' })) -describe('', () => { - let wrapper: RenderResult - const closeDrawerHandler = jest.fn() +const closeSpy = jest.fn() +const openSpy = jest.fn() +const openDialog = () => + render( + + Heading +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +

+

www.ebay.com

+ +
+ ) - beforeEach(() => { - wrapper = render( - - Heading -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis - nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -

-

www.ebay.com

- -
- ) +describe('', () => { + it('should trigger onOpen when dialog appears', () => { + openDialog() + expect(openSpy).toBeCalled() }) it('should have close button', () => { - expect(wrapper.getByText('Confirm')).toBeInTheDocument() + openDialog() + expect(screen.getByText('Confirm')).toBeInTheDocument() }) it('should trigger onClose when close button is clicked', () => { - fireEvent.click(wrapper.getByText('Confirm')) - expect(closeDrawerHandler).toBeCalled() + openDialog() + fireEvent.click(screen.getByText('Confirm')) + expect(closeSpy).toBeCalled() }) }) diff --git a/src/ebay-alert-dialog/__tests__/index.stories.tsx b/src/ebay-alert-dialog/__tests__/index.stories.tsx index bbf7a500..e9f2d79e 100644 --- a/src/ebay-alert-dialog/__tests__/index.stories.tsx +++ b/src/ebay-alert-dialog/__tests__/index.stories.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react' import { EbayAlertDialog } from '../index'; import { EbayDialogHeader } from '../../ebay-dialog-base' +import { action } from '../../../.storybook/action' const story: any = { component: EbayAlertDialog, @@ -22,7 +23,16 @@ export const _Default = () => { Open Dialog

Some outside content...

- + action('onOpen')()} + onConfirm={() => { + action('onConfirm')() + close() + }} + confirmText="Confirm" + a11yCloseText="Close" + > Heading {textParagraph}

www.ebay.com

@@ -40,7 +50,7 @@ export const _WithAnimation= () => { Open Dialog

Some outside content...

- + Heading {textParagraph}

www.ebay.com

diff --git a/src/ebay-alert-dialog/alert-dialog.tsx b/src/ebay-alert-dialog/alert-dialog.tsx index 87f41ad1..afe626e8 100644 --- a/src/ebay-alert-dialog/alert-dialog.tsx +++ b/src/ebay-alert-dialog/alert-dialog.tsx @@ -8,14 +8,12 @@ const classPrefix = 'alert-dialog' export interface Props extends DialogBaseProps { open?: boolean; confirmText: string; - onOpen?: () => void; onConfirm?: () => void; } const EbayAlertDialog: FC = ({ a11yCloseText = 'Close Dialog', confirmText, - onOpen = () => {}, onConfirm = () => {}, ...rest }) => { diff --git a/src/ebay-breadcrumbs/README.md b/src/ebay-breadcrumbs/README.md index 00a2017e..84a7204f 100644 --- a/src/ebay-breadcrumbs/README.md +++ b/src/ebay-breadcrumbs/README.md @@ -33,7 +33,7 @@ Name | Type | Stateful | Description | Data --- | --- | --- | --- | --- `a11yHeadingText` | String | No | heading for breadcrumb which will be clipped (default: 'Page navigation') `a11yHeadingTag` | String | No | heading tag for breadcrumb (default: `h2`) -`onSelect` | Function | No | click breadcrumb items | `{ originalEvent, el }` +`onSelect` | Function | No | click breadcrumb items | `(event: MouseEvent | KeyboardEvent)` All other props will be applied to the main wrapper (`nav`) element. diff --git a/src/ebay-breadcrumbs/__tests__/index.spec.tsx b/src/ebay-breadcrumbs/__tests__/index.spec.tsx index 6988a13f..824ccf36 100644 --- a/src/ebay-breadcrumbs/__tests__/index.spec.tsx +++ b/src/ebay-breadcrumbs/__tests__/index.spec.tsx @@ -1,8 +1,9 @@ import React from 'react' -import { render, fireEvent } from '@testing-library/react' +import { render } from '@testing-library/react' import { initStoryshots } from '../../../config/jest/storyshots' import { EbayBreadcrumbs, EbayBreadcrumbItem } from '../index' +import userEvent from '@testing-library/user-event' describe('', () => { describe('on category click', () => { @@ -13,9 +14,11 @@ describe('', () => { home ) - fireEvent.click(wrapper.getByRole('button')) + const button = wrapper.getByRole('button') + userEvent.click(button) - expect(spy).toBeCalled() + const syntheticEvent = expect.objectContaining( { target: null }) + expect(spy).toBeCalledWith(syntheticEvent) }) }) diff --git a/src/ebay-breadcrumbs/__tests__/index.stories.tsx b/src/ebay-breadcrumbs/__tests__/index.stories.tsx index d47e4b85..005df4cf 100644 --- a/src/ebay-breadcrumbs/__tests__/index.stories.tsx +++ b/src/ebay-breadcrumbs/__tests__/index.stories.tsx @@ -6,7 +6,7 @@ import { EbayBreadcrumbs, EbayBreadcrumbItem as Item } from '../index' storiesOf('ebay-breadcrumb', module) .add('default', () => (<> - + action('select')(e, { el })}> eBay Cell Phones, Smart Watches & Accessories Smart Watch Accessories diff --git a/src/ebay-breadcrumbs/breadcrumbs.tsx b/src/ebay-breadcrumbs/breadcrumbs.tsx index 85db19ee..0429cef5 100644 --- a/src/ebay-breadcrumbs/breadcrumbs.tsx +++ b/src/ebay-breadcrumbs/breadcrumbs.tsx @@ -1,7 +1,8 @@ import React, { Children, cloneElement, ComponentProps, FC, ReactElement, ReactNode } from 'react' import classNames from 'classnames' +import { EbayEventHandler } from '../common/event-utils/types' -type BreadcrumbProps = ComponentProps<'div'> & { +type BreadcrumbProps = Omit, 'onSelect'> & { /** * Breadcrumbs expects `` as children. * Other elements will not work. @@ -12,7 +13,7 @@ type BreadcrumbProps = ComponentProps<'div'> & { id?: string; a11yHeadingTag?: keyof JSX.IntrinsicElements; a11yHeadingText?: string; - onSelect?: (event: MouseEvent | KeyboardEvent, target: HTMLElement) => void; + onSelect?: EbayEventHandler; } const Breadcrumbs: FC = ({ @@ -47,7 +48,7 @@ const Breadcrumbs: FC = ({ isLastItem, href, children, - onClick: event => onSelect(event, event.target) + onClick: event => onSelect(event) } return cloneElement(item, itemProps) diff --git a/src/ebay-button/README.md b/src/ebay-button/README.md index 442ae2b4..4af49542 100644 --- a/src/ebay-button/README.md +++ b/src/ebay-button/README.md @@ -56,7 +56,7 @@ Name | Type | Stateful | Required | Description | Data `truncate` | Boolean | No | No | will truncate the text of the button onto a single line, and adds an ellipsis, when the button's text overflows `borderless` | Boolean | No | No | shows button without border `fixedHeight` | Boolean | No | No | fixes the height based on `size` -`onClick` | Function | - | No | click or action key pressed (`Space` / `Enter`) -`onEscape` | Function | - | No | `Esc`-key pressed -`onFocus` | Function | - | No | triggered on focus -`onBlur` | Function | - | No | triggered on blur +`onClick` | Function | - | No | click or action key pressed (`Space` / `Enter`) | `(event: MouseEvent | KeyboardEvent)` +`onEscape` | Function | - | No | `Esc`-key pressed | `(event: KeyboardEvent)` +`onFocus` | Function | - | No | triggered on focus | `(event: FocusEvent)` +`onBlur` | Function | - | No | triggered on blur | `(event: FocusEvent)` diff --git a/src/ebay-button/__tests__/index.spec.tsx b/src/ebay-button/__tests__/index.spec.tsx index a1ffeb06..83ee2191 100644 --- a/src/ebay-button/__tests__/index.spec.tsx +++ b/src/ebay-button/__tests__/index.spec.tsx @@ -1,8 +1,10 @@ import React from 'react' -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react' import { initStoryshots } from '../../../config/jest/storyshots' import { EbayButton } from '../index' +const anySyntheticEvent = expect.objectContaining( { target: null }) + initStoryshots({ config: ({ configure }) => configure(() => { @@ -34,15 +36,31 @@ describe('', () => { fireEvent.click(wrapper.getByRole('button')) - expect(spy).toBeCalled() + expect(spy).toBeCalledWith(anySyntheticEvent) }) it('on escape', () => { const spy = jest.fn() const wrapper = render() - fireEvent.keyDown(wrapper.getByRole('button'), {key: 'Escape' }) + fireEvent.keyDown(wrapper.getByRole('button'), { key: 'Escape' }) + + expect(spy).toBeCalledWith(anySyntheticEvent) + }) + it('on focus', () => { + const spy = jest.fn() + const wrapper = render() + + fireEvent.focus(wrapper.getByRole('button')) + + expect(spy).toBeCalledWith(anySyntheticEvent) + }) + it('on blur', () => { + const spy = jest.fn() + const wrapper = render() + + fireEvent.blur(wrapper.getByRole('button')) - expect(spy).toBeCalled() + expect(spy).toBeCalledWith(anySyntheticEvent) }) }) }) diff --git a/src/ebay-button/__tests__/index.stories.tsx b/src/ebay-button/__tests__/index.stories.tsx index cc30f4a9..4580abb6 100644 --- a/src/ebay-button/__tests__/index.stories.tsx +++ b/src/ebay-button/__tests__/index.stories.tsx @@ -10,7 +10,7 @@ storiesOf(`ebay-button`, module)

action('focus')(e)} onBlur={action('blur')} onKeyDown={action('key down')} >Hello, I am a button!

diff --git a/src/ebay-carousel/carousel.tsx b/src/ebay-carousel/carousel.tsx index a83f0a43..8fc1503f 100644 --- a/src/ebay-carousel/carousel.tsx +++ b/src/ebay-carousel/carousel.tsx @@ -24,7 +24,7 @@ type CarouselProps = ComponentProps<'div'> & { // TO-DO: // Image slides -// Auto play +// Auto play (+ onPlay/onPause callbacks) const EbayCarousel: FC = ({ gap = 16, diff --git a/src/ebay-checkbox/README.md b/src/ebay-checkbox/README.md index 8ea984ac..849fcd31 100644 --- a/src/ebay-checkbox/README.md +++ b/src/ebay-checkbox/README.md @@ -32,15 +32,16 @@ import { EbayLabel } from '@ebay/ui-core-react/ebay-field'; ## Attributes -Name | Type | Stateful | Description ---- | --- | --- | --- -`size` | String | No | No | Either `large` or `regular` (default). Sets the checkbox icon size. For mweb this should be set to `large`. (Note: The dimensions of the radio will not change, but only the icon) -`disabled` | Boolean | No | -`checked` | Boolean | No | No | indicates the checked value of the input element, required for a controlled component. -`defaultChecked` | Boolean | No | No | indicates the default checked input element value. Use when the component is not controlled. -`onChange` | Function | Callback fired when `checked` is changed, with param `{ originalEvent, value }` -`onFocus` | Function | Callback fired when button is focused, with param `{ originalEvent, value }` - -It supports all the events supported by an input element (e.g. `onChange`) +| Name | Type | Stateful | Description | Data | +|------------------|----------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| `size` | String | No | Either `large` or `regular` (default). Sets the checkbox icon size. For mweb this should be set to `large`. (Note: The dimensions of the radio will not change, but only the icon) | +| `disabled` | Boolean | No | | +| `checked` | Boolean | No | indicates the checked value of the input element, required for a controlled component. | +| `defaultChecked` | Boolean | No | indicates the default checked input element value. Use when the component is not controlled. | +| `onChange` | Function | - | Callback fired on change | `(event: ChangeEvent, { value: string, checked: Boolean })` | | +| `onFocus` | Function | - | Callback fired when button is focused | `(event: FocusEvent, { value: string, checked: Boolean })` | | +| `onKeyDown` | Function | - | Callback fired when key is pressed | `(event: KeyboardEvent, { value: string, checked: Boolean })` | | + +It supports all the events supported by an input element (e.g. `onClick`) Note: For this component, `className`/`style` are applied to the root tag, while all other HTML attributes are applied to the `input` tag. diff --git a/src/ebay-checkbox/__tests__/index.spec.tsx b/src/ebay-checkbox/__tests__/index.spec.tsx index e20793d1..496939d8 100644 --- a/src/ebay-checkbox/__tests__/index.spec.tsx +++ b/src/ebay-checkbox/__tests__/index.spec.tsx @@ -1,20 +1,38 @@ import React from 'react' -import { render } from '@testing-library/react' -import userEvent from '@testing-library/user-event' +import { fireEvent, render, screen } from '@testing-library/react' import { initStoryshots } from '../../../config/jest/storyshots' import { EbayCheckbox } from '../index' +const { getByRole } = screen +const anySyntheticEvent = expect.objectContaining( { type: null }) + describe('', () => { describe('on checkbox-button click', () => { it('should fire an event', () => { const spy = jest.fn() - const { getByLabelText } = render( - - ) - const input = getByLabelText('checkbox') - userEvent.click(input); - expect(spy).toBeCalled() + render() + const input = getByRole('checkbox') + fireEvent.click(input); + expect(spy).toBeCalledWith(anySyntheticEvent, { value: '123', checked: true }) + }) + }) + describe('on checkbox-button focus', () => { + it('should fire an event', () => { + const spy = jest.fn() + render() + const input = getByRole('checkbox') + fireEvent.focus(input); + expect(spy).toBeCalledWith(anySyntheticEvent, { value: '123', checked: false }) + }) + }) + describe('on checkbox-button key down', () => { + it('should fire an event', () => { + const spy = jest.fn() + render() + const input = getByRole('checkbox') + fireEvent.keyDown(input); + expect(spy).toBeCalledWith(anySyntheticEvent, { value: '123', checked: false }) }) }) }) diff --git a/src/ebay-checkbox/__tests__/index.stories.tsx b/src/ebay-checkbox/__tests__/index.stories.tsx index f88d252b..4e53a651 100644 --- a/src/ebay-checkbox/__tests__/index.stories.tsx +++ b/src/ebay-checkbox/__tests__/index.stories.tsx @@ -8,7 +8,13 @@ storiesOf(`ebay-checkbox`, module) .add(`Default checkbox-button`, () => ( <>

- + action('onChange')(e, props)} + onFocus={(e, props) => action('onFocus')(e, props)} + onKeyDown={(e, props) => action('onKeyDown')(e, props)} + > Default

diff --git a/src/ebay-checkbox/checkbox.tsx b/src/ebay-checkbox/checkbox.tsx index 93896e98..56160f05 100644 --- a/src/ebay-checkbox/checkbox.tsx +++ b/src/ebay-checkbox/checkbox.tsx @@ -1,14 +1,17 @@ -import React, { FC, useState, ChangeEvent, ComponentProps, cloneElement } from 'react' +import React, { ChangeEvent, cloneElement, ComponentProps, FC, FocusEvent, KeyboardEvent, useState } from 'react' import classNames from 'classnames' import { EbayIcon } from '../ebay-icon' import { EbayLabel, EbayLabelProps } from '../ebay-field' import { findComponent } from '../common/component-utils' +import { EbayChangeEventHandler, EbayFocusEventHandler, EbayKeyboardEventHandler } from '../common/event-utils/types' type Size = 'default' | 'large' -type InputProps = Omit, 'size' | 'onChange'> +type InputProps = Omit, 'size' | 'onChange' | 'onFocus' | 'onKeyDown'> type EbayCheckboxProps = { size?: Size; - onChange?: (e: ChangeEvent, value: string | number, checked: boolean) => void; + onChange?: EbayChangeEventHandler; + onFocus?: EbayFocusEventHandler; + onKeyDown?: EbayKeyboardEventHandler; inputRef?: React.LegacyRef; } @@ -22,6 +25,8 @@ const EbayCheckbox: FC = ({ checked, defaultChecked = false, onChange = () => {}, + onFocus = () => {}, + onKeyDown = () => {}, children, inputRef, ...rest @@ -29,9 +34,16 @@ const EbayCheckbox: FC = ({ const [isChecked, setChecked] = useState(defaultChecked) const handleChange = (e: ChangeEvent) => { const input = e.target - onChange(e, input?.value, input?.checked) + onChange(e, { value: input?.value, checked: Boolean(input?.checked) }) setChecked(input?.checked) } + const handleFocus = (e: FocusEvent) => + onFocus(e, { value: e.target?.value, checked: Boolean(e.target?.checked) }) + + const handleKeyDown = (e: KeyboardEvent) => { + const input = e.target as EventTarget & HTMLInputElement + onKeyDown(e, { value: input.value, checked: Boolean(input.checked) }) + } const containerClass = classNames('checkbox', className, { 'checkbox--large': size === 'large' }) @@ -54,6 +66,8 @@ const EbayCheckbox: FC = ({ type="checkbox" checked={isControlled(checked) ? checked : isChecked} onChange={handleChange} + onFocus={handleFocus} + onKeyDown={handleKeyDown} ref={inputRef} />