Skip to content

Commit

Permalink
feat(components): add option to delete input of text-input and lineag…
Browse files Browse the repository at this point in the history
…e-filter input with x and sort text-input datalist options
  • Loading branch information
anna-parker committed Jan 2, 2025
1 parent a38a4a0 commit b58faae
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 23 deletions.
34 changes: 34 additions & 0 deletions components/src/preact/lineageFilter/lineage-filter.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Meta, type StoryObj } from '@storybook/preact';
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';

import { LineageFilter, type LineageFilterProps } from './lineage-filter';
import { previewHandles } from '../../../.storybook/preview';
Expand Down Expand Up @@ -55,6 +56,39 @@ export const Default: StoryObj<LineageFilterProps> = {
),
};

export const WithInitialValue: StoryObj<LineageFilterProps> = {
...Default,
args: {
...Default.args,
initialValue: 'XCB',
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

const changedListenerMock = fn();
await step('Setup event listener mock', async () => {
canvasElement.addEventListener('gs-lineage-filter-changed', changedListenerMock);
});

await waitFor(() => {
const input = canvas.getByPlaceholderText('Enter lineage', { exact: false });
expect(input).toHaveValue('XCB');
});

await step('Remove initial value', async () => {
await fireEvent.click(canvas.getByRole('button', { name: '✕' }));

await expect(changedListenerMock).toHaveBeenCalledWith(
expect.objectContaining({
detail: {
host: undefined,
},
}),
);
});
},
};

export const WithNoLapisField: StoryObj<LineageFilterProps> = {
...Default,
args: {
Expand Down
42 changes: 32 additions & 10 deletions components/src/preact/lineageFilter/lineage-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type FunctionComponent } from 'preact';
import { useContext, useRef } from 'preact/hooks';
import { useContext, useRef, useState } from 'preact/hooks';
import z from 'zod';

import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
Expand Down Expand Up @@ -45,6 +45,9 @@ const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({

const inputRef = useRef<HTMLInputElement>(null);

const [hasInput, setHasInput] = useState<boolean>(!!initialValue);
const [inputValue, setInputValue] = useState(initialValue || '');

const { data, error, isLoading } = useQuery(
() => fetchLineageAutocompleteList(lapis, lapisField),
[lapisField, lapis],
Expand Down Expand Up @@ -73,6 +76,8 @@ const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({
composed: true,
}),
);
setHasInput(value !== undefined);
setInputValue(value || '');
}
};

Expand All @@ -85,15 +90,32 @@ const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({

return (
<>
<input
type='text'
class='input input-bordered w-full'
placeholder={placeholderText !== undefined ? placeholderText : lapisField}
onInput={onInput}
ref={inputRef}
list={lapisField}
value={initialValue}
/>
<div className='relative w-full'>
<input
type='text'
class='input input-bordered w-full pr-10'
placeholder={placeholderText !== undefined ? placeholderText : lapisField}
onInput={onInput}
ref={inputRef}
list={lapisField}
value={inputValue}
/>
{hasInput && (
<button
type='button'
name='✕'
className='absolute top-1/2 right-2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700'
onClick={() => {
if (inputRef.current) {
inputRef.current.value = '';
onInput();
}
}}
>
</button>
)}
</div>
<datalist id={lapisField}>
{data.map((item) => (
<option value={item} key={item} />
Expand Down
2 changes: 1 addition & 1 deletion components/src/preact/textInput/fetchAutocompleteList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export async function fetchAutocompleteList(lapis: string, field: string, signal

const data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;

return data.map((item) => item[field]);
return data.map((item) => item[field]).sort();
}
21 changes: 19 additions & 2 deletions components/src/preact/textInput/text-input.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Meta, type StoryObj } from '@storybook/preact';
import { expect, waitFor, within } from '@storybook/test';
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';

import data from './__mockData__/aggregated_hosts.json';
import { TextInput, type TextInputProps } from './text-input';
Expand Down Expand Up @@ -85,13 +85,30 @@ export const WithInitialValue: StoryObj<TextInputProps> = {
...Default.args,
initialValue: 'Homo sapiens',
},
play: async ({ canvasElement }) => {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

const changedListenerMock = fn();
await step('Setup event listener mock', async () => {
canvasElement.addEventListener('gs-text-input-changed', changedListenerMock);
});

await waitFor(() => {
const input = canvas.getByPlaceholderText('Enter a host name', { exact: false });
expect(input).toHaveValue('Homo sapiens');
});

await step('Remove initial value', async () => {
await fireEvent.click(canvas.getByRole('button', { name: '✕' }));

await expect(changedListenerMock).toHaveBeenCalledWith(
expect.objectContaining({
detail: {
host: undefined,
},
}),
);
});
},
};

Expand Down
42 changes: 32 additions & 10 deletions components/src/preact/textInput/text-input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type FunctionComponent } from 'preact';
import { useContext, useRef } from 'preact/hooks';
import { useContext, useRef, useState } from 'preact/hooks';
import z from 'zod';

import { fetchAutocompleteList } from './fetchAutocompleteList';
Expand Down Expand Up @@ -41,6 +41,9 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl

const inputRef = useRef<HTMLInputElement>(null);

const [hasInput, setHasInput] = useState<boolean>(!!initialValue);
const [inputValue, setInputValue] = useState(initialValue || '');

const { data, error, isLoading } = useQuery(() => fetchAutocompleteList(lapis, lapisField), [lapisField, lapis]);

if (isLoading) {
Expand All @@ -66,6 +69,8 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
composed: true,
}),
);
setHasInput(value !== undefined);
setInputValue(value || '');
}
};

Expand All @@ -78,15 +83,32 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl

return (
<>
<input
type='text'
class='input input-bordered w-full'
placeholder={placeholderText ?? lapisField}
onInput={onInput}
ref={inputRef}
list={lapisField}
value={initialValue}
/>
<div className='relative w-full'>
<input
type='text'
className='input input-bordered w-full pr-10'
placeholder={placeholderText ?? lapisField}
onInput={onInput}
ref={inputRef}
list={lapisField}
value={inputValue}
/>
{hasInput && (
<button
type='button'
name='✕'
className='absolute top-1/2 right-2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700'
onClick={() => {
if (inputRef.current) {
inputRef.current.value = '';
onInput();
}
}}
>
</button>
)}
</div>
<datalist id={lapisField}>
{data.map((item) => (
<option value={item} key={item} />
Expand Down

0 comments on commit b58faae

Please sign in to comment.