Skip to content

Commit

Permalink
chore: replace antd Select component in ObjectTags
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin committed Mar 18, 2024
1 parent fdc3d9b commit 49de8c4
Show file tree
Hide file tree
Showing 17 changed files with 93 additions and 467 deletions.
12 changes: 0 additions & 12 deletions frontend/src/lib/components/CloseButton.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ function DefinitionEdit(): JSX.Element {
<ObjectTags
className="definition-popover-edit-form-value"
tags={localDefinition.tags || []}
onChange={(_, tags) => setLocalDefinition({ tags })}
onChange={(tags) => setLocalDefinition({ tags })}
saving={false}
/>
</div>
Expand Down
154 changes: 52 additions & 102 deletions frontend/src/lib/components/ObjectTags/ObjectTags.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
// eslint-disable-next-line no-restricted-imports
import { CloseOutlined, SyncOutlined } from '@ant-design/icons'
import { IconPlus } from '@posthog/icons'
import { LemonTag, LemonTagType } from '@posthog/lemon-ui'
import { Select } from 'antd'
import { IconPencil, IconPlus } from '@posthog/icons'
import { LemonInputSelect, LemonTag, LemonTagType } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { objectTagsLogic } from 'lib/components/ObjectTags/objectTagsLogic'
import { Spinner } from 'lib/lemon-ui/Spinner/Spinner'
import { colorForString } from 'lib/utils'
import { CSSProperties, useMemo } from 'react'
import { sceneLogic } from 'scenes/sceneLogic'

import { AvailableFeature } from '~/types'

import { SelectGradientOverflow } from '../SelectGradientOverflow'

interface ObjectTagsPropsBase {
tags: string[]
saving?: boolean
Expand All @@ -34,7 +28,7 @@ export type ObjectTagsProps =
| (ObjectTagsPropsBase & {
/** Tags CAN be added or removed.*/
staticOnly?: false
onChange?: (tag: string, tags?: string[], id?: string) => void
onChange?: (tags: string[]) => void
/** List of all tags that already exist. */
tagsAvailable?: string[] /** Whether this field should be gated behind a "paywall". */
})
Expand All @@ -55,15 +49,14 @@ export function ObjectTags({
tagsAvailable,
style = {},
staticOnly = false,
id, // For pages that allow multiple object tags
className,
'data-attr': dataAttr,
}: ObjectTagsProps): JSX.Element {
const objectTagId = useMemo(() => uniqueMemoizedIndex++, [])
const logic = objectTagsLogic({ id: objectTagId, onChange, tags })
const logic = objectTagsLogic({ id: objectTagId, onChange })
const { guardAvailableFeature } = useActions(sceneLogic)
const { addingNewTag, cleanedNewTag, deletedTags } = useValues(logic)
const { setAddingNewTag, setNewTag, handleDelete, handleAdd } = useActions(logic)
const { editingTags } = useValues(logic)
const { setEditingTags, setTags } = useActions(logic)

/** Displaying nothing is confusing, so in case of empty static tags we use a dash as a placeholder */
const showPlaceholder = staticOnly && !tags?.length
Expand All @@ -77,99 +70,56 @@ export function ObjectTags({
})
}

const hasTags = tagsAvailable && tagsAvailable.length > 0

return (
// eslint-disable-next-line react/forbid-dom-props
<div style={style} className={clsx(className, 'flex flex-wrap gap-2 items-center')} data-attr={dataAttr}>
{showPlaceholder
? '—'
: tags
.filter((t) => !!t)
.map((tag, index) => {
return (
<LemonTag
key={index}
type={COLOR_OVERRIDES[tag] || colorForString(tag)}
style={{ marginRight: 0 }}
>
{tag}{' '}
{!staticOnly &&
onChange &&
(deletedTags.includes(tag) ? (
<SyncOutlined spin />
) : (
<CloseOutlined
className="click-outside-block"
style={{ cursor: 'pointer' }}
onClick={() =>
onGuardClick(() => {
handleDelete(tag)
})
}
/>
))}
</LemonTag>
)
})}
{saving && <Spinner />}
{!staticOnly && onChange && saving !== undefined && (
<span className="inline-flex font-normal">
<LemonTag
type="none"
onClick={() =>
onGuardClick(() => {
setAddingNewTag(true)
})
}
data-attr="button-add-tag"
icon={<IconPlus />}
className="border border-dashed"
style={{
display: addingNewTag ? 'none' : 'inline-flex',
}}
>
Add tag
</LemonTag>
{addingNewTag && (
<SelectGradientOverflow
size="small"
onBlur={() => setAddingNewTag(false)}
data-attr="new-tag-input"
autoFocus
allowClear
autoClearSearchValue
defaultOpen
showSearch
style={{ width: 160 }}
onChange={(changedValue) => handleAdd(changedValue)}
loading={saving}
onSearch={setNewTag}
placeholder='try "official"'
>
{cleanedNewTag ? (
<Select.Option
key={`${cleanedNewTag}_${id}`}
value={cleanedNewTag}
className="ph-no-capture"
data-attr="new-tag-option"
>
{cleanedNewTag}
</Select.Option>
) : (
(!tagsAvailable || !tagsAvailable.length) && (
<Select.Option key="__" value="__" disabled style={{ color: 'var(--muted)' }}>
Type to add a new tag
</Select.Option>
)
)}
{tagsAvailable &&
tagsAvailable.map((tag) => (
<Select.Option key={tag} value={tag} className="ph-no-capture">
{tag}
</Select.Option>
))}
</SelectGradientOverflow>
{editingTags ? (
<LemonInputSelect
mode="multiple"
allowCustomValues
value={tags}
options={tagsAvailable?.map((t) => ({ key: t, label: t }))}
onChange={setTags}
onBlur={() => setEditingTags(false)}
loading={saving}
data-attr="new-tag-input"
placeholder='try "official"'
autoFocus
/>
) : (
<>
{showPlaceholder
? '—'
: tags
.filter((t) => !!t)
.map((tag, index) => {
return (
<LemonTag key={index} type={COLOR_OVERRIDES[tag] || colorForString(tag)}>
{tag}
</LemonTag>
)
})}
{!staticOnly && onChange && saving !== undefined && (
<span className="inline-flex font-normal">
<LemonTag
type="none"
onClick={() =>
onGuardClick(() => {
setEditingTags(true)
})
}
data-attr="button-add-tag"
icon={hasTags ? <IconPencil /> : <IconPlus />}
className="border border-dashed"
size="small"
>
{hasTags ? 'Edit tags' : 'Add tag'}
</LemonTag>
</span>
)}
</span>
</>
)}
</div>
)
Expand Down
69 changes: 11 additions & 58 deletions frontend/src/lib/components/ObjectTags/objectTagsLogic.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import equal from 'fast-deep-equal'
import { actions, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { actions, kea, key, listeners, path, props, reducers } from 'kea'

import type { objectTagsLogicType } from './objectTagsLogicType'

export interface ObjectTagsLogicProps {
id: number
onChange?: (tag: string, tags: string[]) => void
tags: string[]
onChange?: (tags: string[]) => void
}

function cleanTag(tag?: string): string {
Expand All @@ -21,66 +18,22 @@ export const objectTagsLogic = kea<objectTagsLogicType>([
key((props) => props.id),
actions({
setTags: (tags: string[]) => ({ tags }),
setAddingNewTag: (addingNewTag: boolean) => ({ addingNewTag }),
setNewTag: (newTag: string) => ({ newTag }),
setEditingTags: (editingTags: boolean) => ({ editingTags }),
handleDelete: (tag: string) => ({ tag }),
handleAdd: (addedTag: string) => ({ addedTag }),
handleAdd: (nextTags: string[]) => ({ nextTags }),
}),
reducers(({ props }) => ({
tags: [
props.tags,
{
setTags: (_, { tags }) => tags,
},
],
addingNewTag: [
reducers(() => ({
editingTags: [
false,
{
setAddingNewTag: (_, { addingNewTag }) => addingNewTag,
setTags: () => false,
},
],
newTag: [
'',
{
setNewTag: (_, { newTag }) => newTag,
setTags: () => '',
},
],
deletedTags: [
[],
{
handleDelete: (state, { tag }) => [...state, tag],
setEditingTags: (_, { editingTags }) => editingTags,
},
],
})),
selectors({
cleanedNewTag: [(s) => [s.newTag], (newTag) => cleanTag(newTag)],
}),
listeners(({ values, props, actions }) => ({
handleDelete: async ({ tag }) => {
const newTags = values.tags.filter((_t) => _t !== tag)
props.onChange?.(tag, newTags)

// Update local state so that frontend is not blocked by server requests
actions.setTags(newTags)
},
handleAdd: async ({ addedTag }) => {
const cleanedAddedTag = cleanTag(addedTag)
if (values.tags?.includes(cleanedAddedTag)) {
lemonToast.error(`Tag "${cleanedAddedTag}" already is in the list`)
return
}
const newTags = [...(values.tags || []), cleanedAddedTag]
props.onChange?.(cleanedAddedTag, newTags)

// Update local state so that frontend is not blocked by server requests
actions.setTags(newTags)
listeners(({ props }) => ({
setTags: ({ tags }) => {
const nextTags = tags.map(cleanTag)
props.onChange?.(nextTags)
},
})),
propsChanged(({ actions, props }, oldProps) => {
if (!equal(props.tags, oldProps.tags)) {
actions.setTags(props.tags)
}
}),
])
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ export function PropertyFilters({
disablePopover={disablePopover || orFiltering}
addText={addText}
hasRowOperator={hasRowOperator}
selectProps={{
delayBeforeAutoOpen: 150,
placement: pageKey === 'insight-filters' ? 'bottomLeft' : undefined,
}}
propertyAllowList={propertyAllowList}
taxonomicFilterOptionsFromProp={taxonomicFilterOptionsFromProp}
allowRelativeDateOptions={allowRelativeDateOptions}
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/lib/components/PropertyFilters/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { propertyFilterLogic } from 'lib/components/PropertyFilters/propertyFilterLogic'
import { SelectGradientOverflowProps } from 'lib/components/SelectGradientOverflow'
import {
TaxonomicFilterGroup,
TaxonomicFilterGroupType,
Expand Down Expand Up @@ -36,7 +35,6 @@ export interface TaxonomicPropertyFilterLogicProps extends PropertyFilterBasePro
export interface PropertyFilterInternalProps {
pageKey?: string
index: number
selectProps: Partial<SelectGradientOverflowProps>
onComplete: () => void
disablePopover: boolean
taxonomicGroupTypes?: TaxonomicFilterGroupType[]
Expand Down
49 changes: 0 additions & 49 deletions frontend/src/lib/components/SelectGradientOverflow.scss

This file was deleted.

Loading

0 comments on commit 49de8c4

Please sign in to comment.