@recruitnepal/shared-packages 1.6.0 → 1.7.1

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 (49) hide show
  1. package/dist/components/common/NoData.d.ts +8 -0
  2. package/dist/components/common/NoData.d.ts.map +1 -0
  3. package/dist/components/common/NoData.js +4 -0
  4. package/dist/components/ui/BulletListTextarea.d.ts +19 -0
  5. package/dist/components/ui/BulletListTextarea.d.ts.map +1 -0
  6. package/dist/components/ui/BulletListTextarea.js +60 -0
  7. package/dist/components/ui/DesignationSelect.d.ts +13 -0
  8. package/dist/components/ui/DesignationSelect.d.ts.map +1 -0
  9. package/dist/components/ui/DesignationSelect.js +78 -0
  10. package/dist/components/ui/IndustrySelect.d.ts +13 -0
  11. package/dist/components/ui/IndustrySelect.d.ts.map +1 -0
  12. package/dist/components/ui/IndustrySelect.js +78 -0
  13. package/dist/components/ui/MultiSelectOptions.d.ts +18 -0
  14. package/dist/components/ui/MultiSelectOptions.d.ts.map +1 -0
  15. package/dist/components/ui/MultiSelectOptions.js +85 -0
  16. package/dist/components/ui/RatingStars.d.ts +10 -0
  17. package/dist/components/ui/RatingStars.d.ts.map +1 -0
  18. package/dist/components/ui/RatingStars.js +10 -0
  19. package/dist/components/ui/command.d.ts +68 -0
  20. package/dist/components/ui/command.d.ts.map +1 -0
  21. package/dist/components/ui/command.js +24 -0
  22. package/dist/components/ui/popover.d.ts +7 -0
  23. package/dist/components/ui/popover.d.ts.map +1 -0
  24. package/dist/components/ui/popover.js +10 -0
  25. package/dist/hooks/useDebounce.d.ts +2 -0
  26. package/dist/hooks/useDebounce.d.ts.map +1 -0
  27. package/dist/hooks/useDebounce.js +9 -0
  28. package/dist/hooks/useDesignations.d.ts +51 -0
  29. package/dist/hooks/useDesignations.d.ts.map +1 -0
  30. package/dist/hooks/useDesignations.js +117 -0
  31. package/dist/hooks/useIndustries.d.ts +51 -0
  32. package/dist/hooks/useIndustries.d.ts.map +1 -0
  33. package/dist/hooks/useIndustries.js +118 -0
  34. package/dist/hooks/useSkills.d.ts +19 -0
  35. package/dist/hooks/useSkills.d.ts.map +1 -0
  36. package/dist/hooks/useSkills.js +39 -0
  37. package/dist/index.d.ts +21 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +17 -0
  40. package/dist/utils/cn.d.ts +2 -0
  41. package/dist/utils/cn.d.ts.map +1 -0
  42. package/dist/utils/cn.js +3 -0
  43. package/dist/utils/commonDropdownOptions.d.ts +6 -0
  44. package/dist/utils/commonDropdownOptions.d.ts.map +1 -0
  45. package/dist/utils/commonDropdownOptions.js +15 -0
  46. package/dist/utils/generateSearchQuery.d.ts +2 -0
  47. package/dist/utils/generateSearchQuery.d.ts.map +1 -0
  48. package/dist/utils/generateSearchQuery.js +5 -0
  49. package/package.json +51 -1
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type NoDataProps = {
3
+ message?: string;
4
+ description?: string;
5
+ };
6
+ export declare const NoData: React.FC<NoDataProps>;
7
+ export {};
8
+ //# sourceMappingURL=NoData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoData.d.ts","sourceRoot":"","sources":["../../../src/components/common/NoData.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,KAAK,WAAW,GAAG;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAcxC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const NoData = ({ message = 'No data found', description, }) => {
3
+ return (_jsx("div", { className: "w-full flex flex-col gap-2 items-center justify-center py-5", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-gray-700", children: message }), description && (_jsx("p", { className: "text-gray-500 text-xs mt-1", children: description }))] }) }));
4
+ };
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ declare const BULLET = "\u2022 ";
3
+ declare function normalizeToArray(value: string[] | string | undefined | null): string[];
4
+ declare function stripBullet(line: string): string;
5
+ export interface BulletListTextareaProps {
6
+ /** Array of strings; each item is one bullet point. Empty array is treated as one empty line. */
7
+ value: string[] | string | undefined | null;
8
+ /** Called with array of strings (bullet prefixes stripped). */
9
+ onChange: (value: string[]) => void;
10
+ placeholder?: string;
11
+ rows?: number;
12
+ className?: string;
13
+ disabled?: boolean;
14
+ /** Optional hint text below the textarea. */
15
+ hint?: React.ReactNode;
16
+ }
17
+ declare const BulletListTextarea: React.ForwardRefExoticComponent<BulletListTextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
18
+ export { BulletListTextarea, BULLET, stripBullet, normalizeToArray };
19
+ //# sourceMappingURL=BulletListTextarea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BulletListTextarea.d.ts","sourceRoot":"","sources":["../../../src/components/ui/BulletListTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,QAAA,MAAM,MAAM,YAAO,CAAC;AAEpB,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,EAAE,CAQ/E;AAED,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED,MAAM,WAAW,uBAAuB;IACtC,iGAAiG;IACjG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5C,+DAA+D;IAC/D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,kBAAkB,qGA2EvB,CAAC;AAIF,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { cn } from '../../utils/cn';
5
+ const BULLET = '• ';
6
+ function normalizeToArray(value) {
7
+ if (Array.isArray(value)) {
8
+ return value.length > 0 ? value : [''];
9
+ }
10
+ if (typeof value === 'string' && value.trim()) {
11
+ return value.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
12
+ }
13
+ return [''];
14
+ }
15
+ function stripBullet(line) {
16
+ return line.replace(/^[•·]\s*/, '').trimEnd();
17
+ }
18
+ const BulletListTextarea = React.forwardRef(({ value, onChange, placeholder = `${BULLET}e.g. Built APIs`, rows = 5, className, disabled, hint, }, ref) => {
19
+ const items = normalizeToArray(value);
20
+ const textValue = items.map((line) => BULLET + line).join('\n');
21
+ const handleChange = (e) => {
22
+ const lines = e.target.value.split(/\r?\n/).map(stripBullet);
23
+ onChange(lines.length > 0 ? lines : ['']);
24
+ };
25
+ const handleKeyDown = (e) => {
26
+ const ta = e.currentTarget;
27
+ const start = ta.selectionStart;
28
+ const end = ta.selectionEnd;
29
+ const rawValue = ta.value;
30
+ const before = rawValue.slice(0, start);
31
+ const after = rawValue.slice(end);
32
+ if (e.key === 'Enter') {
33
+ e.preventDefault();
34
+ const newText = before + '\n' + BULLET + after;
35
+ const newLines = newText.split(/\r?\n/).map(stripBullet);
36
+ onChange(newLines.length > 0 ? newLines : ['']);
37
+ const newCursor = start + '\n'.length + BULLET.length;
38
+ requestAnimationFrame(() => {
39
+ ta.selectionStart = ta.selectionEnd = newCursor;
40
+ });
41
+ return;
42
+ }
43
+ if (e.key === 'Backspace' && start === end) {
44
+ const lineStart = '\n' + BULLET;
45
+ if (before.endsWith(lineStart)) {
46
+ e.preventDefault();
47
+ const newText = before.slice(0, -lineStart.length) + after;
48
+ const newLines = newText.split(/\r?\n/).map(stripBullet);
49
+ onChange(newLines.length > 0 ? newLines : ['']);
50
+ const newCursor = before.length - lineStart.length;
51
+ requestAnimationFrame(() => {
52
+ ta.selectionStart = ta.selectionEnd = newCursor;
53
+ });
54
+ }
55
+ }
56
+ };
57
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("textarea", { ref: ref, placeholder: placeholder, value: textValue, onChange: handleChange, onKeyDown: handleKeyDown, rows: rows, disabled: disabled, className: cn('min-h-[120px] w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className) }), hint && _jsx("p", { className: "text-xs text-muted-foreground", children: hint })] }));
58
+ });
59
+ BulletListTextarea.displayName = 'BulletListTextarea';
60
+ export { BulletListTextarea, BULLET, stripBullet, normalizeToArray };
@@ -0,0 +1,13 @@
1
+ export interface DesignationSelectProps {
2
+ value: string;
3
+ onChange: (value: string) => void;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ className?: string;
7
+ valueType?: 'id' | 'title';
8
+ /** Optional auth token (e.g. from useSessionStore) */
9
+ token?: string | null;
10
+ }
11
+ export default function DesignationSelect({ value, onChange, placeholder, disabled, className, valueType, // Default to title since most forms use title
12
+ token, }: DesignationSelectProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=DesignationSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DesignationSelect.d.ts","sourceRoot":"","sources":["../../../src/components/ui/DesignationSelect.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC3B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAQD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACxC,KAAK,EACL,QAAQ,EACR,WAAoC,EACpC,QAAQ,EACR,SAAS,EACT,SAAmB,EAAE,8CAA8C;AACnE,KAAK,GACN,EAAE,sBAAsB,2CA6JxB"}
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { ChevronsUpDown, Check } from 'lucide-react';
5
+ import { useDesignations } from '../../hooks/useDesignations';
6
+ import { cn } from '../../utils/cn';
7
+ import { useDebounce } from '../../hooks/useDebounce';
8
+ import { Popover, PopoverContent, PopoverTrigger } from './popover';
9
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from './command';
10
+ /** Matches ShadCN <SelectTrigger> so it looks like a normal input */
11
+ const selectTriggerClasses = 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed cursor-pointer disabled:opacity-50';
12
+ const toString = (v) => String(v ?? '');
13
+ export default function DesignationSelect({ value, onChange, placeholder = 'Select a designation', disabled, className, valueType = 'title', // Default to title since most forms use title
14
+ token, }) {
15
+ const [open, setOpen] = React.useState(false);
16
+ const [searchQuery, setSearchQuery] = React.useState('');
17
+ const debouncedSearch = useDebounce(searchQuery, 300);
18
+ // Use backend search when there's a search query; infinite query for load-more on scroll
19
+ const { designations = [], designationsQuery, isLoading, } = useDesignations({
20
+ queryObject: debouncedSearch ? { search: debouncedSearch } : {},
21
+ limit: 50,
22
+ token,
23
+ });
24
+ const handleListScroll = React.useCallback((e) => {
25
+ if (!designationsQuery.hasNextPage ||
26
+ designationsQuery.isFetchingNextPage)
27
+ return;
28
+ const el = e.currentTarget;
29
+ if (el.scrollHeight - el.scrollTop - el.clientHeight < 80) {
30
+ designationsQuery.fetchNextPage();
31
+ }
32
+ }, [
33
+ designationsQuery.hasNextPage,
34
+ designationsQuery.isFetchingNextPage,
35
+ designationsQuery.fetchNextPage,
36
+ ]);
37
+ const options = React.useMemo(() => designations.map((d) => ({
38
+ id: String(d.id),
39
+ title: d.title || String(d.id),
40
+ })), [designations]);
41
+ // Find selected option based on valueType
42
+ const selectedOption = React.useMemo(() => {
43
+ if (!value)
44
+ return null;
45
+ if (valueType === 'id') {
46
+ return options.find((o) => toString(o.id) === toString(value));
47
+ }
48
+ else {
49
+ return options.find((o) => o.title === value);
50
+ }
51
+ }, [value, options, valueType]);
52
+ const selectedLabel = selectedOption?.title || '';
53
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("div", { role: "combobox", "aria-expanded": open, "aria-disabled": disabled || isLoading, tabIndex: 0, onClick: () => !(disabled || isLoading) && setOpen((v) => !v), onKeyDown: (e) => {
54
+ if ((e.key === 'Enter' || e.key === ' ') && !(disabled || isLoading)) {
55
+ e.preventDefault();
56
+ setOpen((v) => !v);
57
+ }
58
+ }, className: cn(selectTriggerClasses, className, !selectedLabel && 'text-muted-foreground'), children: [_jsx("span", { className: "truncate", children: selectedLabel || placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: "Search designation...", value: searchQuery, onValueChange: setSearchQuery }), _jsx("div", { className: "max-h-[260px] overflow-y-auto overflow-x-hidden", onScroll: handleListScroll, onWheelCapture: (e) => {
59
+ const el = e.currentTarget;
60
+ el.scrollTop += e.deltaY;
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ }, children: _jsx(CommandList, { className: "max-h-none overflow-visible", children: isLoading ? (_jsx("div", { className: "p-3 text-sm text-muted-foreground", children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(CommandEmpty, { children: debouncedSearch
64
+ ? 'No designations found. Try a different search term.'
65
+ : 'No designations available.' }), _jsxs(CommandGroup, { children: [options.map((o) => {
66
+ const isSelected = valueType === 'id'
67
+ ? toString(value) === toString(o.id)
68
+ : value === o.title;
69
+ return (_jsxs(CommandItem, { value: o.id, onSelect: () => {
70
+ const newValue = valueType === 'id' ? o.id : o.title;
71
+ onChange(newValue);
72
+ setOpen(false);
73
+ setSearchQuery('');
74
+ }, className: "cursor-pointer", children: [_jsx(Check, { className: cn('mr-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0') }), o.title] }, o.id));
75
+ }), designationsQuery.hasNextPage && (_jsx("div", { className: "py-2 text-center text-muted-foreground text-xs", children: designationsQuery.isFetchingNextPage
76
+ ? 'Loading more...'
77
+ : '\u00A0' }))] })] })) }) })] }) })] }));
78
+ }
@@ -0,0 +1,13 @@
1
+ export interface IndustrySelectProps {
2
+ value: string;
3
+ onChange: (value: string) => void;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ className?: string;
7
+ valueType?: 'id' | 'title';
8
+ /** Optional auth token (e.g. from useSessionStore) */
9
+ token?: string | null;
10
+ }
11
+ export default function IndustrySelect({ value, onChange, placeholder, disabled, className, valueType, // Default to ID for backward compatibility
12
+ token, }: IndustrySelectProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=IndustrySelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndustrySelect.d.ts","sourceRoot":"","sources":["../../../src/components/ui/IndustrySelect.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC3B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAQD,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,KAAK,EACL,QAAQ,EACR,WAAkC,EAClC,QAAQ,EACR,SAAS,EACT,SAAgB,EAAE,2CAA2C;AAC7D,KAAK,GACN,EAAE,mBAAmB,2CA6JrB"}
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { ChevronsUpDown, Check } from 'lucide-react';
5
+ import { useIndustries } from '../../hooks/useIndustries';
6
+ import { cn } from '../../utils/cn';
7
+ import { useDebounce } from '../../hooks/useDebounce';
8
+ import { Popover, PopoverContent, PopoverTrigger } from './popover';
9
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from './command';
10
+ /** Matches ShadCN <SelectTrigger> so it looks like a normal input */
11
+ const selectTriggerClasses = 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed cursor-pointer disabled:opacity-50';
12
+ const toString = (v) => String(v ?? '');
13
+ export default function IndustrySelect({ value, onChange, placeholder = 'Select an industry', disabled, className, valueType = 'id', // Default to ID for backward compatibility
14
+ token, }) {
15
+ const [open, setOpen] = React.useState(false);
16
+ const [searchQuery, setSearchQuery] = React.useState('');
17
+ const debouncedSearch = useDebounce(searchQuery, 300);
18
+ // Use backend search when there's a search query; infinite query for load-more on scroll
19
+ const { industries = [], industriesQuery, isLoading, } = useIndustries({
20
+ queryObject: debouncedSearch ? { search: debouncedSearch } : {},
21
+ limit: 50,
22
+ token,
23
+ });
24
+ const handleListScroll = React.useCallback((e) => {
25
+ if (!industriesQuery.hasNextPage ||
26
+ industriesQuery.isFetchingNextPage)
27
+ return;
28
+ const el = e.currentTarget;
29
+ if (el.scrollHeight - el.scrollTop - el.clientHeight < 80) {
30
+ industriesQuery.fetchNextPage();
31
+ }
32
+ }, [
33
+ industriesQuery.hasNextPage,
34
+ industriesQuery.isFetchingNextPage,
35
+ industriesQuery.fetchNextPage,
36
+ ]);
37
+ const options = React.useMemo(() => industries.map((i) => ({
38
+ id: String(i.id),
39
+ title: i.title || i.name || String(i.id),
40
+ })), [industries]);
41
+ // Find selected option based on valueType
42
+ const selectedOption = React.useMemo(() => {
43
+ if (!value)
44
+ return null;
45
+ if (valueType === 'id') {
46
+ return options.find((o) => toString(o.id) === toString(value));
47
+ }
48
+ else {
49
+ return options.find((o) => o.title === value);
50
+ }
51
+ }, [value, options, valueType]);
52
+ const selectedLabel = selectedOption?.title || '';
53
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("div", { role: "combobox", "aria-expanded": open, "aria-disabled": disabled || isLoading, tabIndex: 0, onClick: () => !(disabled || isLoading) && setOpen((v) => !v), onKeyDown: (e) => {
54
+ if ((e.key === 'Enter' || e.key === ' ') && !(disabled || isLoading)) {
55
+ e.preventDefault();
56
+ setOpen((v) => !v);
57
+ }
58
+ }, className: cn(selectTriggerClasses, className, !selectedLabel && 'text-muted-foreground'), children: [_jsx("span", { className: "truncate", children: selectedLabel || placeholder }), _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: "Search industry...", value: searchQuery, onValueChange: setSearchQuery }), _jsx("div", { className: "max-h-[260px] overflow-y-auto overflow-x-hidden", onScroll: handleListScroll, onWheelCapture: (e) => {
59
+ const el = e.currentTarget;
60
+ el.scrollTop += e.deltaY;
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ }, children: _jsx(CommandList, { className: "max-h-none overflow-visible", children: isLoading ? (_jsx("div", { className: "p-3 text-sm text-muted-foreground", children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(CommandEmpty, { children: debouncedSearch
64
+ ? 'No industries found. Try a different search term.'
65
+ : 'No industries available.' }), _jsxs(CommandGroup, { children: [options.map((o) => {
66
+ const isSelected = valueType === 'id'
67
+ ? toString(value) === toString(o.id)
68
+ : value === o.title;
69
+ return (_jsxs(CommandItem, { value: o.id, onSelect: () => {
70
+ const newValue = valueType === 'id' ? o.id : o.title;
71
+ onChange(newValue);
72
+ setOpen(false);
73
+ setSearchQuery('');
74
+ }, className: "cursor-pointer", children: [_jsx(Check, { className: cn('mr-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0') }), o.title] }, o.id));
75
+ }), industriesQuery.hasNextPage && (_jsx("div", { className: "py-2 text-center text-muted-foreground text-xs", children: industriesQuery.isFetchingNextPage
76
+ ? 'Loading more...'
77
+ : '\u00A0' }))] })] })) }) })] }) })] }));
78
+ }
@@ -0,0 +1,18 @@
1
+ import type { InfiniteData, UseInfiniteQueryResult } from '@tanstack/react-query';
2
+ import type { Skill } from '../../hooks/useSkills';
3
+ type Props = {
4
+ value: string[];
5
+ onChange: (value: string[]) => void;
6
+ options: string[];
7
+ query?: UseInfiniteQueryResult<InfiniteData<{
8
+ data: Skill[];
9
+ nextPage: number | null;
10
+ }>, Error>;
11
+ disabled?: boolean;
12
+ staticOnly?: boolean;
13
+ /** Optional auth token (e.g. from useSessionStore) */
14
+ token?: string | null;
15
+ };
16
+ export declare function MultiSelectOptions({ value, onChange, options, query: externalQuery, disabled, staticOnly, token, }: Props): import("react/jsx-runtime").JSX.Element;
17
+ export {};
18
+ //# sourceMappingURL=MultiSelectOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiSelectOptions.d.ts","sourceRoot":"","sources":["../../../src/components/ui/MultiSelectOptions.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,sBAAsB,CAC5B,YAAY,CAAC;QAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,EACxD,KAAK,CACN,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AA8BF,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EAAE,aAAa,EACpB,QAAQ,EACR,UAAkB,EAClB,KAAK,GACN,EAAE,KAAK,2CA+KP"}
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Popover, PopoverTrigger, PopoverContent, } from './popover';
4
+ import { Command, CommandInput, CommandItem, CommandList, CommandGroup, CommandEmpty, } from './command';
5
+ import { X } from 'lucide-react';
6
+ import { useEffect, useState, useMemo, useRef } from 'react';
7
+ import { useSkills } from '../../hooks/useSkills';
8
+ // Sub-component for badges
9
+ function SelectedBadges({ skills, removeSkill, }) {
10
+ return (_jsx("div", { className: "flex flex-wrap gap-1 w-full", children: skills.map((skill) => (_jsxs("span", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded-lg bg-primary text-white text-[11px] leading-tight sm:text-xs max-w-full break-words", children: [_jsx("span", { className: "inline-block max-w-[72vw] sm:max-w-full overflow-hidden text-ellipsis", children: skill }), _jsx(X, { className: "w-3 h-3 cursor-pointer shrink-0", onClick: (e) => removeSkill(skill, e) })] }, skill))) }));
11
+ }
12
+ export function MultiSelectOptions({ value, onChange, options, query: externalQuery, disabled, staticOnly = false, token, }) {
13
+ const [open, setOpen] = useState(false);
14
+ const [search, setSearch] = useState('');
15
+ const [debouncedSearch, setDebouncedSearch] = useState('');
16
+ const loadMoreRef = useRef(null);
17
+ // Debounce search input to reduce API calls
18
+ useEffect(() => {
19
+ const timer = setTimeout(() => {
20
+ setDebouncedSearch(search.trim());
21
+ }, 300); // 300ms debounce delay
22
+ return () => clearTimeout(timer);
23
+ }, [search]);
24
+ // Use internal query with search if no external query provided, or if search is active
25
+ // If external query is provided and no search, use it for backward compatibility
26
+ // Skip API query if staticOnly is true
27
+ const shouldUseInternalQuery = !staticOnly && (!externalQuery || debouncedSearch.length > 0);
28
+ const internalSkills = useSkills({
29
+ // Optimized for initial load: small batch, loads more via infinite scroll
30
+ limit: debouncedSearch ? 50 : 20, // 50 when searching, 20 for initial load
31
+ batchSize: 1, // Single page per load for faster response
32
+ queryObject: debouncedSearch ? { search: debouncedSearch } : {},
33
+ token,
34
+ });
35
+ // Use internal query when searching, otherwise use external query if provided
36
+ // If staticOnly, don't use any query
37
+ const query = staticOnly ? undefined : (shouldUseInternalQuery ? internalSkills.skillsQuery : externalQuery);
38
+ // Combine static + API options (no client-side filtering needed - backend handles it)
39
+ // If staticOnly, filter client-side based on search
40
+ const combinedOptions = useMemo(() => {
41
+ if (staticOnly) {
42
+ // Client-side filtering for static options only
43
+ const lowerSearch = debouncedSearch.toLowerCase();
44
+ const filtered = options.filter((opt) => opt.toLowerCase().includes(lowerSearch));
45
+ return filtered;
46
+ }
47
+ // Original behavior: combine static + API options
48
+ const apiOptions = query?.data?.pages.flatMap((page) => page.data?.map((s) => s.skill).filter(Boolean)) || [];
49
+ return Array.from(new Set([...options, ...apiOptions]));
50
+ }, [options, query?.data, staticOnly, debouncedSearch]);
51
+ // Reset search when popover closes
52
+ useEffect(() => {
53
+ if (!open) {
54
+ setSearch('');
55
+ setDebouncedSearch('');
56
+ }
57
+ }, [open]);
58
+ // Infinite scroll using IntersectionObserver
59
+ useEffect(() => {
60
+ if (!loadMoreRef.current || !query?.hasNextPage || query?.isFetchingNextPage)
61
+ return;
62
+ const observer = new IntersectionObserver((entries) => {
63
+ if (entries[0]?.isIntersecting) {
64
+ query.fetchNextPage?.();
65
+ }
66
+ }, { threshold: 0.1 });
67
+ observer.observe(loadMoreRef.current);
68
+ return () => observer.disconnect();
69
+ }, [query?.hasNextPage, query?.isFetchingNextPage, query]);
70
+ // Handlers
71
+ const toggleSkill = (skill) => onChange(value.includes(skill)
72
+ ? value.filter((s) => s !== skill)
73
+ : [...value, skill]);
74
+ const removeSkill = (skill, e) => {
75
+ e.stopPropagation();
76
+ onChange(value.filter((s) => s !== skill));
77
+ };
78
+ const trimmedSearch = search.trim();
79
+ // No client-side filtering needed - backend handles search
80
+ // Just check if we can add custom option
81
+ const canAddCustom = trimmedSearch &&
82
+ !combinedOptions.includes(trimmedSearch) &&
83
+ !value.includes(trimmedSearch);
84
+ return (_jsx("div", { className: "w-full", children: _jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { disabled: disabled, asChild: true, children: _jsx("button", { type: "button", className: "w-full min-h-10 justify-start border border-slate-200 rounded-md bg-background px-2 py-1 h-auto text-left hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-50", children: value.length > 0 ? (_jsx(SelectedBadges, { skills: value, removeSkill: removeSkill })) : (_jsx("span", { className: "text-muted-foreground", children: "Select Options" })) }) }), _jsx(PopoverContent, { className: "max-w-sm max-h-[300px] overflow-y-auto p-0", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { value: search, onValueChange: setSearch, placeholder: "Search or add..." }), _jsxs(CommandList, { className: "max-h-[250px] overflow-y-auto", children: [!staticOnly && query?.isFetching && combinedOptions.length === 0 && (_jsx("div", { className: "text-center text-muted-foreground text-xs py-4", children: "Searching..." })), _jsxs(CommandGroup, { children: [combinedOptions.map((skill) => (_jsx(CommandItem, { onSelect: () => toggleSkill(skill), className: "cursor-pointer", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: value.includes(skill), readOnly: true, className: "accent-primary" }), _jsx("span", { children: skill })] }) }, skill))), canAddCustom && (_jsxs(CommandItem, { onSelect: () => toggleSkill(trimmedSearch), className: "cursor-pointer text-primary space-x-2", children: [_jsx("span", { className: "px-2 py-0.5 bg-green-600 hover:bg-green-500 rounded-sm text-xs text-white", children: "Add" }), _jsx("span", { children: trimmedSearch })] }))] }), combinedOptions.length === 0 && !canAddCustom && (!staticOnly && !query?.isFetching) && (_jsx(CommandEmpty, { children: "No matches found." })), !staticOnly && query?.hasNextPage && !query?.isFetching && (_jsx("div", { ref: loadMoreRef, className: "text-center text-muted-foreground text-xs py-2", children: "Loading more..." }))] })] }) })] }) }));
85
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ type RatingStarsProps = {
3
+ value?: number;
4
+ onChange?: (v: number) => void;
5
+ "aria-label"?: string;
6
+ disabled?: boolean;
7
+ };
8
+ export declare const RatingStars: React.FC<RatingStarsProps>;
9
+ export {};
10
+ //# sourceMappingURL=RatingStars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RatingStars.d.ts","sourceRoot":"","sources":["../../../src/components/ui/RatingStars.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA6ClD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const RatingStars = ({ value = 0, onChange, disabled, ...rest }) => {
3
+ return (_jsx("div", { className: "flex items-center gap-1 text-yellow-500", role: "radiogroup", ...rest, children: Array.from({ length: 5 }).map((_, i) => {
4
+ const n = i + 1;
5
+ const filled = n <= (value ?? 0);
6
+ return (_jsx("button", { type: "button", role: "radio", "aria-checked": filled, disabled: disabled, onClick: () => onChange?.(n), className: disabled
7
+ ? "opacity-50 cursor-not-allowed h-6 w-6 grid place-items-center rounded"
8
+ : "h-6 w-6 grid place-items-center rounded hover:scale-105", children: _jsx("span", { className: filled ? "text-current" : "", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: filled ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }) }, n));
9
+ }) }));
10
+ };
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ declare const Command: React.ForwardRefExoticComponent<Omit<{
3
+ children?: React.ReactNode;
4
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
5
+ ref?: React.Ref<HTMLDivElement>;
6
+ } & {
7
+ asChild?: boolean;
8
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
9
+ label?: string;
10
+ shouldFilter?: boolean;
11
+ filter?: (value: string, search: string, keywords?: string[]) => number;
12
+ defaultValue?: string;
13
+ value?: string;
14
+ onValueChange?: (value: string) => void;
15
+ loop?: boolean;
16
+ disablePointerSelection?: boolean;
17
+ vimBindings?: boolean;
18
+ } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
19
+ declare const CommandInput: React.ForwardRefExoticComponent<Omit<Omit<Pick<Pick<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "key" | keyof React.InputHTMLAttributes<HTMLInputElement>> & {
20
+ ref?: React.Ref<HTMLInputElement>;
21
+ } & {
22
+ asChild?: boolean;
23
+ }, "key" | "asChild" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "value" | "onChange"> & {
24
+ value?: string;
25
+ onValueChange?: (search: string) => void;
26
+ } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
27
+ declare const CommandList: React.ForwardRefExoticComponent<Omit<{
28
+ children?: React.ReactNode;
29
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
30
+ ref?: React.Ref<HTMLDivElement>;
31
+ } & {
32
+ asChild?: boolean;
33
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & {
34
+ label?: string;
35
+ } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
36
+ declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
37
+ children?: React.ReactNode;
38
+ } & Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
39
+ ref?: React.Ref<HTMLDivElement>;
40
+ } & {
41
+ asChild?: boolean;
42
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
43
+ declare const CommandGroup: React.ForwardRefExoticComponent<Omit<{
44
+ children?: React.ReactNode;
45
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
46
+ ref?: React.Ref<HTMLDivElement>;
47
+ } & {
48
+ asChild?: boolean;
49
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "value" | "heading"> & {
50
+ heading?: React.ReactNode;
51
+ value?: string;
52
+ forceMount?: boolean;
53
+ } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
54
+ declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
55
+ children?: React.ReactNode;
56
+ } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
57
+ ref?: React.Ref<HTMLDivElement>;
58
+ } & {
59
+ asChild?: boolean;
60
+ }, "key" | keyof React.HTMLAttributes<HTMLDivElement> | "asChild">, "value" | "disabled" | "onSelect"> & {
61
+ disabled?: boolean;
62
+ onSelect?: (value: string) => void;
63
+ value?: string;
64
+ keywords?: string[];
65
+ forceMount?: boolean;
66
+ } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
67
+ export { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, };
68
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/components/ui/command.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;sFAYX,CAAC;AAGH,QAAA,MAAM,YAAY;;;;;;;0FAehB,CAAC;AAGH,QAAA,MAAM,WAAW;;;;;;;;sFAef,CAAC;AAGH,QAAA,MAAM,YAAY;;;;;;uJAShB,CAAC;AAGH,QAAA,MAAM,YAAY;;;;;;;;;;sFAYhB,CAAC;AAGH,QAAA,MAAM,WAAW;;;;;;;;;;;;sFAYf,CAAC;AAGH,OAAO,EACL,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,CAAC"}
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { Command as CommandPrimitive } from 'cmdk';
5
+ import { Search } from 'lucide-react';
6
+ import { cn } from '../../utils/cn';
7
+ const Command = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive, { ref: ref, className: cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', className), ...props })));
8
+ Command.displayName = CommandPrimitive.displayName;
9
+ const CommandInput = React.forwardRef(({ className, ...props }, ref) => (_jsxs("div", { className: "flex items-center border-b px-3", children: [_jsx(Search, { className: "mr-2 h-4 w-4 shrink-0 opacity-50" }), _jsx(CommandPrimitive.Input, { ref: ref, className: cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', className), ...props })] })));
10
+ CommandInput.displayName = CommandPrimitive.Input.displayName;
11
+ const CommandList = React.forwardRef(({ className, onWheel, ...props }, ref) => (_jsx(CommandPrimitive.List, { ref: ref, className: cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className), onWheel: (e) => {
12
+ const el = e.currentTarget;
13
+ el.scrollTop += e.deltaY;
14
+ e.preventDefault();
15
+ onWheel?.(e);
16
+ }, ...props })));
17
+ CommandList.displayName = CommandPrimitive.List.displayName;
18
+ const CommandEmpty = React.forwardRef((props, ref) => (_jsx(CommandPrimitive.Empty, { ref: ref, className: "py-6 text-center text-sm", ...props })));
19
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
20
+ const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Group, { ref: ref, className: cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', className), ...props })));
21
+ CommandGroup.displayName = CommandPrimitive.Group.displayName;
22
+ const CommandItem = React.forwardRef(({ className, ...props }, ref) => (_jsx(CommandPrimitive.Item, { ref: ref, className: cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50', className), ...props })));
23
+ CommandItem.displayName = CommandPrimitive.Item.displayName;
24
+ export { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, };
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
3
+ declare const Popover: React.FC<PopoverPrimitive.PopoverProps>;
4
+ declare const PopoverTrigger: React.ForwardRefExoticComponent<PopoverPrimitive.PopoverTriggerProps & React.RefAttributes<HTMLButtonElement>>;
5
+ declare const PopoverContent: React.ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
6
+ export { Popover, PopoverTrigger, PopoverContent };
7
+ //# sourceMappingURL=popover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/components/ui/popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAG5D,QAAA,MAAM,OAAO,yCAAwB,CAAC;AAEtC,QAAA,MAAM,cAAc,gHAA2B,CAAC;AAEhD,QAAA,MAAM,cAAc,gKAgBlB,CAAC;AAGH,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
5
+ import { cn } from '../../utils/cn';
6
+ const Popover = PopoverPrimitive.Root;
7
+ const PopoverTrigger = PopoverPrimitive.Trigger;
8
+ const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { ref: ref, align: align, sideOffset: sideOffset, className: cn('!z-[20050] w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className), ...props }) })));
9
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
10
+ export { Popover, PopoverTrigger, PopoverContent };
@@ -0,0 +1,2 @@
1
+ export declare function useDebounce<T>(value: T, delay?: number): T;
2
+ //# sourceMappingURL=useDebounce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDebounce.d.ts","sourceRoot":"","sources":["../../src/hooks/useDebounce.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,MAAY,GAAG,CAAC,CAS/D"}
@@ -0,0 +1,9 @@
1
+ import { useEffect, useState } from 'react';
2
+ export function useDebounce(value, delay = 300) {
3
+ const [debouncedValue, setDebouncedValue] = useState(value);
4
+ useEffect(() => {
5
+ const handler = setTimeout(() => setDebouncedValue(value), delay);
6
+ return () => clearTimeout(handler);
7
+ }, [value, delay]);
8
+ return debouncedValue;
9
+ }