Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8057bd5
fix: add TypeScript declaration for SVG module
sgroi-l Aug 7, 2025
643e5f3
docs: update header documentation to include compact header variant
sgroi-l Aug 7, 2025
ded16b9
feat: add compact header variant with responsive layout adjustments
sgroi-l Aug 7, 2025
10b7056
feat: implement compact header variant
sgroi-l Aug 7, 2025
26888d8
fix: lint and format
sgroi-l Aug 7, 2025
45256c9
fix: adjust button background color for submit variant
sgroi-l Aug 8, 2025
996513a
feat: add support for submenus in jump menu component
sgroi-l Aug 8, 2025
d3ddd64
feat: update jump menu styles and add subitems support
sgroi-l Aug 8, 2025
60fc574
feat: refactor jump menu initialisation
sgroi-l Aug 8, 2025
a12ddf4
fix: formatting
sgroi-l Aug 8, 2025
71fc0f5
feat: add DataCardSparkline component for sparkline charts using Char…
sgroi-l Aug 15, 2025
69366ed
feat: add DataCard component with story examples for showcasing data …
sgroi-l Aug 15, 2025
386b13e
feat: add data-card component to SCSS index
sgroi-l Aug 15, 2025
77478f7
feat: add compact button variant and update stories
sgroi-l Aug 15, 2025
e1ab753
fix: adjust margin and heading text in compact header variant
sgroi-l Aug 15, 2025
03b14b6
feat: compact country switcher variant
sgroi-l Aug 15, 2025
e7aada9
fix: remove redundant compact button variant and update stories
sgroi-l Aug 15, 2025
b0ff0ea
feat: add compact country switcher to compact header
sgroi-l Aug 15, 2025
242d0e8
fix: remove underline from sublinks
sgroi-l Aug 19, 2025
3468a67
refactor: simplify sparkline initialisation
sgroi-l Aug 20, 2025
30097ae
refactor: update DataCard styles and improve typography variables
sgroi-l Aug 20, 2025
fee6e31
feat: update and add more DataCard stories
sgroi-l Aug 20, 2025
1150896
feat: add landing page layout and masonry styles
sgroi-l Aug 21, 2025
b60094d
feat: update jump menu to use flat structure for border styling
sgroi-l Sep 5, 2025
4f03ad0
fix: masonry layout fixed for safari and docs updated
sgroi-l Sep 5, 2025
76572f5
fix: lint and format
sgroi-l Sep 5, 2025
58f8d0f
fix: adjust font size and white space for conpact header buttons
sgroi-l Sep 8, 2025
59b8bfd
Merge branch 'compact-header' into publishers-page
sgroi-l Sep 8, 2025
70d9441
Merge branch 'data-card' into publishers-page
sgroi-l Sep 8, 2025
bbdb27f
Merge branch 'jump-menu' into publishers-page
sgroi-l Sep 8, 2025
7120a31
feat: add file-card component, publishers-page layout, download icon …
sgroi-l May 6, 2026
818127d
Merge remote-tracking branch 'origin/main' into publishers-page
sgroi-l May 6, 2026
187a1e2
fix: linting
sgroi-l May 6, 2026
8777252
feat: add sortable table headers, plain variant and lighter borders
sgroi-l May 22, 2026
9e2efbd
feat: add validation status and labeled footer to file-card
sgroi-l May 22, 2026
ddc4966
feat: add data tables and section content to publishers page
sgroi-l May 22, 2026
7e352a5
fix: scope table border and header colour changes to publishers page
sgroi-l May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/assets/svg/icon-download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions src/js/components/table/sortable-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
function getRowsContainer(root) {
if (root.tagName === "TABLE") {
return root.querySelector("tbody") || root;
}
const selector = root.getAttribute("data-sort-rows");
if (selector) return root.querySelector(selector);
return (
root.querySelector(".iati-file-card-table__cards") ||
root.querySelector(".iati-file-card-table__rows") ||
root
);
}

function getHeaders(root) {
return Array.from(root.querySelectorAll("[aria-sort]"));
}

function getRows(rowsContainer) {
return Array.from(rowsContainer.children).filter(
(el) =>
el.tagName !== "THEAD" &&
!el.classList.contains("iati-file-card-table__header"),
);
}

function getCellText(row, columnIndex) {
const cells = row.querySelectorAll(
"td, .iati-file-card__cell, .iati-table__cell",
);
const cell = cells[columnIndex];
return cell ? cell.textContent.trim() : "";
}

function compareValues(a, b, type) {
if (type === "number") {
const numA = parseFloat(a.replace(/[^0-9.\-]/g, "")) || 0;
const numB = parseFloat(b.replace(/[^0-9.\-]/g, "")) || 0;
return numA - numB;
}
return a.localeCompare(b, undefined, { numeric: true });
}

function sortRows(root, header) {
const headers = getHeaders(root);
const explicit = header.getAttribute("data-column-index");
const columnIndex =
explicit !== null ? parseInt(explicit, 10) : headers.indexOf(header);
if (columnIndex < 0 || Number.isNaN(columnIndex)) return;

const current = header.getAttribute("aria-sort");
const next = current === "ascending" ? "descending" : "ascending";

headers.forEach((h) => h.setAttribute("aria-sort", "none"));
header.setAttribute("aria-sort", next);

const type = header.getAttribute("data-sort-type") || "string";
const rowsContainer = getRowsContainer(root);
const rows = getRows(rowsContainer);

rows.sort((rowA, rowB) => {
const cmp = compareValues(
getCellText(rowA, columnIndex),
getCellText(rowB, columnIndex),
type,
);
return next === "ascending" ? cmp : -cmp;
});

rows.forEach((row) => rowsContainer.appendChild(row));
}

function attachSortHandlers(root) {
if (root.dataset.sortableInitialised) return;
root.dataset.sortableInitialised = "true";

getHeaders(root).forEach((header) => {
if (!header.querySelector("button")) {
const original = header.innerHTML;
header.innerHTML = `<button type="button">${original}</button>`;
}
const button = header.querySelector("button");
button.addEventListener("click", () => sortRows(root, header));
});
}

function initialiseSortableTables() {
document.querySelectorAll("[data-sortable]").forEach(attachSortHandlers);
}

function setupMutationObserver() {
let debounceTimer;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(initialiseSortableTables, 50);
});

observer.observe(document.body, {
childList: true,
subtree: true,
});
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
initialiseSortableTables();
setupMutationObserver();
});
} else {
initialiseSortableTables();
setupMutationObserver();
}
1 change: 1 addition & 0 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import "../scss/components/select/multi-select.ts";
import "./components/data-card/data-card.js";
import "./components/header/header.js";
import "./components/jump-menu/jump-menu.js";
import "./components/table/sortable-table.js";
1 change: 1 addition & 0 deletions src/scss/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@forward "card/card";
@forward "country-switcher/country-switcher";
@forward "data-card/data-card";
@forward "file-card/file-card";
@forward "figures/figures";
@forward "form/form";
@forward "piped-list/piped-list";
Expand Down
169 changes: 169 additions & 0 deletions src/scss/components/file-card/_file-card.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
@use "../../tokens/color" as *;
@use "../../tokens/font" as *;
@use "../../tokens/spacing" as *;

.iati-file-card-table {
overflow-x: auto;
min-width: 100%;
background: $color-blue-10;

// Table header that appears visually like a table head
&__header {
display: grid;
grid-template-columns: repeat(5, 1fr);
min-width: 800px;
gap: 0;
border: 1px solid $color-teal-60;
border-bottom: 1px solid $color-teal-60;
background-color: $color-teal-30;

.iati-file-card-table__header-cell {
padding: 0.5rem 1rem;
text-transform: uppercase;
color: $color-grey-90;
font-weight: 800;
font-size: 0.75rem;
border-right: 1px solid $color-teal-60;
text-align: center;
min-width: 20ch;

&:last-child {
border-right: none;
}
}
}

// Container for all file cards
&__cards {
display: flex;
flex-direction: column;
gap: $padding-block;
margin-top: $padding-block;
min-width: 800px;
}
}

.iati-file-card {
border: 1px solid $color-teal-60;
border-top: 3px solid $color-teal-60;
background: white;
font-size: 0.75rem;
color: $color-teal-90;
padding: 0;
display: flex;
flex-direction: column;
gap: 0;
min-width: 800px;
width: 100%;
text-align: center;

// First row with 5 cells
&__main-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
min-width: 800px;
gap: 0;
align-items: center;
font-weight: 800;

.iati-file-card__cell {
padding: 0.5rem 0;
border-right: 1px solid $color-teal-60;

&:first-child {
text-transform: uppercase;
}

&:last-child {
border-right: none;
}
}
}

// Second row with 5 cells
&__details-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
min-width: 800px;
gap: 0;
align-items: stretch;
padding-top: 0;
border-top: 1px solid $color-teal-60;

.iati-file-card__cell {
padding: 0.5rem 1rem;
border-right: 1px solid $color-teal-60;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 100%;

.iati-button {
max-width: 125px;
width: auto;
}

.iati-file-card__chart-container {
.iati-data-card__sparkline {
// width: 100px !important;
height: 30px !important;
max-width: 100%;
}

.iati-file-card_chart-caption {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
line-height: 14px;
}
}

&:last-child {
border-right: none;
}
}
}

// Third row: labeled info cells
&__footer-row {
min-width: 800px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
border-top: 1px solid $color-teal-60;
text-align: center;

.iati-file-card__cell {
padding: 0.75rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
}

&__info-label {
text-transform: uppercase;
font-weight: 800;
font-size: 0.75rem;
color: $color-teal-90;
}
}

.iati-file-card__status {
font-weight: 700;

&--success {
color: $color-green-70;
}

&--error {
color: $color-orange-70;
}

&--critical {
color: $color-purple-70;
}
}
Loading
Loading