@tuturuuu/ui 0.0.4

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.
Files changed (104) hide show
  1. package/.checksum +1 -0
  2. package/README.md +46 -0
  3. package/components.json +20 -0
  4. package/eslint.config.mjs +20 -0
  5. package/jsr.json +10 -0
  6. package/package.json +120 -0
  7. package/postcss.config.mjs +8 -0
  8. package/rollup.config.js +40 -0
  9. package/src/components/ui/accordion.tsx +70 -0
  10. package/src/components/ui/alert-dialog.tsx +156 -0
  11. package/src/components/ui/alert.tsx +58 -0
  12. package/src/components/ui/aspect-ratio.tsx +11 -0
  13. package/src/components/ui/avatar.tsx +52 -0
  14. package/src/components/ui/badge.tsx +49 -0
  15. package/src/components/ui/breadcrumb.tsx +108 -0
  16. package/src/components/ui/button.tsx +61 -0
  17. package/src/components/ui/calendar.tsx +212 -0
  18. package/src/components/ui/card.tsx +74 -0
  19. package/src/components/ui/carousel.tsx +240 -0
  20. package/src/components/ui/chart.tsx +365 -0
  21. package/src/components/ui/checkbox.tsx +31 -0
  22. package/src/components/ui/codeblock.tsx +161 -0
  23. package/src/components/ui/collapsible.tsx +33 -0
  24. package/src/components/ui/color-picker.tsx +143 -0
  25. package/src/components/ui/command.tsx +176 -0
  26. package/src/components/ui/context-menu.tsx +251 -0
  27. package/src/components/ui/custom/autosize-textarea.tsx +111 -0
  28. package/src/components/ui/custom/calendar/core.tsx +61 -0
  29. package/src/components/ui/custom/calendar/day-cell.tsx +74 -0
  30. package/src/components/ui/custom/calendar/month-header.tsx +59 -0
  31. package/src/components/ui/custom/calendar/month-view.tsx +110 -0
  32. package/src/components/ui/custom/calendar/utils.ts +76 -0
  33. package/src/components/ui/custom/calendar/year-calendar.tsx +64 -0
  34. package/src/components/ui/custom/calendar/year-view.tsx +58 -0
  35. package/src/components/ui/custom/combobox.tsx +197 -0
  36. package/src/components/ui/custom/common-footer.tsx +215 -0
  37. package/src/components/ui/custom/compared-date-range-picker.tsx +561 -0
  38. package/src/components/ui/custom/date-input.tsx +279 -0
  39. package/src/components/ui/custom/empty-card.tsx +39 -0
  40. package/src/components/ui/custom/feature-summary.tsx +135 -0
  41. package/src/components/ui/custom/file-uploader.tsx +349 -0
  42. package/src/components/ui/custom/input-field.tsx +29 -0
  43. package/src/components/ui/custom/loading-indicator.tsx +28 -0
  44. package/src/components/ui/custom/modifiable-dialog-trigger.tsx +83 -0
  45. package/src/components/ui/custom/month-picker.tsx +157 -0
  46. package/src/components/ui/custom/report-preview.tsx +175 -0
  47. package/src/components/ui/custom/search-bar.tsx +56 -0
  48. package/src/components/ui/custom/select-field.tsx +78 -0
  49. package/src/components/ui/custom/tables/data-table-column-header.tsx +72 -0
  50. package/src/components/ui/custom/tables/data-table-create-button.tsx +31 -0
  51. package/src/components/ui/custom/tables/data-table-faceted-filter.tsx +142 -0
  52. package/src/components/ui/custom/tables/data-table-pagination.tsx +243 -0
  53. package/src/components/ui/custom/tables/data-table-refresh-button.tsx +45 -0
  54. package/src/components/ui/custom/tables/data-table-toolbar.tsx +133 -0
  55. package/src/components/ui/custom/tables/data-table-view-options.tsx +112 -0
  56. package/src/components/ui/custom/tables/data-table.tsx +228 -0
  57. package/src/components/ui/custom/uploaded-files-card.tsx +50 -0
  58. package/src/components/ui/dialog.tsx +137 -0
  59. package/src/components/ui/drawer.tsx +131 -0
  60. package/src/components/ui/dropdown-menu.tsx +256 -0
  61. package/src/components/ui/form.tsx +167 -0
  62. package/src/components/ui/hover-card.tsx +41 -0
  63. package/src/components/ui/icons.tsx +506 -0
  64. package/src/components/ui/input-otp.tsx +78 -0
  65. package/src/components/ui/input.tsx +18 -0
  66. package/src/components/ui/label.tsx +23 -0
  67. package/src/components/ui/markdown.tsx +7 -0
  68. package/src/components/ui/menubar.tsx +275 -0
  69. package/src/components/ui/navigation-menu.tsx +169 -0
  70. package/src/components/ui/pagination.tsx +126 -0
  71. package/src/components/ui/popover.tsx +47 -0
  72. package/src/components/ui/progress.tsx +30 -0
  73. package/src/components/ui/radio-group.tsx +44 -0
  74. package/src/components/ui/resizable.tsx +55 -0
  75. package/src/components/ui/scroll-area.tsx +57 -0
  76. package/src/components/ui/select.tsx +180 -0
  77. package/src/components/ui/separator.tsx +27 -0
  78. package/src/components/ui/sheet.tsx +138 -0
  79. package/src/components/ui/sidebar.tsx +734 -0
  80. package/src/components/ui/skeleton.tsx +13 -0
  81. package/src/components/ui/slider.tsx +62 -0
  82. package/src/components/ui/sonner.tsx +29 -0
  83. package/src/components/ui/switch.tsx +30 -0
  84. package/src/components/ui/table.tsx +112 -0
  85. package/src/components/ui/tabs.tsx +68 -0
  86. package/src/components/ui/tag-input.tsx +141 -0
  87. package/src/components/ui/textarea.tsx +17 -0
  88. package/src/components/ui/time-picker-input.tsx +117 -0
  89. package/src/components/ui/time-picker-utils.tsx +146 -0
  90. package/src/components/ui/toast.tsx +128 -0
  91. package/src/components/ui/toaster.tsx +35 -0
  92. package/src/components/ui/toggle-group.tsx +72 -0
  93. package/src/components/ui/toggle.tsx +46 -0
  94. package/src/components/ui/tooltip.tsx +60 -0
  95. package/src/globals.css +252 -0
  96. package/src/hooks/use-callback-ref.ts +28 -0
  97. package/src/hooks/use-controllable-state.ts +68 -0
  98. package/src/hooks/use-copy-to-clipboard.ts +46 -0
  99. package/src/hooks/use-form.ts +23 -0
  100. package/src/hooks/use-forwarded-ref.ts +17 -0
  101. package/src/hooks/use-mobile.tsx +21 -0
  102. package/src/hooks/use-toast.ts +191 -0
  103. package/src/resolvers.ts +3 -0
  104. package/tsconfig.json +17 -0
@@ -0,0 +1,175 @@
1
+ import { Separator } from '../separator';
2
+ import { ReactNode } from 'react';
3
+
4
+ export default function ReportPreview({
5
+ t,
6
+ lang,
7
+ data,
8
+ parseDynamicText,
9
+ getConfig,
10
+ }: {
11
+ lang: string;
12
+ data?: {
13
+ title: string;
14
+ content: string;
15
+ score: string;
16
+ feedback: string;
17
+ };
18
+ t: any;
19
+ // eslint-disable-next-line no-unused-vars
20
+ parseDynamicText: (text?: string | null) => ReactNode;
21
+ // eslint-disable-next-line no-unused-vars
22
+ getConfig: (id: string) => string | null | undefined;
23
+ }) {
24
+ return (
25
+ <div className="overflow-x-auto xl:flex-none">
26
+ <div
27
+ id="printable-area"
28
+ className="h-fit w-full flex-none rounded-xl dark:bg-foreground/10 print:p-4"
29
+ >
30
+ <div className="h-full rounded-lg border p-4 text-foreground md:p-12">
31
+ <div className="flex flex-wrap items-center justify-between gap-8">
32
+ {getConfig('BRAND_LOGO_URL') && (
33
+ <img
34
+ src={getConfig('BRAND_LOGO_URL')!}
35
+ alt="logo"
36
+ // onLoad={() => setIsLogoLoaded(true)}
37
+ />
38
+ )}
39
+
40
+ <div className="text-center">
41
+ {getConfig('BRAND_NAME') && (
42
+ <div className="text-center text-lg font-bold">
43
+ {getConfig('BRAND_NAME')}
44
+ </div>
45
+ )}
46
+
47
+ {getConfig('BRAND_LOCATION') && (
48
+ <div className="text-center font-semibold">
49
+ {getConfig('BRAND_LOCATION')}
50
+ </div>
51
+ )}
52
+
53
+ {getConfig('BRAND_PHONE_NUMBER') && (
54
+ <div className="flex flex-wrap items-center justify-center gap-2 text-center text-sm font-semibold break-keep print:gap-2">
55
+ {getConfig('BRAND_PHONE_NUMBER')}
56
+ </div>
57
+ )}
58
+ </div>
59
+ </div>
60
+
61
+ {(!!getConfig('BRAND_NAME') ||
62
+ !!getConfig('BRAND_LOCATION') ||
63
+ !!getConfig('BRAND_PHONE_NUMBER')) && (
64
+ <Separator className="my-4" />
65
+ )}
66
+
67
+ <div className="text-center text-lg font-bold text-foreground uppercase">
68
+ {getConfig('REPORT_TITLE_PREFIX')}{' '}
69
+ {new Date().toLocaleDateString(lang, {
70
+ month: 'long',
71
+ })}
72
+ /
73
+ {new Date().toLocaleDateString(lang, {
74
+ year: 'numeric',
75
+ })}{' '}
76
+ {getConfig('REPORT_TITLE_SUFFIX')}
77
+ </div>
78
+
79
+ {getConfig('REPORT_INTRO') && (
80
+ <div className="mt-2 text-left text-sm whitespace-pre-wrap">
81
+ {parseDynamicText(getConfig('REPORT_INTRO'))}
82
+ </div>
83
+ )}
84
+
85
+ {(!!getConfig('REPORT_CONTENT_TEXT') ||
86
+ !!getConfig('REPORT_SCORE_TEXT') ||
87
+ !!getConfig('REPORT_FEEDBACK_TEXT')) && (
88
+ <div className="my-4 flex flex-col justify-stretch rounded border-2 border-foreground/50 text-sm md:flex-row">
89
+ {getConfig('REPORT_CONTENT_TEXT') && (
90
+ <div className="md:flex-[2]">
91
+ <div className="flex h-16 items-center justify-center p-2 text-center text-sm font-bold whitespace-pre-wrap">
92
+ {getConfig('REPORT_CONTENT_TEXT')}
93
+ </div>
94
+ <div
95
+ className={`min-h-[6rem] border-t-2 border-foreground/50 p-2 font-semibold break-words text-ellipsis whitespace-pre-line ${
96
+ !data?.content ? 'text-center underline' : 'text-left'
97
+ }`}
98
+ >
99
+ <span className={data?.content ? '' : 'opacity-50'}>
100
+ {data?.content || t('common.empty')}
101
+ </span>
102
+ </div>
103
+ </div>
104
+ )}
105
+
106
+ {getConfig('REPORT_CONTENT_TEXT') &&
107
+ getConfig('REPORT_SCORE_TEXT') && (
108
+ <div className="h-[2px] min-h-full w-auto shrink-0 bg-foreground/50 md:h-auto md:w-[2px]" />
109
+ )}
110
+
111
+ {getConfig('REPORT_SCORE_TEXT') && (
112
+ <div className="flex-[1] border-foreground/50">
113
+ <div className="flex h-16 flex-col items-center justify-center p-2 text-sm font-bold whitespace-pre-wrap">
114
+ {getConfig('REPORT_SCORE_TEXT')}
115
+ </div>
116
+ <div className="flex min-h-[6rem] justify-center border-t-2 border-foreground/50 p-2 text-center break-words text-ellipsis whitespace-pre-line">
117
+ <span
118
+ className={
119
+ data?.score
120
+ ? 'text-2xl font-bold text-red-600 underline dark:text-red-300'
121
+ : 'font-semibold opacity-50'
122
+ }
123
+ >
124
+ {data?.score || '-'}
125
+ </span>
126
+ </div>
127
+ </div>
128
+ )}
129
+
130
+ {(getConfig('REPORT_SCORE_TEXT') ||
131
+ getConfig('REPORT_CONTENT_TEXT')) &&
132
+ getConfig('REPORT_FEEDBACK_TEXT') && (
133
+ <div className="h-[2px] min-h-full w-auto shrink-0 bg-foreground/50 md:h-auto md:w-[2px]" />
134
+ )}
135
+
136
+ {getConfig('REPORT_FEEDBACK_TEXT') && (
137
+ <div className="flex-[2]">
138
+ <div className="flex h-16 items-center justify-center p-2 text-sm font-bold whitespace-pre-wrap">
139
+ {getConfig('REPORT_FEEDBACK_TEXT')}
140
+ </div>
141
+ <div
142
+ className={`min-h-[6rem] border-t-2 border-foreground/50 p-2 font-semibold break-words text-ellipsis whitespace-pre-line ${
143
+ !data?.feedback ? 'text-center underline' : 'text-left'
144
+ }`}
145
+ >
146
+ <span className={data?.feedback ? '' : 'opacity-50'}>
147
+ {data?.feedback || t('common.empty')}
148
+ </span>
149
+ </div>
150
+ </div>
151
+ )}
152
+ </div>
153
+ )}
154
+
155
+ <div className="text-left text-sm">
156
+ {getConfig('REPORT_CONCLUSION')}
157
+
158
+ {getConfig('REPORT_CONCLUSION') && getConfig('REPORT_CLOSING') && (
159
+ <>
160
+ <br />
161
+ <br />
162
+ </>
163
+ )}
164
+
165
+ {getConfig('REPORT_CLOSING') && (
166
+ <span className="font-semibold">
167
+ {getConfig('REPORT_CLOSING')}
168
+ </span>
169
+ )}
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ );
175
+ }
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { Input } from '../input';
4
+ import { cn } from '@tuturuuu/utils/format';
5
+ import { debounce } from 'lodash';
6
+ import { useCallback, useEffect, useState } from 'react';
7
+
8
+ interface Props {
9
+ t: any;
10
+ defaultValue?: string;
11
+ className?: string;
12
+ // eslint-disable-next-line no-unused-vars
13
+ onSearch?: (query: string) => void;
14
+ }
15
+
16
+ // Assuming the rest of your imports and Props interface are unchanged
17
+
18
+ const SearchBar = ({ t, defaultValue = '', className, onSearch }: Props) => {
19
+ // Memoize the updateQuery function to ensure debounce works correctly
20
+ const updateQuery = useCallback(
21
+ debounce((query: string) => {
22
+ if (onSearch) {
23
+ onSearch(query);
24
+ }
25
+ }, 300),
26
+ [onSearch] // Re-create the debounced function only if onSearch changes
27
+ );
28
+
29
+ const searchPlaceholder = t('search.search-placeholder');
30
+ const [value, setValue] = useState(defaultValue);
31
+
32
+ useEffect(() => {
33
+ setValue(defaultValue);
34
+ }, [defaultValue]);
35
+
36
+ useEffect(() => {
37
+ // Cleanup the debounced function on component unmount
38
+ return () => {
39
+ updateQuery.cancel();
40
+ };
41
+ }, [updateQuery]);
42
+
43
+ return (
44
+ <Input
45
+ placeholder={searchPlaceholder}
46
+ value={value}
47
+ onChange={(e) => {
48
+ setValue(e.target.value);
49
+ updateQuery(e.target.value);
50
+ }}
51
+ className={cn('h-8 min-w-64 placeholder:text-foreground/60', className)}
52
+ />
53
+ );
54
+ };
55
+
56
+ export default SearchBar;
@@ -0,0 +1,78 @@
1
+ import { Label } from '../label';
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue,
8
+ } from '../select';
9
+ import { Root } from '@radix-ui/react-select';
10
+ import { cn } from '@tuturuuu/utils/format';
11
+ import React, { forwardRef } from 'react';
12
+
13
+ type SelectOption = {
14
+ value: string;
15
+ label: string;
16
+ disabled?: boolean;
17
+ };
18
+
19
+ interface ClassNames {
20
+ root?: string;
21
+ label?: string;
22
+ selectTrigger?: string;
23
+ selectContent?: string;
24
+ selectItem?: string;
25
+ selectValue?: string;
26
+ }
27
+
28
+ interface SelectFieldProps {
29
+ id: string;
30
+ label?: string;
31
+ placeholder?: string;
32
+ options: SelectOption[];
33
+ classNames?: ClassNames;
34
+ }
35
+
36
+ export interface SelectProps
37
+ extends React.ComponentPropsWithoutRef<typeof Root> {}
38
+
39
+ // Merge the two interfaces
40
+ type Props = SelectFieldProps & SelectProps;
41
+
42
+ const SelectField = forwardRef<React.ComponentRef<typeof Root>, Props>(
43
+ ({ id, label, placeholder, options, classNames, ...props }, ref) => {
44
+ return (
45
+ <div className={cn('grid gap-2', classNames?.root)}>
46
+ {label && (
47
+ <Label htmlFor={id} className={classNames?.label}>
48
+ {label}
49
+ </Label>
50
+ )}
51
+ <Select {...props}>
52
+ <SelectTrigger ref={ref} className={classNames?.selectTrigger}>
53
+ <SelectValue
54
+ id={id}
55
+ placeholder={placeholder}
56
+ className={classNames?.selectValue}
57
+ />
58
+ </SelectTrigger>
59
+ <SelectContent className={classNames?.selectContent}>
60
+ {options.map((option) => (
61
+ <SelectItem
62
+ key={option.value}
63
+ value={option.value}
64
+ className={classNames?.selectItem}
65
+ disabled={option.disabled}
66
+ >
67
+ {option.label}
68
+ </SelectItem>
69
+ ))}
70
+ </SelectContent>
71
+ </Select>
72
+ </div>
73
+ );
74
+ }
75
+ );
76
+
77
+ SelectField.displayName = 'SelectField';
78
+ export { SelectField };
@@ -0,0 +1,72 @@
1
+ import { Button } from '../../button';
2
+ import {
3
+ DropdownMenu,
4
+ DropdownMenuContent,
5
+ DropdownMenuItem,
6
+ DropdownMenuSeparator,
7
+ DropdownMenuTrigger,
8
+ } from '../../dropdown-menu';
9
+ import { Column } from '@tanstack/react-table';
10
+ import { cn } from '@tuturuuu/utils/format';
11
+ import { ArrowDown, ArrowUp, ChevronDown, EyeOff } from 'lucide-react';
12
+ import React from 'react';
13
+
14
+ interface DataTableColumnHeaderProps<TData, TValue>
15
+ extends React.HTMLAttributes<HTMLDivElement> {
16
+ t: any;
17
+ column: Column<TData, TValue>;
18
+ title?: string;
19
+ }
20
+
21
+ export function DataTableColumnHeader<TData, TValue>({
22
+ t,
23
+ column,
24
+ title,
25
+ className,
26
+ }: DataTableColumnHeaderProps<TData, TValue>) {
27
+ if (!column.getCanSort()) {
28
+ return <div className={cn(className)}>{title}</div>;
29
+ }
30
+
31
+ return (
32
+ <div className={cn('flex items-center space-x-2', className)}>
33
+ <DropdownMenu modal={false}>
34
+ <DropdownMenuTrigger asChild>
35
+ <Button
36
+ size="sm"
37
+ variant="ghost"
38
+ className="-ml-3 h-8 data-[state=open]:bg-accent"
39
+ >
40
+ <span className="line-clamp-1">{title}</span>
41
+ {column.getIsSorted() === 'desc' ? (
42
+ <ArrowDown className="ml-2 h-4 w-4" />
43
+ ) : column.getIsSorted() === 'asc' ? (
44
+ <ArrowUp className="ml-2 h-4 w-4" />
45
+ ) : (
46
+ <ChevronDown className="ml-2 h-4 w-4" />
47
+ )}
48
+ </Button>
49
+ </DropdownMenuTrigger>
50
+ <DropdownMenuContent align="start">
51
+ <DropdownMenuItem className="justify-center" disabled>
52
+ <span className="line-clamp-1">{title}</span>
53
+ </DropdownMenuItem>
54
+ <DropdownMenuSeparator />
55
+ <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
56
+ <ArrowUp className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
57
+ {t('common.ascending')}
58
+ </DropdownMenuItem>
59
+ <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
60
+ <ArrowDown className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
61
+ {t('common.descending')}
62
+ </DropdownMenuItem>
63
+ <DropdownMenuSeparator />
64
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
65
+ <EyeOff className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
66
+ {t('common.hide_column')}
67
+ </DropdownMenuItem>
68
+ </DropdownMenuContent>
69
+ </DropdownMenu>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { Button } from '../../button';
4
+ import { DialogContent, DialogTrigger } from '../../dialog';
5
+ import { Plus } from 'lucide-react';
6
+ import { ReactNode } from 'react';
7
+
8
+ export interface DataTableCreateButtonProps {
9
+ newObjectTitle?: string;
10
+ createButtonText?: string;
11
+ editContent?: ReactNode;
12
+ }
13
+
14
+ export function DataTableCreateButton(props: DataTableCreateButtonProps) {
15
+ return (
16
+ <>
17
+ <DialogTrigger asChild>
18
+ <Button size="sm" className="col-span-full ml-auto h-8 w-full md:w-fit">
19
+ <Plus className="h-4 w-4" />
20
+ {props.newObjectTitle || props.createButtonText}
21
+ </Button>
22
+ </DialogTrigger>
23
+ <DialogContent
24
+ className="sm:max-w-[425px]"
25
+ onOpenAutoFocus={(e) => e.preventDefault()}
26
+ >
27
+ {props.editContent}
28
+ </DialogContent>
29
+ </>
30
+ );
31
+ }
@@ -0,0 +1,142 @@
1
+ import { Badge } from '../../badge';
2
+ import { Button } from '../../button';
3
+ import {
4
+ Command,
5
+ CommandEmpty,
6
+ CommandGroup,
7
+ CommandInput,
8
+ CommandItem,
9
+ CommandList,
10
+ CommandSeparator,
11
+ } from '../../command';
12
+ import { Popover, PopoverContent, PopoverTrigger } from '../../popover';
13
+ import { Separator } from '../../separator';
14
+ import { Column } from '@tanstack/react-table';
15
+ import { cn } from '@tuturuuu/utils/format';
16
+ import { Check, PlusCircle } from 'lucide-react';
17
+ import * as React from 'react';
18
+
19
+ interface DataTableFacetedFilterProps<TData, TValue> {
20
+ column?: Column<TData, TValue>;
21
+ title?: string;
22
+ options: {
23
+ label: string;
24
+ value: string;
25
+ icon?: React.ComponentType<{ className?: string }>;
26
+ }[];
27
+ }
28
+
29
+ export function DataTableFacetedFilter<TData, TValue>({
30
+ column,
31
+ title,
32
+ options,
33
+ }: DataTableFacetedFilterProps<TData, TValue>) {
34
+ const facets = column?.getFacetedUniqueValues();
35
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
36
+
37
+ return (
38
+ <Popover>
39
+ <PopoverTrigger asChild>
40
+ <Button variant="outline" size="sm" className="h-8 border-dashed">
41
+ <PlusCircle className="mr-2 h-4 w-4" />
42
+ {title}
43
+ {selectedValues?.size > 0 && (
44
+ <>
45
+ <Separator orientation="vertical" className="mx-2 h-4" />
46
+ <Badge
47
+ variant="secondary"
48
+ className="rounded-sm px-1 font-normal lg:hidden"
49
+ >
50
+ {selectedValues.size}
51
+ </Badge>
52
+ <div className="hidden space-x-1 lg:flex">
53
+ {selectedValues.size > 2 ? (
54
+ <Badge
55
+ variant="secondary"
56
+ className="rounded-sm px-1 font-normal"
57
+ >
58
+ {selectedValues.size} selected
59
+ </Badge>
60
+ ) : (
61
+ options
62
+ .filter((option) => selectedValues.has(option.value))
63
+ .map((option) => (
64
+ <Badge
65
+ variant="secondary"
66
+ key={option.value}
67
+ className="rounded-sm px-1 font-normal"
68
+ >
69
+ {option.label}
70
+ </Badge>
71
+ ))
72
+ )}
73
+ </div>
74
+ </>
75
+ )}
76
+ </Button>
77
+ </PopoverTrigger>
78
+ <PopoverContent className="w-[200px] p-0" align="start">
79
+ <Command>
80
+ <CommandInput placeholder={title} />
81
+ <CommandList>
82
+ <CommandEmpty>No results found.</CommandEmpty>
83
+ <CommandGroup>
84
+ {options.map((option) => {
85
+ const isSelected = selectedValues.has(option.value);
86
+ return (
87
+ <CommandItem
88
+ key={option.value}
89
+ onSelect={() => {
90
+ if (isSelected) {
91
+ selectedValues.delete(option.value);
92
+ } else {
93
+ selectedValues.add(option.value);
94
+ }
95
+ const filterValues = Array.from(selectedValues);
96
+ column?.setFilterValue(
97
+ filterValues.length ? filterValues : undefined
98
+ );
99
+ }}
100
+ >
101
+ <div
102
+ className={cn(
103
+ 'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
104
+ isSelected
105
+ ? 'bg-primary text-primary-foreground'
106
+ : 'opacity-50 [&_svg]:invisible'
107
+ )}
108
+ >
109
+ <Check className={cn('h-4 w-4')} />
110
+ </div>
111
+ {option.icon && (
112
+ <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
113
+ )}
114
+ <span>{option.label}</span>
115
+ {facets?.get(option.value) && (
116
+ <span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
117
+ {facets.get(option.value)}
118
+ </span>
119
+ )}
120
+ </CommandItem>
121
+ );
122
+ })}
123
+ </CommandGroup>
124
+ {selectedValues.size > 0 && (
125
+ <>
126
+ <CommandSeparator />
127
+ <CommandGroup>
128
+ <CommandItem
129
+ onSelect={() => column?.setFilterValue(undefined)}
130
+ className="justify-center text-center"
131
+ >
132
+ Clear filters
133
+ </CommandItem>
134
+ </CommandGroup>
135
+ </>
136
+ )}
137
+ </CommandList>
138
+ </Command>
139
+ </PopoverContent>
140
+ </Popover>
141
+ );
142
+ }