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,