Skip to content

Commit

Permalink
Merge pull request #106 from tegonal/fix/#102
Browse files Browse the repository at this point in the history
#102: dont add null values to list of tags, replace deprecated components
  • Loading branch information
toggm authored Dec 9, 2024
2 parents 393d28f + f6d37ac commit 0581631
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 93 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@headlessui/react": "^2.1.2",
"@headlessui/react": "^2.2.0",
"@hookform/error-message": "^2.0.1",
"@nivo/bar": "^0.88.0",
"@nivo/core": "^0.88.0",
Expand Down
28 changes: 19 additions & 9 deletions frontend/src/components/forms/input/inputSelectAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
*
*/

import { Combobox } from '@headlessui/react';
import {
Combobox,
ComboboxButton,
ComboboxInput,
ComboboxOptions,
ComboboxOption,
} from '@headlessui/react';
import { DropdownList } from 'components/forms/input/shared/dropdownList';
import { DropdownListItem } from 'components/forms/input/shared/dropdownListItem';
import { cleanStrForCmp } from 'lib/strings';
Expand Down Expand Up @@ -102,21 +108,25 @@ export const InputSelectAutocomplete: React.FC<Props> = ({
render={({ field: { value, onChange } }) => (
<Combobox
value={value}
onChange={(change: SelectAutocompleteSuggestionType) => onChange(change.id)}
onChange={(change: SelectAutocompleteSuggestionType) => {
if (change?.id) {
onChange(change.id);
}
}}
>
{({ open }) => (
<>
{/* Wrapping the input with a Button is a hack for https://github.com/tailwindlabs/headlessui/discussions/1236,
Without that the combobox does not open when you click in the input */}
<Combobox.Button as={Box}>
<Combobox.Input
<ComboboxButton as={Box}>
<ComboboxInput
as={Input}
onChange={(e) => setInputText(e.currentTarget.value)}
placeholder={t('Select project')}
autoComplete="off"
value={inputText}
/>
</Combobox.Button>
</ComboboxButton>
{(selected || inputText) && (
<Box
sx={{
Expand All @@ -131,11 +141,11 @@ export const InputSelectAutocomplete: React.FC<Props> = ({
<Icon name="remove-circle-interface-essential" size={20} />
</Box>
)}
<Combobox.Options as={Box}>
<ComboboxOptions as={Box}>
{open && availableSuggestions.length > 0 && (
<DropdownList>
{availableSuggestions.map((suggestion) => (
<Combobox.Option as={Box} key={suggestion.key} value={suggestion}>
<ComboboxOption as={Box} key={suggestion.key} value={suggestion}>
{({ active, selected }) => (
<DropdownListItem
key={suggestion.id}
Expand All @@ -145,11 +155,11 @@ export const InputSelectAutocomplete: React.FC<Props> = ({
selected={selected}
/>
)}
</Combobox.Option>
</ComboboxOption>
))}
</DropdownList>
)}
</Combobox.Options>
</ComboboxOptions>
</>
)}
</Combobox>
Expand Down
87 changes: 47 additions & 40 deletions frontend/src/components/forms/input/inputTagsAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import { Combobox } from '@headlessui/react';
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from '@headlessui/react';
import { Tag, TagList } from 'components/shared/tagList';
import { differenceBy, filter, noop, uniqBy } from 'lodash';
import { useTranslation } from 'next-i18next';
Expand Down Expand Up @@ -69,15 +69,17 @@ export const InputTagsAdmin: React.FC<Props> = ({ name, tags = [], tagGroupIndex
};

const addTag = (tag: any) => {
const tags = uniqBy([...selectedTags, tag], 'id');
setInputText('');
setSelectedTags(tags);
if (name === 'tagGroups') {
const currentTags = parentFormContext.getValues(name);
currentTags[tagGroupIndex].relatedTags = tags;
parentFormContext.setValue(name, currentTags);
} else {
parentFormContext.setValue(name, tags);
if (tag) {
const tags = uniqBy([...selectedTags, tag], 'id');
setInputText('');
setSelectedTags(tags);
if (name === 'tagGroups') {
const currentTags = parentFormContext.getValues(name);
currentTags[tagGroupIndex].relatedTags = tags;
parentFormContext.setValue(name, currentTags);
} else {
parentFormContext.setValue(name, tags);
}
}
};

Expand All @@ -91,6 +93,15 @@ export const InputTagsAdmin: React.FC<Props> = ({ name, tags = [], tagGroupIndex
if (inputText) e.preventDefault();
};

const inputValueChanged = (e: any) => {
if (e.currentTarget.value == ' ') {
// ignore initial space as this should only open the combobox
setInputText('');
} else {
setInputText(e.currentTarget.value);
}
};

return (
<Box sx={{ label: 'InputTagsAdmin' }}>
{Array.isArray(selectedTags) && selectedTags.length > 0 && (
Expand All @@ -106,37 +117,33 @@ export const InputTagsAdmin: React.FC<Props> = ({ name, tags = [], tagGroupIndex
<Combobox value={selectedTags} onChange={addTag}>
{({ open }) => (
<>
{/* Wrapping the input with a Button is a hack for https://github.com/tailwindlabs/headlessui/discussions/1236,
Without that the combobox does not open when you click in the input */}
<Combobox.Button as={Box}>
<Combobox.Input
as={Input}
onChange={(e) => setInputText(e.currentTarget.value)}
onClick={preventDefault}
displayValue={() => inputText}
placeholder={t('Enter a tag to add it')}
autoComplete="off"
/>
{inputText && (
<Box
sx={{
position: 'absolute',
right: 2,
top: '50%',
transform: 'translateY(-50%)',
...clickableStyle(),
}}
onClick={() => setInputText('')}
>
<Icon name="remove-circle-interface-essential" size={20} />
</Box>
)}
</Combobox.Button>
<Combobox.Options as={Box}>
<ComboboxInput
as={Input}
onChange={inputValueChanged}
onClick={preventDefault}
displayValue={() => inputText}
placeholder={t('Enter a tag to add it')}
autoComplete="off"
/>
{inputText && (
<Box
sx={{
position: 'absolute',
right: 2,
top: '50%',
transform: 'translateY(-50%)',
...clickableStyle(),
}}
onClick={() => setInputText('')}
>
<Icon name="remove-circle-interface-essential" size={20} />
</Box>
)}
<ComboboxOptions as={Box}>
{open && displayCreateTag && (
<DropdownList sx={{ px: 2, display: 'flex', gap: 0, flexWrap: 'wrap' }}>
{displayCreateTag && (
<Combobox.Option
<ComboboxOption
as={Flex}
key="create_tag"
sx={{
Expand All @@ -160,11 +167,11 @@ export const InputTagsAdmin: React.FC<Props> = ({ name, tags = [], tagGroupIndex
/>
</>
)}
</Combobox.Option>
</ComboboxOption>
)}
</DropdownList>
)}
</Combobox.Options>
</ComboboxOptions>
</>
)}
</Combobox>
Expand Down
89 changes: 49 additions & 40 deletions frontend/src/components/forms/input/inputTagsAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import { Combobox } from '@headlessui/react';
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from '@headlessui/react';
import { DropdownList } from 'components/forms/input/shared/dropdownList';
import { Tag, TagList } from 'components/shared/tagList';
import { cleanStrForCmp } from 'lib/strings';
Expand Down Expand Up @@ -71,22 +71,34 @@ export const InputTagsAutocomplete: React.FC<Props> = ({ suggestions = [], name
};

const addTag = (tag: any) => {
const tags = uniqBy([...selectedTags, tag], 'id');
setInputText('');
setSelectedTags(tags);
parentFormContext.setValue(name, tags);
if (tag) {
const tags = uniqBy([...selectedTags, tag], 'id');
setInputText('');
setSelectedTags(tags);
parentFormContext.setValue(name, tags);
}
};

const inputTag: ModelsSimpleTag = { id: inputText, type: 'SimpleTag' };

const displayCreateTag = inputText.length > 0 && !selectedTags.find((s) => s.id === inputText);
const displayCreateTag =
inputText.length > 0 && !selectedTags.find((s) => s && s.id === inputText);

if (!parentFormContext) return null;

const preventDefault = (e: any) => {
if (inputText) e.preventDefault();
};

const inputValueChanged = (e: any) => {
if (e.currentTarget.value == ' ') {
// ignore initial space as this should only open the combobox
setInputText('');
} else {
setInputText(e.currentTarget.value);
}
};

return (
<Box sx={{ label: 'InputTagsAutocomplete' }}>
{selectedTags.length > 0 && (
Expand All @@ -102,37 +114,34 @@ export const InputTagsAutocomplete: React.FC<Props> = ({ suggestions = [], name
<Combobox value={selectedTags} onChange={addTag}>
{({ open }) => (
<>
{/* Wrapping the input with a Button is a hack for https://github.com/tailwindlabs/headlessui/discussions/1236,
Without that the combobox does not open when you click in the input */}
<Combobox.Button as={Box}>
<Combobox.Input
as={Input}
onChange={(e) => setInputText(e.currentTarget.value)}
onClick={preventDefault}
displayValue={() => inputText}
placeholder={t('Choose or enter tags')}
autoComplete="off"
/>
{inputText && (
<Box
sx={{
position: 'absolute',
right: 2,
top: '50%',
transform: 'translateY(-50%)',
...clickableStyle(),
}}
onClick={() => setInputText('')}
>
<Icon name="remove-circle-interface-essential" size={20} />
</Box>
)}
</Combobox.Button>
<Combobox.Options as={Box}>
<ComboboxInput
as={Input}
onChange={inputValueChanged}
onClick={preventDefault}
displayValue={() => inputText}
placeholder={t('Choose or enter tags')}
autoComplete="off"
value={inputText}
/>
{inputText && (
<Box
sx={{
position: 'absolute',
right: 2,
top: '50%',
transform: 'translateY(-50%)',
...clickableStyle(),
}}
onClick={() => setInputText('')}
>
<Icon name="remove-circle-interface-essential" size={20} />
</Box>
)}
<ComboboxOptions as={Box}>
{open && (displayCreateTag || availableSuggestions.length > 0) && (
<DropdownList sx={{ px: 2, display: 'flex', gap: 0, flexWrap: 'wrap' }}>
{displayCreateTag && (
<Combobox.Option
<ComboboxOption
as={Flex}
key="create_tag"
sx={{
Expand All @@ -145,21 +154,21 @@ export const InputTagsAutocomplete: React.FC<Props> = ({ suggestions = [], name
}}
value={inputTag}
>
{({ active }) => (
{({ focus }) => (
<>
<Box sx={{ fontSize: 1 }}>{`${t('Custom tag')}: `}</Box>
<Tag
active={active}
active={focus}
item={inputTag}
clickHandler={noop}
hideRemoveIcon
/>
</>
)}
</Combobox.Option>
</ComboboxOption>
)}
{availableSuggestions.map((item) => (
<Combobox.Option
<ComboboxOption
as={Box}
key={item.id}
value={item}
Expand All @@ -171,11 +180,11 @@ export const InputTagsAutocomplete: React.FC<Props> = ({ suggestions = [], name
{({ active }) => (
<Tag active={active} item={item} clickHandler={noop} hideRemoveIcon />
)}
</Combobox.Option>
</ComboboxOption>
))}
</DropdownList>
)}
</Combobox.Options>
</ComboboxOptions>
</>
)}
</Combobox>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/shared/tagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const TagList: React.FC<Props> = ({ items, clickHandler, hideRemoveIcon =
return (
<Flex sx={{ label: 'TagList', gap: 1, flexWrap: 'wrap' }}>
{items
.filter((item) => !!item.id.trim())
.filter((item) => !!item?.id?.trim())
.map((item) => (
<Tag
key={item.id}
Expand Down
4 changes: 2 additions & 2 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2047,7 +2047,7 @@ __metadata:
languageName: node
linkType: hard

"@headlessui/react@npm:^2.1.2":
"@headlessui/react@npm:^2.2.0":
version: 2.2.0
resolution: "@headlessui/react@npm:2.2.0"
dependencies:
Expand Down Expand Up @@ -8545,7 +8545,7 @@ __metadata:
dependencies:
"@emotion/eslint-plugin": "npm:^11.12.0"
"@emotion/react": "npm:^11.13.3"
"@headlessui/react": "npm:^2.1.2"
"@headlessui/react": "npm:^2.2.0"
"@hookform/error-message": "npm:^2.0.1"
"@next/bundle-analyzer": "npm:^15.0.0"
"@nivo/bar": "npm:^0.88.0"
Expand Down

0 comments on commit 0581631

Please sign in to comment.