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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # jsonresume-theme-reference
2
2
 
3
+ ## 0.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - @jsonresume/core@0.3.2
8
+
9
+ ## 0.2.3
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [ff09f75]
14
+ - @jsonresume/core@0.3.1
15
+
3
16
  ## 0.2.2
4
17
 
5
18
  ### Patch Changes
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 = function shallowEqual(objA, objB, compare, compareContext) {
45
- var ret = compare ? compare.call(compareContext, objA, objB) : void 0;
46
- if (ret !== void 0) {
47
- return !!ret;
48
- }
49
- if (objA === objB) {
50
- return true;
51
- }
52
- if (typeof objA !== "object" || !objA || typeof objB !== "object" || !objB) {
53
- return false;
54
- }
55
- var keysA = Object.keys(objA);
56
- var keysB = Object.keys(objB);
57
- if (keysA.length !== keysB.length) {
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 valueA = objA[key];
67
- var valueB = objB[key];
68
- ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;
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
- return true;
74
- };
75
- const p = /* @__PURE__ */ getDefaultExportFromCjs(shallowequal);
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) return start;
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.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.0"
29
+ "@jsonresume/core": "0.3.2"
30
30
  },
31
31
  "devDependencies": {
32
- "vitest": "^1.6.0",
32
+ "vitest": "^3.2.6",
33
33
  "@vitejs/plugin-react": "^4.3.4",
34
- "vite": "^5.4.21"
34
+ "vite": "^6.4.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^18.0.0 || ^19.0.0",
@@ -1,14 +1,14 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { render } from '../src/index.js';
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
- expect(html).toContain('Awards & Honors');
89
+ // JSX escapes the ampersand in the section title
90
+ expect(html).toContain('Awards &amp; 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 stylesheet', () => {
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('tokens.css');
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 (defined in inline styles)
181
- expect(html).toContain('font-family: var(--resume-font-sans)');
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).toContain('max-width: var(--resume-max-width)');
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
- }