Skip to content

Commit f97774a

Browse files
authored
Merge pull request #104 from kit-data-manager/tags
[Breaking] Fix generic result view tag types
2 parents d563a79 + 2337a83 commit f97774a

File tree

7 files changed

+40
-50
lines changed

7 files changed

+40
-50
lines changed

package-lock.json

Lines changed: 4 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
"tailwindcss-animate": "^1.0.7",
107107
"tailwindcss-scoped-preflight": "^3.4.12",
108108
"ts-loader": "^9.5.2",
109-
"tsc-alias": "^1.8.10",
109+
"tsc-alias": "1.8.13",
110110
"typescript": "^5.7.3",
111111
"typescript-eslint": "^8.15.0",
112112
"vite": "^6.0.9",

src/components/result/GenericResultViewTag.tsx

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { MouseEvent, ReactNode, useCallback, useEffect, useMemo, useState } from "react"
22
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
33
import { Badge } from "@/components/ui/badge"
4-
import { SearchResult } from "@elastic/search-ui"
4+
import { SearchResult, FieldValue } from "@elastic/search-ui"
55
import { useCopyToClipboard } from "usehooks-ts"
66
import { CheckIcon } from "lucide-react"
77
import { autoUnwrap } from "@/lib/utils"
@@ -11,6 +11,10 @@ export interface GenericResultViewTagProps {
1111
* The elasticsearch field that this tag will display
1212
*/
1313
field: string
14+
/**
15+
* When specified, does not read the field from elastic and instead just displays this value
16+
*/
17+
valueOverride?: string | number | boolean
1418
result: SearchResult
1519
/**
1620
* Icon for this tag, can be any react component. Ideally a [lucide icon](https://lucide.dev) with 16px by 16px site.
@@ -25,27 +29,20 @@ export interface GenericResultViewTagProps {
2529
* Can't the used together with `singleValueMapper`
2630
* @param value
2731
*/
28-
valueMapper?: (value: string | string[]) => ReactNode
32+
valueMapper?: (value: FieldValue) => ReactNode
2933
/**
3034
* Optional, here you can map each value of the elasticsearch field to a string or a React component. Can't be used
3135
* together with `valueMapper`
3236
* @param value
3337
*/
34-
singleValueMapper?: (value: string) => ReactNode
35-
onClick?: (e: MouseEvent<HTMLDivElement>, tagValue: ReactNode, fieldValue: string | string[]) => void
38+
singleValueMapper?: (value: string | number | boolean) => ReactNode
39+
onClick?: (e: MouseEvent<HTMLDivElement>, tagValue: ReactNode, fieldValue: FieldValue) => void
3640
clickBehavior?: "copy-text" | "follow-url" | string
3741
}
3842

39-
export function GenericResultViewTag({
40-
field,
41-
result,
42-
icon,
43-
label,
44-
valueMapper,
45-
singleValueMapper,
46-
clickBehavior = "copy-text",
47-
onClick
48-
}: GenericResultViewTagProps) {
43+
export function GenericResultViewTag(props: GenericResultViewTagProps) {
44+
const { field, valueOverride, result, icon, label, valueMapper, singleValueMapper, clickBehavior = "copy-text", onClick } = props
45+
4946
const [showCopiedNotice, setShowCopiedNotice] = useState(false)
5047

5148
useEffect(() => {
@@ -57,11 +54,12 @@ export function GenericResultViewTag({
5754
}, [showCopiedNotice])
5855

5956
const fieldValue = useMemo(() => {
60-
return autoUnwrap(result[field]) as string | string[]
61-
}, [field, result])
57+
if (valueOverride !== undefined) return valueOverride
58+
return autoUnwrap(result[field]) as FieldValue
59+
}, [field, result, valueOverride])
6260

6361
const value = useMemo(() => {
64-
if (!fieldValue) return undefined
62+
if (fieldValue === null || fieldValue === undefined) return undefined
6563
if (valueMapper) return valueMapper(fieldValue)
6664
if (singleValueMapper) return Array.isArray(fieldValue) ? fieldValue.map(singleValueMapper) : singleValueMapper(fieldValue)
6765
else return fieldValue
@@ -79,13 +77,13 @@ export function GenericResultViewTag({
7977
)
8078

8179
const handleClick = useCallback(
82-
(fieldValue: string | string[], value: ReactNode, e: MouseEvent<HTMLDivElement>) => {
80+
(fieldValue: FieldValue, value: ReactNode, e: MouseEvent<HTMLDivElement>) => {
8381
if (onClick) onClick(e, value, fieldValue)
8482
if (clickBehavior === "copy-text" && !showCopiedNotice) {
8583
copyTagValue(e)
8684
setShowCopiedNotice(true)
8785
} else if (clickBehavior === "follow-url" && !Array.isArray(fieldValue)) {
88-
window.open(fieldValue, "_blank")
86+
window.open(fieldValue.toString(), "_blank")
8987
}
9088
},
9189
[clickBehavior, copyTagValue, onClick, showCopiedNotice]
@@ -101,7 +99,7 @@ export function GenericResultViewTag({
10199
}, [clickBehavior, onClick])
102100

103101
const base = useCallback(
104-
(fieldValue: string | string[], value: ReactNode, key?: string) => {
102+
(fieldValue: FieldValue, value: ReactNode, key?: string) => {
105103
return (
106104
<Badge key={key} variant="secondary" className="rfs-truncate" onClick={(e) => handleClick(fieldValue, value, e)}>
107105
<span className="rfs-flex rfs-truncate">
@@ -121,19 +119,12 @@ export function GenericResultViewTag({
121119
[handleClick, icon, showCopiedNotice]
122120
)
123121

124-
if (!label) return Array.isArray(value) ? value.map((v, i) => base(fieldValue[value.indexOf(v)], v, field + i)) : base(fieldValue, value)
125-
if (!value) return null
122+
if (value === undefined) return null
126123

127-
if (Array.isArray(value)) {
128-
return value.map((entry, i) => (
129-
<Tooltip delayDuration={500} key={field + i}>
130-
<TooltipTrigger>{base(fieldValue[value.indexOf(entry)], entry)}</TooltipTrigger>
131-
<TooltipContent>
132-
<div>{label}</div>
133-
<div className="rfs-text-xs rfs-text-muted-foreground">{clickBehaviourText}</div>
134-
</TooltipContent>
135-
</Tooltip>
136-
))
124+
if (Array.isArray(value) && Array.isArray(fieldValue)) {
125+
return value.map((entry, i) => <GenericResultViewTag {...props} key={field + i} valueOverride={fieldValue[value.indexOf(entry)]} />)
126+
} else if (!label) {
127+
return base(fieldValue, value)
137128
}
138129

139130
return (

src/lib/config/SearchConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FilterType, FilterValueValue, RequestState, SearchFieldConfiguration, SortOption } from "@elastic/search-ui"
1+
import { FieldValue, FilterType, RequestState, SearchFieldConfiguration, SortOption } from "@elastic/search-ui"
22
import { ReactNode } from "react"
33

44
export interface CoreFacetConfig {
@@ -32,7 +32,7 @@ export interface CoreFacetConfig {
3232
*
3333
* @param value
3434
*/
35-
singleValueMapper?: (value: FilterValueValue) => ReactNode
35+
singleValueMapper?: (value: FieldValue) => ReactNode
3636

3737
/**
3838
* Not properly implemented at the moment. Use with caution.

src/lib/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function prettyPrintURL(url: string) {
2626
}
2727

2828
export function autoUnwrap<E>(item?: E | { raw?: E }) {
29-
if (!item) {
29+
if (item === undefined || item === null) {
3030
return undefined
3131
} else if (typeof item === "object" && "raw" in item) {
3232
return item.raw

src/stories/Others/GenericResultView.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,14 @@ export const Full: Story = {
123123
{
124124
icon: <GlobeIcon className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
125125
field: "hadPrimarySource",
126-
singleValueMapper: prettyPrintURL,
126+
singleValueMapper: (v) => prettyPrintURL(v + ""),
127127
label: "Primary Source",
128128
clickBehavior: "follow-url"
129129
},
130130
{
131131
icon: <ScaleIcon className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
132132
field: "licenseURL",
133-
singleValueMapper: prettyPrintURL,
133+
singleValueMapper: (v) => prettyPrintURL(v + ""),
134134
label: "License URL",
135135
clickBehavior: "follow-url"
136136
},
@@ -161,7 +161,7 @@ export const Full: Story = {
161161
},
162162
{
163163
field: "stringArrayTest",
164-
singleValueMapper: (v) => v.toUpperCase()
164+
singleValueMapper: (v) => (v + "").toUpperCase()
165165
}
166166
]
167167
}

src/stories/ReactSearchComponent.stories.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export const GenericResultRenderer: Story = {
159159
icon: <UserIcon className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
160160
label: "Contact",
161161
field: "contact",
162-
singleValueMapper: (v) => <OrcidDisplay orcid={v} />,
162+
singleValueMapper: (v) => <OrcidDisplay orcid={v + ""} />,
163163
clickBehavior: "follow-url"
164164
},
165165
{
@@ -170,7 +170,7 @@ export const GenericResultRenderer: Story = {
170170
{
171171
icon: <GlobeIcon className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
172172
field: "hadPrimarySource",
173-
singleValueMapper: (v) => mapPrimarySource(v),
173+
singleValueMapper: (v) => mapPrimarySource(v + ""),
174174
label: "Source",
175175
onClick: (e) =>
176176
"innerText" in e.target &&
@@ -180,7 +180,7 @@ export const GenericResultRenderer: Story = {
180180
{
181181
icon: <ScaleIcon className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
182182
field: "licenseURL",
183-
singleValueMapper: (v) => <PidNameDisplay pid={v} />,
183+
singleValueMapper: (v) => <PidNameDisplay pid={v + ""} />,
184184
label: "License URL",
185185
clickBehavior: "follow-url"
186186
},
@@ -194,13 +194,13 @@ export const GenericResultRenderer: Story = {
194194
icon: <Microscope className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
195195
label: "NMR Method",
196196
field: "NMR_Method",
197-
singleValueMapper: (v) => <PidNameDisplay pid={v} />
197+
singleValueMapper: (v) => <PidNameDisplay pid={v + ""} />
198198
},
199199
{
200200
icon: <FlaskConical className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
201201
label: "NMR Solvent",
202202
field: "NMR_Solvent",
203-
singleValueMapper: (v) => <PidNameDisplay pid={v} />
203+
singleValueMapper: (v) => <PidNameDisplay pid={v + ""} />
204204
},
205205
{
206206
icon: <AudioLines className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
@@ -211,7 +211,7 @@ export const GenericResultRenderer: Story = {
211211
icon: <CircleDot className="rfs-shrink-0 rfs-size-4 rfs-mr-2" />,
212212
label: "Acquisition Nucleus",
213213
field: "Acquisition_Nucleus",
214-
singleValueMapper: (v) => <PidNameDisplay pid={v} />
214+
singleValueMapper: (v) => <PidNameDisplay pid={v + ""} />
215215
}
216216
]}
217217
titleField="name"

0 commit comments

Comments
 (0)