feat(cloud data source config): GUI and API for configuring a cloud data source with Google cloud healthcare implementation (#3589)
This commit is contained in:
1 parent
a2ef2b0fcb
commit
a336992971
28 files changed
+1155
-56
No files matched your search
@@ -1,6 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h16v16H0z"/>
|
||||
<rect stroke="#7BB2CE" fill="#0D0E24" x=".5" y=".5" width="15" height="15" rx="7.5"/>
|
||||
<rect stroke="currentColor" fill="#0D0E24" x=".5" y=".5" width="15" height="15" rx="7.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 273 B |
@@ -1,4 +1,4 @@
|
||||
import Button, {ButtonType, ButtonSize} from '../Button';
|
||||
import {Button, ButtonEnums} from '../../../components';
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
import {
|
||||
createComponentTemplate,
|
||||
@@ -45,8 +45,8 @@ There can be different types of buttons: `primary`, and `secondary`.
|
||||
<Canvas>
|
||||
<Story name="Types">
|
||||
<div className="flex space-x-2">
|
||||
<Button type={ButtonType.primary}>Primary Button</Button>
|
||||
<Button type={ButtonType.secondary}>Secondary Button</Button>
|
||||
<Button type={ButtonEnums.type.primary}>Primary Button</Button>
|
||||
<Button type={ButtonEnums.type.secondary}>Secondary Button</Button>
|
||||
</div>
|
||||
</Story>
|
||||
</Canvas>
|
||||
@@ -59,8 +59,8 @@ to the button's height.
|
||||
<Canvas>
|
||||
<Story name="Colors">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size={ButtonSize.small}>Small Button</Button>
|
||||
<Button size={ButtonSize.medium}>Medium Button</Button>
|
||||
<Button size={ButtonEnums.size.small}>Small Button</Button>
|
||||
<Button size={ButtonEnums.size.medium}>Medium Button</Button>
|
||||
</div>
|
||||
</Story>
|
||||
</Canvas>
|
||||
@@ -71,7 +71,7 @@ You can mix different props together to create a button.
|
||||
|
||||
<Canvas>
|
||||
<Story name="Custom">
|
||||
<Button type={ButtonType.secondary} size={ButtonSize.small}>
|
||||
<Button type={ButtonEnums.type.secondary} size={ButtonEnums.size.small}>
|
||||
Small, Secondary Button
|
||||
</Button>
|
||||
</Story>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import classNames from 'classnames';
|
||||
import debounce from 'lodash.debounce';
|
||||
import React, {
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import Icon from '../Icon';
|
||||
|
||||
type InputFilterTextProps = {
|
||||
className?: string;
|
||||
value?: string;
|
||||
placeholder: string;
|
||||
onDebounceChange?: (val: string) => void;
|
||||
onChange?: (val: string) => void;
|
||||
debounceTime?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component to use as the input for text to filter by/on. A debounced callback is automatically provided
|
||||
* so that the filtering in-turn will be debounced. There is also a straight onChange callback when the filter value is
|
||||
* required immediately and NOT debounced. The debounce time is also configurable.
|
||||
*/
|
||||
const InputFilterText = ({
|
||||
className,
|
||||
value = '',
|
||||
placeholder,
|
||||
onDebounceChange,
|
||||
onChange,
|
||||
debounceTime = 200,
|
||||
}: InputFilterTextProps): ReactElement => {
|
||||
const [filterValue, setFilterValue] = useState<string>(value);
|
||||
|
||||
const searchInputRef = useRef(null);
|
||||
|
||||
const debouncedOnChange = useMemo(() => {
|
||||
return debounce(onDebounceChange || (() => {}), debounceTime);
|
||||
}, []);
|
||||
|
||||
// This allows for the filter value to be updated via the props.
|
||||
useEffect(() => setFilterValue(value), [value]);
|
||||
|
||||
useEffect(() => {
|
||||
return debouncedOnChange?.cancel();
|
||||
}, []);
|
||||
|
||||
const handleFilterTextChanged = useCallback(value => {
|
||||
setFilterValue(value);
|
||||
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
if (onDebounceChange) {
|
||||
debouncedOnChange(value);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<label className={classNames('relative', className)}>
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
|
||||
<Icon name="icon-search"></Icon>
|
||||
</span>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
className="block bg-black w-full shadow transition duration-300 appearance-none border border-inputfield-main focus:border-inputfield-focus focus:outline-none disabled:border-inputfield-disabled rounded-md w-full py-2 px-9 text-base leading-tight placeholder:text-inputfield-placeholder"
|
||||
placeholder={placeholder}
|
||||
onChange={event => handleFilterTextChanged(event.target.value)}
|
||||
autoComplete="off"
|
||||
value={filterValue}
|
||||
></input>
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<Icon
|
||||
name="icon-clear-field"
|
||||
className={classNames('cursor-pointer', filterValue ? '' : 'hidden')}
|
||||
onClick={() => {
|
||||
searchInputRef.current.value = '';
|
||||
handleFilterTextChanged('');
|
||||
}}
|
||||
></Icon>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputFilterText;
|
||||
@@ -0,0 +1,54 @@
|
||||
import InputFilterText from '../InputFilterText';
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
export const argTypes = {
|
||||
component: InputFilterText,
|
||||
title: 'Components/InputFilterText',
|
||||
};
|
||||
|
||||
<Meta title="Components/InputFilterText" component={InputFilterText} />
|
||||
|
||||
export const InputFilterTextTemplate = args => (
|
||||
<div className="w-80">
|
||||
<InputFilterText {...args} />
|
||||
</div>
|
||||
)
|
||||
|
||||
<Heading title="InputFilterText" componentRelativePath="InputFilterText/InputFilterText.tsx" />
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Props](#props)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
## Overview
|
||||
|
||||
InputFilterText is a component that is styled such that it can be used as a text input to
|
||||
filter a list of textual items. It allows you to enter any text. There are two (optional)
|
||||
callbacks that can be invoked as the characters of the text are entered: one
|
||||
callback is invoked as each character is typed and another is debounced so that
|
||||
any filtering can occur once the user has entered a significant amount of info and
|
||||
pausing by a configurable amount of time in milliseconds. The component also
|
||||
provides a button on the far right of the component that when clicked will clear the input
|
||||
text. The button only appears when there is text in the component.
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Overview"
|
||||
args={{
|
||||
className: "w-full text-white",
|
||||
placeholder: "Search for...",
|
||||
onChange: () => console.log('input text changed'),
|
||||
onDebounceChange: () => console.log('debounce text changed'),
|
||||
}}
|
||||
>
|
||||
{InputFilterTextTemplate.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
<ArgsTable of={InputFilterText} />
|
||||
|
||||
## Contribute
|
||||
|
||||
<Footer componentRelativePath="InputFilterText/__stories__/InputFilterText.stories.mdx" />
|
||||
@@ -0,0 +1,2 @@
|
||||
import InputFilterText from './InputFilterText';
|
||||
export default InputFilterText;
|
||||
@@ -15,6 +15,7 @@ const StudyListFilter = ({
|
||||
isFiltering,
|
||||
numOfStudies,
|
||||
onUploadClick,
|
||||
getDataSourceConfigurationComponent,
|
||||
}) => {
|
||||
const { t } = useTranslation('StudyList');
|
||||
const { sortBy, sortDirection } = filterValues;
|
||||
@@ -30,13 +31,15 @@ const StudyListFilter = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<div className="bg-primary-dark">
|
||||
<div className="container relative flex flex-col pt-5 m-auto">
|
||||
<div className="flex flex-row justify-between px-12 mb-5">
|
||||
<div className="flex flex-row">
|
||||
<Typography variant="h4" className="mr-6 text-primary-light">
|
||||
<div className="bg-black">
|
||||
<div className="container relative flex flex-col pt-5 mx-auto">
|
||||
<div className="flex flex-row justify-between mb-5">
|
||||
<div className="flex flex-row items-center gap-6 shrink min-w-[1px]">
|
||||
<Typography variant="h6" className="text-white">
|
||||
{t('StudyList')}
|
||||
</Typography>
|
||||
{getDataSourceConfigurationComponent &&
|
||||
getDataSourceConfigurationComponent()}
|
||||
{onUploadClick && (
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer text-primary-active text-lg self-center font-semibold"
|
||||
@@ -63,7 +66,7 @@ const StudyListFilter = ({
|
||||
</LegacyButton>
|
||||
)}
|
||||
<Typography
|
||||
variant="h4"
|
||||
variant="h6"
|
||||
className="mr-2"
|
||||
data-cy={'num-studies'}
|
||||
>
|
||||
@@ -71,7 +74,7 @@ const StudyListFilter = ({
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="self-end pb-1 text-common-light"
|
||||
className="self-end pb-1 text-primary-light"
|
||||
>
|
||||
{t('Studies')}
|
||||
</Typography>
|
||||
@@ -80,8 +83,8 @@ const StudyListFilter = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky z-10 border-b-4 border-black -top-1">
|
||||
<div className="pt-3 pb-3 bg-primary-dark ">
|
||||
<div className="sticky z-10 border-b-4 border-black -top-1 mx-auto">
|
||||
<div className="pt-3 pb-3 bg-primary-dark">
|
||||
<InputGroup
|
||||
inputMeta={filtersMeta}
|
||||
values={filterValues}
|
||||
@@ -134,6 +137,7 @@ StudyListFilter.propTypes = {
|
||||
clearFilters: PropTypes.func.isRequired,
|
||||
isFiltering: PropTypes.bool.isRequired,
|
||||
onUploadClick: PropTypes.func,
|
||||
getDataSourceConfigurationComponent: PropTypes.func,
|
||||
};
|
||||
|
||||
export default StudyListFilter;
|
||||
@@ -13,6 +13,7 @@ import Icon from './Icon';
|
||||
import IconButton from './IconButton';
|
||||
import Input from './Input';
|
||||
import InputDateRange from './InputDateRange';
|
||||
import InputFilterText from './InputFilterText';
|
||||
import InputGroup from './InputGroup';
|
||||
import InputLabelWrapper from './InputLabelWrapper';
|
||||
import InputMultiSelect from './InputMultiSelect';
|
||||
@@ -97,6 +98,7 @@ export {
|
||||
IconButton,
|
||||
Input,
|
||||
InputDateRange,
|
||||
InputFilterText,
|
||||
InputGroup,
|
||||
InputRange,
|
||||
InputNumber,
|
||||
|
||||
@@ -58,6 +58,7 @@ export {
|
||||
InputRange,
|
||||
InputNumber,
|
||||
InputDateRange,
|
||||
InputFilterText,
|
||||
InputGroup,
|
||||
InputLabelWrapper,
|
||||
InputMultiSelect,
|
||||
|
||||
Reference in new issue
Block a user