jsonresume-theme-minimalist-grid 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,24 @@
1
+ import React from 'react';
2
+ import { renderToString } from 'react-dom/server';
3
+ import { ServerStyleSheet } from 'styled-components';
4
+ import Resume from './src/Resume.jsx';
5
+
6
+ export function render(resume) {
7
+ const sheet = new ServerStyleSheet();
8
+ const html = renderToString(sheet.collectStyles(<Resume resume={resume} />));
9
+ const styles = sheet.getStyleTags();
10
+ const title = (resume.basics && resume.basics.name) || 'Resume';
11
+
12
+ return `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <title>${title}</title>
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <link rel="preconnect" href="https://fonts.googleapis.com">
19
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
20
+ ${styles}
21
+ </head>
22
+ <body>${html}</body>
23
+ </html>`;
24
+ }
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "jsonresume-theme-minimalist-grid",
3
+ "version": "0.1.0",
4
+ "description": "Clean, contemporary theme with geometric precision and baseline grid alignment. For minimalists and designers.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "peerDependencies": {
8
+ "react": "^18.0.0 || ^19.0.0",
9
+ "react-dom": "^18.0.0 || ^19.0.0"
10
+ },
11
+ "dependencies": {
12
+ "styled-components": "^6.1.19",
13
+ "@resume/core": "0.1.0"
14
+ }
15
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,498 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ Section,
5
+ SectionTitle,
6
+ DateRange,
7
+ Badge,
8
+ BadgeList,
9
+ ContactInfo,
10
+ Link,
11
+ safeUrl,
12
+ } from '@resume/core';
13
+
14
+ const Layout = styled.div`
15
+ max-width: 800px;
16
+ margin: 0 auto;
17
+ padding: 48px 32px;
18
+ background: white;
19
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
20
+ color: #374151;
21
+ line-height: 1.75;
22
+ font-size: 15px;
23
+ font-weight: 300;
24
+
25
+ @media print {
26
+ padding: 24px;
27
+ }
28
+ `;
29
+
30
+ const Header = styled.header`
31
+ margin-bottom: 48px;
32
+ padding-bottom: 24px;
33
+ border-bottom: 1px solid #e5e7eb;
34
+ `;
35
+
36
+ const Name = styled.h1`
37
+ font-size: 36px;
38
+ font-weight: 300;
39
+ margin: 0 0 8px 0;
40
+ color: #111827;
41
+ letter-spacing: 2px;
42
+ text-transform: uppercase;
43
+ `;
44
+
45
+ const Label = styled.div`
46
+ font-size: 14px;
47
+ color: #6b7280;
48
+ font-weight: 400;
49
+ margin-bottom: 16px;
50
+ letter-spacing: 1.5px;
51
+ text-transform: uppercase;
52
+ `;
53
+
54
+ const ContactWrapper = styled.div`
55
+ display: flex;
56
+ flex-wrap: wrap;
57
+ gap: 16px;
58
+ font-size: 13px;
59
+ color: #9ca3af;
60
+ letter-spacing: 0.5px;
61
+ `;
62
+
63
+ const SummarySection = styled(Section)`
64
+ margin-bottom: 40px;
65
+ padding-bottom: 40px;
66
+ border-bottom: 1px solid #e5e7eb;
67
+ `;
68
+
69
+ const SummaryText = styled.p`
70
+ margin: 0;
71
+ font-size: 15px;
72
+ line-height: 1.75;
73
+ color: #374151;
74
+ font-weight: 300;
75
+ `;
76
+
77
+ const MainSection = styled(Section)`
78
+ margin-bottom: 40px;
79
+ padding-bottom: 40px;
80
+ border-bottom: 1px solid #e5e7eb;
81
+
82
+ &:last-child {
83
+ border-bottom: none;
84
+ margin-bottom: 0;
85
+ padding-bottom: 0;
86
+ }
87
+ `;
88
+
89
+ const MainSectionTitle = styled(SectionTitle)`
90
+ font-size: 12px;
91
+ font-weight: 500;
92
+ color: #9ca3af;
93
+ margin: 0 0 32px 0;
94
+ text-transform: uppercase;
95
+ letter-spacing: 2px;
96
+ `;
97
+
98
+ const WorkItem = styled.div`
99
+ margin-bottom: 32px;
100
+
101
+ &:last-child {
102
+ margin-bottom: 0;
103
+ }
104
+ `;
105
+
106
+ const WorkHeader = styled.div`
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: baseline;
110
+ margin-bottom: 8px;
111
+ flex-wrap: wrap;
112
+ gap: 8px;
113
+ `;
114
+
115
+ const WorkTitle = styled.h3`
116
+ font-size: 17px;
117
+ font-weight: 400;
118
+ margin: 0;
119
+ color: #111827;
120
+ letter-spacing: 0.5px;
121
+ `;
122
+
123
+ const WorkMeta = styled.div`
124
+ font-size: 12px;
125
+ color: #9ca3af;
126
+ font-weight: 400;
127
+ letter-spacing: 0.5px;
128
+ `;
129
+
130
+ const WorkCompany = styled.div`
131
+ font-size: 14px;
132
+ color: #6b7280;
133
+ font-weight: 400;
134
+ margin-bottom: 8px;
135
+ letter-spacing: 0.5px;
136
+ `;
137
+
138
+ const WorkDescription = styled.p`
139
+ font-size: 15px;
140
+ line-height: 1.75;
141
+ margin: 12px 0;
142
+ color: #374151;
143
+ font-weight: 300;
144
+ `;
145
+
146
+ const WorkHighlights = styled.ul`
147
+ margin: 12px 0 0 0;
148
+ padding-left: 20px;
149
+ list-style: none;
150
+
151
+ li {
152
+ margin-bottom: 8px;
153
+ font-size: 15px;
154
+ line-height: 1.75;
155
+ color: #374151;
156
+ font-weight: 300;
157
+ position: relative;
158
+ padding-left: 12px;
159
+
160
+ &::before {
161
+ content: '–';
162
+ position: absolute;
163
+ left: 0;
164
+ color: #d1d5db;
165
+ font-weight: 300;
166
+ }
167
+ }
168
+ `;
169
+
170
+ const SkillsGrid = styled.div`
171
+ display: grid;
172
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
173
+ gap: 24px;
174
+ `;
175
+
176
+ const SkillItem = styled.div`
177
+ margin-bottom: 16px;
178
+
179
+ &:last-child {
180
+ margin-bottom: 0;
181
+ }
182
+ `;
183
+
184
+ const SkillName = styled.h4`
185
+ font-size: 12px;
186
+ font-weight: 500;
187
+ margin: 0 0 12px 0;
188
+ color: #6b7280;
189
+ text-transform: uppercase;
190
+ letter-spacing: 1.5px;
191
+ `;
192
+
193
+ const StyledBadgeList = styled(BadgeList)`
194
+ display: flex;
195
+ flex-wrap: wrap;
196
+ gap: 8px;
197
+ `;
198
+
199
+ const StyledBadge = styled(Badge)`
200
+ font-size: 12px;
201
+ padding: 6px 12px;
202
+ background: white;
203
+ border: 1px solid #e5e7eb;
204
+ color: #6b7280;
205
+ font-weight: 400;
206
+ border-radius: 0;
207
+ letter-spacing: 0.3px;
208
+ `;
209
+
210
+ const SimpleList = styled.div`
211
+ display: grid;
212
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
213
+ gap: 16px;
214
+ `;
215
+
216
+ const SimpleItem = styled.div`
217
+ font-size: 15px;
218
+ line-height: 1.75;
219
+ font-weight: 300;
220
+
221
+ strong {
222
+ font-weight: 400;
223
+ color: #111827;
224
+ letter-spacing: 0.3px;
225
+ }
226
+ `;
227
+
228
+ function Resume({ resume }) {
229
+ const {
230
+ basics = {},
231
+ work = [],
232
+ education = [],
233
+ skills = [],
234
+ projects = [],
235
+ volunteer = [],
236
+ awards = [],
237
+ publications = [],
238
+ languages = [],
239
+ interests = [],
240
+ references = [],
241
+ } = resume;
242
+
243
+ return (
244
+ <Layout>
245
+ <Header>
246
+ {basics.name && <Name>{basics.name}</Name>}
247
+ {basics.label && <Label>{basics.label}</Label>}
248
+ <ContactWrapper>
249
+ {basics.email && (
250
+ <ContactInfo type="email">{basics.email}</ContactInfo>
251
+ )}
252
+ {basics.phone && (
253
+ <ContactInfo type="phone">{basics.phone}</ContactInfo>
254
+ )}
255
+ {basics.location?.city && basics.location?.region && (
256
+ <ContactInfo type="location">
257
+ {basics.location.city}, {basics.location.region}
258
+ </ContactInfo>
259
+ )}
260
+ {basics.url && (
261
+ <ContactInfo type="url">
262
+ <a href={safeUrl(basics.url)}>{basics.url}</a>
263
+ </ContactInfo>
264
+ )}
265
+ {basics.profiles?.map((profile, index) => (
266
+ <ContactInfo key={index} type="social">
267
+ <a href={safeUrl(profile.url)}>{profile.network}</a>
268
+ </ContactInfo>
269
+ ))}
270
+ </ContactWrapper>
271
+ </Header>
272
+
273
+ {basics.summary && (
274
+ <SummarySection>
275
+ <SummaryText>{basics.summary}</SummaryText>
276
+ </SummarySection>
277
+ )}
278
+
279
+ {work.length > 0 && (
280
+ <MainSection>
281
+ <MainSectionTitle>Experience</MainSectionTitle>
282
+ {work.map((job, index) => (
283
+ <WorkItem key={index}>
284
+ <WorkHeader>
285
+ <WorkTitle>{job.position || job.name}</WorkTitle>
286
+ <WorkMeta>
287
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
288
+ </WorkMeta>
289
+ </WorkHeader>
290
+ {job.name && <WorkCompany>{job.name}</WorkCompany>}
291
+ {job.summary && <WorkDescription>{job.summary}</WorkDescription>}
292
+ {job.highlights && job.highlights.length > 0 && (
293
+ <WorkHighlights>
294
+ {job.highlights.map((highlight, i) => (
295
+ <li key={i}>{highlight}</li>
296
+ ))}
297
+ </WorkHighlights>
298
+ )}
299
+ </WorkItem>
300
+ ))}
301
+ </MainSection>
302
+ )}
303
+
304
+ {skills.length > 0 && (
305
+ <MainSection>
306
+ <MainSectionTitle>Skills</MainSectionTitle>
307
+ <SkillsGrid>
308
+ {skills.map((skill, index) => (
309
+ <SkillItem key={index}>
310
+ <SkillName>{skill.name}</SkillName>
311
+ <StyledBadgeList>
312
+ {skill.keywords?.map((keyword, i) => (
313
+ <StyledBadge key={i}>{keyword}</StyledBadge>
314
+ ))}
315
+ </StyledBadgeList>
316
+ </SkillItem>
317
+ ))}
318
+ </SkillsGrid>
319
+ </MainSection>
320
+ )}
321
+
322
+ {projects.length > 0 && (
323
+ <MainSection>
324
+ <MainSectionTitle>Projects</MainSectionTitle>
325
+ {projects.map((project, index) => (
326
+ <WorkItem key={index}>
327
+ <WorkHeader>
328
+ <WorkTitle>
329
+ {project.url ? (
330
+ <Link href={safeUrl(project.url)}>{project.name}</Link>
331
+ ) : (
332
+ project.name
333
+ )}
334
+ </WorkTitle>
335
+ {(project.startDate || project.endDate) && (
336
+ <WorkMeta>
337
+ <DateRange
338
+ startDate={project.startDate}
339
+ endDate={project.endDate}
340
+ />
341
+ </WorkMeta>
342
+ )}
343
+ </WorkHeader>
344
+ {project.description && (
345
+ <WorkDescription>{project.description}</WorkDescription>
346
+ )}
347
+ {project.highlights && project.highlights.length > 0 && (
348
+ <WorkHighlights>
349
+ {project.highlights.map((highlight, i) => (
350
+ <li key={i}>{highlight}</li>
351
+ ))}
352
+ </WorkHighlights>
353
+ )}
354
+ </WorkItem>
355
+ ))}
356
+ </MainSection>
357
+ )}
358
+
359
+ {education.length > 0 && (
360
+ <MainSection>
361
+ <MainSectionTitle>Education</MainSectionTitle>
362
+ {education.map((edu, index) => (
363
+ <WorkItem key={index}>
364
+ <WorkHeader>
365
+ <WorkTitle>{edu.institution}</WorkTitle>
366
+ <WorkMeta>
367
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
368
+ </WorkMeta>
369
+ </WorkHeader>
370
+ {edu.studyType && edu.area && (
371
+ <WorkCompany>
372
+ {edu.studyType} in {edu.area}
373
+ </WorkCompany>
374
+ )}
375
+ {edu.score && <WorkDescription>GPA: {edu.score}</WorkDescription>}
376
+ </WorkItem>
377
+ ))}
378
+ </MainSection>
379
+ )}
380
+
381
+ {volunteer.length > 0 && (
382
+ <MainSection>
383
+ <MainSectionTitle>Volunteer</MainSectionTitle>
384
+ {volunteer.map((vol, index) => (
385
+ <WorkItem key={index}>
386
+ <WorkHeader>
387
+ <WorkTitle>{vol.position}</WorkTitle>
388
+ <WorkMeta>
389
+ <DateRange startDate={vol.startDate} endDate={vol.endDate} />
390
+ </WorkMeta>
391
+ </WorkHeader>
392
+ <WorkCompany>{vol.organization}</WorkCompany>
393
+ {vol.summary && <WorkDescription>{vol.summary}</WorkDescription>}
394
+ {vol.highlights && vol.highlights.length > 0 && (
395
+ <WorkHighlights>
396
+ {vol.highlights.map((highlight, i) => (
397
+ <li key={i}>{highlight}</li>
398
+ ))}
399
+ </WorkHighlights>
400
+ )}
401
+ </WorkItem>
402
+ ))}
403
+ </MainSection>
404
+ )}
405
+
406
+ {awards.length > 0 && (
407
+ <MainSection>
408
+ <MainSectionTitle>Awards</MainSectionTitle>
409
+ <SimpleList>
410
+ {awards.map((award, index) => (
411
+ <SimpleItem key={index}>
412
+ <strong>{award.title}</strong>
413
+ {award.awarder && ` — ${award.awarder}`}
414
+ {award.date && (
415
+ <div
416
+ style={{
417
+ fontSize: '12px',
418
+ color: '#9ca3af',
419
+ marginTop: '4px',
420
+ }}
421
+ >
422
+ {award.date}
423
+ </div>
424
+ )}
425
+ </SimpleItem>
426
+ ))}
427
+ </SimpleList>
428
+ </MainSection>
429
+ )}
430
+
431
+ {publications.length > 0 && (
432
+ <MainSection>
433
+ <MainSectionTitle>Publications</MainSectionTitle>
434
+ <SimpleList>
435
+ {publications.map((pub, index) => (
436
+ <SimpleItem key={index}>
437
+ <strong>{pub.name}</strong>
438
+ {pub.publisher && ` — ${pub.publisher}`}
439
+ {pub.releaseDate && (
440
+ <div
441
+ style={{
442
+ fontSize: '12px',
443
+ color: '#9ca3af',
444
+ marginTop: '4px',
445
+ }}
446
+ >
447
+ {pub.releaseDate}
448
+ </div>
449
+ )}
450
+ </SimpleItem>
451
+ ))}
452
+ </SimpleList>
453
+ </MainSection>
454
+ )}
455
+
456
+ {languages.length > 0 && (
457
+ <MainSection>
458
+ <MainSectionTitle>Languages</MainSectionTitle>
459
+ <SimpleList>
460
+ {languages.map((lang, index) => (
461
+ <SimpleItem key={index}>
462
+ <strong>{lang.language}</strong>
463
+ {lang.fluency && ` — ${lang.fluency}`}
464
+ </SimpleItem>
465
+ ))}
466
+ </SimpleList>
467
+ </MainSection>
468
+ )}
469
+
470
+ {interests.length > 0 && (
471
+ <MainSection>
472
+ <MainSectionTitle>Interests</MainSectionTitle>
473
+ <SimpleList>
474
+ {interests.map((interest, index) => (
475
+ <SimpleItem key={index}>{interest.name}</SimpleItem>
476
+ ))}
477
+ </SimpleList>
478
+ </MainSection>
479
+ )}
480
+
481
+ {references.length > 0 && (
482
+ <MainSection>
483
+ <MainSectionTitle>References</MainSectionTitle>
484
+ {references.map((ref, index) => (
485
+ <WorkItem key={index}>
486
+ <WorkTitle>{ref.name}</WorkTitle>
487
+ {ref.reference && (
488
+ <WorkDescription>{ref.reference}</WorkDescription>
489
+ )}
490
+ </WorkItem>
491
+ ))}
492
+ </MainSection>
493
+ )}
494
+ </Layout>
495
+ );
496
+ }
497
+
498
+ export default Resume;
package/src/index.js ADDED
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { renderToString } from 'react-dom/server';
3
+ import { ServerStyleSheet } from 'styled-components';
4
+ import Resume from './Resume.jsx';
5
+
6
+ export function render(resume) {
7
+ const sheet = new ServerStyleSheet();
8
+
9
+ try {
10
+ const html = renderToString(
11
+ sheet.collectStyles(<Resume resume={resume} />)
12
+ );
13
+ const styles = sheet.getStyleTags();
14
+
15
+ return `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>${resume.basics?.name || 'Resume'} - Resume</title>
21
+ <link rel="preconnect" href="https://fonts.googleapis.com">
22
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
23
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400&display=swap" rel="stylesheet">
24
+ ${styles}
25
+ <style>
26
+ * {
27
+ box-sizing: border-box;
28
+ margin: 0;
29
+ padding: 0;
30
+ }
31
+ body {
32
+ margin: 0;
33
+ padding: 0;
34
+ background: #fafafa;
35
+ }
36
+ @media print {
37
+ body {
38
+ background: white;
39
+ }
40
+ @page {
41
+ margin: 0.5in;
42
+ }
43
+ }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ ${html}
48
+ </body>
49
+ </html>`;
50
+ } finally {
51
+ sheet.seal();
52
+ }
53
+ }