@recruitnepal/shared-packages 1.5.0 → 1.7.0
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.
- package/dist/components/common/NoData.d.ts +8 -0
- package/dist/components/common/NoData.d.ts.map +1 -0
- package/dist/components/common/NoData.js +4 -0
- package/dist/components/cv/TemplatePicker.d.ts +14 -0
- package/dist/components/cv/TemplatePicker.d.ts.map +1 -0
- package/dist/components/cv/TemplatePicker.js +161 -0
- package/dist/components/cv/TemplateRenderer.d.ts +20 -0
- package/dist/components/cv/TemplateRenderer.d.ts.map +1 -0
- package/dist/components/cv/TemplateRenderer.js +14 -0
- package/dist/components/ui/BulletListTextarea.d.ts +19 -0
- package/dist/components/ui/BulletListTextarea.d.ts.map +1 -0
- package/dist/components/ui/BulletListTextarea.js +60 -0
- package/dist/components/ui/DesignationSelect.d.ts +13 -0
- package/dist/components/ui/DesignationSelect.d.ts.map +1 -0
- package/dist/components/ui/DesignationSelect.js +78 -0
- package/dist/components/ui/IndustrySelect.d.ts +13 -0
- package/dist/components/ui/IndustrySelect.d.ts.map +1 -0
- package/dist/components/ui/IndustrySelect.js +78 -0
- package/dist/components/ui/MultiSelectOptions.d.ts +18 -0
- package/dist/components/ui/MultiSelectOptions.d.ts.map +1 -0
- package/dist/components/ui/MultiSelectOptions.js +85 -0
- package/dist/components/ui/RatingStars.d.ts +10 -0
- package/dist/components/ui/RatingStars.d.ts.map +1 -0
- package/dist/components/ui/RatingStars.js +10 -0
- package/dist/components/ui/command.d.ts +68 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +24 -0
- package/dist/components/ui/popover.d.ts +7 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +10 -0
- package/dist/hooks/useDebounce.d.ts +2 -0
- package/dist/hooks/useDebounce.d.ts.map +1 -0
- package/dist/hooks/useDebounce.js +9 -0
- package/dist/hooks/useDesignations.d.ts +51 -0
- package/dist/hooks/useDesignations.d.ts.map +1 -0
- package/dist/hooks/useDesignations.js +117 -0
- package/dist/hooks/useIndustries.d.ts +51 -0
- package/dist/hooks/useIndustries.d.ts.map +1 -0
- package/dist/hooks/useIndustries.js +118 -0
- package/dist/hooks/useSkills.d.ts +19 -0
- package/dist/hooks/useSkills.d.ts.map +1 -0
- package/dist/hooks/useSkills.js +39 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -0
- package/dist/stores/cvDrafts.store.d.ts +44 -0
- package/dist/stores/cvDrafts.store.d.ts.map +1 -0
- package/dist/stores/cvDrafts.store.js +86 -0
- package/dist/stores/cvLayout.store.d.ts +24 -0
- package/dist/stores/cvLayout.store.d.ts.map +1 -0
- package/dist/stores/cvLayout.store.js +116 -0
- package/dist/stores/cvPreview.store.d.ts +29 -0
- package/dist/stores/cvPreview.store.d.ts.map +1 -0
- package/dist/stores/cvPreview.store.js +80 -0
- package/dist/types/cv-blocks.types.d.ts +44 -0
- package/dist/types/cv-blocks.types.d.ts.map +1 -0
- package/dist/types/cv-blocks.types.js +1 -0
- package/dist/types/cv-draft.types.d.ts +32 -0
- package/dist/types/cv-draft.types.d.ts.map +1 -0
- package/dist/types/cv-draft.types.js +1 -0
- package/dist/types/template.types.d.ts +122 -0
- package/dist/types/template.types.d.ts.map +1 -0
- package/dist/types/template.types.js +1 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.d.ts.map +1 -0
- package/dist/utils/cn.js +3 -0
- package/dist/utils/commonDropdownOptions.d.ts +6 -0
- package/dist/utils/commonDropdownOptions.d.ts.map +1 -0
- package/dist/utils/commonDropdownOptions.js +15 -0
- package/dist/utils/cv/pdf/printReset.d.ts +3 -0
- package/dist/utils/cv/pdf/printReset.d.ts.map +1 -0
- package/dist/utils/cv/pdf/printReset.js +41 -0
- package/dist/utils/cv/pdf/styles/base.d.ts +2 -0
- package/dist/utils/cv/pdf/styles/base.d.ts.map +1 -0
- package/dist/utils/cv/pdf/styles/base.js +44 -0
- package/dist/utils/cv/pdf/styles/preprocessCssClient.d.ts +3 -0
- package/dist/utils/cv/pdf/styles/preprocessCssClient.d.ts.map +1 -0
- package/dist/utils/cv/pdf/styles/preprocessCssClient.js +33 -0
- package/dist/utils/cv-block-converter.d.ts +11 -0
- package/dist/utils/cv-block-converter.d.ts.map +1 -0
- package/dist/utils/cv-block-converter.js +83 -0
- package/dist/utils/generateSearchQuery.d.ts +2 -0
- package/dist/utils/generateSearchQuery.d.ts.map +1 -0
- package/dist/utils/generateSearchQuery.js +5 -0
- package/package.json +51 -44
|
@@ -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,14 @@
|
|
|
1
|
+
import type { TemplateData } from '../../types/template.types';
|
|
2
|
+
import type { CvTemplateRegistry } from './TemplateRenderer';
|
|
3
|
+
export declare function generateSampleTemplateData(seed: string): TemplateData;
|
|
4
|
+
export type TemplatePickerProps = {
|
|
5
|
+
/** Template registry from the host app */
|
|
6
|
+
registry: CvTemplateRegistry;
|
|
7
|
+
/** Optional list of slugs to show; defaults to Object.keys(registry) */
|
|
8
|
+
allowedSlugs?: string[];
|
|
9
|
+
/** Optional display names: slug -> title */
|
|
10
|
+
slugTitles?: Record<string, string>;
|
|
11
|
+
onPicked: (slug: string) => void;
|
|
12
|
+
};
|
|
13
|
+
export default function TemplatePicker({ registry, allowedSlugs, slugTitles, onPicked, }: TemplatePickerProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=TemplatePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplatePicker.d.ts","sourceRoot":"","sources":["../../../src/components/cv/TemplatePicker.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AA0B7D,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAqFrE;AAuED,MAAM,MAAM,mBAAmB,GAAG;IAChC,0CAA0C;IAC1C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AA+BF,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,QAAQ,EACR,YAAY,EACZ,UAAe,EACf,QAAQ,GACT,EAAE,mBAAmB,2CAwBrB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import TemplateRenderer from './TemplateRenderer';
|
|
5
|
+
const FIRST_NAMES = ['Prakash', 'Bikash', 'Nabin', 'Suresh', 'Raj', 'Amit', 'Kumar', 'Sita', 'Gita', 'Pooja'];
|
|
6
|
+
const LAST_NAMES = ['Shrestha', 'Pradhan', 'Sharma', 'Pandey', 'Thapa', 'Karki', 'Gurung', 'Rai', 'Tamang', 'Lama'];
|
|
7
|
+
const TITLES = ['Software Engineer', 'Data Scientist', 'Product Manager', 'Frontend Developer', 'Full Stack Engineer'];
|
|
8
|
+
const COMPANIES = ['TechCorp', 'InnovateLab', 'Digital Solutions', 'Cloud Systems', 'Smart Solutions'];
|
|
9
|
+
const CITIES = ['Kathmandu', 'Pokhara', 'Lalitpur', 'Bhaktapur', 'Biratnagar'];
|
|
10
|
+
const INSTITUTIONS = ['Kathmandu University', 'Tribhuvan University', 'Pokhara University', 'NEC'];
|
|
11
|
+
const DEGREES = ['B.E.', 'B.Sc.', 'M.Sc.', 'M.B.A.'];
|
|
12
|
+
const FIELDS = ['Computer Engineering', 'Computer Science', 'Information Technology', 'Data Science'];
|
|
13
|
+
const PROJECT_NAMES = ['E-Commerce Platform', 'Task Management System', 'Analytics Dashboard', 'Mobile Banking App'];
|
|
14
|
+
const TECH_STACKS = [['React', 'Next.js', 'TypeScript'], ['Python', 'Django', 'PostgreSQL'], ['Node.js', 'Express', 'MongoDB']];
|
|
15
|
+
function seededRandom(seed) {
|
|
16
|
+
let h = 0;
|
|
17
|
+
for (let i = 0; i < seed.length; i++) {
|
|
18
|
+
h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
|
|
19
|
+
}
|
|
20
|
+
return function next() {
|
|
21
|
+
h = (Math.imul(1664525, h) + 1013904223) >>> 0;
|
|
22
|
+
return h / 0x100000000;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const pick = (rng, arr) => arr[Math.floor(rng() * arr.length)];
|
|
26
|
+
export function generateSampleTemplateData(seed) {
|
|
27
|
+
const rng = seededRandom(seed);
|
|
28
|
+
const randomItem = (arr) => pick(rng, arr);
|
|
29
|
+
const firstName = randomItem(FIRST_NAMES);
|
|
30
|
+
const lastName = randomItem(LAST_NAMES);
|
|
31
|
+
const title = randomItem(TITLES);
|
|
32
|
+
const company = randomItem(COMPANIES);
|
|
33
|
+
const city = randomItem(CITIES);
|
|
34
|
+
const institution = randomItem(INSTITUTIONS);
|
|
35
|
+
const degree = randomItem(DEGREES);
|
|
36
|
+
const field = randomItem(FIELDS);
|
|
37
|
+
const projectName = randomItem(PROJECT_NAMES);
|
|
38
|
+
const techStack = randomItem(TECH_STACKS);
|
|
39
|
+
const currentYear = new Date().getFullYear();
|
|
40
|
+
const startYear = currentYear - Math.floor(rng() * 5) - 1;
|
|
41
|
+
const endYear = currentYear - Math.floor(rng() * 2);
|
|
42
|
+
const eduStartYear = startYear - 4;
|
|
43
|
+
const eduEndYear = startYear;
|
|
44
|
+
const gpa = (3.0 + rng() * 1.0).toFixed(1);
|
|
45
|
+
return {
|
|
46
|
+
personalInfo: {
|
|
47
|
+
firstName,
|
|
48
|
+
lastName,
|
|
49
|
+
title,
|
|
50
|
+
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
|
|
51
|
+
phone: `+977-98${Math.floor(rng() * 10000000).toString().padStart(7, '0')}`,
|
|
52
|
+
location: { city },
|
|
53
|
+
summary: `${title} specializing in modern technologies.`,
|
|
54
|
+
links: [
|
|
55
|
+
{ type: 'LinkedIn', label: `linkedin.com/in/${firstName.toLowerCase()}`, url: `https://linkedin.com/in/${firstName.toLowerCase()}` },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
sections: {
|
|
59
|
+
experience: [
|
|
60
|
+
{
|
|
61
|
+
id: 'exp1',
|
|
62
|
+
position: title,
|
|
63
|
+
company,
|
|
64
|
+
location: city,
|
|
65
|
+
startDate: `${startYear}-01-01`,
|
|
66
|
+
endDate: `${endYear}-12-31`,
|
|
67
|
+
current: rng() > 0.5,
|
|
68
|
+
description: `Responsible for developing solutions.`,
|
|
69
|
+
highlights: ['Improved performance.', 'Led implementation.'],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
education: [
|
|
73
|
+
{
|
|
74
|
+
id: 'edu1',
|
|
75
|
+
degree,
|
|
76
|
+
field,
|
|
77
|
+
institution,
|
|
78
|
+
location: city,
|
|
79
|
+
startDate: `${eduStartYear}-01-01`,
|
|
80
|
+
endDate: `${eduEndYear}-12-31`,
|
|
81
|
+
current: false,
|
|
82
|
+
gpa: `${gpa}/4.0`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
projects: [
|
|
86
|
+
{
|
|
87
|
+
id: 'prj1',
|
|
88
|
+
name: projectName,
|
|
89
|
+
description: `A comprehensive ${projectName.toLowerCase()} built with modern technologies.`,
|
|
90
|
+
technologies: techStack,
|
|
91
|
+
url: `https://example.com`,
|
|
92
|
+
startDate: `${endYear - 1}-01-01`,
|
|
93
|
+
endDate: `${endYear - 1}-10-01`,
|
|
94
|
+
highlights: ['Implemented core features.', 'Deployed to production.'],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
skills: [
|
|
98
|
+
{ id: 's1', category: 'Languages', skills: techStack.slice(0, 3).map((name) => ({ name })) },
|
|
99
|
+
{ id: 's2', category: 'Tools', skills: ['Git', 'Docker', 'AWS'].map((name) => ({ name })) },
|
|
100
|
+
],
|
|
101
|
+
certifications: [
|
|
102
|
+
{ id: 'c1', name: 'AWS Certified Cloud Practitioner', issuer: 'AWS', date: `${endYear - 1}-06-01` },
|
|
103
|
+
],
|
|
104
|
+
languages: [
|
|
105
|
+
{ id: 'l1', language: 'English', proficiency: 'professional' },
|
|
106
|
+
{ id: 'l2', language: 'Nepali', proficiency: 'native' },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const MM_PER_IN = 25.4;
|
|
112
|
+
const DPI = 96;
|
|
113
|
+
const PDF_MARGINS_MM = { top: 10, bottom: 10, left: 10, right: 10 };
|
|
114
|
+
const mmToPx = (mm) => (mm / MM_PER_IN) * DPI;
|
|
115
|
+
const A4_W_PX = mmToPx(210);
|
|
116
|
+
const A4_H_PX = mmToPx(297);
|
|
117
|
+
const CONTENT_W_PX = A4_W_PX - mmToPx(PDF_MARGINS_MM.left + PDF_MARGINS_MM.right);
|
|
118
|
+
const CONTENT_H_PX = A4_H_PX - mmToPx(PDF_MARGINS_MM.top + PDF_MARGINS_MM.bottom);
|
|
119
|
+
function FitPreview({ registry, slug, data, }) {
|
|
120
|
+
const wrapRef = useRef(null);
|
|
121
|
+
const [scale, setScale] = useState(0.25);
|
|
122
|
+
useLayoutEffect(() => {
|
|
123
|
+
const update = () => {
|
|
124
|
+
const el = wrapRef.current;
|
|
125
|
+
if (!el)
|
|
126
|
+
return;
|
|
127
|
+
setScale(Math.max(0.1, el.clientWidth / CONTENT_W_PX));
|
|
128
|
+
};
|
|
129
|
+
update();
|
|
130
|
+
const ro = new ResizeObserver(update);
|
|
131
|
+
if (wrapRef.current)
|
|
132
|
+
ro.observe(wrapRef.current);
|
|
133
|
+
return () => ro.disconnect();
|
|
134
|
+
}, []);
|
|
135
|
+
return (_jsx("div", { ref: wrapRef, className: "relative w-full overflow-hidden rounded-xl bg-white", style: { aspectRatio: `${CONTENT_W_PX} / ${CONTENT_H_PX}` }, children: _jsx("div", { className: "absolute top-0 left-0", style: {
|
|
136
|
+
width: A4_W_PX,
|
|
137
|
+
height: A4_H_PX,
|
|
138
|
+
transform: `scale(${scale})`,
|
|
139
|
+
transformOrigin: 'top left',
|
|
140
|
+
pointerEvents: 'none',
|
|
141
|
+
}, children: _jsx("div", { style: {
|
|
142
|
+
position: 'relative',
|
|
143
|
+
width: '100%',
|
|
144
|
+
height: '100%',
|
|
145
|
+
paddingTop: mmToPx(PDF_MARGINS_MM.top),
|
|
146
|
+
paddingBottom: mmToPx(PDF_MARGINS_MM.bottom),
|
|
147
|
+
paddingLeft: mmToPx(PDF_MARGINS_MM.left),
|
|
148
|
+
paddingRight: mmToPx(PDF_MARGINS_MM.right),
|
|
149
|
+
boxSizing: 'border-box',
|
|
150
|
+
overflow: 'hidden',
|
|
151
|
+
}, children: _jsx(TemplateRenderer, { registry: registry, slug: slug, data: data, printMode: true }) }) }) }));
|
|
152
|
+
}
|
|
153
|
+
function TemplateCard({ registry, slug, title, onPick, }) {
|
|
154
|
+
const [templateData] = useState(() => generateSampleTemplateData(slug));
|
|
155
|
+
return (_jsxs("button", { type: "button", onClick: () => onPick(slug), className: "w-full text-left rounded-2xl border bg-white hover:shadow-lg transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: [_jsxs("div", { className: "p-4 border-b rounded-t-2xl", children: [_jsx("div", { className: "text-sm font-medium", children: title }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Click to select" })] }), _jsx("div", { className: "p-4", children: _jsx(FitPreview, { registry: registry, slug: slug, data: templateData }) })] }));
|
|
156
|
+
}
|
|
157
|
+
export default function TemplatePicker({ registry, allowedSlugs, slugTitles = {}, onPicked, }) {
|
|
158
|
+
const slugs = allowedSlugs ?? Object.keys(registry);
|
|
159
|
+
const titleFor = (slug) => slugTitles[slug] ?? slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
160
|
+
return (_jsxs("div", { className: "custom-container py-8", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Select a CV Template" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Preview templates with sample data and pick one to start." })] }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6", children: slugs.map((slug) => (_jsx(TemplateCard, { registry: registry, slug: slug, title: titleFor(slug), onPick: onPicked }, slug))) })] }));
|
|
161
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TemplateData, TemplateProps } from '../../types/template.types';
|
|
3
|
+
/** Registry map: template slug -> component that receives TemplateProps */
|
|
4
|
+
export type CvTemplateRegistry = Record<string, React.ComponentType<TemplateProps>>;
|
|
5
|
+
export type TemplateRendererProps = {
|
|
6
|
+
/** Template registry provided by the host app (each project defines its own templates) */
|
|
7
|
+
registry: CvTemplateRegistry;
|
|
8
|
+
/** Template slug (e.g. 'modern', 'professional') */
|
|
9
|
+
slug: string;
|
|
10
|
+
/** CV data to render */
|
|
11
|
+
data: TemplateData;
|
|
12
|
+
printMode?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Renders a CV template by slug using the registry provided by the host app.
|
|
17
|
+
* Host app must pass its own TEMPLATE_REGISTRY (no templates are shipped in the package).
|
|
18
|
+
*/
|
|
19
|
+
export default function TemplateRenderer({ registry, slug, data, printMode, className, }: TemplateRendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
20
|
+
//# sourceMappingURL=TemplateRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateRenderer.d.ts","sourceRoot":"","sources":["../../../src/components/cv/TemplateRenderer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE9E,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,0FAA0F;IAC1F,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,SAAiB,EACjB,SAAc,GACf,EAAE,qBAAqB,kDAOvB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Renders a CV template by slug using the registry provided by the host app.
|
|
5
|
+
* Host app must pass its own TEMPLATE_REGISTRY (no templates are shipped in the package).
|
|
6
|
+
*/
|
|
7
|
+
export default function TemplateRenderer({ registry, slug, data, printMode = false, className = '', }) {
|
|
8
|
+
const fallbackSlug = Object.keys(registry)[0] ?? 'modern';
|
|
9
|
+
const Comp = registry[slug] ?? registry[fallbackSlug];
|
|
10
|
+
if (!Comp) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return _jsx(Comp, { data: data, printMode: printMode, className: className });
|
|
14
|
+
}
|
|
@@ -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"}
|