diff --git a/.changeset/giant-rules-vanish.md b/.changeset/giant-rules-vanish.md new file mode 100644 index 00000000..6574f3fc --- /dev/null +++ b/.changeset/giant-rules-vanish.md @@ -0,0 +1,22 @@ +--- +"@igloo-ui/action-menu": minor +"@igloo-ui/alert": minor +"@igloo-ui/button": minor +"@igloo-ui/combobox": minor +"@igloo-ui/filter": minor +"@igloo-ui/icon-button": minor +"@igloo-ui/list": minor +"@igloo-ui/select": minor +"@igloo-ui/stepper": minor +"@igloo-ui/toaster": minor +"@igloo-ui/datepicker": minor +"@igloo-ui/dialog": minor +"@igloo-ui/modal": minor +"@igloo-ui/popover": minor +"@igloo-ui/stacked-bar": minor +"@igloo-ui/tag": minor +"@igloo-ui/tag-picker": minor +"@igloo-ui/text-editor": minor +--- + +Updated typings diff --git a/packages/ActionMenu/src/ActionMenu.test.tsx b/packages/ActionMenu/src/ActionMenu.test.tsx index 9a4d0095..4f498cc2 100644 --- a/packages/ActionMenu/src/ActionMenu.test.tsx +++ b/packages/ActionMenu/src/ActionMenu.test.tsx @@ -78,4 +78,25 @@ describe('ActionMenu', () => { } expect(selected).toBeTruthy(); }); + + test('It calls the onClick of the option even with an eventHandler', () => { + let selected = false; + setup({ + ...actionMenuProps, options: [ + { + label: 'Add Item', + value: 'add', + onClick: (e) => { + e?.preventDefault(); + selected = true; + } + }, + ] + }); + const listItem = screen.getByText('Add Item'); + if (listItem) { + fireEvent.click(listItem); + } + expect(selected).toBeTruthy(); + }); }); diff --git a/packages/ActionMenu/src/ActionMenu.tsx b/packages/ActionMenu/src/ActionMenu.tsx index 6e9c9d34..f3c666e5 100644 --- a/packages/ActionMenu/src/ActionMenu.tsx +++ b/packages/ActionMenu/src/ActionMenu.tsx @@ -23,7 +23,7 @@ export interface ActionMenuOption extends Omit { /** Whether or not the action menu should close when an option is selected */ closeOnSelect?: boolean | ((option: OptionType) => boolean); /** Callback when an option is selected */ - onClick?: () => void; + onClick?: (e?: React.SyntheticEvent) => void; } export interface ActionMenuProps extends React.ComponentProps<"div"> { @@ -119,14 +119,12 @@ const ActionMenu: React.FunctionComponent = ({ return closeOnSelect; }; - const selectOption = (option: OptionType): void => { + const selectOption = (option: OptionType, e?: React.SyntheticEvent): void => { const actionMenuOption = options.find( actionMenuOption => actionMenuOption.value === option.value ); - const onOptionSelect = actionMenuOption?.onClick; - if (onOptionSelect) { - onOptionSelect(); - } + + actionMenuOption?.onClick?.(e); if (closeMenuOnSelect(option)) { toggleMenu(false); @@ -187,7 +185,7 @@ const ActionMenu: React.FunctionComponent = ({ keyboardEvent.preventDefault(); keyboardEvent.stopPropagation(); if (currentFocusedOption) { - selectOption(currentFocusedOption); + selectOption(currentFocusedOption, keyboardEvent); } if ((!currentFocusedOption && showMenu) || !showMenu) { toggleMenu(!showMenu); diff --git a/packages/Alert/src/Alert.tsx b/packages/Alert/src/Alert.tsx index a961a782..86bff7e2 100644 --- a/packages/Alert/src/Alert.tsx +++ b/packages/Alert/src/Alert.tsx @@ -30,7 +30,7 @@ export type Appearance = "card" | "inline" | "horizontal"; export interface AlertButton { label: React.ReactNode; - onClick: () => void; + onClick: (e?: React.SyntheticEvent) => void; } export interface AlertProps extends Omit, "title"> { diff --git a/packages/Button/src/Button.tsx b/packages/Button/src/Button.tsx index a82a39cb..d9908074 100644 --- a/packages/Button/src/Button.tsx +++ b/packages/Button/src/Button.tsx @@ -56,7 +56,7 @@ export interface ButtonOwnProps { /** Display only the icon in mobile */ showOnlyIconOnMobile?: boolean; /** Callback when clicked */ - onClick?: () => void; + onClick?: (e?: React.SyntheticEvent) => void; /** Optional prop to specify the type of the Button */ type?: "button" | "reset" | "submit"; /** Add a data-intercom-target with unique id to link a diff --git a/packages/Combobox/src/Combobox.stories.tsx b/packages/Combobox/src/Combobox.stories.tsx index b22ff1a0..fc1585a2 100644 --- a/packages/Combobox/src/Combobox.stories.tsx +++ b/packages/Combobox/src/Combobox.stories.tsx @@ -61,6 +61,23 @@ const smallOptionList: ComboboxOption[] = [ }, ]; +const NodeLabelOptionList: ComboboxOption[] = [ + { + label: Text option, + value: 'text', + }, + { + label: Disabled option, + value: 'disabled', + disabled: true, + }, + { + label: Text option with icon, + value: 'icon', + icon: , + }, + ]; + const largeOptionList: ComboboxOption[] = [ { label: 'Text 1', @@ -154,21 +171,38 @@ const listWithAction: ComboboxOption[] = [ type Story = StoryObj; export const Overview: Story = { - args: { - children: comboboxPlaceholder, - options: smallOptionList, - }, + args: { + children: comboboxPlaceholder, + options: smallOptionList, + }, - play: async ({ canvasElement }) => { - const body = canvasElement.ownerDocument.body; - const canvas = within(body); + play: async ({ canvasElement }) => { + const body = canvasElement.ownerDocument.body; + const canvas = within(body); - await userEvent.click(canvas.getByRole('button')); - const firstOption = await canvas.findByText('Text option'); + await userEvent.click(canvas.getByRole('button')); + const firstOption = await canvas.findByText('Text option'); - await expect(firstOption).toBeInTheDocument(); - }, -}; + await expect(firstOption).toBeInTheDocument(); + }, + }; + + export const Nodes: Story = { + args: { + children: comboboxPlaceholder, + options: NodeLabelOptionList, + }, + + play: async ({ canvasElement }) => { + const body = canvasElement.ownerDocument.body; + const canvas = within(body); + + await userEvent.click(canvas.getByRole('button')); + const firstOption = await canvas.findByText('Text option'); + + await expect(firstOption).toBeInTheDocument(); + }, + }; export const Sizes: Story = { render: () => ( diff --git a/packages/Combobox/src/Combobox.test.tsx b/packages/Combobox/src/Combobox.test.tsx index 79f53563..195a9f23 100644 --- a/packages/Combobox/src/Combobox.test.tsx +++ b/packages/Combobox/src/Combobox.test.tsx @@ -155,6 +155,21 @@ describe('Combobox', () => { expect(comboboxOptions.length).toBe(options.length); }); + test('It should support react nodes', () => { + const options: ComboboxOption[] = [ + { label: 1, value: 1 }, + { label: 2, value: 2 }, + { label: 3, value: 3 }, + { label: 4, value: 4 }, + ]; + + setup({ isOpen: true }, options); + const combobox = screen.getByTestId('combobox1'); + const comboboxOptions = combobox.querySelectorAll('.ids-list-item'); + + expect(comboboxOptions.length).toBe(options.length); + }); + test("It should put the option label in the header after it's clicked", () => { const options = [{ label: '1', value: 1 }]; diff --git a/packages/Combobox/src/Combobox.tsx b/packages/Combobox/src/Combobox.tsx index 87154c95..0ec48f44 100644 --- a/packages/Combobox/src/Combobox.tsx +++ b/packages/Combobox/src/Combobox.tsx @@ -167,7 +167,7 @@ const Combobox: React.FunctionComponent = ({ } }, [comboboxOptions]); - const optionText = (option: OptionType | undefined): string | undefined => { + const optionText = (option: OptionType | undefined): React.ReactNode | undefined => { if (option?.type === "member") { return option?.member; } diff --git a/packages/Filter/src/Filter.tsx b/packages/Filter/src/Filter.tsx index b13e24de..1e6e2f43 100644 --- a/packages/Filter/src/Filter.tsx +++ b/packages/Filter/src/Filter.tsx @@ -13,7 +13,7 @@ export interface FilterProps extends React.ComponentProps<"button"> { /** True if the filter should be disabled */ disabled?: boolean; /** Add an event for when the filter is clicked */ - onClick?: () => void; + onClick?: (e?: React.SyntheticEvent) => void; /** True if the tag is selected */ selected?: boolean; } diff --git a/packages/IconButton/src/IconButton.test.tsx b/packages/IconButton/src/IconButton.test.tsx index e45077a7..292b02c1 100644 --- a/packages/IconButton/src/IconButton.test.tsx +++ b/packages/IconButton/src/IconButton.test.tsx @@ -2,13 +2,14 @@ * @jest-environment jsdom */ import React from 'react'; + import { render, screen } from '@testing-library/react'; import Plus from '@igloo-ui/icons/dist/Plus'; import IconButton from './IconButton'; const setUp = (props = {}) => { - return render( + return render( } dataTest="ids-icon-btn" diff --git a/packages/IconButton/src/IconButton.tsx b/packages/IconButton/src/IconButton.tsx index a75845d0..e0ae2736 100644 --- a/packages/IconButton/src/IconButton.tsx +++ b/packages/IconButton/src/IconButton.tsx @@ -15,7 +15,7 @@ export interface IconButtonProps extends Omit { icon: React.ReactNode; /** Callback function that will be called * when the user clicks on the button */ - onClick?: () => void; + onClick?: (e?: React.SyntheticEvent) => void; /** True if the control is disabled and shows a disabled state. * The user cannot click on the button */ disabled?: boolean; diff --git a/packages/List/src/List.tsx b/packages/List/src/List.tsx index 57908445..18f4aeac 100644 --- a/packages/List/src/List.tsx +++ b/packages/List/src/List.tsx @@ -14,7 +14,7 @@ export interface ListProps extends React.ComponentPropsWithRef<"ul"> { disableTabbing?: boolean; /** The option that is currently being focused or hovered */ focusedOption?: OptionType | null; - /** True for a compact appearance + /** True for a compact appearance * (Corresponds to "small" in Figma for compact, and "medium" for non-compact) */ isCompact?: boolean; /** Whether or not the list is loading */ @@ -25,7 +25,7 @@ export interface ListProps extends React.ComponentPropsWithRef<"ul"> { /** Called when an option becomes focused or hovered */ onOptionFocus?: (option: OptionType) => void; /** Called when an option is selected */ - onOptionChange?: (option: OptionType) => void; + onOptionChange?: (option: OptionType, e?: React.SyntheticEvent) => void; /** Called when the mouse moves outside of the option * or the option loses focus */ onOptionBlur?: (option: OptionType) => void; diff --git a/packages/List/src/ListItem.tsx b/packages/List/src/ListItem.tsx index 7c895bb9..58769146 100644 --- a/packages/List/src/ListItem.tsx +++ b/packages/List/src/ListItem.tsx @@ -30,7 +30,7 @@ export interface Option extends ListItem { /** Whether or not the option is disabled */ disabled?: boolean; /** The option label */ - label: string; + label: React.ReactNode; /** The option type */ type: "list"; } @@ -64,7 +64,7 @@ export interface ListItemProps extends React.ComponentProps<"li"> { /** Called when an option becomes focused or hovered */ onOptionFocus?: (option: OptionType) => void; /** Called when an option is selected */ - onOptionChange?: (option: OptionType) => void; + onOptionChange?: (option: OptionType, e?: React.SyntheticEvent) => void; /** Called when the mouse moves outside of the option * or the option loses focus */ onOptionBlur?: (option: OptionType) => void; @@ -127,9 +127,9 @@ const ListItem: React.FunctionComponent = ({ } }; - const handleOptionChange = (item: OptionType): void => { + const handleOptionChange = (item: OptionType, e?: React.SyntheticEvent): void => { if (!isOptionDisabled() && onOptionChange) { - onOptionChange(item); + onOptionChange(item, e); } }; @@ -195,7 +195,7 @@ const ListItem: React.FunctionComponent = ({ {option?.member} {option?.manager && ( - isWorkleap ? + isWorkleap ? : )} @@ -218,7 +218,7 @@ const ListItem: React.FunctionComponent = ({ !(target as HTMLElement).closest("button") ) { if (option) { - handleOptionChange(option); + handleOptionChange(option, e); } } }} diff --git a/packages/Select/src/Select.tsx b/packages/Select/src/Select.tsx index e59db986..8d22a2ac 100644 --- a/packages/Select/src/Select.tsx +++ b/packages/Select/src/Select.tsx @@ -92,7 +92,7 @@ const Select: React.FunctionComponent = ({ React.useState(selectedOption); const [showMenu, setShowMenu] = React.useState(isOpen); - const optionText = (option: OptionType | undefined): string | undefined => { + const optionText = (option: OptionType | undefined): React.ReactNode | undefined => { if (option?.type === "member") { return option?.member; } diff --git a/packages/Stepper/src/Stepper.test.tsx b/packages/Stepper/src/Stepper.test.tsx index 98935c91..98fe6afd 100644 --- a/packages/Stepper/src/Stepper.test.tsx +++ b/packages/Stepper/src/Stepper.test.tsx @@ -53,16 +53,17 @@ describe('Stepper', () => { }); test('Calls the step onClick callback when a step is clicked', () => { - const onStepChange = jest.fn(); + let currentStep = 0; + const onStepChange = jest.fn((step: number) => currentStep = step); const stepsWithClicks = [ - { title: 'Step 1', onClick: onStepChange }, - { title: 'Step 2', onClick: onStepChange }, - { title: 'Step 3', onClick: onStepChange }, + { title: 'Step 1', onClick: () => onStepChange(1) }, + { title: 'Step 2', onClick: () => onStepChange(2) }, + { title: 'Step 3', onClick: () => onStepChange(3) }, ]; const {container} = setup({steps: stepsWithClicks, currentStep: 2}); - const stepElements = container.querySelectorAll('.ids-step'); - fireEvent.click(stepElements[0]); - expect(onStepChange).toHaveBeenCalledWith(0); + const [firstStep] = container.querySelectorAll('.ids-step'); + fireEvent.click(firstStep); + expect(onStepChange).toHaveBeenCalledWith(1); }); test('Disables steps after the current step if clickableNextSteps is false', () => { @@ -76,13 +77,11 @@ describe('Stepper', () => { test('Enables steps after the current step if clickableNextSteps is true', () => { const currentStep = 1; - + const {container} = setup({steps: steps, currentStep: currentStep, clickableNextSteps: true}); const stepElements = container.querySelectorAll('.ids-step'); for (let i = currentStep + 1; i < stepElements.length; i++) { expect(stepElements[i]).not.toBeDisabled(); } }); - - }); diff --git a/packages/Stepper/src/Stepper.tsx b/packages/Stepper/src/Stepper.tsx index 555ad3ad..2c61c358 100644 --- a/packages/Stepper/src/Stepper.tsx +++ b/packages/Stepper/src/Stepper.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import cx from "classnames"; +import type { PressEvent } from "react-aria"; import Step from "./Step"; @@ -7,7 +8,7 @@ import "./stepper.scss"; export interface Step { /** The callback function that is called when the step is clicked */ - onClick?: (index: number) => void; + onClick?: ((index: number, e?: PressEvent) => void); /** The title for the step */ title: string; } @@ -54,7 +55,9 @@ const Stepper: React.FunctionComponent = ({ isComplete={isComplete} isCurrent={isCurrent} disabled={disabled} - onPress={() => step.onClick?.(index)} + onPress={(e: PressEvent) => { + step.onClick?.(index, e); + }} /> {index < steps.length - 1 && (
{ ); -}; \ No newline at end of file +}; + +export const NodeMessageSuccessToast = () => { + return ( + + + + + ); + }; diff --git a/packages/Toaster/src/Toaster.tsx b/packages/Toaster/src/Toaster.tsx index 12d91fa0..86c1d054 100644 --- a/packages/Toaster/src/Toaster.tsx +++ b/packages/Toaster/src/Toaster.tsx @@ -11,7 +11,7 @@ import "./toaster.scss"; export interface ToastArgs { isClosable?: boolean; status?: "success" | "error"; - message: string; + message: React.ReactNode; } export interface ToastQueueOptionsProps { @@ -20,8 +20,8 @@ export interface ToastQueueOptionsProps { } export interface ToastQueueProps { - success: (message: string, options?: ToastQueueOptionsProps) => void; - error: (message: string, options?: ToastQueueOptionsProps) => void; + success: (message: React.ReactNode, options?: ToastQueueOptionsProps) => void; + error: (message: React.ReactNode, options?: ToastQueueOptionsProps) => void; } const TOAST_DURATION = 4000 as const; @@ -66,7 +66,7 @@ const useActiveToastContainer = (): unknown => { }; const addToast = ( - message: string, + message: React.ReactNode, status: "success" | "error", duration: number | "infinite", isClosable: boolean