jsonresume-theme-reference 0.2.2 → 0.2.4
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/CHANGELOG.md +13 -0
- package/dist/index.css +1 -0
- package/dist/index.js +43 -31
- package/package.json +4 -4
- package/tests/theme.test.js +15 -10
- package/dist/style.css +0 -139
package/CHANGELOG.md
CHANGED
package/dist/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--resume-font-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;--resume-font-serif: Cambria, Georgia, "Times New Roman", serif;--resume-font-mono: "Courier New", Courier, monospace;--resume-size-name: 36px;--resume-size-heading: 16px;--resume-size-subheading: 14px;--resume-size-body: 11px;--resume-size-small: 10px;--resume-weight-normal: 400;--resume-weight-medium: 500;--resume-weight-semibold: 600;--resume-weight-bold: 700;--resume-line-height-tight: 1.2;--resume-line-height-normal: 1.5;--resume-line-height-relaxed: 1.75;--resume-color-primary: #1a1a1a;--resume-color-secondary: #4a4a4a;--resume-color-accent: #2563eb;--resume-color-background: #ffffff;--resume-color-border: #e5e7eb;--resume-space-section: 24px;--resume-space-item: 16px;--resume-space-tight: 8px;--resume-space-margin: 48px;--resume-max-width: 660px;--resume-column-gap: 24px;--resume-radius-sm: 4px;--resume-radius-md: 8px;--resume-radius-lg: 12px;--resume-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, .05);--resume-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .1)}[data-theme=modern]{--resume-color-primary: #0f172a;--resume-color-secondary: #475569;--resume-color-accent: #8b5cf6;--resume-font-sans: "Inter", -apple-system, BlinkMacSystemFont, sans-serif}[data-theme=classic]{--resume-color-primary: #000000;--resume-color-secondary: #333333;--resume-color-accent: #0066cc;--resume-font-sans: Georgia, "Times New Roman", serif}[data-theme=minimal]{--resume-color-primary: #18181b;--resume-color-secondary: #71717a;--resume-color-accent: #000000;--resume-space-section: 32px;--resume-space-item: 20px}[data-theme=high-contrast]{--resume-color-primary: #000000;--resume-color-secondary: #000000;--resume-color-accent: #0000ff;--resume-color-background: #ffffff;--resume-color-border: #000000}@media print{:root{--resume-space-section: 18px;--resume-space-item: 12px}@page{size:A4;margin:.5in}.resume-section{page-break-inside:avoid}.resume-item{break-inside:avoid}p,li{widows:3;orphans:3}.resume-description{hyphens:auto}.no-print{display:none!important}}[dir=rtl]{text-align:right}[dir=rtl] .resume-item{padding-left:0;padding-right:var(--resume-space-tight)}
|
package/dist/index.js
CHANGED
|
@@ -41,38 +41,46 @@ var isPropValid = /* @__PURE__ */ memoize(
|
|
|
41
41
|
function getDefaultExportFromCjs(x2) {
|
|
42
42
|
return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
|
|
43
43
|
}
|
|
44
|
-
var shallowequal
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
|
|
61
|
-
for (var idx = 0; idx < keysA.length; idx++) {
|
|
62
|
-
var key = keysA[idx];
|
|
63
|
-
if (!bHasOwnProperty(key)) {
|
|
44
|
+
var shallowequal;
|
|
45
|
+
var hasRequiredShallowequal;
|
|
46
|
+
function requireShallowequal() {
|
|
47
|
+
if (hasRequiredShallowequal) return shallowequal;
|
|
48
|
+
hasRequiredShallowequal = 1;
|
|
49
|
+
shallowequal = function shallowEqual(objA, objB, compare, compareContext) {
|
|
50
|
+
var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
|
|
51
|
+
if (ret !== void 0) {
|
|
52
|
+
return !!ret;
|
|
53
|
+
}
|
|
54
|
+
if (objA === objB) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
|
|
64
58
|
return false;
|
|
65
59
|
}
|
|
66
|
-
var
|
|
67
|
-
var
|
|
68
|
-
|
|
69
|
-
if (ret === false || ret === void 0 && valueA !== valueB) {
|
|
60
|
+
var keysA = Object.keys(objA);
|
|
61
|
+
var keysB = Object.keys(objB);
|
|
62
|
+
if (keysA.length !== keysB.length) {
|
|
70
63
|
return false;
|
|
71
64
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
|
|
66
|
+
for (var idx = 0; idx < keysA.length; idx++) {
|
|
67
|
+
var key = keysA[idx];
|
|
68
|
+
if (!bHasOwnProperty(key)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
var valueA = objA[key];
|
|
72
|
+
var valueB = objB[key];
|
|
73
|
+
ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
|
|
74
|
+
if (ret === false || ret === void 0 && valueA !== valueB) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
return shallowequal;
|
|
81
|
+
}
|
|
82
|
+
var shallowequalExports = requireShallowequal();
|
|
83
|
+
const p = /* @__PURE__ */ getDefaultExportFromCjs(shallowequalExports);
|
|
76
84
|
var MS = "-ms-";
|
|
77
85
|
var MOZ = "-moz-";
|
|
78
86
|
var WEBKIT = "-webkit-";
|
|
@@ -1490,6 +1498,7 @@ function formatDateRange({
|
|
|
1490
1498
|
};
|
|
1491
1499
|
const formatDate = (dateStr) => {
|
|
1492
1500
|
if (!dateStr) return getPresentLabel();
|
|
1501
|
+
const isDateOnlyIso = typeof dateStr === "string" && /^\d{4}(-\d{2}){0,2}$/.test(dateStr);
|
|
1493
1502
|
const date = typeof dateStr === "string" ? new Date(dateStr) : dateStr;
|
|
1494
1503
|
if (isNaN(date.getTime())) return dateStr;
|
|
1495
1504
|
const monthFormats = {
|
|
@@ -1501,6 +1510,9 @@ function formatDateRange({
|
|
|
1501
1510
|
...monthFormats[format],
|
|
1502
1511
|
year: "numeric"
|
|
1503
1512
|
};
|
|
1513
|
+
if (isDateOnlyIso) {
|
|
1514
|
+
options.timeZone = "UTC";
|
|
1515
|
+
}
|
|
1504
1516
|
if (numberingSystem) {
|
|
1505
1517
|
options.numberingSystem = numberingSystem;
|
|
1506
1518
|
}
|
|
@@ -1508,7 +1520,9 @@ function formatDateRange({
|
|
|
1508
1520
|
return formatter.format(date);
|
|
1509
1521
|
};
|
|
1510
1522
|
const start = formatDate(startDate);
|
|
1511
|
-
if (endDate === void 0)
|
|
1523
|
+
if (endDate === void 0) {
|
|
1524
|
+
return start;
|
|
1525
|
+
}
|
|
1512
1526
|
const end = formatDate(endDate);
|
|
1513
1527
|
return `${start} - ${end}`;
|
|
1514
1528
|
}
|
|
@@ -1629,7 +1643,6 @@ function safeUrl(url) {
|
|
|
1629
1643
|
const trimmed = url.trim();
|
|
1630
1644
|
const dangerousProtocols = /^(javascript|data|vbscript|file|about):/i;
|
|
1631
1645
|
if (dangerousProtocols.test(trimmed)) {
|
|
1632
|
-
console.warn(`[Security] Blocked dangerous URL: ${trimmed.slice(0, 50)}`);
|
|
1633
1646
|
return null;
|
|
1634
1647
|
}
|
|
1635
1648
|
const safeProtocols = /^(https?|mailto|tel|sms|ftp):/i;
|
|
@@ -1645,7 +1658,6 @@ function safeUrl(url) {
|
|
|
1645
1658
|
if (/^[a-z0-9][a-z0-9.-]+\.[a-z]{2,}$/i.test(trimmed)) {
|
|
1646
1659
|
return `https://${trimmed}`;
|
|
1647
1660
|
}
|
|
1648
|
-
console.warn(`[Security] Uncertain URL safety: ${trimmed.slice(0, 50)}`);
|
|
1649
1661
|
return trimmed;
|
|
1650
1662
|
}
|
|
1651
1663
|
function getLinkRel(url, openInNewTab = false) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsonresume-theme-reference",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Reference theme demonstrating @resume/core best practices - ATS-friendly, framework-agnostic",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"react": "^19.2.0",
|
|
27
27
|
"react-dom": "^19.2.0",
|
|
28
28
|
"styled-components": "^6.1.19",
|
|
29
|
-
"@jsonresume/core": "0.3.
|
|
29
|
+
"@jsonresume/core": "0.3.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"vitest": "^
|
|
32
|
+
"vitest": "^3.2.6",
|
|
33
33
|
"@vitejs/plugin-react": "^4.3.4",
|
|
34
|
-
"vite": "^
|
|
34
|
+
"vite": "^6.4.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": "^18.0.0 || ^19.0.0",
|
package/tests/theme.test.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { render } from '../src/index.
|
|
2
|
+
import { render } from '../src/index.jsx';
|
|
3
3
|
import completeResume from './fixtures/complete-resume.json';
|
|
4
4
|
|
|
5
5
|
describe('Reference Theme', () => {
|
|
6
6
|
it('renders complete resume HTML', () => {
|
|
7
7
|
const html = render(completeResume);
|
|
8
8
|
|
|
9
|
-
// Should be valid HTML document
|
|
9
|
+
// Should be valid HTML document (lang + dir come from i18n options)
|
|
10
10
|
expect(html).toContain('<!DOCTYPE html>');
|
|
11
|
-
expect(html).toContain('<html lang="en">');
|
|
11
|
+
expect(html).toContain('<html lang="en" dir="ltr">');
|
|
12
12
|
expect(html).toContain('</html>');
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -86,7 +86,8 @@ describe('Reference Theme', () => {
|
|
|
86
86
|
it('renders awards section', () => {
|
|
87
87
|
const html = render(completeResume);
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// JSX escapes the ampersand in the section title
|
|
90
|
+
expect(html).toContain('Awards & Honors');
|
|
90
91
|
expect(html).toContain('Hackathon Winner');
|
|
91
92
|
expect(html).toContain('Employee of the Year');
|
|
92
93
|
});
|
|
@@ -139,11 +140,14 @@ describe('Reference Theme', () => {
|
|
|
139
140
|
expect(html).toContain('</html>');
|
|
140
141
|
});
|
|
141
142
|
|
|
142
|
-
it('includes @jsonresume/core
|
|
143
|
+
it('includes @jsonresume/core design tokens (inlined)', () => {
|
|
143
144
|
const html = render(completeResume);
|
|
144
145
|
|
|
146
|
+
// Design tokens are inlined in a <style> block (no external CDN link)
|
|
147
|
+
// so the document is self-contained for print/offline/CLI rendering
|
|
145
148
|
expect(html).toContain('@jsonresume/core');
|
|
146
|
-
expect(html).toContain('
|
|
149
|
+
expect(html).toContain('--resume-font-sans:');
|
|
150
|
+
expect(html).toContain('--resume-max-width:');
|
|
147
151
|
});
|
|
148
152
|
|
|
149
153
|
it('uses all @jsonresume/core primitives', () => {
|
|
@@ -177,15 +181,16 @@ describe('Reference Theme', () => {
|
|
|
177
181
|
// No tables or complex layouts
|
|
178
182
|
expect(html).not.toContain('<table');
|
|
179
183
|
|
|
180
|
-
// Standard fonts (
|
|
181
|
-
|
|
184
|
+
// Standard fonts via design token (styled-components minifies whitespace
|
|
185
|
+
// and appends font fallbacks inside var())
|
|
186
|
+
expect(html).toMatch(/font-family:\s*var\(\s*--resume-font-sans/);
|
|
182
187
|
});
|
|
183
188
|
|
|
184
189
|
it('is print-ready', () => {
|
|
185
190
|
const html = render(completeResume);
|
|
186
191
|
|
|
187
|
-
// Should be single-page optimized
|
|
188
|
-
expect(html).
|
|
192
|
+
// Should be single-page optimized (token used with a px fallback)
|
|
193
|
+
expect(html).toMatch(/max-width:\s*var\(--resume-max-width/);
|
|
189
194
|
|
|
190
195
|
// Standard A4/Letter dimensions assumed via CSS variables
|
|
191
196
|
});
|
package/dist/style.css
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resume Core Design Tokens
|
|
3
|
-
* Framework-agnostic CSS variables for theming
|
|
4
|
-
* Based on 2025 resume best practices and ATS compatibility research
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
:root {
|
|
8
|
-
/* Typography - ATS-friendly fonts */
|
|
9
|
-
--resume-font-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
10
|
-
--resume-font-serif: Cambria, Georgia, "Times New Roman", serif;
|
|
11
|
-
--resume-font-mono: "Courier New", Courier, monospace;
|
|
12
|
-
|
|
13
|
-
/* Font Sizes - Optimal readability */
|
|
14
|
-
--resume-size-name: 36px; /* Large, prominent name */
|
|
15
|
-
--resume-size-heading: 16px; /* Section titles */
|
|
16
|
-
--resume-size-subheading: 14px; /* Job titles, degrees */
|
|
17
|
-
--resume-size-body: 11px; /* Body text, descriptions */
|
|
18
|
-
--resume-size-small: 10px; /* Dates, locations, metadata */
|
|
19
|
-
|
|
20
|
-
/* Font Weights */
|
|
21
|
-
--resume-weight-normal: 400;
|
|
22
|
-
--resume-weight-medium: 500;
|
|
23
|
-
--resume-weight-semibold: 600;
|
|
24
|
-
--resume-weight-bold: 700;
|
|
25
|
-
|
|
26
|
-
/* Line Heights */
|
|
27
|
-
--resume-line-height-tight: 1.2;
|
|
28
|
-
--resume-line-height-normal: 1.5;
|
|
29
|
-
--resume-line-height-relaxed: 1.75;
|
|
30
|
-
|
|
31
|
-
/* Colors - Professional Theme (default) */
|
|
32
|
-
--resume-color-primary: #1a1a1a;
|
|
33
|
-
--resume-color-secondary: #4a4a4a;
|
|
34
|
-
--resume-color-accent: #2563eb;
|
|
35
|
-
--resume-color-background: #ffffff;
|
|
36
|
-
--resume-color-border: #e5e7eb;
|
|
37
|
-
|
|
38
|
-
/* Spacing - Consistent rhythm */
|
|
39
|
-
--resume-space-section: 24px; /* Between major sections */
|
|
40
|
-
--resume-space-item: 16px; /* Between list items */
|
|
41
|
-
--resume-space-tight: 8px; /* Within items */
|
|
42
|
-
--resume-space-margin: 48px; /* Page margins */
|
|
43
|
-
|
|
44
|
-
/* Layout - Optimal line length */
|
|
45
|
-
--resume-max-width: 660px; /* ~80 chars at body size */
|
|
46
|
-
--resume-column-gap: 24px;
|
|
47
|
-
|
|
48
|
-
/* Border Radius */
|
|
49
|
-
--resume-radius-sm: 4px;
|
|
50
|
-
--resume-radius-md: 8px;
|
|
51
|
-
--resume-radius-lg: 12px;
|
|
52
|
-
|
|
53
|
-
/* Shadows - Subtle depth */
|
|
54
|
-
--resume-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
55
|
-
--resume-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* Modern Theme Variant */
|
|
59
|
-
[data-theme="modern"] {
|
|
60
|
-
--resume-color-primary: #0f172a;
|
|
61
|
-
--resume-color-secondary: #475569;
|
|
62
|
-
--resume-color-accent: #8b5cf6;
|
|
63
|
-
--resume-font-sans: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* Classic Theme Variant */
|
|
67
|
-
[data-theme="classic"] {
|
|
68
|
-
--resume-color-primary: #000000;
|
|
69
|
-
--resume-color-secondary: #333333;
|
|
70
|
-
--resume-color-accent: #0066cc;
|
|
71
|
-
--resume-font-sans: Georgia, "Times New Roman", serif;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* Minimal Theme Variant */
|
|
75
|
-
[data-theme="minimal"] {
|
|
76
|
-
--resume-color-primary: #18181b;
|
|
77
|
-
--resume-color-secondary: #71717a;
|
|
78
|
-
--resume-color-accent: #000000;
|
|
79
|
-
--resume-space-section: 32px;
|
|
80
|
-
--resume-space-item: 20px;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* High Contrast Variant (Accessibility) */
|
|
84
|
-
[data-theme="high-contrast"] {
|
|
85
|
-
--resume-color-primary: #000000;
|
|
86
|
-
--resume-color-secondary: #000000;
|
|
87
|
-
--resume-color-accent: #0000ff;
|
|
88
|
-
--resume-color-background: #ffffff;
|
|
89
|
-
--resume-color-border: #000000;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Print Styles - ATS-Friendly PDF Export */
|
|
93
|
-
@media print {
|
|
94
|
-
:root {
|
|
95
|
-
/* Optimize for print */
|
|
96
|
-
--resume-space-section: 18px;
|
|
97
|
-
--resume-space-item: 12px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@page {
|
|
101
|
-
size: A4;
|
|
102
|
-
margin: 0.5in;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/* Prevent awkward breaks */
|
|
106
|
-
.resume-section {
|
|
107
|
-
page-break-inside: avoid;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.resume-item {
|
|
111
|
-
break-inside: avoid;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/* Widows and orphans control */
|
|
115
|
-
p, li {
|
|
116
|
-
widows: 3;
|
|
117
|
-
orphans: 3;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* Hyphenation for long words */
|
|
121
|
-
.resume-description {
|
|
122
|
-
hyphens: auto;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* Hide interactive elements */
|
|
126
|
-
.no-print {
|
|
127
|
-
display: none !important;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* RTL Support */
|
|
132
|
-
[dir="rtl"] {
|
|
133
|
-
text-align: right;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
[dir="rtl"] .resume-item {
|
|
137
|
-
padding-left: 0;
|
|
138
|
-
padding-right: var(--resume-space-tight);
|
|
139
|
-
}
|