Skip to content

Commit f2b68ab

Browse files
committed
add(select-inputs): add action selector
1 parent 5f6b2b7 commit f2b68ab

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

src/components/MultiSelectInput/SearchMultiSelectInput.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import { rankedSearchOnList } from '../../utils';
1212
import styles from './styles.css';
1313

1414
interface OptionProps {
15+
actions?: React.ReactNode;
1516
children: React.ReactNode;
1617
isActive: boolean;
1718
}
1819
function Option(props: OptionProps) {
1920
const {
21+
actions,
2022
children,
2123
isActive,
2224
} = props;
@@ -29,6 +31,9 @@ function Option(props: OptionProps) {
2931
<div className={styles.label}>
3032
{ children }
3133
</div>
34+
<div className={styles.actions}>
35+
{actions}
36+
</div>
3237
</>
3338
);
3439
}
@@ -50,6 +55,7 @@ export type SearchMultiSelectInputProps<
5055
searchOptions?: O[] | undefined | null;
5156
keySelector: (option: O) => T;
5257
labelSelector: (option: O) => string;
58+
actionSelector?: (option: O) => React.ReactNode;
5359
hideOptionFilter?: (option: O) => boolean;
5460
name: K;
5561
disabled?: boolean;
@@ -97,6 +103,7 @@ function SearchMultiSelectInput<
97103
const {
98104
keySelector,
99105
labelSelector,
106+
actionSelector,
100107
name,
101108
onChange,
102109
onOptionsChange,
@@ -237,10 +244,11 @@ function SearchMultiSelectInput<
237244
children: labelSelector(option),
238245
containerClassName: _cs(styles.option, isActive && styles.active),
239246
title: labelSelector(option),
247+
actions: actionSelector?.(option),
240248
isActive,
241249
};
242250
},
243-
[labelSelector, value],
251+
[labelSelector, value, actionSelector],
244252
);
245253

246254
// FIXME: value should not be on dependency list, also try to pass options like in SelectInput

src/components/SelectInput/SearchSelectInput.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import { rankedSearchOnList } from '../../utils';
1212
import styles from './styles.css';
1313

1414
interface OptionProps {
15+
actions?: React.ReactNode;
1516
children: React.ReactNode;
1617
}
1718
function Option(props: OptionProps) {
1819
const {
20+
actions,
1921
children,
2022
} = props;
2123

@@ -27,6 +29,9 @@ function Option(props: OptionProps) {
2729
<div className={styles.label}>
2830
{ children }
2931
</div>
32+
<div className={styles.actions}>
33+
{actions}
34+
</div>
3035
</>
3136
);
3237
}
@@ -47,6 +52,7 @@ export type SearchSelectInputProps<
4752
searchOptions?: O[] | undefined | null;
4853
keySelector: (option: O) => T;
4954
labelSelector: (option: O) => string;
55+
actionSelector?: (option: O) => React.ReactNode;
5056
hideOptionFilter?: (option: O) => boolean;
5157
name: K;
5258
disabled?: boolean;
@@ -102,6 +108,7 @@ function SearchSelectInput<
102108
const {
103109
keySelector,
104110
labelSelector,
111+
actionSelector,
105112
name,
106113
onChange,
107114
onOptionsChange,
@@ -230,11 +237,12 @@ function SearchSelectInput<
230237

231238
return {
232239
children: labelSelector(option),
240+
actions: actionSelector?.(option),
233241
containerClassName: _cs(styles.option, isActive && styles.active),
234242
title: labelSelector(option),
235243
};
236244
},
237-
[value, labelSelector],
245+
[value, labelSelector, actionSelector],
238246
);
239247

240248
const handleOptionClick = useCallback(

src/stories/MultiSelectInput.stories.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { Story } from '@storybook/react/types-6-0';
33
import { useArgs } from '@storybook/client-api';
4+
import { IoOpenOutline } from 'react-icons/io5';
45
import MultiSelectInput, { MultiSelectInputProps } from '#components/MultiSelectInput';
6+
import QuickActionButton from '#components/QuickActionButton';
57

68
export default {
79
title: 'Input/MultiSelectInput',
@@ -46,6 +48,27 @@ const Template: Story<MultiSelectInputProps<string, string, Option, { containerC
4648
);
4749
};
4850

51+
function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
52+
// NOTE: This intentionally breaks HTML semantics (link inside a button).
53+
// This workaround is to allow clickable links inside SelectInput options
54+
// where a button wrapper is required.
55+
e.stopPropagation();
56+
window.open('https://www.google.com/search?q=story', '_blank');
57+
}
58+
59+
export const WithActions = Template.bind({});
60+
WithActions.args = {
61+
actionSelector: () => (
62+
<QuickActionButton
63+
name={undefined}
64+
onClick={handleClick}
65+
transparent
66+
>
67+
<IoOpenOutline />
68+
</QuickActionButton>
69+
),
70+
};
71+
4972
export const NoValue = Template.bind({});
5073
NoValue.args = {
5174
value: undefined,

src/stories/SearchMultiSelectInput.stories.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { useState } from 'react';
22
import { Story } from '@storybook/react/types-6-0';
33
import { useArgs } from '@storybook/client-api';
4+
import { IoOpenOutline } from 'react-icons/io5';
45
import SearchMultiSelectInput, { SearchMultiSelectInputProps } from '#components/MultiSelectInput/SearchMultiSelectInput';
6+
import QuickActionButton from '#components/QuickActionButton';
57
import useQuery, { entityListTransformer } from '../utils/useQuery';
68

79
export default {
@@ -188,6 +190,27 @@ const Template: Story<SearchMultiSelectInputProps<string, string, Option, { cont
188190
);
189191
};
190192

193+
function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
194+
// NOTE: This intentionally breaks HTML semantics (link inside a button).
195+
// This workaround is to allow clickable links inside SelectInput options
196+
// where a button wrapper is required.
197+
e.stopPropagation();
198+
window.open('https://www.google.com/search?q=story', '_blank');
199+
}
200+
201+
export const WithActions = Template.bind({});
202+
WithActions.args = {
203+
actionSelector: () => (
204+
<QuickActionButton
205+
name={undefined}
206+
onClick={handleClick}
207+
transparent
208+
>
209+
<IoOpenOutline />
210+
</QuickActionButton>
211+
),
212+
};
213+
191214
export const NoValue = Template.bind({});
192215
NoValue.args = {
193216
value: undefined,

src/stories/SearchSelectInput.stories.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { useState } from 'react';
22
import { Story } from '@storybook/react/types-6-0';
33
import { useArgs } from '@storybook/client-api';
4+
import { IoOpenOutline } from 'react-icons/io5';
45
import SearchSelectInput, { SearchSelectInputProps } from '#components/SelectInput/SearchSelectInput';
6+
import QuickActionButton from '#components/QuickActionButton';
57
import useQuery, { entityListTransformer } from '../utils/useQuery';
68

79
export default {
@@ -193,6 +195,27 @@ Default.args = {
193195
value: '1',
194196
};
195197

198+
function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
199+
// NOTE: This intentionally breaks HTML semantics (link inside a button).
200+
// This workaround is to allow clickable links inside SelectInput options
201+
// where a button wrapper is required.
202+
e.stopPropagation();
203+
window.open('https://www.google.com/search?q=story', '_blank');
204+
}
205+
206+
export const WithActions = Template.bind({});
207+
WithActions.args = {
208+
actionSelector: () => (
209+
<QuickActionButton
210+
name={undefined}
211+
onClick={handleClick}
212+
transparent
213+
>
214+
<IoOpenOutline />
215+
</QuickActionButton>
216+
),
217+
};
218+
196219
export const Disabled = Template.bind({});
197220
Disabled.args = {
198221
value: '1',

src/stories/SelectInput.stories.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { Story } from '@storybook/react/types-6-0';
33
import { useArgs } from '@storybook/client-api';
4+
import { IoOpenOutline } from 'react-icons/io5';
45
import SelectInput, { SelectInputProps } from '#components/SelectInput';
6+
import QuickActionButton from '#components/QuickActionButton';
57

68
export default {
79
title: 'Input/SelectInput',
@@ -45,6 +47,27 @@ const Template: Story<SelectInputProps<string, string, Option, { containerClassN
4547
);
4648
};
4749

50+
function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
51+
// NOTE: This intentionally breaks HTML semantics (link inside a button).
52+
// This workaround is to allow clickable links inside SelectInput options
53+
// where a button wrapper is required.
54+
e.stopPropagation();
55+
window.open('https://www.google.com/search?q=story', '_blank');
56+
}
57+
58+
export const WithActions = Template.bind({});
59+
WithActions.args = {
60+
actionSelector: () => (
61+
<QuickActionButton
62+
name={undefined}
63+
onClick={handleClick}
64+
transparent
65+
>
66+
<IoOpenOutline />
67+
</QuickActionButton>
68+
),
69+
};
70+
4871
export const NoValue = Template.bind({});
4972
NoValue.args = {
5073
value: undefined,

0 commit comments

Comments
 (0)