jsonresume-theme-minimalist-grid 1.0.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/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "jsonresume-theme-minimalist-grid",
3
+ "version": "1.0.0",
4
+ "description": "A minimalist theme with perfect spacing and geometric clarity",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "jsonresume",
10
+ "theme",
11
+ "minimalist",
12
+ "grid",
13
+ "modern",
14
+ "clean"
15
+ ],
16
+ "peerDependencies": {
17
+ "react": "^18.0.0 || ^19.0.0",
18
+ "react-dom": "^18.0.0 || ^19.0.0"
19
+ },
20
+ "dependencies": {
21
+ "styled-components": "^6.1.19",
22
+ "@resume/core": "0.1.0"
23
+ }
24
+ }
package/src/Resume.jsx ADDED
@@ -0,0 +1,426 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { Section, SectionTitle, DateRange, ContactInfo } from '@resume/core';
4
+
5
+ const Layout = styled.div`
6
+ max-width: 800px;
7
+ margin: 0 auto;
8
+ padding: 80px 60px;
9
+ background: white;
10
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
11
+ sans-serif;
12
+ color: #374151;
13
+ font-weight: 300;
14
+ line-height: 1.8;
15
+
16
+ @media print {
17
+ padding: 40px;
18
+ }
19
+ `;
20
+
21
+ const Header = styled.header`
22
+ margin-bottom: 64px;
23
+ padding-bottom: 40px;
24
+ border-bottom: 1px solid #e5e7eb;
25
+ `;
26
+
27
+ const Name = styled.h1`
28
+ font-size: 40px;
29
+ font-weight: 300;
30
+ color: #111827;
31
+ margin: 0 0 12px 0;
32
+ letter-spacing: 0.08em;
33
+ text-transform: uppercase;
34
+ `;
35
+
36
+ const Label = styled.div`
37
+ font-size: 14px;
38
+ color: #6b7280;
39
+ margin-bottom: 24px;
40
+ font-weight: 300;
41
+ letter-spacing: 0.1em;
42
+ text-transform: uppercase;
43
+ `;
44
+
45
+ const StyledContactInfo = styled(ContactInfo)`
46
+ font-size: 14px;
47
+ letter-spacing: 0.05em;
48
+
49
+ a {
50
+ font-size: 14px;
51
+ font-weight: 300;
52
+ }
53
+ `;
54
+
55
+ const Summary = styled.p`
56
+ font-size: 15px;
57
+ line-height: 1.9;
58
+ color: #4b5563;
59
+ margin: 28px 0 0 0;
60
+ font-weight: 300;
61
+ letter-spacing: 0.02em;
62
+ `;
63
+
64
+ const StyledSectionTitle = styled(SectionTitle)`
65
+ font-size: 14px;
66
+ font-weight: 400;
67
+ color: #111827;
68
+ margin: 56px 0 32px 0;
69
+ padding-bottom: 12px;
70
+ border-bottom: 1px solid #e5e7eb;
71
+ letter-spacing: 0.12em;
72
+ text-transform: uppercase;
73
+ `;
74
+
75
+ const WorkItem = styled.div`
76
+ margin-bottom: 40px;
77
+
78
+ &:last-child {
79
+ margin-bottom: 0;
80
+ }
81
+ `;
82
+
83
+ const WorkHeader = styled.div`
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: baseline;
87
+ margin-bottom: 12px;
88
+ gap: 20px;
89
+
90
+ @media (max-width: 640px) {
91
+ flex-direction: column;
92
+ align-items: flex-start;
93
+ gap: 8px;
94
+ }
95
+ `;
96
+
97
+ const Position = styled.h3`
98
+ font-size: 17px;
99
+ font-weight: 400;
100
+ color: #111827;
101
+ margin: 0;
102
+ letter-spacing: 0.04em;
103
+ `;
104
+
105
+ const Company = styled.div`
106
+ font-size: 15px;
107
+ color: #6b7280;
108
+ font-weight: 300;
109
+ margin-top: 6px;
110
+ letter-spacing: 0.03em;
111
+ `;
112
+
113
+ const DateText = styled.div`
114
+ font-size: 13px;
115
+ color: #9ca3af;
116
+ font-weight: 300;
117
+ white-space: nowrap;
118
+ letter-spacing: 0.05em;
119
+ `;
120
+
121
+ const WorkSummary = styled.p`
122
+ margin: 16px 0;
123
+ color: #4b5563;
124
+ line-height: 1.9;
125
+ font-size: 15px;
126
+ font-weight: 300;
127
+ letter-spacing: 0.02em;
128
+ `;
129
+
130
+ const Highlights = styled.ul`
131
+ margin: 16px 0 0 0;
132
+ padding-left: 24px;
133
+ list-style-type: none;
134
+
135
+ li {
136
+ margin: 10px 0;
137
+ color: #4b5563;
138
+ line-height: 1.9;
139
+ padding-left: 0;
140
+ font-weight: 300;
141
+ letter-spacing: 0.02em;
142
+ position: relative;
143
+
144
+ &:before {
145
+ content: '·';
146
+ position: absolute;
147
+ left: -16px;
148
+ color: #d1d5db;
149
+ }
150
+ }
151
+ `;
152
+
153
+ const EducationItem = styled.div`
154
+ margin-bottom: 32px;
155
+
156
+ &:last-child {
157
+ margin-bottom: 0;
158
+ }
159
+ `;
160
+
161
+ const Institution = styled.h3`
162
+ font-size: 16px;
163
+ font-weight: 400;
164
+ color: #111827;
165
+ margin: 0 0 8px 0;
166
+ letter-spacing: 0.04em;
167
+ `;
168
+
169
+ const Degree = styled.div`
170
+ font-size: 15px;
171
+ color: #4b5563;
172
+ margin-bottom: 6px;
173
+ font-weight: 300;
174
+ letter-spacing: 0.02em;
175
+ `;
176
+
177
+ const EducationDate = styled.div`
178
+ font-size: 13px;
179
+ color: #9ca3af;
180
+ font-weight: 300;
181
+ letter-spacing: 0.05em;
182
+ `;
183
+
184
+ const SkillsGrid = styled.div`
185
+ display: grid;
186
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
187
+ gap: 24px;
188
+ `;
189
+
190
+ const SkillCategory = styled.div`
191
+ padding: 20px;
192
+ background: #fafafa;
193
+ border-left: 1px solid #e5e7eb;
194
+ `;
195
+
196
+ const SkillName = styled.h4`
197
+ font-size: 14px;
198
+ font-weight: 400;
199
+ color: #111827;
200
+ margin: 0 0 12px 0;
201
+ letter-spacing: 0.06em;
202
+ text-transform: uppercase;
203
+ `;
204
+
205
+ const SkillTags = styled.div`
206
+ font-size: 14px;
207
+ color: #6b7280;
208
+ line-height: 1.8;
209
+ font-weight: 300;
210
+ letter-spacing: 0.02em;
211
+ `;
212
+
213
+ function Resume({ resume }) {
214
+ const {
215
+ basics = {},
216
+ work = [],
217
+ education = [],
218
+ skills = [],
219
+ projects = [],
220
+ volunteer = [],
221
+ awards = [],
222
+ publications = [],
223
+ languages = [],
224
+ interests = [],
225
+ references = [],
226
+ } = resume;
227
+
228
+ return (
229
+ <Layout>
230
+ <Header>
231
+ <Name>{basics.name}</Name>
232
+ {basics.label && <Label>{basics.label}</Label>}
233
+ <StyledContactInfo basics={basics} />
234
+ {basics.summary && <Summary>{basics.summary}</Summary>}
235
+ </Header>
236
+
237
+ {work?.length > 0 && (
238
+ <Section>
239
+ <StyledSectionTitle>Experience</StyledSectionTitle>
240
+ {work.map((job, index) => (
241
+ <WorkItem key={index}>
242
+ <WorkHeader>
243
+ <div>
244
+ <Position>{job.position}</Position>
245
+ {job.name && <Company>{job.name}</Company>}
246
+ </div>
247
+ <DateText>
248
+ <DateRange startDate={job.startDate} endDate={job.endDate} />
249
+ </DateText>
250
+ </WorkHeader>
251
+ {job.summary && <WorkSummary>{job.summary}</WorkSummary>}
252
+ {job.highlights?.length > 0 && (
253
+ <Highlights>
254
+ {job.highlights.map((highlight, i) => (
255
+ <li key={i}>{highlight}</li>
256
+ ))}
257
+ </Highlights>
258
+ )}
259
+ </WorkItem>
260
+ ))}
261
+ </Section>
262
+ )}
263
+
264
+ {skills?.length > 0 && (
265
+ <Section>
266
+ <StyledSectionTitle>Skills</StyledSectionTitle>
267
+ <SkillsGrid>
268
+ {skills.map((skill, index) => (
269
+ <SkillCategory key={index}>
270
+ <SkillName>{skill.name}</SkillName>
271
+ {skill.keywords?.length > 0 && (
272
+ <SkillTags>{skill.keywords.join(', ')}</SkillTags>
273
+ )}
274
+ </SkillCategory>
275
+ ))}
276
+ </SkillsGrid>
277
+ </Section>
278
+ )}
279
+
280
+ {education?.length > 0 && (
281
+ <Section>
282
+ <StyledSectionTitle>Education</StyledSectionTitle>
283
+ {education.map((edu, index) => (
284
+ <EducationItem key={index}>
285
+ <Institution>{edu.institution}</Institution>
286
+ <Degree>
287
+ {edu.studyType} in {edu.area}
288
+ {edu.score && ` • ${edu.score}`}
289
+ </Degree>
290
+ <EducationDate>
291
+ <DateRange startDate={edu.startDate} endDate={edu.endDate} />
292
+ </EducationDate>
293
+ </EducationItem>
294
+ ))}
295
+ </Section>
296
+ )}
297
+
298
+ {projects?.length > 0 && (
299
+ <Section>
300
+ <StyledSectionTitle>Projects</StyledSectionTitle>
301
+ {projects.map((project, index) => (
302
+ <WorkItem key={index}>
303
+ <Position>{project.name}</Position>
304
+ {project.description && (
305
+ <WorkSummary>{project.description}</WorkSummary>
306
+ )}
307
+ {project.highlights?.length > 0 && (
308
+ <Highlights>
309
+ {project.highlights.map((highlight, i) => (
310
+ <li key={i}>{highlight}</li>
311
+ ))}
312
+ </Highlights>
313
+ )}
314
+ </WorkItem>
315
+ ))}
316
+ </Section>
317
+ )}
318
+
319
+ {volunteer?.length > 0 && (
320
+ <Section>
321
+ <StyledSectionTitle>Volunteer</StyledSectionTitle>
322
+ {volunteer.map((vol, index) => (
323
+ <WorkItem key={index}>
324
+ <WorkHeader>
325
+ <div>
326
+ <Position>{vol.position}</Position>
327
+ {vol.organization && <Company>{vol.organization}</Company>}
328
+ </div>
329
+ {(vol.startDate || vol.endDate) && (
330
+ <DateText>
331
+ <DateRange
332
+ startDate={vol.startDate}
333
+ endDate={vol.endDate}
334
+ />
335
+ </DateText>
336
+ )}
337
+ </WorkHeader>
338
+ {vol.summary && <WorkSummary>{vol.summary}</WorkSummary>}
339
+ {vol.highlights?.length > 0 && (
340
+ <Highlights>
341
+ {vol.highlights.map((highlight, i) => (
342
+ <li key={i}>{highlight}</li>
343
+ ))}
344
+ </Highlights>
345
+ )}
346
+ </WorkItem>
347
+ ))}
348
+ </Section>
349
+ )}
350
+
351
+ {awards?.length > 0 && (
352
+ <Section>
353
+ <StyledSectionTitle>Awards</StyledSectionTitle>
354
+ {awards.map((award, index) => (
355
+ <EducationItem key={index}>
356
+ <Institution>{award.title}</Institution>
357
+ {award.awarder && <Degree>Awarded by {award.awarder}</Degree>}
358
+ {award.date && <EducationDate>{award.date}</EducationDate>}
359
+ {award.summary && <WorkSummary>{award.summary}</WorkSummary>}
360
+ </EducationItem>
361
+ ))}
362
+ </Section>
363
+ )}
364
+
365
+ {publications?.length > 0 && (
366
+ <Section>
367
+ <StyledSectionTitle>Publications</StyledSectionTitle>
368
+ {publications.map((pub, index) => (
369
+ <EducationItem key={index}>
370
+ <Institution>{pub.name}</Institution>
371
+ {pub.publisher && <Degree>Published by {pub.publisher}</Degree>}
372
+ {pub.releaseDate && (
373
+ <EducationDate>{pub.releaseDate}</EducationDate>
374
+ )}
375
+ {pub.summary && <WorkSummary>{pub.summary}</WorkSummary>}
376
+ </EducationItem>
377
+ ))}
378
+ </Section>
379
+ )}
380
+
381
+ {languages?.length > 0 && (
382
+ <Section>
383
+ <StyledSectionTitle>Languages</StyledSectionTitle>
384
+ <SkillsGrid>
385
+ {languages.map((lang, index) => (
386
+ <SkillCategory key={index}>
387
+ <SkillName>{lang.language}</SkillName>
388
+ {lang.fluency && <SkillTags>{lang.fluency}</SkillTags>}
389
+ </SkillCategory>
390
+ ))}
391
+ </SkillsGrid>
392
+ </Section>
393
+ )}
394
+
395
+ {interests?.length > 0 && (
396
+ <Section>
397
+ <StyledSectionTitle>Interests</StyledSectionTitle>
398
+ <SkillsGrid>
399
+ {interests.map((interest, index) => (
400
+ <SkillCategory key={index}>
401
+ <SkillName>{interest.name}</SkillName>
402
+ {interest.keywords?.length > 0 && (
403
+ <SkillTags>{interest.keywords.join(', ')}</SkillTags>
404
+ )}
405
+ </SkillCategory>
406
+ ))}
407
+ </SkillsGrid>
408
+ </Section>
409
+ )}
410
+
411
+ {references?.length > 0 && (
412
+ <Section>
413
+ <StyledSectionTitle>References</StyledSectionTitle>
414
+ {references.map((ref, index) => (
415
+ <EducationItem key={index}>
416
+ <Institution>{ref.name}</Institution>
417
+ {ref.reference && <WorkSummary>{ref.reference}</WorkSummary>}
418
+ </EducationItem>
419
+ ))}
420
+ </Section>
421
+ )}
422
+ </Layout>
423
+ );
424
+ }
425
+
426
+ 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
+ }