PublicationsComboBox.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react'
  2. import { useEffect, useState } from 'react'
  3. import { ControllerRenderProps } from 'react-hook-form'
  4. import {
  5. Badge,
  6. Button,
  7. cn,
  8. Command,
  9. CommandEmpty,
  10. CommandGroup,
  11. CommandInput,
  12. CommandItem,
  13. CommandList,
  14. CommandSeparator,
  15. Popover,
  16. PopoverContent,
  17. PopoverTrigger,
  18. ScrollArea,
  19. } from 'ui'
  20. import type { ReplicationPublication } from '@/data/replication/publications-query'
  21. interface PublicationsComboBoxProps {
  22. publications: ReplicationPublication[]
  23. isLoadingPublications: boolean
  24. onNewPublicationClick: () => void
  25. field: ControllerRenderProps<any, 'publicationName'>
  26. }
  27. export const PublicationsComboBox = ({
  28. publications,
  29. isLoadingPublications,
  30. onNewPublicationClick,
  31. field,
  32. }: PublicationsComboBoxProps) => {
  33. const [dropdownOpen, setDropdownOpen] = useState(false)
  34. const [selectedPublication, setSelectedPublication] = useState<string>(field?.value || '')
  35. const [searchTerm, setSearchTerm] = useState('')
  36. function handlePublicationSelect(pub: string) {
  37. setSelectedPublication(pub)
  38. setDropdownOpen(false)
  39. field.onChange(pub)
  40. }
  41. useEffect(() => {
  42. setSelectedPublication(field?.value || '')
  43. }, [field?.value])
  44. return (
  45. <Popover
  46. modal={false}
  47. open={dropdownOpen}
  48. onOpenChange={(open) => {
  49. setDropdownOpen(open)
  50. if (!open && field?.onBlur) {
  51. field.onBlur()
  52. }
  53. }}
  54. >
  55. <PopoverTrigger asChild>
  56. <Button
  57. type="default"
  58. size="medium"
  59. className={cn(
  60. 'w-full [&>span]:w-full text-left',
  61. !selectedPublication && 'text-foreground-muted'
  62. )}
  63. iconRight={<ChevronsUpDown className="text-foreground-muted" strokeWidth={2} size={14} />}
  64. name={field.name}
  65. onBlur={field.onBlur}
  66. >
  67. {selectedPublication || 'Select publication'}
  68. </Button>
  69. </PopoverTrigger>
  70. <PopoverContent sameWidthAsTrigger className="p-0" align="start">
  71. <Command>
  72. <CommandInput
  73. placeholder="Find publication..."
  74. className="text-xs"
  75. value={searchTerm}
  76. onValueChange={setSearchTerm}
  77. />
  78. <div className="px-2 pt-2 pb-1">
  79. <p className="text-xs text-foreground-lighter">
  80. Publications with no tables are hidden
  81. </p>
  82. </div>
  83. <CommandList>
  84. <CommandEmpty>
  85. {isLoadingPublications ? (
  86. <div className="flex items-center gap-2 text-center justify-center">
  87. <Loader2 size={12} className="animate-spin" />
  88. Loading...
  89. </div>
  90. ) : (
  91. 'No publications found'
  92. )}
  93. </CommandEmpty>
  94. <CommandGroup>
  95. {publications.length === 0 && (
  96. <p className="text-foreground-lighter text-xs py-3 px-2">
  97. No publications available
  98. </p>
  99. )}
  100. <ScrollArea className={publications.length > 7 ? 'h-[210px]' : ''}>
  101. {publications.map((pub) => (
  102. <CommandItem
  103. key={pub.name}
  104. className="cursor-pointer flex items-center justify-between space-x-2 w-full"
  105. onSelect={() => {
  106. handlePublicationSelect(pub.name)
  107. }}
  108. onClick={() => {
  109. handlePublicationSelect(pub.name)
  110. }}
  111. >
  112. <span>{pub.name}</span>
  113. <div className="flex items-center gap-2">
  114. <Badge
  115. variant="default"
  116. className="rounded-full px-2 py-0.5 text-[10px] font-normal border border-border bg-surface-100"
  117. >
  118. {pub.tables.length} {pub.tables.length === 1 ? 'table' : 'tables'}
  119. </Badge>
  120. {selectedPublication === pub.name && (
  121. <Check className="text-brand" strokeWidth={2} size={13} />
  122. )}
  123. </div>
  124. </CommandItem>
  125. ))}
  126. </ScrollArea>
  127. </CommandGroup>
  128. <CommandSeparator />
  129. <CommandGroup>
  130. <CommandItem
  131. className="cursor-pointer w-full"
  132. onSelect={onNewPublicationClick}
  133. onClick={onNewPublicationClick}
  134. >
  135. <Plus size={14} strokeWidth={1.5} className="mr-2" />
  136. <p>New publication</p>
  137. </CommandItem>
  138. </CommandGroup>
  139. </CommandList>
  140. </Command>
  141. </PopoverContent>
  142. </Popover>
  143. )
  144. }