1
1
import { MouseEvent , ReactNode , useCallback , useEffect , useMemo , useState } from "react"
2
2
import { Tooltip , TooltipContent , TooltipTrigger } from "@/components/ui/tooltip"
3
3
import { Badge } from "@/components/ui/badge"
4
- import { SearchResult } from "@elastic/search-ui"
4
+ import { SearchResult , FieldValue } from "@elastic/search-ui"
5
5
import { useCopyToClipboard } from "usehooks-ts"
6
6
import { CheckIcon } from "lucide-react"
7
7
import { autoUnwrap } from "@/lib/utils"
@@ -11,6 +11,10 @@ export interface GenericResultViewTagProps {
11
11
* The elasticsearch field that this tag will display
12
12
*/
13
13
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
14
18
result : SearchResult
15
19
/**
16
20
* 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 {
25
29
* Can't the used together with `singleValueMapper`
26
30
* @param value
27
31
*/
28
- valueMapper ?: ( value : string | string [ ] ) => ReactNode
32
+ valueMapper ?: ( value : FieldValue ) => ReactNode
29
33
/**
30
34
* Optional, here you can map each value of the elasticsearch field to a string or a React component. Can't be used
31
35
* together with `valueMapper`
32
36
* @param value
33
37
*/
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
36
40
clickBehavior ?: "copy-text" | "follow-url" | string
37
41
}
38
42
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
+
49
46
const [ showCopiedNotice , setShowCopiedNotice ] = useState ( false )
50
47
51
48
useEffect ( ( ) => {
@@ -57,11 +54,12 @@ export function GenericResultViewTag({
57
54
} , [ showCopiedNotice ] )
58
55
59
56
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 ] )
62
60
63
61
const value = useMemo ( ( ) => {
64
- if ( ! fieldValue ) return undefined
62
+ if ( fieldValue === null || fieldValue === undefined ) return undefined
65
63
if ( valueMapper ) return valueMapper ( fieldValue )
66
64
if ( singleValueMapper ) return Array . isArray ( fieldValue ) ? fieldValue . map ( singleValueMapper ) : singleValueMapper ( fieldValue )
67
65
else return fieldValue
@@ -79,13 +77,13 @@ export function GenericResultViewTag({
79
77
)
80
78
81
79
const handleClick = useCallback (
82
- ( fieldValue : string | string [ ] , value : ReactNode , e : MouseEvent < HTMLDivElement > ) => {
80
+ ( fieldValue : FieldValue , value : ReactNode , e : MouseEvent < HTMLDivElement > ) => {
83
81
if ( onClick ) onClick ( e , value , fieldValue )
84
82
if ( clickBehavior === "copy-text" && ! showCopiedNotice ) {
85
83
copyTagValue ( e )
86
84
setShowCopiedNotice ( true )
87
85
} else if ( clickBehavior === "follow-url" && ! Array . isArray ( fieldValue ) ) {
88
- window . open ( fieldValue , "_blank" )
86
+ window . open ( fieldValue . toString ( ) , "_blank" )
89
87
}
90
88
} ,
91
89
[ clickBehavior , copyTagValue , onClick , showCopiedNotice ]
@@ -101,7 +99,7 @@ export function GenericResultViewTag({
101
99
} , [ clickBehavior , onClick ] )
102
100
103
101
const base = useCallback (
104
- ( fieldValue : string | string [ ] , value : ReactNode , key ?: string ) => {
102
+ ( fieldValue : FieldValue , value : ReactNode , key ?: string ) => {
105
103
return (
106
104
< Badge key = { key } variant = "secondary" className = "rfs-truncate" onClick = { ( e ) => handleClick ( fieldValue , value , e ) } >
107
105
< span className = "rfs-flex rfs-truncate" >
@@ -121,19 +119,12 @@ export function GenericResultViewTag({
121
119
[ handleClick , icon , showCopiedNotice ]
122
120
)
123
121
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
126
123
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 )
137
128
}
138
129
139
130
return (
0 commit comments