goobs-frontend 0.9.11 → 0.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,33 @@
1
1
  'use client'
2
2
 
3
- import React, { useState, useEffect, SyntheticEvent } from 'react'
3
+ import React, { useState, useEffect, SyntheticEvent, useRef } from 'react'
4
4
  import {
5
5
  Autocomplete,
6
6
  InputLabel,
7
7
  OutlinedInput,
8
8
  FormHelperText,
9
9
  FormControl,
10
+ Box,
11
+ Tabs,
12
+ Tab,
13
+ styled as muiStyled,
14
+ Popper,
10
15
  } from '@mui/material'
11
16
  import { styled } from '@mui/material/styles'
12
17
  import { black, white } from '../../../../styles/palette'
13
18
  import Typography from '../../../Typography'
14
19
  import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
20
+ import HistoryIcon from '@mui/icons-material/History'
21
+ import SearchIcon from '@mui/icons-material/Search'
22
+ import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'
23
+
24
+ // Define a history item type with timestamp
25
+ interface HistoryItem {
26
+ text: string
27
+ timestamp: Date
28
+ // Optional formatted date string for display
29
+ formattedDate?: string
30
+ }
15
31
 
16
32
  export interface DropdownOption {
17
33
  value: string
@@ -44,10 +60,12 @@ export interface SearchableDropdownProps {
44
60
  placeholder?: string
45
61
  disabled?: boolean
46
62
  width?: string
47
- // Added style property to allow additional styling (e.g., marginBottom)
48
63
  style?: React.CSSProperties
49
- // New variant property to determine display style
50
64
  variant?: 'simple' | 'complex'
65
+ // Update search history type to support timestamps
66
+ searchHistory?: HistoryItem[] | string[]
67
+ onSearch?: (searchTerm: string, timestamp?: Date) => void
68
+ maxHistoryItems?: number
51
69
  }
52
70
 
53
71
  const StyledFormControl = styled(FormControl)<{ width?: string }>(
@@ -99,6 +117,26 @@ interface StyledAutocompleteProps {
99
117
  variant?: 'simple' | 'complex'
100
118
  }
101
119
 
120
+ // Styled tab component for the dropdown footer
121
+ const StyledTab = muiStyled(Tab)(() => ({
122
+ minHeight: '36px',
123
+ fontSize: '12px',
124
+ padding: '6px 12px',
125
+ color: black.main,
126
+ '&.Mui-selected': {
127
+ color: black.main,
128
+ fontWeight: 'bold',
129
+ },
130
+ }))
131
+
132
+ const StyledTabs = muiStyled(Tabs)(() => ({
133
+ minHeight: '36px',
134
+ borderTop: `1px solid ${black.light}`,
135
+ '& .MuiTabs-indicator': {
136
+ backgroundColor: black.main,
137
+ },
138
+ }))
139
+
102
140
  const StyledAutocomplete = styled(
103
141
  Autocomplete<DropdownOption, false, false, true>
104
142
  )<StyledAutocompleteProps>(props => {
@@ -236,10 +274,138 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
236
274
  width,
237
275
  style,
238
276
  variant = 'simple', // Default to simple variant
277
+ searchHistory = [], // Default to empty array
278
+ onSearch,
279
+ maxHistoryItems = 5, // Default to showing 5 history items
239
280
  }) => {
240
281
  const [value, setValue] = useState<DropdownOption | string | null>(null)
241
282
  const [inputValue, setInputValue] = useState('')
242
283
  const [isFocused, setIsFocused] = useState(false)
284
+ // Update local history state to support timestamps
285
+ const [localHistory, setLocalHistory] = useState<HistoryItem[]>([])
286
+
287
+ // Add state for active tab - 0 for All Options, 1 for History
288
+ const [activeTab, setActiveTab] = useState<number>(0)
289
+
290
+ // Use ref to track if we've done initial history setup to avoid loops
291
+ const initializedRef = React.useRef(false)
292
+
293
+ // Always use controlled open state
294
+ const [isOpen, setIsOpen] = useState(false)
295
+
296
+ // Reference to the tab container
297
+ const tabsRef = useRef<HTMLDivElement | null>(null)
298
+
299
+ // Ref for the input/autocomplete
300
+ const autocompleteRef = useRef<HTMLDivElement>(null)
301
+
302
+ // Use enhanced effect to handle tab clicks without closing dropdown
303
+ useEnhancedEffect(() => {
304
+ // If tabs aren't mounted yet, do nothing
305
+ if (!tabsRef.current) return
306
+
307
+ const tabsElement = tabsRef.current
308
+
309
+ // Prevent any click inside the tabs from bubbling up
310
+ const preventClose = (e: MouseEvent) => {
311
+ e.preventDefault()
312
+ e.stopPropagation()
313
+ }
314
+
315
+ // Add click handler to the tabs element
316
+ tabsElement.addEventListener('mousedown', preventClose, true)
317
+ tabsElement.addEventListener('click', preventClose, true)
318
+
319
+ // Cleanup
320
+ return () => {
321
+ tabsElement.removeEventListener('mousedown', preventClose, true)
322
+ tabsElement.removeEventListener('click', preventClose, true)
323
+ }
324
+ }, [tabsRef.current])
325
+
326
+ // Function to format date for display
327
+ const formatDate = (date: Date): string => {
328
+ const now = new Date()
329
+ const diff = now.getTime() - date.getTime()
330
+
331
+ // If less than a minute ago
332
+ if (diff < 60 * 1000) {
333
+ return 'Just now'
334
+ }
335
+
336
+ // If less than an hour ago
337
+ if (diff < 60 * 60 * 1000) {
338
+ const minutes = Math.floor(diff / (60 * 1000))
339
+ return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`
340
+ }
341
+
342
+ // If less than a day ago
343
+ if (diff < 24 * 60 * 60 * 1000) {
344
+ const hours = Math.floor(diff / (60 * 60 * 1000))
345
+ return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`
346
+ }
347
+
348
+ // If less than a week ago
349
+ if (diff < 7 * 24 * 60 * 60 * 1000) {
350
+ const days = Math.floor(diff / (24 * 60 * 60 * 1000))
351
+ return `${days} ${days === 1 ? 'day' : 'days'} ago`
352
+ }
353
+
354
+ // Otherwise, format as date
355
+ return date.toLocaleDateString(undefined, {
356
+ year: 'numeric',
357
+ month: 'short',
358
+ day: 'numeric',
359
+ })
360
+ }
361
+
362
+ // Initialize history with timestamps if using simple strings
363
+ useEffect(() => {
364
+ if (!initializedRef.current && searchHistory && searchHistory.length > 0) {
365
+ // Check if searchHistory items are strings or already HistoryItems
366
+ if (typeof searchHistory[0] === 'string') {
367
+ const historyWithTimestamps = (searchHistory as string[]).map(
368
+ (text, index) => {
369
+ // Create timestamps with slight variations so older items are truly older
370
+ const timestamp = new Date()
371
+ timestamp.setMinutes(timestamp.getMinutes() - (index + 1))
372
+
373
+ return {
374
+ text,
375
+ timestamp,
376
+ formattedDate: formatDate(timestamp),
377
+ }
378
+ }
379
+ )
380
+ setLocalHistory(historyWithTimestamps)
381
+ } else {
382
+ // Already the right format, just update formatted dates
383
+ const formattedHistory = (searchHistory as HistoryItem[]).map(item => ({
384
+ ...item,
385
+ formattedDate: formatDate(item.timestamp),
386
+ }))
387
+ setLocalHistory(formattedHistory)
388
+ }
389
+
390
+ initializedRef.current = true
391
+ }
392
+ }, [searchHistory])
393
+
394
+ // Refresh formatted dates every minute for "minutes ago" style timestamps
395
+ useEffect(() => {
396
+ const intervalId = setInterval(() => {
397
+ if (localHistory.length > 0) {
398
+ setLocalHistory(prev =>
399
+ prev.map(item => ({
400
+ ...item,
401
+ formattedDate: formatDate(item.timestamp),
402
+ }))
403
+ )
404
+ }
405
+ }, 60000) // Update every minute
406
+
407
+ return () => clearInterval(intervalId)
408
+ }, [localHistory.length])
243
409
 
244
410
  useEffect(() => {
245
411
  const defaultOption = options.find(option => option.value === defaultValue)
@@ -260,6 +426,26 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
260
426
  setValue(newValue)
261
427
  setInputValue(newValue)
262
428
  onChange?.(null)
429
+
430
+ // Add to search history if it's a string input
431
+ if (newValue.trim()) {
432
+ const now = new Date()
433
+
434
+ if (onSearch) {
435
+ onSearch(newValue.trim(), now)
436
+ } else {
437
+ // Only update local history if we're not using onSearch callback
438
+ setLocalHistory(prev => {
439
+ const newItem = {
440
+ text: newValue.trim(),
441
+ timestamp: now,
442
+ formattedDate: formatDate(now),
443
+ }
444
+ const filteredPrev = prev.filter(h => h.text !== newItem.text)
445
+ return [newItem, ...filteredPrev].slice(0, maxHistoryItems)
446
+ })
447
+ }
448
+ }
263
449
  } else {
264
450
  setValue(newValue)
265
451
  if (newValue) {
@@ -268,6 +454,24 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
268
454
  newValue.value.replace(/_/g, ' ').slice(1)
269
455
  setInputValue(displayText)
270
456
  onChange?.(newValue)
457
+
458
+ // Add to search history
459
+ const now = new Date()
460
+
461
+ if (onSearch) {
462
+ onSearch(displayText, now)
463
+ } else if (newValue.value.trim()) {
464
+ // Only update local history if we're not using onSearch callback
465
+ setLocalHistory(prev => {
466
+ const newItem = {
467
+ text: displayText,
468
+ timestamp: now,
469
+ formattedDate: formatDate(now),
470
+ }
471
+ const filteredPrev = prev.filter(h => h.text !== newItem.text)
472
+ return [newItem, ...filteredPrev].slice(0, maxHistoryItems)
473
+ })
474
+ }
271
475
  } else {
272
476
  setInputValue('')
273
477
  onChange?.(null)
@@ -275,6 +479,34 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
275
479
  }
276
480
  }
277
481
 
482
+ // Handler for when the input is submitted (e.g., Enter key)
483
+ const handleInputSubmit = () => {
484
+ if (inputValue.trim()) {
485
+ const now = new Date()
486
+
487
+ if (onSearch) {
488
+ onSearch(inputValue.trim(), now)
489
+ } else {
490
+ // Only update local history if we're not using onSearch callback
491
+ setLocalHistory(prev => {
492
+ const newItem = {
493
+ text: inputValue.trim(),
494
+ timestamp: now,
495
+ formattedDate: formatDate(now),
496
+ }
497
+ const filteredPrev = prev.filter(h => h.text !== newItem.text)
498
+ return [newItem, ...filteredPrev].slice(0, maxHistoryItems)
499
+ })
500
+ }
501
+ }
502
+ }
503
+
504
+ const handleKeyDown = (event: React.KeyboardEvent) => {
505
+ if (event.key === 'Enter') {
506
+ handleInputSubmit()
507
+ }
508
+ }
509
+
278
510
  const handleFocus = () => {
279
511
  setIsFocused(true)
280
512
  }
@@ -283,10 +515,245 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
283
515
  if (!value && !inputValue) {
284
516
  setIsFocused(false)
285
517
  }
518
+ // Save search when blurring
519
+ if (inputValue.trim()) {
520
+ handleInputSubmit()
521
+ }
522
+ }
523
+
524
+ // Function to handle tab change
525
+ const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
526
+ // Set tab value
527
+ setActiveTab(newValue)
528
+
529
+ // Keep the dropdown open when switching tabs
530
+ setIsOpen(true)
531
+
532
+ // Prevent any bubbling that might close the dropdown
533
+ _event.preventDefault()
534
+ _event.stopPropagation()
286
535
  }
287
536
 
288
537
  const labelId = `${name}-label`
289
538
 
539
+ // Use a combined history that prioritizes prop history when available but falls back to local history
540
+ const combinedHistory = React.useMemo(() => {
541
+ if (searchHistory?.length) {
542
+ // Convert string[] to HistoryItem[] if needed
543
+ if (typeof searchHistory[0] === 'string') {
544
+ return (searchHistory as string[]).map((text, index) => {
545
+ // Create timestamps with slight variations
546
+ const timestamp = new Date()
547
+ timestamp.setMinutes(timestamp.getMinutes() - (index + 1))
548
+
549
+ return {
550
+ text,
551
+ timestamp,
552
+ formattedDate: formatDate(timestamp),
553
+ }
554
+ })
555
+ }
556
+
557
+ // Already HistoryItem[] format
558
+ return searchHistory as HistoryItem[]
559
+ }
560
+
561
+ return localHistory
562
+ }, [searchHistory, localHistory])
563
+
564
+ // Create a combined options array based on active tab and input value
565
+ const getFilteredOptions = React.useCallback(() => {
566
+ const currentInputVal = inputValue.trim()
567
+
568
+ // HISTORY TAB - only apply when variant is complex
569
+ if (activeTab === 1 && variant === 'complex') {
570
+ if (combinedHistory.length === 0) {
571
+ // Show a placeholder message if no history
572
+ return [
573
+ {
574
+ value: 'No search history',
575
+ uniqueKey: 'no-history',
576
+ attribute1: 'Try searching for something first',
577
+ },
578
+ ]
579
+ }
580
+
581
+ // Map history items to dropdown options
582
+ return combinedHistory.map(item => {
583
+ // Check if this history item matches any of the original options
584
+ const matchingOption = options.find(
585
+ opt =>
586
+ opt.value.toLowerCase() === item.text.toLowerCase() ||
587
+ (
588
+ opt.value.replace(/_/g, ' ').charAt(0).toUpperCase() +
589
+ opt.value.replace(/_/g, ' ').slice(1)
590
+ ).toLowerCase() === item.text.toLowerCase()
591
+ )
592
+
593
+ if (matchingOption) {
594
+ // If we have a matching original option, use its attributes
595
+ return {
596
+ value: item.text,
597
+ uniqueKey: `history-${item.text}`,
598
+ attribute1: 'Search History',
599
+ attribute2: item.formattedDate || formatDate(item.timestamp),
600
+ attribute3: matchingOption.attribute1,
601
+ attribute4: matchingOption.attribute2,
602
+ attribute5: matchingOption.attribute3,
603
+ attribute6: matchingOption.attribute4,
604
+ }
605
+ } else {
606
+ // Otherwise just use the basic history item with timestamp
607
+ return {
608
+ value: item.text,
609
+ uniqueKey: `history-${item.text}`,
610
+ attribute1: 'Search History',
611
+ attribute2: item.formattedDate || formatDate(item.timestamp),
612
+ }
613
+ }
614
+ })
615
+ }
616
+
617
+ // ALL OPTIONS TAB (activeTab === 0)
618
+ // If input is empty, return all options
619
+ if (!currentInputVal) {
620
+ return options
621
+ }
622
+
623
+ // Filter options based on current input
624
+ const filteredOpts = options.filter(opt =>
625
+ opt.value.toLowerCase().includes(currentInputVal.toLowerCase())
626
+ )
627
+
628
+ // If no matches found, add the current input as a custom option
629
+ if (filteredOpts.length === 0) {
630
+ return [
631
+ {
632
+ value: currentInputVal,
633
+ uniqueKey: `current-${currentInputVal}`,
634
+ attribute1: 'Search',
635
+ },
636
+ ]
637
+ }
638
+
639
+ // Check if the input exactly matches any option
640
+ const exactMatch = filteredOpts.some(
641
+ opt => opt.value.toLowerCase() === currentInputVal.toLowerCase()
642
+ )
643
+
644
+ // If no exact match, add the current input as the first option
645
+ if (!exactMatch) {
646
+ return [
647
+ {
648
+ value: currentInputVal,
649
+ uniqueKey: `current-${currentInputVal}`,
650
+ attribute1: 'Search',
651
+ },
652
+ ...filteredOpts,
653
+ ]
654
+ }
655
+
656
+ return filteredOpts
657
+ }, [inputValue, combinedHistory, options, activeTab, variant])
658
+
659
+ // Create the footer component for the dropdown with tabs
660
+ const ListboxFooter = React.forwardRef<HTMLDivElement>((_, ref) => (
661
+ <Box
662
+ ref={(node: HTMLDivElement | null) => {
663
+ // Set both refs
664
+ if (ref) {
665
+ if (typeof ref === 'function') {
666
+ ref(node)
667
+ } else {
668
+ ref.current = node
669
+ }
670
+ }
671
+ tabsRef.current = node
672
+ }}
673
+ sx={{
674
+ position: 'sticky',
675
+ bottom: 0,
676
+ backgroundColor: white.main,
677
+ zIndex: 2,
678
+ borderTop: `1px solid ${black.light}`,
679
+ }}
680
+ onClick={e => {
681
+ e.preventDefault()
682
+ e.stopPropagation()
683
+ }}
684
+ onMouseDown={e => {
685
+ e.preventDefault()
686
+ e.stopPropagation()
687
+ }}
688
+ >
689
+ <StyledTabs
690
+ value={activeTab}
691
+ onChange={handleTabChange}
692
+ centered
693
+ sx={{
694
+ pointerEvents: 'all',
695
+ }}
696
+ >
697
+ <StyledTab
698
+ icon={<SearchIcon fontSize="small" />}
699
+ label="ALL OPTIONS"
700
+ iconPosition="start"
701
+ sx={{
702
+ pointerEvents: 'all',
703
+ }}
704
+ />
705
+ <StyledTab
706
+ icon={<HistoryIcon fontSize="small" />}
707
+ label="HISTORY"
708
+ iconPosition="start"
709
+ sx={{
710
+ pointerEvents: 'all',
711
+ }}
712
+ />
713
+ </StyledTabs>
714
+ </Box>
715
+ ))
716
+
717
+ // Add display name to ListboxFooter
718
+ ListboxFooter.displayName = 'ListboxFooter'
719
+
720
+ // Custom Listbox component that adds the footer
721
+ const CustomListbox = React.forwardRef<
722
+ HTMLElement,
723
+ React.HTMLAttributes<HTMLElement>
724
+ >((props, ref) => {
725
+ const { children, ...other } = props
726
+ return (
727
+ <div
728
+ ref={ref as React.Ref<HTMLDivElement>}
729
+ onClick={e => {
730
+ e.stopPropagation()
731
+ return false
732
+ }}
733
+ style={{ pointerEvents: 'auto' }}
734
+ >
735
+ <ul {...other}>{children}</ul>
736
+ {variant === 'complex' && <ListboxFooter />}
737
+ </div>
738
+ )
739
+ })
740
+
741
+ // Add display name to CustomListbox
742
+ CustomListbox.displayName = 'CustomListbox'
743
+
744
+ // Memoize the filtered options to prevent recreation on every render
745
+ const filteredOptions = React.useMemo(
746
+ () => getFilteredOptions(),
747
+ [getFilteredOptions]
748
+ )
749
+
750
+ // Ensure activeTab is always 0 for simple variant
751
+ useEffect(() => {
752
+ if (variant === 'simple' && activeTab !== 0) {
753
+ setActiveTab(0)
754
+ }
755
+ }, [variant, activeTab])
756
+
290
757
  return (
291
758
  <StyledFormControl
292
759
  error={error}
@@ -308,7 +775,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
308
775
  </StyledInputLabel>
309
776
  <StyledAutocomplete
310
777
  id={name}
311
- options={options}
778
+ options={filteredOptions}
312
779
  freeSolo
313
780
  value={value}
314
781
  onChange={handleChange}
@@ -321,6 +788,18 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
321
788
  onFocus={handleFocus}
322
789
  onBlur={handleBlur}
323
790
  forcePopupIcon
791
+ open={isOpen}
792
+ onOpen={() => {
793
+ setIsOpen(true)
794
+ }}
795
+ onClose={event => {
796
+ // Don't close when clicking tabs
797
+ if (tabsRef.current?.contains(event.target as Node)) {
798
+ return
799
+ }
800
+
801
+ setIsOpen(false)
802
+ }}
324
803
  popupIcon={
325
804
  <ArrowDropDownIcon
326
805
  sx={{ color: disabled ? 'rgba(0, 0, 0, 0.38)' : black.main }}
@@ -328,8 +807,69 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
328
807
  }
329
808
  disablePortal={false}
330
809
  ListboxProps={{
331
- style: { maxHeight: '300px', overflowY: 'auto' },
810
+ style: {
811
+ maxHeight: '300px',
812
+ overflowY: 'auto',
813
+ pointerEvents: 'all',
814
+ },
815
+ }}
816
+ ListboxComponent={CustomListbox}
817
+ componentsProps={{
818
+ popper: {
819
+ onClick: e => {
820
+ // Prevent clicks in the popper from closing the dropdown
821
+ e.stopPropagation()
822
+ },
823
+ style: {
824
+ pointerEvents: 'all',
825
+ // Ensure clicks inside the popper don't close it
826
+ inset: '0px auto auto 0px',
827
+ zIndex: 9999,
828
+ },
829
+ },
830
+ paper: {
831
+ onClick: e => {
832
+ // Prevent clicks on the paper from closing the dropdown
833
+ e.stopPropagation()
834
+ },
835
+ style: { pointerEvents: 'all' },
836
+ },
332
837
  }}
838
+ ref={autocompleteRef}
839
+ PopperComponent={props => (
840
+ <Popper
841
+ {...props}
842
+ onClick={e => {
843
+ e.stopPropagation()
844
+ }}
845
+ style={{
846
+ ...props.style,
847
+ pointerEvents: 'all',
848
+ zIndex: 9999,
849
+ }}
850
+ modifiers={[
851
+ {
852
+ name: 'preventOverflow',
853
+ enabled: true,
854
+ options: {
855
+ altAxis: true,
856
+ altBoundary: true,
857
+ tether: true,
858
+ rootBoundary: 'document',
859
+ padding: 8,
860
+ },
861
+ },
862
+ {
863
+ name: 'offset',
864
+ options: {
865
+ offset: [0, 0],
866
+ },
867
+ },
868
+ ]}
869
+ >
870
+ {props.children}
871
+ </Popper>
872
+ )}
333
873
  disabled={disabled}
334
874
  backgroundcolor={backgroundcolor}
335
875
  outlinecolor={outlinecolor}
@@ -337,10 +877,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
337
877
  inputfontcolor={inputfontcolor}
338
878
  placeholdercolor={placeholdercolor}
339
879
  variant={variant}
340
- filterOptions={(opts, state) => {
341
- const input = state.inputValue.toLowerCase()
342
- return opts.filter(o => o.value.toLowerCase().includes(input))
343
- }}
880
+ filterOptions={opts => opts} // We're handling filtering ourselves
344
881
  getOptionLabel={(option: DropdownOption | string) => {
345
882
  if (typeof option === 'string') {
346
883
  return option
@@ -373,24 +910,63 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
373
910
  // Use the uniqueKey prop if available, otherwise fall back to the provided key
374
911
  const optionKey = option.uniqueKey || key
375
912
 
913
+ // Check if this is a history item
914
+ const isHistoryItem = option.uniqueKey?.startsWith('history-')
915
+ const isCurrentInput = option.uniqueKey?.startsWith('current-')
916
+ const isNoHistoryPlaceholder = option.uniqueKey === 'no-history'
917
+
376
918
  return (
377
919
  <li key={optionKey} {...restLiProps} style={liStyle}>
378
920
  {/* Main value - both variants */}
379
921
  <Typography
380
922
  fontvariant="merriparagraph"
381
- text={option.value.replace(/_/g, ' ')}
923
+ text={
924
+ isNoHistoryPlaceholder
925
+ ? option.value
926
+ : isCurrentInput
927
+ ? `Search: "${option.value}"`
928
+ : isHistoryItem
929
+ ? `History: ${option.value.replace(/_/g, ' ')}`
930
+ : option.value.replace(/_/g, ' ')
931
+ }
382
932
  fontcolor={black.main}
383
933
  sx={{
384
934
  fontSize: '14px',
385
- fontWeight: variant === 'complex' ? '500' : 'normal',
935
+ fontWeight: isCurrentInput
936
+ ? '500'
937
+ : isHistoryItem
938
+ ? '400'
939
+ : variant === 'complex'
940
+ ? '500'
941
+ : 'normal',
942
+ fontStyle: isHistoryItem ? 'italic' : 'normal',
386
943
  lineHeight: '20px',
387
944
  width: '100%',
388
945
  textAlign: 'left',
389
946
  }}
390
947
  />
391
948
 
949
+ {/* For history items, show the timestamp */}
950
+ {isHistoryItem && option.attribute2 && (
951
+ <Typography
952
+ fontvariant="merriparagraph"
953
+ text={option.attribute2}
954
+ fontcolor="rgba(0, 0, 0, 0.6)"
955
+ sx={{
956
+ fontSize: '11px',
957
+ lineHeight: '14px',
958
+ width: '100%',
959
+ textAlign: 'left',
960
+ fontStyle: 'italic',
961
+ }}
962
+ />
963
+ )}
964
+
392
965
  {/* For simple variant - show attribute1 and attribute2 on one line */}
393
966
  {variant === 'simple' &&
967
+ !isHistoryItem &&
968
+ !isCurrentInput &&
969
+ !isNoHistoryPlaceholder &&
394
970
  (option.attribute1 || option.attribute2) && (
395
971
  <Typography
396
972
  fontvariant="merriparagraph"
@@ -408,26 +984,68 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
408
984
  )}
409
985
 
410
986
  {/* For complex variant - show attributes on separate lines */}
411
- {variant === 'complex' && (
412
- <>
413
- {/* First line of attributes */}
414
- {(option.attribute1 || option.attribute2) && (
415
- <Typography
416
- fontvariant="merriparagraph"
417
- text={[option.attribute1, option.attribute2]
418
- .filter(Boolean)
419
- .join(' | ')}
420
- fontcolor="rgba(0, 0, 0, 0.6)"
421
- sx={{
422
- fontSize: '12px',
423
- lineHeight: '16px',
424
- width: '100%',
425
- textAlign: 'left',
426
- }}
427
- />
428
- )}
987
+ {variant === 'complex' &&
988
+ !isHistoryItem &&
989
+ !isCurrentInput &&
990
+ !isNoHistoryPlaceholder && (
991
+ <>
992
+ {/* First line of attributes */}
993
+ {(option.attribute1 || option.attribute2) && (
994
+ <Typography
995
+ fontvariant="merriparagraph"
996
+ text={[option.attribute1, option.attribute2]
997
+ .filter(Boolean)
998
+ .join(' | ')}
999
+ fontcolor="rgba(0, 0, 0, 0.6)"
1000
+ sx={{
1001
+ fontSize: '12px',
1002
+ lineHeight: '16px',
1003
+ width: '100%',
1004
+ textAlign: 'left',
1005
+ }}
1006
+ />
1007
+ )}
1008
+
1009
+ {/* Second line of attributes */}
1010
+ {(option.attribute3 || option.attribute4) && (
1011
+ <Typography
1012
+ fontvariant="merriparagraph"
1013
+ text={[option.attribute3, option.attribute4]
1014
+ .filter(Boolean)
1015
+ .join(' | ')}
1016
+ fontcolor="rgba(0, 0, 0, 0.6)"
1017
+ sx={{
1018
+ fontSize: '12px',
1019
+ lineHeight: '16px',
1020
+ width: '100%',
1021
+ textAlign: 'left',
1022
+ }}
1023
+ />
1024
+ )}
429
1025
 
430
- {/* Second line of attributes */}
1026
+ {/* Third line of attributes */}
1027
+ {(option.attribute5 || option.attribute6) && (
1028
+ <Typography
1029
+ fontvariant="merriparagraph"
1030
+ text={[option.attribute5, option.attribute6]
1031
+ .filter(Boolean)
1032
+ .join(' | ')}
1033
+ fontcolor="rgba(0, 0, 0, 0.6)"
1034
+ sx={{
1035
+ fontSize: '12px',
1036
+ lineHeight: '16px',
1037
+ width: '100%',
1038
+ textAlign: 'left',
1039
+ }}
1040
+ />
1041
+ )}
1042
+ </>
1043
+ )}
1044
+
1045
+ {/* For history items, show additional attributes from original options */}
1046
+ {isHistoryItem && variant === 'complex' && (
1047
+ <>
1048
+ {/* Show attribute3/4 as first additional line for history */}
431
1049
  {(option.attribute3 || option.attribute4) && (
432
1050
  <Typography
433
1051
  fontvariant="merriparagraph"
@@ -440,11 +1058,12 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
440
1058
  lineHeight: '16px',
441
1059
  width: '100%',
442
1060
  textAlign: 'left',
1061
+ fontStyle: 'italic',
443
1062
  }}
444
1063
  />
445
1064
  )}
446
1065
 
447
- {/* Third line of attributes */}
1066
+ {/* Show attribute5/6 as second additional line for history */}
448
1067
  {(option.attribute5 || option.attribute6) && (
449
1068
  <Typography
450
1069
  fontvariant="merriparagraph"
@@ -457,11 +1076,43 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
457
1076
  lineHeight: '16px',
458
1077
  width: '100%',
459
1078
  textAlign: 'left',
1079
+ fontStyle: 'italic',
460
1080
  }}
461
1081
  />
462
1082
  )}
463
1083
  </>
464
1084
  )}
1085
+
1086
+ {/* For current input search, show a simpler display */}
1087
+ {isCurrentInput && option.attribute1 && (
1088
+ <Typography
1089
+ fontvariant="merriparagraph"
1090
+ text={option.attribute1}
1091
+ fontcolor="rgba(0, 0, 0, 0.6)"
1092
+ sx={{
1093
+ fontSize: '12px',
1094
+ lineHeight: '16px',
1095
+ width: '100%',
1096
+ textAlign: 'left',
1097
+ }}
1098
+ />
1099
+ )}
1100
+
1101
+ {/* For no history placeholder, show the instructions */}
1102
+ {isNoHistoryPlaceholder && option.attribute1 && (
1103
+ <Typography
1104
+ fontvariant="merriparagraph"
1105
+ text={option.attribute1}
1106
+ fontcolor="rgba(0, 0, 0, 0.6)"
1107
+ sx={{
1108
+ fontSize: '12px',
1109
+ lineHeight: '16px',
1110
+ width: '100%',
1111
+ textAlign: 'left',
1112
+ fontStyle: 'italic',
1113
+ }}
1114
+ />
1115
+ )}
465
1116
  </li>
466
1117
  )
467
1118
  }}
@@ -471,6 +1122,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
471
1122
  inputProps={{
472
1123
  ...params.inputProps,
473
1124
  'aria-labelledby': labelId,
1125
+ onKeyDown: handleKeyDown,
474
1126
  }}
475
1127
  placeholder={placeholder}
476
1128
  error={error}