@internetstiftelsen/styleguide 5.1.14 → 5.1.15

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.
@@ -0,0 +1,246 @@
1
+ // table-advanced.js (AG Grid Community, vanilla JS)
2
+
3
+ import 'ag-grid-community/styles/ag-grid.css';
4
+ import 'ag-grid-community/styles/ag-theme-quartz.css';
5
+
6
+ import * as agGrid from "ag-grid-community";
7
+ import { themeQuartz, iconSetMaterial } from "ag-grid-community";
8
+ import { ModuleRegistry, AllCommunityModule } from "ag-grid-community";
9
+
10
+ ModuleRegistry.registerModules([AllCommunityModule]);
11
+
12
+ // THEME
13
+ const iisTheme = themeQuartz
14
+ .withPart(iconSetMaterial)
15
+ .withParams({
16
+ accentColor: "#0477CE",
17
+ backgroundColor: "#FFFFFF",
18
+ borderRadius: 3,
19
+ browserColorScheme: "light",
20
+ cellTextColor: "#1F2A36",
21
+ chromeBackgroundColor: { ref: "foregroundColor", mix: 0.07, onto: "backgroundColor" },
22
+ fontFamily: "inherit",
23
+ fontSize: 16,
24
+ foregroundColor: "#1F2A36",
25
+ headerBackgroundColor: "#D8D8D8",
26
+ headerFontFamily: "inherit",
27
+ headerFontSize: 16,
28
+ headerFontWeight: 400,
29
+ headerTextColor: "#1F2A36",
30
+ oddRowBackgroundColor: "#EDEDED",
31
+ wrapperBorderRadius: 3,
32
+ });
33
+
34
+ // RENDERERS
35
+ const renderStatusIcon = (params) => {
36
+ const status = params.value;
37
+ if (!status) return "";
38
+
39
+ const map = {
40
+ passed: { icon: "#icon-security-variant", color: "#25c279", label: "Passed" },
41
+ failed: { icon: "#icon-unsecure-variant", color: "#d9002f", label: "Failed" },
42
+ warning: { icon: "#icon-warning-variant", color: "#f99963", label: "Warning" },
43
+ error: { icon: "#icon-unsecure-variant", color: "#8E9297", label: "Error" },
44
+ not_tested: { icon: "#icon-unsecure-variant", color: "#d8d8d8", label: "Not tested" },
45
+ informational: { icon: "#icon-info-variant", color: "#50b2fc", label: "Informational" },
46
+ };
47
+
48
+ const { icon, color, label } = map[status] || {};
49
+ return icon
50
+ ? `<span class="cell-center">
51
+ <svg class="status-icon" fill="${color}" width="20" height="20" aria-label="${label}">
52
+ <use xlink:href="${icon}"></use>
53
+ </svg>
54
+ <span class="status-text">${label}</span>
55
+ </span>`
56
+ : status;
57
+ };
58
+
59
+ // UTIL: pretty header from key
60
+ const titleCase = (s) => s.replace(/[_\-\.]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
61
+
62
+ // Build columnDefs from data (handles your Internet.nl shape and a generic fallback)
63
+ function buildColumnDefsFromData(data) {
64
+ if (!Array.isArray(data) || data.length === 0) {
65
+ return [{ headerName: 'No Data', field: 'noData' }];
66
+ }
67
+ const sample = data[0];
68
+
69
+ // Special case: your dataset (domain/score/report + results.{key}.status / .since)
70
+ if (sample && typeof sample === 'object' && sample.results && typeof sample.results === 'object') {
71
+ const base = [
72
+ { headerName: 'Domain', field: 'domain', minWidth: 200 },
73
+ { headerName: 'Score (%)', field: 'score_percent', filter: 'agNumberColumnFilter', minWidth: 140 },
74
+ {
75
+ headerName: 'Report',
76
+ field: 'report_url',
77
+ cellRenderer: (p) => p.value ? `<a href="${p.value}" target="_blank" rel="noopener">View</a>` : '',
78
+ maxWidth: 220,
79
+ flex: 0
80
+ }
81
+ ];
82
+
83
+ const resultKeys = Object.keys(sample.results);
84
+ const children = resultKeys.map((k) => ({
85
+ headerName: titleCase(k),
86
+ field: `results.${k}.status`,
87
+ cellRenderer: renderStatusIcon,
88
+ tooltipValueGetter: p => {
89
+ const since = p?.data?.results?.[k]?.since;
90
+ return since ? `Since: ${since}` : '';
91
+ },
92
+ cellClass: (p) => `status-${p.value}`,
93
+ minWidth: 150,
94
+ sortable: true,
95
+ filter: true,
96
+ }));
97
+
98
+ base.push({ headerName: 'Results', children });
99
+ return base;
100
+ }
101
+
102
+ // Generic fallback: make columns for top-level primitives and one-level nested primitives
103
+ const cols = [];
104
+ for (const [key, val] of Object.entries(sample)) {
105
+ if (val && typeof val === 'object' && !Array.isArray(val)) {
106
+ for (const subKey of Object.keys(val)) {
107
+ if (val[subKey] !== null && typeof val[subKey] !== 'object') {
108
+ cols.push({
109
+ headerName: `${titleCase(key)} • ${titleCase(subKey)}`,
110
+ field: `${key}.${subKey}`,
111
+ sortable: true,
112
+ filter: true,
113
+ minWidth: 140
114
+ });
115
+ }
116
+ }
117
+ } else {
118
+ cols.push({
119
+ headerName: titleCase(key),
120
+ field: key,
121
+ sortable: true,
122
+ filter: true,
123
+ minWidth: 120
124
+ });
125
+ }
126
+ }
127
+ return cols;
128
+ }
129
+
130
+ // VERSION-SAFE COLUMN STATE HELPER
131
+ function setColumnsState(params, { state = [], defaultState = { hide: false, pinned: null } }) {
132
+ const columnApi =
133
+ params.columnApi ||
134
+ (params.api && typeof params.api.getColumnApi === "function" ? params.api.getColumnApi() : null);
135
+
136
+ if (columnApi && typeof columnApi.applyColumnState === "function") {
137
+ columnApi.applyColumnState({ state, defaultState });
138
+ return;
139
+ }
140
+ if (params.api && typeof params.api.applyColumnState === "function") {
141
+ params.api.applyColumnState({ state, defaultState });
142
+ return;
143
+ }
144
+ if (columnApi && typeof columnApi.setColumnState === "function") {
145
+ if (typeof columnApi.resetColumnState === "function") columnApi.resetColumnState();
146
+ columnApi.setColumnState(state);
147
+ return;
148
+ }
149
+ console.warn("No applicable column state API found in this AG Grid version.");
150
+ }
151
+
152
+ // Responsive visibility (no api.getGridSize)
153
+ function applyResponsiveVisibility(params, el) {
154
+ const w = el?.getBoundingClientRect().width || el?.clientWidth || 0;
155
+
156
+ if (w < 520) {
157
+ setColumnsState(params, {
158
+ state: [
159
+ { colId: "domain", pinned: "left" },
160
+ { colId: "results.rpki.status", hide: true },
161
+ { colId: "results.security_options.status", hide: true },
162
+ { colId: "results.ipv6.status", hide: true },
163
+ ],
164
+ });
165
+ } else if (w < 760) {
166
+ setColumnsState(params, {
167
+ state: [
168
+ { colId: "domain", pinned: null },
169
+ { colId: "results.rpki.status", hide: true },
170
+ ],
171
+ });
172
+ } else {
173
+ setColumnsState(params, { state: [] });
174
+ }
175
+ }
176
+
177
+ // GRID
178
+ document.addEventListener('DOMContentLoaded', () => {
179
+ const gridDiv = document.querySelector("#myGrid");
180
+ if (!gridDiv) return;
181
+
182
+ // Theme class + real height before createGrid
183
+ gridDiv.classList.add('ag-theme-quartz');
184
+ if (!gridDiv.style.height) gridDiv.style.height = '600px';
185
+ if (!gridDiv.style.width) gridDiv.style.width = '100%';
186
+
187
+ const gridOptions = {
188
+ theme: iisTheme,
189
+ defaultColDef: {
190
+ resizable: true,
191
+ sortable: true,
192
+ filter: true,
193
+ flex: 1,
194
+ minWidth: 150,
195
+ unSortIcon: true, // show sort affordance when unsorted
196
+ },
197
+ columnDefs: [], // will be set after we inspect the JSON
198
+ async onGridReady(params) {
199
+ try {
200
+ // 1) Read the data-attribute (relative to THIS JS file)
201
+ // Example in HTML: <div id="myGrid" data-json="./table.json"></div>
202
+ const attr = gridDiv.dataset.json || './table.json';
203
+ const jsonUrl = new URL(attr, import.meta.url).toString();
204
+
205
+ // 2) Dynamic JSON import (works in modern bundlers)
206
+ // Some bundlers need the comment to skip pre-bundling the URL:
207
+ // and some require the JSON import assertion (uncomment if needed).
208
+ let mod;
209
+ try {
210
+ mod = await import(/* @vite-ignore */ jsonUrl);
211
+ } catch (e1) {
212
+ // Fallback with JSON import assertion (enable if your bundler requires it)
213
+ // mod = await import(/* @vite-ignore */ jsonUrl, { assert: { type: 'json' } });
214
+ throw e1; // if you want to rely solely on the first form, keep this
215
+ }
216
+
217
+ const data = mod?.default ?? mod; // Vite/Webpack expose JSON on .default
218
+ const cols = buildColumnDefsFromData(data);
219
+
220
+ params.api.setGridOption('columnDefs', cols);
221
+ params.api.setGridOption('rowData', data);
222
+
223
+ params.api.sizeColumnsToFit();
224
+ } catch (err) {
225
+ console.error('Dynamic JSON import failed:', err);
226
+ }
227
+ },
228
+ onGridSizeChanged(params) {
229
+ applyResponsiveVisibility(params, gridDiv);
230
+ params.api.sizeColumnsToFit();
231
+ },
232
+ animateRows: true,
233
+ };
234
+
235
+ agGrid.createGrid(gridDiv, gridOptions);
236
+
237
+ // If the grid might be created while hidden, keep it healthy
238
+ const ro = new ResizeObserver(() => {
239
+ const api = gridOptions.api;
240
+ if (api) {
241
+ api.onGridSizeChanged();
242
+ api.sizeColumnsToFit();
243
+ }
244
+ });
245
+ ro.observe(gridDiv);
246
+ });
@@ -1,5 +1,6 @@
1
1
  @charset "UTF-8";
2
2
  @use "sass:color";
3
+ @use "sass:math";
3
4
  @use '../../configurations/mixins' as mixin;
4
5
  @use '../../configurations/extends';
5
6
  @use '../../configurations/bem' as bem;
@@ -9,25 +10,41 @@
9
10
  @use '../../vendor/grid/breakpoints' as breakpoint;
10
11
 
11
12
  @include mixin.organism(selectable) {
12
- [aria-hidden="true"] {
13
- display: none;
13
+ fieldset button {
14
+ margin-bottom: func.rhythm(1);
15
+
16
+ @include breakpoint.bp-up(md) {
17
+ margin-bottom: 0;
18
+ }
14
19
  }
15
20
 
16
21
  @include bem.e(item) {
17
22
  border-top: 1px solid colors.$color-concrete;
18
- padding-top: func.rhythm(4);
19
- margin-top: func.rhythm(3);
23
+ padding-top: func.rhythm(2);
24
+ margin-top: func.rhythm(1.5);
25
+
26
+ @include breakpoint.bp-up(lg) {
27
+ padding-top: func.rhythm(4);
28
+ margin-top: func.rhythm(3);
29
+ }
20
30
 
21
31
  .js &:not(:first-child):not([aria-hidden]) {
22
32
  display: none;
23
33
  }
34
+
35
+ &[aria-hidden="true"] {
36
+ display: none;
37
+ }
24
38
  }
25
39
 
26
40
  @include bem.m(padded) {
27
- padding: func.rhythm(1);
41
+ padding: func.rhythm(2);
28
42
 
29
- @include breakpoint.bp-up(md) {
30
- padding: func.rhythm(2);
43
+ @include bem.e(item) {
44
+ .wp-block-graph .wrapper {
45
+ padding-left: 0;
46
+ padding-right: 0;
47
+ }
31
48
  }
32
49
  }
33
50
 
@@ -42,4 +59,42 @@
42
59
  @include bem.m(shadow-large) {
43
60
  @include mixin.card-shadow(colors.$color-cyberspace, 0.2);
44
61
  }
62
+
63
+ @include bem.m(article-indent) {
64
+ @include breakpoint.bp-up(lg) {
65
+ @include bem.e(item) {
66
+ > p:not(.preamble),
67
+ > h2,
68
+ > h3,
69
+ > h4,
70
+ > h5,
71
+ > h6,
72
+ > ul,
73
+ > ol,
74
+ > .wp-block-image > figure,
75
+ > figure,
76
+ > blockquote {
77
+ &:not(.alignfull):not(.alignleft):not(.alignright):not(.alignwide):not(.wp-block-iis-hero) {
78
+ max-width: 46.364%;
79
+ margin-right: math.div(var.$grid-gutter-width, 2);
80
+ margin-left: calc(var.$indent * 2 - func.rhythm(2));
81
+ }
82
+ }
83
+
84
+ > [class*='meta'] {
85
+ margin-left: calc(var.$indent * 2 - func.rhythm(2));
86
+ }
87
+
88
+ > p.preamble {
89
+ max-width: 46.364%;
90
+ margin-left: calc(var.$indent * 2 - func.rhythm(2));
91
+ }
92
+
93
+ .wp-block-iis-info {
94
+ max-width: calc(46.364% + #{(var.$indent * 2)});
95
+ margin-left: calc(var.$indent * 2 - func.rhythm(2));
96
+ }
97
+ }
98
+ }
99
+ }
45
100
  }