desy-html 15.0.3 → 16.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/AGENTS.md +180 -0
- package/README.md +22 -4
- package/TESTING_PLAN.md +369 -0
- package/branding/BRANDING.md +369 -0
- package/branding/branding.config.js +69 -0
- package/branding/branding.config.yourorganization.js +65 -0
- package/branding/logos/aragon-compact.svg +1 -0
- package/branding/logos/aragon-expanded.svg +1 -0
- package/branding/logos/aragon-mini.svg +1 -0
- package/branding/logos/yourlogo-compact.svg +13 -0
- package/branding/logos/yourlogo-expanded.svg +17 -0
- package/branding/logos/yourlogo-mini.svg +17 -0
- package/branding/vite-branding-plugin.js +128 -0
- package/docs/_global.head.njk +12 -4
- package/docs/_macro.example-render.njk +6 -0
- package/docs/catalogo.html +2 -2
- package/docs/componentes.html +2 -2
- package/docs/estilos.html +1 -1
- package/docs/examples-accordion-history.html +1 -1
- package/docs/examples-accordion.html +1 -1
- package/docs/examples-alert.html +1 -1
- package/docs/examples-breadcrumbs.html +1 -1
- package/docs/examples-button-loader.html +1 -1
- package/docs/examples-button.html +1 -1
- package/docs/examples-card.html +2 -2
- package/docs/examples-character-count.html +1 -1
- package/docs/examples-checkboxes.html +2 -2
- package/docs/examples-collapsible.html +2 -2
- package/docs/examples-date-input.html +1 -1
- package/docs/examples-datepicker.html +1 -1
- package/docs/examples-description-list.html +1 -1
- package/docs/examples-details.html +1 -1
- package/docs/examples-dialog.html +2 -2
- package/docs/examples-dropdown.html +1 -1
- package/docs/examples-error-message.html +1 -1
- package/docs/examples-error-summary.html +1 -1
- package/docs/examples-fieldset.html +1 -1
- package/docs/examples-file-upload.html +1 -1
- package/docs/examples-footer.html +1 -1
- package/docs/examples-header-advanced.html +1 -1
- package/docs/examples-header-mini.html +1 -1
- package/docs/examples-header.html +1 -1
- package/docs/examples-hint.html +1 -1
- package/docs/examples-input-group.html +1 -1
- package/docs/examples-input.html +1 -1
- package/docs/examples-item.html +1 -1
- package/docs/examples-label.html +1 -1
- package/docs/examples-links-list.html +2 -2
- package/docs/examples-listbox.html +1 -1
- package/docs/examples-media-object.html +2 -2
- package/docs/examples-menu-horizontal.html +1 -1
- package/docs/examples-menu-navigation.html +1 -1
- package/docs/examples-menu-vertical.html +2 -2
- package/docs/examples-menubar.html +2 -2
- package/docs/examples-modal.html +2 -2
- package/docs/examples-nav.html +1 -1
- package/docs/examples-notification.html +1 -1
- package/docs/examples-pagination.html +1 -1
- package/docs/examples-pill.html +1 -1
- package/docs/examples-radios.html +1 -1
- package/docs/examples-searchbar.html +1 -1
- package/docs/examples-select.html +1 -2
- package/docs/examples-skip-link.html +1 -1
- package/docs/examples-spinner.html +1 -1
- package/docs/examples-status-item.html +1 -1
- package/docs/examples-status.html +1 -1
- package/docs/examples-table-advanced.html +1 -1
- package/docs/examples-table.html +1 -1
- package/docs/examples-tabs.html +1 -1
- package/docs/examples-textarea.html +1 -1
- package/docs/examples-toggle.html +1 -1
- package/docs/examples-tooltip.html +1 -1
- package/docs/examples-tree.html +1 -1
- package/docs/examples-treegrid.html +1 -1
- package/docs/index.html +10 -3
- package/docs/pagina-accesibilidad.html +4 -4
- package/docs/pagina-mapa-web.html +3 -3
- package/docs/pagina-prueba.html +2 -2
- package/docs/plantilla-con-header-advanced.html +2 -2
- package/docs/plantilla-editar-con-cabecera-fija-y-sidebar-sticky.html +2 -2
- package/docs/plantilla-editar-con-cabecera-fija.html +2 -2
- package/docs/plantilla-logueado-con-cabecera-fija-headroom.html +2 -2
- package/docs/plantilla-logueado-con-cabecera-fija.html +2 -2
- package/docs/plantilla-logueado-con-selector-de-app-y-sidebar.html +2 -2
- package/docs/plantilla-logueado-con-selector-de-app-y-subheader.html +2 -2
- package/docs/plantilla-logueado-con-selector-de-app.html +2 -2
- package/docs/plantilla-logueado-con-titulo-de-app.html +2 -2
- package/docs/plantilla-sin-loguear.html +2 -2
- package/docs/plantillas.html +3 -3
- package/docs/spinner-plantilla-con-header-advanced.html +2 -2
- package/docs/spinner-plantilla-editar-con-cabecera-fija.html +1 -2
- package/docs/spinner-plantilla-logueado-con-cabecera-fija.html +2 -2
- package/docs/spinner-plantilla-logueado-con-selector-de-app-y-subheader.html +1 -3
- package/docs/spinner-plantilla-logueado-con-titulo-de-app.html +2 -2
- package/docs/spinner-plantilla-sin-loguear.html +2 -2
- package/package.json +7 -2
- package/replit.md +2 -2
- package/src/css/branding-variables.css +37 -0
- package/src/css/component.text.css +5 -0
- package/src/css/styles.css +18 -3
- package/src/js/aria/notification.js +6 -6
- package/src/js/desy-html.js +5 -0
- package/src/templates/components/accordion/_examples.accordion.njk +84 -0
- package/src/templates/components/accordion-history/_examples.accordion-history.njk +90 -0
- package/src/templates/components/alert/_examples.alert.njk +12 -0
- package/src/templates/components/breadcrumbs/_examples.breadcrumbs.njk +90 -0
- package/src/templates/components/breadcrumbs/_template.breadcrumbs.njk +1 -1
- package/src/templates/components/button/_examples.button.njk +216 -0
- package/src/templates/components/button-loader/_examples.button-loader.njk +204 -0
- package/src/templates/components/card/_examples.card.njk +78 -0
- package/src/templates/components/character-count/_examples.character-count.njk +60 -0
- package/src/templates/components/checkboxes/_examples.checkboxes.njk +120 -0
- package/src/templates/components/collapsible/_examples.collapsible.njk +30 -0
- package/src/templates/components/date-input/_examples.date-input.njk +78 -0
- package/src/templates/components/datepicker/_examples.datepicker.njk +84 -0
- package/src/templates/components/datepicker/_template.datepicker.njk +1 -1
- package/src/templates/components/description-list/_examples.description-list.njk +66 -0
- package/src/templates/components/details/_examples.details.njk +24 -0
- package/src/templates/components/dialog/_examples.dialog.njk +18 -0
- package/src/templates/components/dropdown/_examples.dropdown.njk +90 -0
- package/src/templates/components/error-message/_examples.error-message.njk +12 -0
- package/src/templates/components/error-summary/_examples.error-summary.njk +30 -0
- package/src/templates/components/fieldset/_examples.fieldset.njk +18 -0
- package/src/templates/components/file-upload/_examples.file-upload.njk +30 -0
- package/src/templates/components/footer/_examples.footer.njk +108 -0
- package/src/templates/components/footer/_styles.footer.css +20 -20
- package/src/templates/components/footer/_template.footer.njk +21 -6
- package/src/templates/components/footer/params.footer.yaml +4 -4
- package/src/templates/components/header/_examples.header.njk +85 -1
- package/src/templates/components/header/_template.header.njk +20 -4
- package/src/templates/components/header/params.header.yaml +2 -2
- package/src/templates/components/header-advanced/_examples.header-advanced.njk +84 -7
- package/src/templates/components/header-advanced/_template.header-advanced.njk +3 -1
- package/src/templates/components/header-mini/_examples.header-mini.njk +15 -3
- package/src/templates/components/header-mini/_template.header-mini.njk +14 -2
- package/src/templates/components/header-mini/params.header-mini.yaml +1 -1
- package/src/templates/components/hint/_examples.hint.njk +12 -0
- package/src/templates/components/input/_examples.input.njk +120 -0
- package/src/templates/components/input/_template.input.njk +1 -1
- package/src/templates/components/input-group/_examples.input-group.njk +54 -0
- package/src/templates/components/item/_examples.item.njk +96 -0
- package/src/templates/components/label/_examples.label.njk +24 -0
- package/src/templates/components/links-list/_examples.links-list.njk +114 -0
- package/src/templates/components/listbox/_examples.listbox.njk +140 -20
- package/src/templates/components/media-object/_examples.media-object.njk +30 -0
- package/src/templates/components/menu-horizontal/_examples.menu-horizontal.njk +84 -0
- package/src/templates/components/menu-navigation/_examples.menu-navigation.njk +102 -0
- package/src/templates/components/menu-vertical/_examples.menu-vertical.njk +96 -0
- package/src/templates/components/menubar/_examples.menubar.njk +66 -0
- package/src/templates/components/modal/_examples.modal.njk +78 -0
- package/src/templates/components/nav/_examples.nav.njk +66 -0
- package/src/templates/components/notification/_examples.notification.njk +78 -0
- package/src/templates/components/pagination/_examples.pagination.njk +42 -0
- package/src/templates/components/pill/_examples.pill.njk +78 -0
- package/src/templates/components/radios/_examples.radios.njk +96 -0
- package/src/templates/components/searchbar/_examples.searchbar.njk +48 -0
- package/src/templates/components/searchbar/_template.searchbar.njk +1 -1
- package/src/templates/components/select/_examples.select.njk +54 -0
- package/src/templates/components/select/_template.select.njk +1 -1
- package/src/templates/components/skip-link/_examples.skip-link.njk +12 -0
- package/src/templates/components/spinner/_examples.spinner.njk +49 -1
- package/src/templates/components/status/_examples.status.njk +31 -1
- package/src/templates/components/status-item/_examples.status-item.njk +73 -1
- package/src/templates/components/table/_examples.table.njk +37 -1
- package/src/templates/components/table-advanced/_examples.table-advanced.njk +54 -0
- package/src/templates/components/table-advanced/_styles.table-advanced.css +0 -2
- package/src/templates/components/tabs/_examples.tabs.njk +72 -0
- package/src/templates/components/textarea/_examples.textarea.njk +54 -0
- package/src/templates/components/textarea/_template.textarea.njk +1 -1
- package/src/templates/components/toggle/_examples.toggle.njk +60 -0
- package/src/templates/components/tooltip/_examples.tooltip.njk +48 -0
- package/src/templates/components/tree/_examples.tree.njk +150 -0
- package/src/templates/components/treegrid/_examples.treegrid.njk +30 -0
- package/src/templates/components/treegrid/_template.treegrid.njk +1 -1
- package/src/templates/pages/_page.head.njk +11 -3
- package/vite.config.js +215 -0
- package/attached_assets/Pasted--desy-html-starter-Node-Version-https-img-shields-io-ba_1765448923362.txt +0 -431
- /package/{public/images/general-lg.svg → branding/images/header-background-lg.svg} +0 -0
- /package/{public/images/general.svg → branding/images/header-background.svg} +0 -0
- /package/{public/images/logo-ue.svg → branding/logos/eu/logo-eu.svg} +0 -0
- /package/{public/images → branding/logos/eu}/logo-feader.svg +0 -0
- /package/{public/images → branding/logos/eu}/logo-feder.svg +0 -0
- /package/{public/images → branding/logos/eu}/logo-fse.svg +0 -0
- /package/{public/images → branding/logos/eu}/logo-plurifondo.svg +0 -0
package/vite.config.js
CHANGED
|
@@ -9,6 +9,14 @@ import { outdent } from 'outdent';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import sharp from 'sharp';
|
|
11
11
|
import { defineConfig } from 'vite';
|
|
12
|
+
import { brandingPlugin, loadBrandingConfig } from './branding/vite-branding-plugin.js';
|
|
13
|
+
|
|
14
|
+
// Load branding configuration
|
|
15
|
+
const brandingConfig = await loadBrandingConfig(
|
|
16
|
+
process.env.BRANDING_CONFIG || 'default',
|
|
17
|
+
process.cwd(),
|
|
18
|
+
'branding'
|
|
19
|
+
);
|
|
12
20
|
|
|
13
21
|
// Helper functions
|
|
14
22
|
function kebabCaseToPascalCase(value) {
|
|
@@ -43,6 +51,7 @@ function setupNunjucksEnvironment(paths, options = {}) {
|
|
|
43
51
|
|
|
44
52
|
// Global configuration
|
|
45
53
|
env.addGlobal('assetsUrl', '/src');
|
|
54
|
+
env.addGlobal('branding', brandingConfig);
|
|
46
55
|
|
|
47
56
|
env.addGlobal('loadComponentParams', function (componentName) {
|
|
48
57
|
try {
|
|
@@ -431,12 +440,57 @@ async function copyAndOptimizeImages() {
|
|
|
431
440
|
}
|
|
432
441
|
}
|
|
433
442
|
|
|
443
|
+
// Copy branding assets to dist/images
|
|
444
|
+
async function copyBrandingAssets() {
|
|
445
|
+
try {
|
|
446
|
+
const brandingDir = path.resolve(process.cwd(), 'branding');
|
|
447
|
+
const distImagesDir = path.resolve(process.cwd(), 'dist/images');
|
|
448
|
+
|
|
449
|
+
if (!fs.existsSync(brandingDir)) {
|
|
450
|
+
console.log('📁 branding/ does not exist, skipping branding assets');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
await fs.promises.mkdir(distImagesDir, { recursive: true });
|
|
455
|
+
|
|
456
|
+
// Copy logos (flattened to images/)
|
|
457
|
+
const logosDir = path.join(brandingDir, 'logos');
|
|
458
|
+
if (fs.existsSync(logosDir)) {
|
|
459
|
+
const logoFiles = await glob('**/*.svg', { cwd: logosDir, nodir: true });
|
|
460
|
+
for (const file of logoFiles) {
|
|
461
|
+
const sourcePath = path.join(logosDir, file);
|
|
462
|
+
const fileName = path.basename(file); // Get just the filename to flatten structure
|
|
463
|
+
const destPath = path.join(distImagesDir, fileName);
|
|
464
|
+
await fs.promises.copyFile(sourcePath, destPath);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Copy header background images
|
|
469
|
+
const imagesDir = path.join(brandingDir, 'images');
|
|
470
|
+
if (fs.existsSync(imagesDir)) {
|
|
471
|
+
const imageFiles = await glob('*.svg', { cwd: imagesDir, nodir: true });
|
|
472
|
+
for (const file of imageFiles) {
|
|
473
|
+
const sourcePath = path.join(imagesDir, file);
|
|
474
|
+
const destPath = path.join(distImagesDir, file);
|
|
475
|
+
await fs.promises.copyFile(sourcePath, destPath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log('✅ Branding assets copied to dist/images/');
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error('❌ Error in copyBrandingAssets:', error);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
434
485
|
// Function to copy assets (updated to use optimization)
|
|
435
486
|
async function copyAssets() {
|
|
436
487
|
try {
|
|
437
488
|
// Copy and optimize images
|
|
438
489
|
await copyAndOptimizeImages();
|
|
439
490
|
|
|
491
|
+
// Copy branding assets
|
|
492
|
+
await copyBrandingAssets();
|
|
493
|
+
|
|
440
494
|
// Copy license if it exists
|
|
441
495
|
const licenseFile = path.resolve(process.cwd(), 'EUPL-1.2.txt');
|
|
442
496
|
if (fs.existsSync(licenseFile)) {
|
|
@@ -527,6 +581,41 @@ async function rewriteCssImagePaths() {
|
|
|
527
581
|
await fs.promises.writeFile(stylePath, content);
|
|
528
582
|
}
|
|
529
583
|
|
|
584
|
+
// Correct HTML image paths for production (from /images/, /branding/logos/, /branding/images/ to ../images/)
|
|
585
|
+
async function rewriteHtmlImagePaths() {
|
|
586
|
+
const distDir = path.resolve(process.cwd(), 'dist');
|
|
587
|
+
if (!fs.existsSync(distDir)) return;
|
|
588
|
+
|
|
589
|
+
const htmlFiles = await glob('**/*.html', { cwd: distDir, nodir: true });
|
|
590
|
+
|
|
591
|
+
for (const htmlFile of htmlFiles) {
|
|
592
|
+
const filePath = path.join(distDir, htmlFile);
|
|
593
|
+
let content = await fs.promises.readFile(filePath, 'utf8');
|
|
594
|
+
|
|
595
|
+
// Replace src="/images/ with src="../images/
|
|
596
|
+
let modified = content.replace(/src="(\/images\/)/g, 'src="../images/');
|
|
597
|
+
|
|
598
|
+
// Replace srcset="/images/ with srcset="../images/
|
|
599
|
+
modified = modified.replace(/srcset="(\/images\/)/g, 'srcset="../images/');
|
|
600
|
+
|
|
601
|
+
// Replace src="/branding/logos/ with src="../images/
|
|
602
|
+
modified = modified.replace(/src="(\/branding\/logos\/)/g, 'src="../images/');
|
|
603
|
+
|
|
604
|
+
// Replace srcset="/branding/logos/ with srcset="../images/
|
|
605
|
+
modified = modified.replace(/srcset="(\/branding\/logos\/)/g, 'srcset="../images/');
|
|
606
|
+
|
|
607
|
+
// Replace src="/branding/images/ with src="../images/
|
|
608
|
+
modified = modified.replace(/src="(\/branding\/images\/)/g, 'src="../images/');
|
|
609
|
+
|
|
610
|
+
// Replace srcset="/branding/images/ with srcset="../images/
|
|
611
|
+
modified = modified.replace(/srcset="(\/branding\/images\/)/g, 'srcset="../images/');
|
|
612
|
+
|
|
613
|
+
if (modified !== content) {
|
|
614
|
+
await fs.promises.writeFile(filePath, modified);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
530
619
|
// Custom Nunjucks plugin
|
|
531
620
|
function customNunjucksPlugin() {
|
|
532
621
|
let env;
|
|
@@ -550,6 +639,128 @@ function customNunjucksPlugin() {
|
|
|
550
639
|
});
|
|
551
640
|
});
|
|
552
641
|
|
|
642
|
+
// Middleware to serve branding assets in development
|
|
643
|
+
middlewares.use(async (req, res, next) => {
|
|
644
|
+
if (req.url.startsWith('/branding/')) {
|
|
645
|
+
const filePath = path.join(process.cwd(), req.url);
|
|
646
|
+
if (fs.existsSync(filePath)) {
|
|
647
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
648
|
+
const mimeTypes = {
|
|
649
|
+
'.svg': 'image/svg+xml',
|
|
650
|
+
'.png': 'image/png',
|
|
651
|
+
'.jpg': 'image/jpeg',
|
|
652
|
+
'.jpeg': 'image/jpeg',
|
|
653
|
+
'.webp': 'image/webp'
|
|
654
|
+
};
|
|
655
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
|
|
656
|
+
return res.end(fs.readFileSync(filePath));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
next();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Helper function to find file recursively in directories
|
|
663
|
+
function findFileRecursive(dir, fileName) {
|
|
664
|
+
if (!fs.existsSync(dir)) return null;
|
|
665
|
+
const files = fs.readdirSync(dir);
|
|
666
|
+
for (const file of files) {
|
|
667
|
+
const fullPath = path.join(dir, file);
|
|
668
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
669
|
+
const found = findFileRecursive(fullPath, fileName);
|
|
670
|
+
if (found) return found;
|
|
671
|
+
} else if (file === fileName) {
|
|
672
|
+
return fullPath;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Middleware to serve branding assets from /images/ in development
|
|
679
|
+
middlewares.use(async (req, res, next) => {
|
|
680
|
+
if (req.url.startsWith('/images/')) {
|
|
681
|
+
let filePath = path.join(process.cwd(), 'public', req.url);
|
|
682
|
+
|
|
683
|
+
if (!fs.existsSync(filePath)) {
|
|
684
|
+
const fileName = path.basename(req.url);
|
|
685
|
+
|
|
686
|
+
// Search in branding/logos (including subdirectories like eu/)
|
|
687
|
+
const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
|
|
688
|
+
const foundInLogos = findFileRecursive(brandingLogosPath, fileName);
|
|
689
|
+
|
|
690
|
+
if (foundInLogos) {
|
|
691
|
+
filePath = foundInLogos;
|
|
692
|
+
} else {
|
|
693
|
+
// Search in branding/images
|
|
694
|
+
const brandingImagesPath = path.join(process.cwd(), 'branding/images');
|
|
695
|
+
const foundInImages = findFileRecursive(brandingImagesPath, fileName);
|
|
696
|
+
if (foundInImages) {
|
|
697
|
+
filePath = foundInImages;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (fs.existsSync(filePath)) {
|
|
703
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
704
|
+
const mimeTypes = {
|
|
705
|
+
'.svg': 'image/svg+xml',
|
|
706
|
+
'.png': 'image/png',
|
|
707
|
+
'.jpg': 'image/jpeg',
|
|
708
|
+
'.jpeg': 'image/jpeg',
|
|
709
|
+
'.webp': 'image/webp'
|
|
710
|
+
};
|
|
711
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
|
|
712
|
+
return res.end(fs.readFileSync(filePath));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
next();
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Middleware to serve branding assets from /branding/logos/ in development
|
|
719
|
+
middlewares.use(async (req, res, next) => {
|
|
720
|
+
if (req.url.startsWith('/branding/logos/')) {
|
|
721
|
+
const fileName = path.basename(req.url);
|
|
722
|
+
const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
|
|
723
|
+
const filePath = findFileRecursive(brandingLogosPath, fileName);
|
|
724
|
+
|
|
725
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
726
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
727
|
+
const mimeTypes = {
|
|
728
|
+
'.svg': 'image/svg+xml',
|
|
729
|
+
'.png': 'image/png',
|
|
730
|
+
'.jpg': 'image/jpeg',
|
|
731
|
+
'.jpeg': 'image/jpeg',
|
|
732
|
+
'.webp': 'image/webp'
|
|
733
|
+
};
|
|
734
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
|
|
735
|
+
return res.end(fs.readFileSync(filePath));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
next();
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Middleware to serve branding assets from /branding/images/ in development
|
|
742
|
+
middlewares.use(async (req, res, next) => {
|
|
743
|
+
if (req.url.startsWith('/branding/images/')) {
|
|
744
|
+
const fileName = path.basename(req.url);
|
|
745
|
+
const brandingImagesPath = path.join(process.cwd(), 'branding/images');
|
|
746
|
+
const filePath = findFileRecursive(brandingImagesPath, fileName);
|
|
747
|
+
|
|
748
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
749
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
750
|
+
const mimeTypes = {
|
|
751
|
+
'.svg': 'image/svg+xml',
|
|
752
|
+
'.png': 'image/png',
|
|
753
|
+
'.jpg': 'image/jpeg',
|
|
754
|
+
'.jpeg': 'image/jpeg',
|
|
755
|
+
'.webp': 'image/webp'
|
|
756
|
+
};
|
|
757
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
|
|
758
|
+
return res.end(fs.readFileSync(filePath));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
next();
|
|
762
|
+
});
|
|
763
|
+
|
|
553
764
|
// Middleware to transform paths in development (keep as is)
|
|
554
765
|
middlewares.use(async (req, res, next) => {
|
|
555
766
|
// Intercept requests to /css/ and redirect to /src/css/
|
|
@@ -664,6 +875,9 @@ function customNunjucksPlugin() {
|
|
|
664
875
|
// Step 4: Process Nunjucks files (images are already in dist)
|
|
665
876
|
await processNunjucksFiles(env);
|
|
666
877
|
|
|
878
|
+
// Step 4b: Correct HTML image paths for production (after HTML files are generated)
|
|
879
|
+
await rewriteHtmlImagePaths();
|
|
880
|
+
|
|
667
881
|
// Step 5: Final validation to find and fix common errors
|
|
668
882
|
await validateBuild();
|
|
669
883
|
|
|
@@ -675,6 +889,7 @@ function customNunjucksPlugin() {
|
|
|
675
889
|
// Main Vite configuration
|
|
676
890
|
export default defineConfig({
|
|
677
891
|
plugins: [
|
|
892
|
+
brandingPlugin(brandingConfig),
|
|
678
893
|
customNunjucksPlugin(),
|
|
679
894
|
tailwindcss()
|
|
680
895
|
],
|
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
# desy-html-starter
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
[](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12)
|
|
6
|
-
|
|
7
|
-
A starter project for building user interfaces for Government of Aragón web applications using the **desy-html** design system library.
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
**desy-html-starter** provides a pre-configured environment with all the dependencies needed to build accessible, consistent web applications using the desy-html component library. It includes:
|
|
12
|
-
|
|
13
|
-
- **Vite** - Fast build tool and development server with hot module replacement
|
|
14
|
-
- **Tailwind CSS v4** - Utility-first CSS framework
|
|
15
|
-
- **Nunjucks** - Powerful templating engine for generating HTML
|
|
16
|
-
- **Sharp** - Image optimization for production builds
|
|
17
|
-
- **Accessible Components** - Pre-built UI components following ARIA patterns
|
|
18
|
-
|
|
19
|
-
## Quick Links
|
|
20
|
-
|
|
21
|
-
- **Documentation:** [https://desy.aragon.es/](https://desy.aragon.es/)
|
|
22
|
-
- **Repository:** [https://bitbucket.org/sdaragon/desy-html-starter/](https://bitbucket.org/sdaragon/desy-html-starter/)
|
|
23
|
-
|
|
24
|
-
## Prerequisites
|
|
25
|
-
|
|
26
|
-
Before you begin, ensure you have the following installed:
|
|
27
|
-
|
|
28
|
-
- **Node.js** ^22.0.0
|
|
29
|
-
- **npm** ^10.0.0
|
|
30
|
-
|
|
31
|
-
## Getting Started
|
|
32
|
-
|
|
33
|
-
### 1. Clone or Fork the Repository
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
git clone https://bitbucket.org/sdaragon/desy-html-starter.git my-project
|
|
37
|
-
cd my-project
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### 2. Install Dependencies
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npm install
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 3. Start Development Server
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
npm run dev
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
This starts the Vite development server with hot module replacement. Open your browser at the URL displayed in the terminal output.
|
|
53
|
-
|
|
54
|
-
### 4. Build for Production
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
npm run build
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This compiles HTML, purges and minifies CSS, and optimizes JavaScript into the `/dist` folder.
|
|
61
|
-
|
|
62
|
-
### 5. Preview Production Build
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
npm run preview
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Project Structure
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
desy-html-starter/
|
|
72
|
-
├── public/
|
|
73
|
-
│ └── images/ # Project images (override desy-html images here)
|
|
74
|
-
├── src/
|
|
75
|
-
│ ├── css/
|
|
76
|
-
│ │ ├── styles.css # Main stylesheet (imports desy-html styles)
|
|
77
|
-
│ │ └── custom.css # Your custom project styles
|
|
78
|
-
│ ├── js/
|
|
79
|
-
│ │ └── index.js # JavaScript entry point (component initialization)
|
|
80
|
-
│ └── templates/
|
|
81
|
-
│ ├── components/ # Custom project components
|
|
82
|
-
│ ├── includes/ # Reusable template partials
|
|
83
|
-
│ └── pages/ # Page templates
|
|
84
|
-
├── vite.config.js # Vite configuration
|
|
85
|
-
├── package.json
|
|
86
|
-
└── README.md
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Working with Components
|
|
90
|
-
|
|
91
|
-
### Using desy-html Components
|
|
92
|
-
|
|
93
|
-
Find component examples on [desy.aragon.es](https://desy.aragon.es) such as [this button component](https://desy.aragon.es/componente-botones-codigo.html). Copy the Nunjucks code and paste it into your template's content area.
|
|
94
|
-
|
|
95
|
-
Components are available as Nunjucks macros. Import and use them in your templates:
|
|
96
|
-
|
|
97
|
-
```njk
|
|
98
|
-
{% from "components/button/_macro.button.njk" import componentButton %}
|
|
99
|
-
|
|
100
|
-
{{ componentButton({
|
|
101
|
-
"text": "Transparent",
|
|
102
|
-
"classes": "c-button--transparent"
|
|
103
|
-
}) }}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Available Components
|
|
107
|
-
|
|
108
|
-
The desy-html library includes many [accessible components](https://desy.aragon.es/componentes.html):
|
|
109
|
-
|
|
110
|
-
- **Navigation:** Menu, Menubar, Nav, Tabs
|
|
111
|
-
- **Forms:** Checkbox, Radio Button, Listbox, Datepicker, Toggle
|
|
112
|
-
- **Feedback:** Alert, Notification, Dialog, Tooltip
|
|
113
|
-
- **Layout:** Accordion, Collapsible, Tree, Treegrid
|
|
114
|
-
- **Data:** Table Advanced, Dropdown
|
|
115
|
-
|
|
116
|
-
## Styling
|
|
117
|
-
|
|
118
|
-
### Using Tailwind CSS
|
|
119
|
-
|
|
120
|
-
The project uses Tailwind CSS v4. All Tailwind utility classes are available in your templates:
|
|
121
|
-
|
|
122
|
-
```html
|
|
123
|
-
<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow">
|
|
124
|
-
<h1 class="text-2xl font-bold text-gray-900">Hello World</h1>
|
|
125
|
-
</div>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Adding Custom Styles
|
|
129
|
-
|
|
130
|
-
Add your project-specific styles in `src/css/custom.css`:
|
|
131
|
-
|
|
132
|
-
```css
|
|
133
|
-
/* src/css/custom.css */
|
|
134
|
-
.my-custom-class {
|
|
135
|
-
/* Your custom styles */
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Theme Variables
|
|
140
|
-
|
|
141
|
-
desy-html provides CSS custom properties (variables) for consistent theming. These are automatically included when you import the main [stylesheet](https://bitbucket.org/sdaragon/desy-html/src/master/src/css/styles.css).
|
|
142
|
-
|
|
143
|
-
## Templates
|
|
144
|
-
|
|
145
|
-
### Page Templates
|
|
146
|
-
|
|
147
|
-
Create new pages by extending the base layouts. Several templates are provided:
|
|
148
|
-
|
|
149
|
-
| Template | Description |
|
|
150
|
-
|----------|-------------|
|
|
151
|
-
| `plantilla-sin-loguear.html` | Public pages (not logged in) |
|
|
152
|
-
| `plantilla-logueado-con-cabecera-fija.html` | Logged-in pages with fixed header |
|
|
153
|
-
| `plantilla-logueado-con-selector-de-app.html` | Logged-in with app selector |
|
|
154
|
-
| `plantilla-con-header-advanced.html` | Advanced header layout |
|
|
155
|
-
|
|
156
|
-
### Creating Custom Components
|
|
157
|
-
|
|
158
|
-
Custom components follow a consistent file structure. Create a new folder in `src/templates/components/` with the following files:
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
src/templates/components/my-component/
|
|
162
|
-
├── _macro.my-component.njk # Exports the macro (entry point)
|
|
163
|
-
├── _template.my-component.njk # HTML template with the component markup
|
|
164
|
-
├── _examples.my-component.njk # Example data for testing/documentation
|
|
165
|
-
└── params.my-component.yaml # Parameter documentation
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
#### Step 1: Create the Macro File
|
|
169
|
-
|
|
170
|
-
The macro file exports your component and includes the template:
|
|
171
|
-
|
|
172
|
-
```njk
|
|
173
|
-
{# _macro.my-component.njk #}
|
|
174
|
-
{% macro componentMyComponent(params) %}
|
|
175
|
-
{% include "./_template.my-component.njk" %}
|
|
176
|
-
{% endmacro %}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
#### Step 2: Create the Template File
|
|
180
|
-
|
|
181
|
-
The template contains the actual HTML markup. Use the `params` object to access component properties:
|
|
182
|
-
|
|
183
|
-
```njk
|
|
184
|
-
{# _template.my-component.njk #}
|
|
185
|
-
<div {%- if params.classes %} class="{{ params.classes }}"{% endif %}
|
|
186
|
-
{%- for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %}>
|
|
187
|
-
{{ params.html | safe if params.html else params.text }}
|
|
188
|
-
</div>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
#### Step 3: Define Examples (Optional)
|
|
192
|
-
|
|
193
|
-
Examples help document component usage and can be used for testing:
|
|
194
|
-
|
|
195
|
-
```njk
|
|
196
|
-
{# _examples.my-component.njk #}
|
|
197
|
-
{% set exampleComponent = "my-component" %}
|
|
198
|
-
{% set examples = [
|
|
199
|
-
{
|
|
200
|
-
"name": "default",
|
|
201
|
-
"data": {
|
|
202
|
-
"text": "This is my component"
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"name": "with html",
|
|
207
|
-
"data": {
|
|
208
|
-
"html": "This is <strong class=\"font-bold\">my component</strong>"
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
] %}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
#### Step 4: Document Parameters (Optional)
|
|
215
|
-
|
|
216
|
-
The YAML file documents available parameters for the component:
|
|
217
|
-
|
|
218
|
-
```yaml
|
|
219
|
-
# params.my-component.yaml
|
|
220
|
-
params:
|
|
221
|
-
- name: text
|
|
222
|
-
type: string
|
|
223
|
-
required: true
|
|
224
|
-
description: Text content for the component. Ignored if `html` is provided.
|
|
225
|
-
- name: html
|
|
226
|
-
type: string
|
|
227
|
-
required: false
|
|
228
|
-
description: HTML content for the component. Takes priority over `text`.
|
|
229
|
-
- name: classes
|
|
230
|
-
type: string
|
|
231
|
-
required: false
|
|
232
|
-
description: CSS classes to add to the component.
|
|
233
|
-
- name: attributes
|
|
234
|
-
type: object
|
|
235
|
-
required: false
|
|
236
|
-
description: HTML attributes (e.g., data attributes) to add to the component.
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
#### Using Your Component
|
|
240
|
-
|
|
241
|
-
Import and use your component in any template. The project configures Nunjucks to resolve paths from `src/templates/`, so use the `components/` prefix:
|
|
242
|
-
|
|
243
|
-
```njk
|
|
244
|
-
{% from "components/my-component/_macro.my-component.njk" import componentMyComponent %}
|
|
245
|
-
|
|
246
|
-
{{ componentMyComponent({
|
|
247
|
-
text: "Hello world",
|
|
248
|
-
classes: "text-lg text-gray-700"
|
|
249
|
-
}) }}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### Components with Nested Content (Caller)
|
|
253
|
-
|
|
254
|
-
For components that need to accept nested content, use Nunjucks' `call` block pattern:
|
|
255
|
-
|
|
256
|
-
```njk
|
|
257
|
-
{# _template.my-container.njk #}
|
|
258
|
-
<div class="container">
|
|
259
|
-
<p>{{ params.text }}</p>
|
|
260
|
-
{% if caller %}
|
|
261
|
-
{{ caller() }}
|
|
262
|
-
{% elseif params.caller %}
|
|
263
|
-
{{ params.caller | safe }}
|
|
264
|
-
{% endif %}
|
|
265
|
-
</div>
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
Usage with `call` block:
|
|
269
|
-
|
|
270
|
-
```njk
|
|
271
|
-
{% call componentMyContainer({ text: "Container title" }) %}
|
|
272
|
-
<p>This nested content appears inside the container.</p>
|
|
273
|
-
<button>Click me</button>
|
|
274
|
-
{% endcall %}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Using Includes
|
|
278
|
-
|
|
279
|
-
Includes are reusable template fragments that can be inserted into multiple pages. Unlike components (which receive parameters), includes are static snippets that can import and use components internally.
|
|
280
|
-
|
|
281
|
-
#### Creating an Include
|
|
282
|
-
|
|
283
|
-
Create include files in `src/templates/includes/` with names starting with underscore:
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
src/templates/includes/
|
|
287
|
-
└── _my-include.njk
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Example include file:
|
|
291
|
-
|
|
292
|
-
```njk
|
|
293
|
-
{# _my-include.njk #}
|
|
294
|
-
{% from "components/test-component/_macro.test-component.njk" import componentTestComponent %}
|
|
295
|
-
|
|
296
|
-
<div class="p-base border border-neutral-base">
|
|
297
|
-
<p>This is reusable content</p>
|
|
298
|
-
{{ componentTestComponent({
|
|
299
|
-
"text": "A component inside the include"
|
|
300
|
-
}) }}
|
|
301
|
-
</div>
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
#### Using an Include
|
|
305
|
-
|
|
306
|
-
Insert an include into any page or template with the `{% include %}` tag:
|
|
307
|
-
|
|
308
|
-
```njk
|
|
309
|
-
{% include "includes/_my-include.njk" %}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### When to Use Includes vs Components
|
|
313
|
-
|
|
314
|
-
| Use Case | Includes | Components |
|
|
315
|
-
|----------|----------|------------|
|
|
316
|
-
| Static, repeated content | Yes | No |
|
|
317
|
-
| Configurable, reusable UI elements | No | Yes |
|
|
318
|
-
| Content that needs parameters | No | Yes |
|
|
319
|
-
| Quick content reuse without configuration | Yes | No |
|
|
320
|
-
|
|
321
|
-
## Images
|
|
322
|
-
|
|
323
|
-
### Image Inheritance System
|
|
324
|
-
|
|
325
|
-
This project uses an image inheritance system that allows you to use images from the desy-html library while also being able to override them with your own:
|
|
326
|
-
|
|
327
|
-
1. **Base images** come from `node_modules/desy-html/public/images/`
|
|
328
|
-
2. **Project images** in `public/images/` override base images with the same name
|
|
329
|
-
|
|
330
|
-
### Adding Project Images
|
|
331
|
-
|
|
332
|
-
Place your images in `public/images/`. Reference them in templates using the `/images/` path:
|
|
333
|
-
|
|
334
|
-
```html
|
|
335
|
-
<img src="/images/my-image.png" alt="Description">
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Overriding desy-html Images
|
|
339
|
-
|
|
340
|
-
To replace a desy-html image with your own version, simply place a file with the same name in `public/images/`. Your image will take priority.
|
|
341
|
-
|
|
342
|
-
### Using Images in CSS
|
|
343
|
-
|
|
344
|
-
For background images in your custom CSS, use the `/images/` path:
|
|
345
|
-
|
|
346
|
-
```css
|
|
347
|
-
.my-element {
|
|
348
|
-
background-image: url('/images/my-background.png');
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### Image Optimization
|
|
353
|
-
|
|
354
|
-
During production builds, images are automatically optimized using Sharp:
|
|
355
|
-
|
|
356
|
-
- Images from both desy-html and your project are processed
|
|
357
|
-
- Supported formats: jpg, png, webp, avif
|
|
358
|
-
- File sizes are optimized for production
|
|
359
|
-
- Paths are automatically adjusted for the dist folder
|
|
360
|
-
|
|
361
|
-
## Development Tips
|
|
362
|
-
|
|
363
|
-
### Hot Module Replacement
|
|
364
|
-
|
|
365
|
-
The development server supports HMR. Changes to CSS, JavaScript, and templates will automatically refresh in the browser.
|
|
366
|
-
|
|
367
|
-
### Nunjucks Template Syntax
|
|
368
|
-
|
|
369
|
-
```njk
|
|
370
|
-
{# Comments #}
|
|
371
|
-
{% set variable = "value" %}
|
|
372
|
-
{{ variable }}
|
|
373
|
-
|
|
374
|
-
{% if condition %}
|
|
375
|
-
...
|
|
376
|
-
{% endif %}
|
|
377
|
-
|
|
378
|
-
{% for item in items %}
|
|
379
|
-
{{ item }}
|
|
380
|
-
{% endfor %}
|
|
381
|
-
|
|
382
|
-
{% include "includes/_partial.njk" %}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### Debugging
|
|
386
|
-
|
|
387
|
-
- Check the browser console for JavaScript errors
|
|
388
|
-
- Use the Vite terminal output for build errors
|
|
389
|
-
- Inspect generated HTML in browser developer tools
|
|
390
|
-
|
|
391
|
-
## Scripts Reference
|
|
392
|
-
|
|
393
|
-
| Command | Description |
|
|
394
|
-
|---------|-------------|
|
|
395
|
-
| `npm run dev` | Start development server with HMR |
|
|
396
|
-
| `npm run build` | Build for production |
|
|
397
|
-
| `npm run preview` | Preview production build locally |
|
|
398
|
-
|
|
399
|
-
## Accessibility
|
|
400
|
-
|
|
401
|
-
All desy-html components are built with accessibility in mind:
|
|
402
|
-
|
|
403
|
-
- Proper ARIA attributes and roles
|
|
404
|
-
- Keyboard navigation support
|
|
405
|
-
- Screen reader compatibility
|
|
406
|
-
- Focus management
|
|
407
|
-
|
|
408
|
-
When creating custom components, follow the same accessibility patterns.
|
|
409
|
-
|
|
410
|
-
## Browser Support
|
|
411
|
-
|
|
412
|
-
The project targets modern browsers. For specific compatibility information, refer to the [desy documentation](https://desy.aragon.es/).
|
|
413
|
-
|
|
414
|
-
## Contributing
|
|
415
|
-
|
|
416
|
-
1. Fork the repository
|
|
417
|
-
2. Create a feature branch
|
|
418
|
-
3. Make your changes
|
|
419
|
-
4. Submit a pull request
|
|
420
|
-
|
|
421
|
-
## Author
|
|
422
|
-
|
|
423
|
-
**SDA Servicios Digitales de Aragón**
|
|
424
|
-
|
|
425
|
-
## License
|
|
426
|
-
|
|
427
|
-
This project is licensed under the [EUPL-1.2](https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12) license.
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
For more information and detailed component documentation, visit [https://desy.aragon.es/](https://desy.aragon.es/)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|