Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@togglecorp/toggle-ui",
"version": "0.18.3",
"version": "0.18.4",
"description": "React component library by togglecorp",
"files": [
"/build"
Expand Down
55 changes: 39 additions & 16 deletions src/components/GenericOption/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React, { useCallback, useRef, useEffect } from 'react';
import { _cs } from '@togglecorp/fujs';
import {
_cs,
isNotDefined,
} from '@togglecorp/fujs';

import RawButton from '../RawButton';

Expand All @@ -15,6 +18,7 @@ export interface GenericOptionParams<P extends ContentBaseProps, OK extends Opti
optionContainerClassName?: string;
contentRenderer: (props: Pick<P, Exclude<keyof P, 'containerClassName' | 'title'>>) => React.ReactNode;
contentRendererParam: (key: OK, opt: O) => P;
actionsSelector?: (props: O) => React.ReactNode;
option: O;
optionKey: OK;
onClick: (optionKey: OK, option: O) => void;
Expand All @@ -25,6 +29,7 @@ function GenericOption<P extends ContentBaseProps, OK extends OptionKey, O>({
optionContainerClassName,
contentRenderer,
contentRendererParam,
actionsSelector,
option,
onClick,
onFocus,
Expand Down Expand Up @@ -81,23 +86,41 @@ function GenericOption<P extends ContentBaseProps, OK extends OptionKey, O>({
[],
);

if (isNotDefined(actionsSelector)) {
return (
<RawButton
elementRef={divRef}
className={_cs(styles.optionRenderer, containerClassName, optionContainerClassName)}
onClick={handleClick}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
title={title}
name={optionKey}
focused={isFocused}
>
{contentRenderer(props)}
</RawButton>
);
}

return (
<RawButton
elementRef={divRef}
className={_cs(
styles.optionRenderer,
optionContainerClassName,
containerClassName,
)}
onClick={handleClick}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
title={title}
name={optionKey}
focused={isFocused}
<div
className={styles.optionContainer}
>
{contentRenderer(props)}
</RawButton>
<RawButton
elementRef={divRef}
className={_cs(styles.optionRenderer, containerClassName, optionContainerClassName)}
onClick={handleClick}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
title={title}
name={optionKey}
focused={isFocused}
>
{contentRenderer(props)}
</RawButton>
{actionsSelector(option)}
</div>
);
}
export default GenericOption;
9 changes: 9 additions & 0 deletions src/components/GenericOption/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,12 @@
padding: calc(var(--tui-spacing-large) - var(--tui-spacing-small));
text-align: left;
}
.option-container {
display: flex;
align-items: center;
padding-right: var(--tui-spacing-small);

.option-renderer {
flex-grow: 1;
}
}
10 changes: 3 additions & 7 deletions src/components/MultiSelectInput/SearchMultiSelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ 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;
Expand All @@ -31,9 +30,6 @@ function Option(props: OptionProps) {
<div className={styles.label}>
{ children }
</div>
<div className={styles.actions}>
{actions}
</div>
</>
);
}
Expand Down Expand Up @@ -244,11 +240,10 @@ function SearchMultiSelectInput<
children: labelSelector(option),
containerClassName: _cs(styles.option, isActive && styles.active),
title: labelSelector(option),
actions: actionsSelector?.(option),
isActive,
};
},
[labelSelector, value, actionsSelector],
[labelSelector, value],
);

// FIXME: value should not be on dependency list, also try to pass options like in SelectInput
Expand Down Expand Up @@ -296,6 +291,7 @@ function SearchMultiSelectInput<
optionKeySelector={keySelector}
optionRenderer={Option}
optionRendererParams={optionRendererParams}
actionsSelector={actionsSelector}
optionContainerClassName={styles.optionContainer}
onOptionClick={handleOptionClick}
valueDisplay={valueDisplay}
Expand Down
9 changes: 2 additions & 7 deletions src/components/SelectInput/SearchSelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ 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;

Expand All @@ -29,9 +27,6 @@ function Option(props: OptionProps) {
<div className={styles.label}>
{ children }
</div>
<div className={styles.actions}>
{actions}
</div>
</>
);
}
Expand Down Expand Up @@ -237,12 +232,11 @@ function SearchSelectInput<

return {
children: labelSelector(option),
actions: actionsSelector?.(option),
containerClassName: _cs(styles.option, isActive && styles.active),
title: labelSelector(option),
};
},
[value, labelSelector, actionsSelector],
[value, labelSelector],
);

const handleOptionClick = useCallback(
Expand Down Expand Up @@ -283,6 +277,7 @@ function SearchSelectInput<
optionKeySelector={keySelector}
optionRenderer={Option}
optionRendererParams={optionRendererParams}
actionsSelector={actionsSelector}
optionContainerClassName={styles.optionContainer}
onOptionClick={handleOptionClick}
valueDisplay={valueDisplay}
Expand Down
4 changes: 4 additions & 0 deletions src/components/SelectInputContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type SelectInputContainerProps<
optionKeySelector: (datum: O, index: number) => OK;
optionRenderer: (props: Pick<P, Exclude<keyof P, 'containerClassName' | 'title'>>) => React.ReactNode;
optionRendererParams: (optionKey: OK, option: O) => P;
actionsSelector?: (option: O) => React.ReactNode;
totalOptionsCount?: number;
optionsPopupContentClassName?: string;
options: O[] | undefined | null;
Expand Down Expand Up @@ -112,6 +113,7 @@ function SelectInputContainer<OK extends OptionKey, N extends string, O extends
optionKeySelector,
optionRenderer,
optionRendererParams,
actionsSelector,
options: optionsFromProps,
optionsPopupClassName,
optionsPopupContentClassName,
Expand Down Expand Up @@ -221,6 +223,7 @@ function SelectInputContainer<OK extends OptionKey, N extends string, O extends
optionKey: key,
focusedKey,
contentRenderer: optionRenderer,
actionsSelector,
onClick: handleOptionClick,
onFocus: onFocusedKeyChange,
optionContainerClassName: _cs(optionContainerClassName, styles.listItem),
Expand All @@ -232,6 +235,7 @@ function SelectInputContainer<OK extends OptionKey, N extends string, O extends
optionContainerClassName,
optionRenderer,
optionRendererParams,
actionsSelector,
],
);

Expand Down
19 changes: 5 additions & 14 deletions src/stories/MultiSelectInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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',
Expand Down Expand Up @@ -48,24 +47,16 @@ const Template: Story<MultiSelectInputProps<string, string, Option, { containerC
);
};

function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
// 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: () => (
<QuickActionButton
name={undefined}
onClick={handleClick}
transparent
<a
href="https://www.google.com/search?q=story"
target="_blank"
rel="noopener noreferrer"
>
<IoOpenOutline />
</QuickActionButton>
</a>
),
};

Expand Down
19 changes: 5 additions & 14 deletions src/stories/SearchMultiSelectInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 {
Expand Down Expand Up @@ -190,24 +189,16 @@ const Template: Story<SearchMultiSelectInputProps<string, string, Option, { cont
);
};

function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
// 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: () => (
<QuickActionButton
name={undefined}
onClick={handleClick}
transparent
<a
href="https://www.google.com/search?q=story"
target="_blank"
rel="noopener noreferrer"
>
<IoOpenOutline />
</QuickActionButton>
</a>
),
};

Expand Down
19 changes: 5 additions & 14 deletions src/stories/SearchSelectInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 {
Expand Down Expand Up @@ -195,24 +194,16 @@ Default.args = {
value: '1',
};

function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
// 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: () => (
<QuickActionButton
name={undefined}
onClick={handleClick}
transparent
<a
href="https://www.google.com/search?q=story"
target="_blank"
rel="noopener noreferrer"
>
<IoOpenOutline />
</QuickActionButton>
</a>
),
};

Expand Down
19 changes: 5 additions & 14 deletions src/stories/SelectInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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',
Expand Down Expand Up @@ -47,24 +46,16 @@ const Template: Story<SelectInputProps<string, string, Option, { containerClassN
);
};

function handleClick(_:string | undefined, e: React.MouseEvent<HTMLButtonElement>) {
// 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: () => (
<QuickActionButton
name={undefined}
onClick={handleClick}
transparent
<a
href="https://www.google.com/search?q=story"
target="_blank"
rel="noopener noreferrer"
>
<IoOpenOutline />
</QuickActionButton>
</a>
),
};

Expand Down