jsonresume-theme-product-manager-canvas 0.1.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/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import { renderToString } from 'react-dom/server';
2
+ import { ServerStyleSheet } from 'styled-components';
3
+ import Resume from './src/Resume.jsx';
4
+
5
+ export function render(resume) {
6
+ const sheet = new ServerStyleSheet();
7
+ const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
8
+ const styles = sheet.getStyleTags();
9
+ const title = (resume.basics && resume.basics.name) || 'Resume';
10
+
11
+ return `<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <title>${title}</title>
16
+ <meta name="viewport" content="width=device-width, initial-scale=1">
17
+ <link rel="preconnect" href="https://fonts.googleapis.com">
18
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
19
+ ${styles}
20
+ </head>
21
+ <body>${html}</body>
22
+ </html>`;
23
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "jsonresume-theme-product-manager-canvas",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "peerDependencies": {
7
+ "react": "^18.0.0 || ^19.0.0",
8
+ "react-dom": "^18.0.0 || ^19.0.0"
9
+ },
10
+ "dependencies": {
11
+ "styled-components": "^6.1.19",
12
+ "@resume/core": "0.1.0"
13
+ }
14
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,623 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ Section,
5
+ SectionTitle,
6
+ DateRange,
7
+ ContactInfo,
8
+ Link,
9
+ Badge,
10
+ BadgeList,
11
+ } from '@resume/core';
12
+
13
+ const Layout = styled.div`
14
+ max-width: 850px;
15
+ margin: 0 auto;
16
+ padding: 60px 40px;
17
+ background: #ffffff;
18
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
19
+ sans-serif;
20
+ color: #1f2937;
21
+ line-height: 1.6;
22
+
23
+ @media print {
24
+ padding: 40px;
25
+ background: white;
26
+ }
27
+
28
+ @media (max-width: 640px) {
29
+ padding: 40px 20px;
30
+ }
31
+ `;
32
+
33
+ const Header = styled.header`
34
+ margin-bottom: 48px;
35
+ padding-bottom: 32px;
36
+ border-bottom: 2px solid #e9d5ff;
37
+ `;
38
+
39
+ const Name = styled.h1`
40
+ font-size: 42px;
41
+ font-weight: 800;
42
+ color: #111827;
43
+ margin: 0 0 8px 0;
44
+ letter-spacing: -0.5px;
45
+ `;
46
+
47
+ const Label = styled.p`
48
+ font-size: 18px;
49
+ font-weight: 500;
50
+ color: #7c3aed;
51
+ margin: 0 0 20px 0;
52
+ `;
53
+
54
+ const StyledContactInfo = styled(ContactInfo)`
55
+ font-size: 15px;
56
+ color: #6b7280;
57
+ margin-bottom: 24px;
58
+
59
+ a {
60
+ font-size: 15px;
61
+ color: #7c3aed;
62
+ text-decoration: none;
63
+
64
+ &:hover {
65
+ text-decoration: underline;
66
+ }
67
+ }
68
+ `;
69
+
70
+ const Summary = styled.p`
71
+ font-size: 16px;
72
+ line-height: 1.8;
73
+ color: #374151;
74
+ margin: 24px 0 0 0;
75
+ `;
76
+
77
+ const StyledSection = styled(Section)`
78
+ margin-bottom: 48px;
79
+ `;
80
+
81
+ const StyledSectionTitle = styled(SectionTitle)`
82
+ font-size: 24px;
83
+ font-weight: 800;
84
+ color: #111827;
85
+ margin: 0 0 24px 0;
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.5px;
88
+ position: relative;
89
+ padding-bottom: 12px;
90
+
91
+ &::after {
92
+ content: '';
93
+ position: absolute;
94
+ bottom: 0;
95
+ left: 0;
96
+ width: 60px;
97
+ height: 3px;
98
+ background: #7c3aed;
99
+ }
100
+ `;
101
+
102
+ const WorkItem = styled.div`
103
+ margin-bottom: 40px;
104
+ padding: 24px;
105
+ background: #faf5ff;
106
+ border-left: 6px solid #7c3aed;
107
+ border-radius: 4px;
108
+
109
+ &:last-child {
110
+ margin-bottom: 0;
111
+ }
112
+
113
+ @media print {
114
+ background: white;
115
+ border: 1px solid #e9d5ff;
116
+ border-left: 6px solid #7c3aed;
117
+ }
118
+ `;
119
+
120
+ const WorkHeader = styled.div`
121
+ margin-bottom: 16px;
122
+ `;
123
+
124
+ const WorkTitle = styled.div`
125
+ display: flex;
126
+ justify-content: space-between;
127
+ align-items: baseline;
128
+ flex-wrap: wrap;
129
+ gap: 8px;
130
+ margin-bottom: 8px;
131
+ `;
132
+
133
+ const Position = styled.h3`
134
+ font-size: 20px;
135
+ font-weight: 700;
136
+ color: #111827;
137
+ margin: 0;
138
+ `;
139
+
140
+ const Company = styled.div`
141
+ font-size: 16px;
142
+ font-weight: 600;
143
+ color: #7c3aed;
144
+ margin-top: 4px;
145
+ `;
146
+
147
+ const StyledDateRange = styled(DateRange)`
148
+ font-size: 14px;
149
+ color: #6b7280;
150
+ font-weight: 500;
151
+ `;
152
+
153
+ const WorkSummary = styled.p`
154
+ margin: 14px 0;
155
+ color: #374151;
156
+ line-height: 1.8;
157
+ font-size: 15px;
158
+ `;
159
+
160
+ const ImpactLabel = styled.div`
161
+ font-size: 13px;
162
+ font-weight: 800;
163
+ color: #7c3aed;
164
+ text-transform: uppercase;
165
+ letter-spacing: 1.2px;
166
+ margin: 16px 0 8px 0;
167
+ `;
168
+
169
+ const HighlightsList = styled.ul`
170
+ margin: 0;
171
+ padding-left: 20px;
172
+ list-style: none;
173
+
174
+ li {
175
+ position: relative;
176
+ margin-bottom: 8px;
177
+ padding-left: 0;
178
+ color: #374151;
179
+ line-height: 1.7;
180
+
181
+ &::before {
182
+ content: '▸';
183
+ position: absolute;
184
+ left: -20px;
185
+ color: #7c3aed;
186
+ font-weight: bold;
187
+ }
188
+
189
+ strong {
190
+ color: #7c3aed;
191
+ font-weight: 700;
192
+ }
193
+ }
194
+ `;
195
+
196
+ const EducationItem = styled.div`
197
+ margin-bottom: 28px;
198
+ padding-bottom: 28px;
199
+ border-bottom: 1px solid #e5e7eb;
200
+
201
+ &:last-child {
202
+ border-bottom: none;
203
+ padding-bottom: 0;
204
+ margin-bottom: 0;
205
+ }
206
+ `;
207
+
208
+ const EducationHeader = styled.div`
209
+ display: flex;
210
+ justify-content: space-between;
211
+ align-items: baseline;
212
+ flex-wrap: wrap;
213
+ gap: 8px;
214
+ margin-bottom: 8px;
215
+ `;
216
+
217
+ const Degree = styled.h3`
218
+ font-size: 18px;
219
+ font-weight: 700;
220
+ color: #111827;
221
+ margin: 0;
222
+ `;
223
+
224
+ const Institution = styled.div`
225
+ font-size: 16px;
226
+ color: #6b7280;
227
+ font-weight: 500;
228
+ margin-top: 4px;
229
+ `;
230
+
231
+ const StudyType = styled.div`
232
+ font-size: 15px;
233
+ color: #7c3aed;
234
+ font-weight: 500;
235
+ margin-top: 4px;
236
+ `;
237
+
238
+ const SkillsGrid = styled.div`
239
+ display: grid;
240
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
241
+ gap: 20px;
242
+ `;
243
+
244
+ const SkillCard = styled.div`
245
+ background: white;
246
+ border: 2px solid #e9d5ff;
247
+ border-radius: 6px;
248
+ padding: 18px;
249
+ transition: all 0.2s ease;
250
+
251
+ &:hover {
252
+ border-color: #7c3aed;
253
+ box-shadow: 0 2px 8px rgba(124, 58, 237, 0.1);
254
+ }
255
+
256
+ @media print {
257
+ break-inside: avoid;
258
+ }
259
+ `;
260
+
261
+ const SkillName = styled.h4`
262
+ font-size: 16px;
263
+ font-weight: 700;
264
+ color: #111827;
265
+ margin: 0 0 12px 0;
266
+ `;
267
+
268
+ const KeywordList = styled.div`
269
+ display: flex;
270
+ flex-wrap: wrap;
271
+ gap: 8px;
272
+ `;
273
+
274
+ const Keyword = styled.span`
275
+ font-size: 13px;
276
+ color: #7c3aed;
277
+ background: #faf5ff;
278
+ padding: 4px 10px;
279
+ border-radius: 4px;
280
+ font-weight: 500;
281
+ `;
282
+
283
+ const ProjectItem = styled.div`
284
+ margin-bottom: 32px;
285
+ padding-bottom: 32px;
286
+ border-bottom: 1px solid #e5e7eb;
287
+
288
+ &:last-child {
289
+ border-bottom: none;
290
+ padding-bottom: 0;
291
+ margin-bottom: 0;
292
+ }
293
+ `;
294
+
295
+ const ProjectHeader = styled.div`
296
+ margin-bottom: 12px;
297
+ `;
298
+
299
+ const ProjectName = styled.h3`
300
+ font-size: 18px;
301
+ font-weight: 700;
302
+ color: #111827;
303
+ margin: 0 0 8px 0;
304
+ `;
305
+
306
+ const ProjectDescription = styled.p`
307
+ font-size: 15px;
308
+ color: #374151;
309
+ line-height: 1.7;
310
+ margin: 0;
311
+ `;
312
+
313
+ const ProjectHighlights = styled.ul`
314
+ margin: 12px 0 0 0;
315
+ padding-left: 20px;
316
+ list-style: none;
317
+
318
+ li {
319
+ position: relative;
320
+ margin-bottom: 6px;
321
+ padding-left: 0;
322
+ color: #4b5563;
323
+ font-size: 14px;
324
+
325
+ &::before {
326
+ content: '•';
327
+ position: absolute;
328
+ left: -20px;
329
+ color: #7c3aed;
330
+ font-weight: bold;
331
+ }
332
+ }
333
+ `;
334
+
335
+ const SimpleList = styled.div`
336
+ display: grid;
337
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
338
+ gap: 16px;
339
+ `;
340
+
341
+ const SimpleItem = styled.div`
342
+ padding: 16px;
343
+ background: #f9fafb;
344
+ border-left: 3px solid #7c3aed;
345
+ border-radius: 4px;
346
+ `;
347
+
348
+ const ItemTitle = styled.h4`
349
+ font-size: 16px;
350
+ font-weight: 600;
351
+ color: #111827;
352
+ margin: 0 0 8px 0;
353
+ `;
354
+
355
+ const ItemMeta = styled.div`
356
+ font-size: 14px;
357
+ color: #6b7280;
358
+ margin-bottom: 6px;
359
+ `;
360
+
361
+ const ItemDescription = styled.p`
362
+ font-size: 14px;
363
+ color: #4b5563;
364
+ margin: 8px 0 0 0;
365
+ line-height: 1.6;
366
+ `;
367
+
368
+ function Resume({ resume }) {
369
+ const {
370
+ basics = {},
371
+ work = [],
372
+ education = [],
373
+ skills = [],
374
+ projects = [],
375
+ volunteer = [],
376
+ awards = [],
377
+ publications = [],
378
+ languages = [],
379
+ interests = [],
380
+ references = [],
381
+ } = resume;
382
+
383
+ return (
384
+ <Layout>
385
+ <Header>
386
+ <Name>{basics.name}</Name>
387
+ {basics.label && <Label>{basics.label}</Label>}
388
+ <StyledContactInfo basics={basics} />
389
+ {basics.summary && <Summary>{basics.summary}</Summary>}
390
+ </Header>
391
+
392
+ {work && work.length > 0 && (
393
+ <StyledSection>
394
+ <StyledSectionTitle>Experience</StyledSectionTitle>
395
+ {work.map((job, index) => (
396
+ <WorkItem key={index}>
397
+ <WorkHeader>
398
+ <WorkTitle>
399
+ <div>
400
+ <Position>{job.position}</Position>
401
+ <Company>{job.name}</Company>
402
+ </div>
403
+ <StyledDateRange
404
+ startDate={job.startDate}
405
+ endDate={job.endDate}
406
+ />
407
+ </WorkTitle>
408
+ </WorkHeader>
409
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
410
+ {job.highlights && job.highlights.length > 0 && (
411
+ <>
412
+ <ImpactLabel>Impact & Achievements</ImpactLabel>
413
+ <HighlightsList>
414
+ {job.highlights.map((highlight, i) => (
415
+ <li
416
+ key={i}
417
+ dangerouslySetInnerHTML={{ __html: highlight }}
418
+ />
419
+ ))}
420
+ </HighlightsList>
421
+ </>
422
+ )}
423
+ </WorkItem>
424
+ ))}
425
+ </StyledSection>
426
+ )}
427
+
428
+ {skills && skills.length > 0 && (
429
+ <StyledSection>
430
+ <StyledSectionTitle>Skills</StyledSectionTitle>
431
+ <SkillsGrid>
432
+ {skills.map((skill, index) => (
433
+ <SkillCard key={index}>
434
+ <SkillName>{skill.name}</SkillName>
435
+ {skill.keywords && skill.keywords.length > 0 && (
436
+ <KeywordList>
437
+ {skill.keywords.map((keyword, i) => (
438
+ <Keyword key={i}>{keyword}</Keyword>
439
+ ))}
440
+ </KeywordList>
441
+ )}
442
+ </SkillCard>
443
+ ))}
444
+ </SkillsGrid>
445
+ </StyledSection>
446
+ )}
447
+
448
+ {education && education.length > 0 && (
449
+ <StyledSection>
450
+ <StyledSectionTitle>Education</StyledSectionTitle>
451
+ {education.map((edu, index) => (
452
+ <EducationItem key={index}>
453
+ <EducationHeader>
454
+ <div>
455
+ <Degree>{edu.area}</Degree>
456
+ {edu.studyType && <StudyType>{edu.studyType}</StudyType>}
457
+ <Institution>{edu.institution}</Institution>
458
+ </div>
459
+ <StyledDateRange
460
+ startDate={edu.startDate}
461
+ endDate={edu.endDate}
462
+ />
463
+ </EducationHeader>
464
+ {edu.score && <ItemMeta>GPA: {edu.score}</ItemMeta>}
465
+ {edu.courses && edu.courses.length > 0 && (
466
+ <ItemDescription>
467
+ Relevant coursework: {edu.courses.join(', ')}
468
+ </ItemDescription>
469
+ )}
470
+ </EducationItem>
471
+ ))}
472
+ </StyledSection>
473
+ )}
474
+
475
+ {projects && projects.length > 0 && (
476
+ <StyledSection>
477
+ <StyledSectionTitle>Projects</StyledSectionTitle>
478
+ {projects.map((project, index) => (
479
+ <ProjectItem key={index}>
480
+ <ProjectHeader>
481
+ <ProjectName>
482
+ {project.url ? (
483
+ <Link href={project.url}>{project.name}</Link>
484
+ ) : (
485
+ project.name
486
+ )}
487
+ </ProjectName>
488
+ {project.description && (
489
+ <ProjectDescription>{project.description}</ProjectDescription>
490
+ )}
491
+ </ProjectHeader>
492
+ {project.highlights && project.highlights.length > 0 && (
493
+ <ProjectHighlights>
494
+ {project.highlights.map((highlight, i) => (
495
+ <li key={i}>{highlight}</li>
496
+ ))}
497
+ </ProjectHighlights>
498
+ )}
499
+ </ProjectItem>
500
+ ))}
501
+ </StyledSection>
502
+ )}
503
+
504
+ {volunteer && volunteer.length > 0 && (
505
+ <StyledSection>
506
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
507
+ <SimpleList>
508
+ {volunteer.map((vol, index) => (
509
+ <SimpleItem key={index}>
510
+ <ItemTitle>{vol.position}</ItemTitle>
511
+ <ItemMeta>
512
+ {vol.organization}
513
+ {vol.startDate && (
514
+ <>
515
+ {' • '}
516
+ <DateRange
517
+ startDate={vol.startDate}
518
+ endDate={vol.endDate}
519
+ />
520
+ </>
521
+ )}
522
+ </ItemMeta>
523
+ {vol.summary && (
524
+ <ItemDescription>{vol.summary}</ItemDescription>
525
+ )}
526
+ </SimpleItem>
527
+ ))}
528
+ </SimpleList>
529
+ </StyledSection>
530
+ )}
531
+
532
+ {awards && awards.length > 0 && (
533
+ <StyledSection>
534
+ <StyledSectionTitle>Awards</StyledSectionTitle>
535
+ <SimpleList>
536
+ {awards.map((award, index) => (
537
+ <SimpleItem key={index}>
538
+ <ItemTitle>{award.title}</ItemTitle>
539
+ <ItemMeta>
540
+ {award.awarder}
541
+ {award.date && <> • {award.date}</>}
542
+ </ItemMeta>
543
+ {award.summary && (
544
+ <ItemDescription>{award.summary}</ItemDescription>
545
+ )}
546
+ </SimpleItem>
547
+ ))}
548
+ </SimpleList>
549
+ </StyledSection>
550
+ )}
551
+
552
+ {publications && publications.length > 0 && (
553
+ <StyledSection>
554
+ <StyledSectionTitle>Publications</StyledSectionTitle>
555
+ {publications.map((pub, index) => (
556
+ <ProjectItem key={index}>
557
+ <ProjectHeader>
558
+ <ProjectName>
559
+ {pub.url ? <Link href={pub.url}>{pub.name}</Link> : pub.name}
560
+ </ProjectName>
561
+ <ItemMeta>
562
+ {pub.publisher}
563
+ {pub.releaseDate && <> • {pub.releaseDate}</>}
564
+ </ItemMeta>
565
+ </ProjectHeader>
566
+ {pub.summary && (
567
+ <ProjectDescription>{pub.summary}</ProjectDescription>
568
+ )}
569
+ </ProjectItem>
570
+ ))}
571
+ </StyledSection>
572
+ )}
573
+
574
+ {languages && languages.length > 0 && (
575
+ <StyledSection>
576
+ <StyledSectionTitle>Languages</StyledSectionTitle>
577
+ <SimpleList>
578
+ {languages.map((lang, index) => (
579
+ <SimpleItem key={index}>
580
+ <ItemTitle>{lang.language}</ItemTitle>
581
+ {lang.fluency && <ItemMeta>{lang.fluency}</ItemMeta>}
582
+ </SimpleItem>
583
+ ))}
584
+ </SimpleList>
585
+ </StyledSection>
586
+ )}
587
+
588
+ {interests && interests.length > 0 && (
589
+ <StyledSection>
590
+ <StyledSectionTitle>Interests</StyledSectionTitle>
591
+ <SimpleList>
592
+ {interests.map((interest, index) => (
593
+ <SimpleItem key={index}>
594
+ <ItemTitle>{interest.name}</ItemTitle>
595
+ {interest.keywords && interest.keywords.length > 0 && (
596
+ <ItemDescription>
597
+ {interest.keywords.join(', ')}
598
+ </ItemDescription>
599
+ )}
600
+ </SimpleItem>
601
+ ))}
602
+ </SimpleList>
603
+ </StyledSection>
604
+ )}
605
+
606
+ {references && references.length > 0 && (
607
+ <StyledSection>
608
+ <StyledSectionTitle>References</StyledSectionTitle>
609
+ {references.map((ref, index) => (
610
+ <ProjectItem key={index}>
611
+ <ItemTitle>{ref.name}</ItemTitle>
612
+ {ref.reference && (
613
+ <ItemDescription>{ref.reference}</ItemDescription>
614
+ )}
615
+ </ProjectItem>
616
+ ))}
617
+ </StyledSection>
618
+ )}
619
+ </Layout>
620
+ );
621
+ }
622
+
623
+ export default Resume;
package/src/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import { renderToString } from 'react-dom/server';
2
+ import { ServerStyleSheet } from 'styled-components';
3
+ import Resume from './Resume.jsx';
4
+
5
+ export function render(resume) {
6
+ const sheet = new ServerStyleSheet();
7
+ const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
8
+ const styles = sheet.getStyleTags();
9
+ return `<!DOCTYPE html><head>
10
+ <title>${resume.basics.name} - Resume</title>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
16
+ ${styles}
17
+ </head><body>${html}</body></html>`;
18
+ }
19
+
20
+ export default { render };