diff --git a/src/components/MultiSelectInput/SearchMultiSelectInput.tsx b/src/components/MultiSelectInput/SearchMultiSelectInput.tsx index 7956de0..59f8f69 100644 --- a/src/components/MultiSelectInput/SearchMultiSelectInput.tsx +++ b/src/components/MultiSelectInput/SearchMultiSelectInput.tsx @@ -12,11 +12,13 @@ import { rankedSearchOnList } from '../../utils'; import styles from './styles.css'; interface OptionProps { + actions?: React.ReactNode; children: React.ReactNode; isActive: boolean; } function Option(props: OptionProps) { const { + actions, children, isActive, } = props; @@ -29,6 +31,9 @@ function Option(props: OptionProps) {
{ children }
+
+ {actions} +
); } @@ -50,6 +55,7 @@ export type SearchMultiSelectInputProps< searchOptions?: O[] | undefined | null; keySelector: (option: O) => T; labelSelector: (option: O) => string; + actionsSelector?: (option: O) => React.ReactNode; hideOptionFilter?: (option: O) => boolean; name: K; disabled?: boolean; @@ -97,6 +103,7 @@ function SearchMultiSelectInput< const { keySelector, labelSelector, + actionsSelector, name, onChange, onOptionsChange, @@ -237,10 +244,11 @@ function SearchMultiSelectInput< children: labelSelector(option), containerClassName: _cs(styles.option, isActive && styles.active), title: labelSelector(option), + actions: actionsSelector?.(option), isActive, }; }, - [labelSelector, value], + [labelSelector, value, actionsSelector], ); // FIXME: value should not be on dependency list, also try to pass options like in SelectInput diff --git a/src/components/SelectInput/SearchSelectInput.tsx b/src/components/SelectInput/SearchSelectInput.tsx index 26acd96..7d00378 100644 --- a/src/components/SelectInput/SearchSelectInput.tsx +++ b/src/components/SelectInput/SearchSelectInput.tsx @@ -12,10 +12,12 @@ import { rankedSearchOnList } from '../../utils'; import styles from './styles.css'; interface OptionProps { + actions?: React.ReactNode; children: React.ReactNode; } function Option(props: OptionProps) { const { + actions, children, } = props; @@ -27,6 +29,9 @@ function Option(props: OptionProps) {
{ children }
+
+ {actions} +
); } @@ -47,6 +52,7 @@ export type SearchSelectInputProps< searchOptions?: O[] | undefined | null; keySelector: (option: O) => T; labelSelector: (option: O) => string; + actionsSelector?: (option: O) => React.ReactNode; hideOptionFilter?: (option: O) => boolean; name: K; disabled?: boolean; @@ -102,6 +108,7 @@ function SearchSelectInput< const { keySelector, labelSelector, + actionsSelector, name, onChange, onOptionsChange, @@ -230,11 +237,12 @@ function SearchSelectInput< return { children: labelSelector(option), + actions: actionsSelector?.(option), containerClassName: _cs(styles.option, isActive && styles.active), title: labelSelector(option), }; }, - [value, labelSelector], + [value, labelSelector, actionsSelector], ); const handleOptionClick = useCallback( diff --git a/src/stories/MultiSelectInput.stories.tsx b/src/stories/MultiSelectInput.stories.tsx index 623f595..41dcbcc 100644 --- a/src/stories/MultiSelectInput.stories.tsx +++ b/src/stories/MultiSelectInput.stories.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Story } from '@storybook/react/types-6-0'; import { useArgs } from '@storybook/client-api'; +import { IoOpenOutline } from 'react-icons/io5'; import MultiSelectInput, { MultiSelectInputProps } from '#components/MultiSelectInput'; +import QuickActionButton from '#components/QuickActionButton'; export default { title: 'Input/MultiSelectInput', @@ -46,6 +48,27 @@ const Template: Story) { + // NOTE: This intentionally breaks HTML semantics (link inside a button). + // This workaround is to allow clickable links inside SelectInput options + // where a button wrapper is required. + e.stopPropagation(); + window.open('https://www.google.com/search?q=story', '_blank'); +} + +export const WithActions = Template.bind({}); +WithActions.args = { + actionsSelector: () => ( + + + + ), +}; + export const NoValue = Template.bind({}); NoValue.args = { value: undefined, diff --git a/src/stories/SearchMultiSelectInput.stories.tsx b/src/stories/SearchMultiSelectInput.stories.tsx index 39d83b4..e943fb0 100644 --- a/src/stories/SearchMultiSelectInput.stories.tsx +++ b/src/stories/SearchMultiSelectInput.stories.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { Story } from '@storybook/react/types-6-0'; import { useArgs } from '@storybook/client-api'; +import { IoOpenOutline } from 'react-icons/io5'; import SearchMultiSelectInput, { SearchMultiSelectInputProps } from '#components/MultiSelectInput/SearchMultiSelectInput'; +import QuickActionButton from '#components/QuickActionButton'; import useQuery, { entityListTransformer } from '../utils/useQuery'; export default { @@ -188,6 +190,27 @@ const Template: Story) { + // NOTE: This intentionally breaks HTML semantics (link inside a button). + // This workaround is to allow clickable links inside SelectInput options + // where a button wrapper is required. + e.stopPropagation(); + window.open('https://www.google.com/search?q=story', '_blank'); +} + +export const WithActions = Template.bind({}); +WithActions.args = { + actionsSelector: () => ( + + + + ), +}; + export const NoValue = Template.bind({}); NoValue.args = { value: undefined, diff --git a/src/stories/SearchSelectInput.stories.tsx b/src/stories/SearchSelectInput.stories.tsx index f2a983e..2f7555e 100644 --- a/src/stories/SearchSelectInput.stories.tsx +++ b/src/stories/SearchSelectInput.stories.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { Story } from '@storybook/react/types-6-0'; import { useArgs } from '@storybook/client-api'; +import { IoOpenOutline } from 'react-icons/io5'; import SearchSelectInput, { SearchSelectInputProps } from '#components/SelectInput/SearchSelectInput'; +import QuickActionButton from '#components/QuickActionButton'; import useQuery, { entityListTransformer } from '../utils/useQuery'; export default { @@ -193,6 +195,27 @@ Default.args = { value: '1', }; +function handleClick(_:string | undefined, e: React.MouseEvent) { + // NOTE: This intentionally breaks HTML semantics (link inside a button). + // This workaround is to allow clickable links inside SelectInput options + // where a button wrapper is required. + e.stopPropagation(); + window.open('https://www.google.com/search?q=story', '_blank'); +} + +export const WithActions = Template.bind({}); +WithActions.args = { + actionsSelector: () => ( + + + + ), +}; + export const Disabled = Template.bind({}); Disabled.args = { value: '1', diff --git a/src/stories/SelectInput.stories.tsx b/src/stories/SelectInput.stories.tsx index 08aad30..4bc649a 100644 --- a/src/stories/SelectInput.stories.tsx +++ b/src/stories/SelectInput.stories.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Story } from '@storybook/react/types-6-0'; import { useArgs } from '@storybook/client-api'; +import { IoOpenOutline } from 'react-icons/io5'; import SelectInput, { SelectInputProps } from '#components/SelectInput'; +import QuickActionButton from '#components/QuickActionButton'; export default { title: 'Input/SelectInput', @@ -45,6 +47,27 @@ const Template: Story) { + // NOTE: This intentionally breaks HTML semantics (link inside a button). + // This workaround is to allow clickable links inside SelectInput options + // where a button wrapper is required. + e.stopPropagation(); + window.open('https://www.google.com/search?q=story', '_blank'); +} + +export const WithActions = Template.bind({}); +WithActions.args = { + actionsSelector: () => ( + + + + ), +}; + export const NoValue = Template.bind({}); NoValue.args = { value: undefined,