Skip to content

Commit d71ba9b

Browse files
authored
Merge pull request #71 from IATI/data-card
Data card
2 parents 9800f10 + 73f626a commit d71ba9b

File tree

13 files changed

+489
-0
lines changed

13 files changed

+489
-0
lines changed

package-lock.json

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"vite": "^5.4.4"
5353
},
5454
"dependencies": {
55+
"chart.js": "^4.5.0",
5556
"normalize-scss": "^8.0.0"
5657
},
5758
"commitlint": {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
CategoryScale,
3+
Chart as ChartJS,
4+
Filler,
5+
LineController,
6+
LineElement,
7+
LinearScale,
8+
PointElement,
9+
} from "chart.js";
10+
11+
// Register Chart.js components
12+
ChartJS.register(
13+
CategoryScale,
14+
LinearScale,
15+
PointElement,
16+
LineElement,
17+
LineController,
18+
Filler,
19+
);
20+
21+
// Initialise sparklines when DOM is ready
22+
function initialiseSparklines() {
23+
const sparklines = document.querySelectorAll(".iati-data-card__sparkline");
24+
25+
sparklines.forEach((canvas) => {
26+
const dataAttr = canvas.getAttribute("data-sparkline");
27+
28+
if (dataAttr) {
29+
try {
30+
const data = JSON.parse(dataAttr);
31+
const ctx = canvas.getContext("2d");
32+
33+
new ChartJS(ctx, {
34+
type: "line",
35+
data: {
36+
labels: data.labels,
37+
datasets: [
38+
{
39+
data: data.values,
40+
borderColor: "#155366",
41+
borderWidth: 2,
42+
fill: true,
43+
backgroundColor: "#E6F9FE",
44+
pointRadius: 0,
45+
},
46+
],
47+
},
48+
options: {
49+
responsive: true,
50+
maintainAspectRatio: false,
51+
plugins: {
52+
legend: {
53+
display: false,
54+
},
55+
},
56+
elements: {
57+
point: {
58+
radius: 0,
59+
},
60+
},
61+
scales: {
62+
y: {
63+
display: false,
64+
},
65+
x: {
66+
display: false,
67+
},
68+
},
69+
},
70+
});
71+
} catch (e) {
72+
console.error("Failed to create sparkline:", e);
73+
}
74+
}
75+
});
76+
}
77+
78+
// MutationObserver for Storybook dynamic content
79+
function setupMutationObserver() {
80+
const observer = new MutationObserver(() => {
81+
setTimeout(initialiseSparklines, 50);
82+
});
83+
84+
observer.observe(document.body, {
85+
childList: true,
86+
subtree: true,
87+
});
88+
}
89+
90+
// Initialise when DOM is ready
91+
if (document.readyState === "loading") {
92+
document.addEventListener("DOMContentLoaded", () => {
93+
initialiseSparklines();
94+
setupMutationObserver();
95+
});
96+
} else {
97+
initialiseSparklines();
98+
setupMutationObserver();
99+
}
100+
101+
export { initialiseSparklines };

src/js/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
import "./components/data-card/data-card.js";
12
import "./components/header/header.js";
23
import "./components/jump-menu/jump-menu.js";

src/scss/components/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
@forward "callout/callout";
55
@forward "card/card";
66
@forward "country-switcher/country-switcher";
7+
@forward "data-card/data-card";
78
@forward "figures/figures";
89
@forward "piped-list/piped-list";
910
@forward "icon/icon";
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@use "../../tokens/color" as *;
2+
@use "../../tokens/font" as *;
3+
@use "../../tokens/spacing" as *;
4+
@use "../../base/mixins";
5+
6+
.iati-data-card {
7+
color: $color-teal-90;
8+
background-color: $color-teal-20;
9+
padding: 1.25rem;
10+
padding-top: $padding-block;
11+
display: flex;
12+
flex-direction: column;
13+
gap: 0.75rem;
14+
max-width: 297px;
15+
text-align: center;
16+
17+
& :first-child {
18+
margin-top: 0;
19+
}
20+
21+
& :last-child {
22+
margin-bottom: 0;
23+
}
24+
25+
&__title {
26+
margin: 0;
27+
font-family: $font-stack-heading;
28+
font-weight: $font-weight-body-xstrong;
29+
font-size: 1.625rem;
30+
}
31+
32+
&__tagline {
33+
margin: 0;
34+
font-size: 0.9rem;
35+
font-weight: $font-weight-body-xstrong;
36+
line-height: 1.4;
37+
}
38+
39+
&__stats {
40+
display: flex;
41+
gap: 0.5rem;
42+
margin: 0.5rem 0;
43+
flex-wrap: wrap;
44+
justify-content: center;
45+
}
46+
47+
&__stat {
48+
display: flex;
49+
flex-direction: column;
50+
background-color: $color-teal-10;
51+
padding: 0.75rem;
52+
gap: 0.5rem;
53+
min-width: 0;
54+
55+
&:not(:only-child) {
56+
max-width: calc(50% - 0.25rem);
57+
}
58+
59+
&-label {
60+
font-size: 0.75rem;
61+
text-transform: uppercase;
62+
font-weight: $font-weight-body-xstrong;
63+
letter-spacing: 0.5px;
64+
}
65+
66+
&-value {
67+
font-family: $font-stack-heading;
68+
font-weight: $font-weight-heading;
69+
font-size: 1.5rem;
70+
color: $color-teal-90;
71+
margin: 0;
72+
}
73+
}
74+
75+
&__graph {
76+
border-radius: 4px;
77+
display: flex;
78+
align-items: center;
79+
justify-content: center;
80+
width: 90%;
81+
height: 53.5px;
82+
margin: auto;
83+
}
84+
85+
&__caption {
86+
font-size: 0.75rem;
87+
font-weight: $font-weight-body-xxstrong;
88+
text-transform: uppercase;
89+
}
90+
91+
&__button {
92+
margin-top: 1rem;
93+
}
94+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { Meta, StoryObj } from "@storybook/web-components";
2+
3+
import { html } from "lit";
4+
5+
const meta: Meta = {
6+
title: "Components/Data Card",
7+
parameters: {
8+
backgrounds: {
9+
default: "light",
10+
},
11+
},
12+
};
13+
14+
export default meta;
15+
type Story = StoryObj;
16+
17+
export const Stat: Story = {
18+
render: () => html`
19+
<div class="iati-data-card">
20+
<h3 class="iati-data-card__title">IATI Publishers</h3>
21+
<p class="iati-data-card__tagline">
22+
How many organisations are publishing IATI data?
23+
</p>
24+
<div class="iati-data-card__stats">
25+
<div class="iati-data-card__stat">
26+
<div class="iati-data-card__stat-label">Total Iati Publishers</div>
27+
<div class="iati-data-card__stat-value">1719</div>
28+
</div>
29+
</div>
30+
<div class="iati-data-card__button">
31+
<button class="iati-button">Learn more about IATI Publishers</button>
32+
</div>
33+
</div>
34+
`,
35+
};
36+
37+
export const ManyStats: Story = {
38+
render: () => html`
39+
<div class="iati-data-card">
40+
<h3 class="iati-data-card__title">Organisation Identifiers</h3>
41+
<p class="iati-data-card__tagline">
42+
Which versions of the IATI Standard are being used?
43+
</p>
44+
<div class="iati-data-card__stats">
45+
<div class="iati-data-card__stat">
46+
<div class="iati-data-card__stat-label">Active Files</div>
47+
<div class="iati-data-card__stat-value">845</div>
48+
</div>
49+
<div class="iati-data-card__stat">
50+
<div class="iati-data-card__stat-label">Number</div>
51+
<div class="iati-data-card__stat-value">$1000</div>
52+
</div>
53+
<div class="iati-data-card__stat">
54+
<div class="iati-data-card__stat-label">Stat 3</div>
55+
<div class="iati-data-card__stat-value">502,476</div>
56+
</div>
57+
<div class="iati-data-card__stat">
58+
<div class="iati-data-card__stat-label">Stat</div>
59+
<div class="iati-data-card__stat-value">4071</div>
60+
</div>
61+
</div>
62+
63+
<div class="iati-data-card__button">
64+
<button class="iati-button">
65+
Learn more about Organisation Identifiers
66+
</button>
67+
</div>
68+
</div>
69+
`,
70+
};
71+
72+
export const Graph: Story = {
73+
render: () => html`
74+
<div class="iati-data-card">
75+
<h3 class="iati-data-card__title">Codelists</h3>
76+
<p class="iati-data-card__tagline">
77+
How are codelists used in IATI data?
78+
</p>
79+
<div class="iati-data-card__graph">
80+
<canvas
81+
class="iati-data-card__sparkline"
82+
data-sparkline='{"labels":["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30"],"values":[100,120,90,150,80,140,110,95,160,75,130,130,132,136,130,70,155,125,95,170,85,60,135,132,80,90,145,110,150,145]}'
83+
></canvas>
84+
</div>
85+
<div class="iati-data-card__caption">
86+
Description below chart showing changes over time
87+
</div>
88+
<div class="iati-data-card__button">
89+
<button class="iati-button">Full Report</button>
90+
</div>
91+
</div>
92+
`,
93+
};
94+
95+
export const Complete: Story = {
96+
render: () => html`
97+
<div class="iati-data-card">
98+
<h3 class="iati-data-card__title">IATI Files</h3>
99+
<p class="iati-data-card__tagline">How many IATI files are published?</p>
100+
<div class="iati-data-card__stats">
101+
<div class="iati-data-card__stat">
102+
<div class="iati-data-card__stat-label">Big number title</div>
103+
<div class="iati-data-card__stat-value">897,548</div>
104+
</div>
105+
<div class="iati-data-card__stat">
106+
<div class="iati-data-card__stat-label">Small number title</div>
107+
<div class="iati-data-card__stat-value">12</div>
108+
</div>
109+
</div>
110+
<div class="iati-data-card__graph">
111+
<canvas
112+
class="iati-data-card__sparkline"
113+
data-sparkline='{"labels":["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30"],"values":[100,120,90,150,80,140,110,95,160,75,130,130,132,136,130,70,155,125,95,170,85,60,135,132,80,90,145,110,150,145]}'
114+
></canvas>
115+
</div>
116+
<div class="iati-data-card__caption">
117+
Description below chart showing changes over time
118+
</div>
119+
<div class="iati-data-card__button">
120+
<button class="iati-button">Full Report</button>
121+
</div>
122+
</div>
123+
`,
124+
};

src/scss/layout/_index.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
@forward "page/page";
2+
@forward "masonry/masonry";
3+
@forward "landing-page/landing-page";

0 commit comments

Comments
 (0)