@internetstiftelsen/styleguide 5.1.14-beta.0.5 → 5.1.14-beta.0.6
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/dist/assets/js/youtube.js +3 -3
- package/dist/atoms/progress/progress.js +7 -3
- package/dist/atoms/textarea/rich-text.js +3 -3
- package/dist/components.js +1 -0
- package/dist/molecules/modal/modal.js +6 -6
- package/dist/molecules/table-advanced/table-advanced.js +195 -0
- package/package.json +1 -1
- package/src/molecules/table/_table.scss +4 -0
- package/src/molecules/table-advanced/_table-advanced.scss +73 -0
- package/src/molecules/table-advanced/table-advanced.config.js +18 -0
- package/src/molecules/table-advanced/table-advanced.js +293 -37
- package/src/molecules/table-advanced/table-small.json +72 -0
- package/src/molecules/table-advanced/table.json +2027 -0
- package/src/molecules/table-advanced/test +246 -0
- package/src/organisms/selectable/_selectable.scss +4 -4
|
@@ -1,44 +1,300 @@
|
|
|
1
|
-
import * as agGrid from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import * as agGrid from 'ag-grid-community';
|
|
2
|
+
import { themeQuartz, iconSetMaterial } from 'ag-grid-community';
|
|
3
|
+
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
|
|
4
|
+
|
|
5
|
+
ModuleRegistry.registerModules([AllCommunityModule]);
|
|
6
|
+
|
|
7
|
+
// ---- THEME -----------------------------------------------------------
|
|
8
|
+
const iisTheme = themeQuartz
|
|
9
|
+
.withPart(iconSetMaterial)
|
|
10
|
+
.withParams({
|
|
11
|
+
accentColor: '#0477CE',
|
|
12
|
+
backgroundColor: '#FFFFFF',
|
|
13
|
+
borderRadius: 3,
|
|
14
|
+
browserColorScheme: 'light',
|
|
15
|
+
cellTextColor: '#1F2A36',
|
|
16
|
+
chromeBackgroundColor: { ref: 'foregroundColor', mix: 0.07, onto: 'backgroundColor' },
|
|
17
|
+
fontFamily: 'inherit',
|
|
18
|
+
fontSize: 16,
|
|
19
|
+
foregroundColor: '#1F2A36',
|
|
20
|
+
headerBackgroundColor: '#D8D8D8',
|
|
21
|
+
headerFontFamily: 'inherit',
|
|
22
|
+
headerFontSize: 16,
|
|
23
|
+
headerFontWeight: 400,
|
|
24
|
+
headerTextColor: '#1F2A36',
|
|
25
|
+
oddRowBackgroundColor: '#EDEDED',
|
|
26
|
+
wrapperBorderRadius: 3,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ---- RENDERERS -------------------------------------------------------
|
|
30
|
+
const renderStatusIcon = (params) => {
|
|
31
|
+
const status = params.value;
|
|
32
|
+
if (!status) return '';
|
|
33
|
+
const map = {
|
|
34
|
+
passed: { icon: '#icon-security-variant', color: '#25c279', label: 'Passed' },
|
|
35
|
+
failed: { icon: '#icon-unsecure-variant', color: '#d9002f', label: 'Failed' },
|
|
36
|
+
warning: { icon: '#icon-warning-variant', color: '#f99963', label: 'Warning' },
|
|
37
|
+
error: { icon: '#icon-unsecure-variant', color: '#8E9297', label: 'Error' },
|
|
38
|
+
not_tested: { icon: '#icon-unsecure-variant', color: '#d8d8d8', label: 'Not tested' },
|
|
39
|
+
informational: { icon: '#icon-info-variant', color: '#50b2fc', label: 'Informational' },
|
|
40
|
+
};
|
|
41
|
+
const { icon, color, label } = map[status] || {};
|
|
42
|
+
return icon
|
|
43
|
+
? `<span class="cell-center">
|
|
44
|
+
<svg class="status-icon" fill="${color}" width="20" height="20" aria-label="${label}">
|
|
45
|
+
<use xlink:href="${icon}"></use>
|
|
46
|
+
</svg>
|
|
47
|
+
<span class="status-text">${label}</span>
|
|
48
|
+
</span>`
|
|
49
|
+
: status;
|
|
26
50
|
};
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
// ---- HELPERS ---------------------------------------------------------
|
|
53
|
+
const titleCase = (s) => s.replace(/[_\-\.]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
54
|
+
|
|
55
|
+
function buildColumnDefsFromData(data) {
|
|
56
|
+
if (!Array.isArray(data) || data.length === 0) return [{ headerName: 'No Data', field: 'noData' }];
|
|
57
|
+
const sample = data[0];
|
|
58
|
+
|
|
59
|
+
// Internet.nl-like shape
|
|
60
|
+
if (sample && sample.results && typeof sample.results === 'object') {
|
|
61
|
+
const base = [
|
|
62
|
+
{ headerName: 'Domain', field: 'domain', minWidth: 200 },
|
|
63
|
+
{ headerName: 'Score (%)', field: 'score_percent', filter: 'agNumberColumnFilter', minWidth: 140 },
|
|
64
|
+
{
|
|
65
|
+
headerName: 'Report',
|
|
66
|
+
field: 'report_url',
|
|
67
|
+
cellRenderer: (p) => (p.value ? `<a href="${p.value}" target="_blank" rel="noopener">View</a>` : ''),
|
|
68
|
+
maxWidth: 220,
|
|
69
|
+
flex: 0,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const resultKeys = Object.keys(sample.results);
|
|
74
|
+
const childColIds = [];
|
|
75
|
+
const children = resultKeys.map((k, idx) => {
|
|
76
|
+
const colId = `results.${k}.status`;
|
|
77
|
+
childColIds.push(colId);
|
|
78
|
+
return {
|
|
79
|
+
colId,
|
|
80
|
+
headerName: titleCase(k),
|
|
81
|
+
field: colId,
|
|
82
|
+
cellRenderer: renderStatusIcon,
|
|
83
|
+
tooltipValueGetter: (p) => {
|
|
84
|
+
const since = p?.data?.results?.[k]?.since;
|
|
85
|
+
return since ? `Since: ${since}` : '';
|
|
86
|
+
},
|
|
87
|
+
cellClass: (p) => `status-${p.value}`,
|
|
88
|
+
minWidth: 150,
|
|
89
|
+
sortable: true,
|
|
90
|
+
filter: true,
|
|
91
|
+
hide: idx > 0, // start collapsed (only first child visible)
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
base.push({
|
|
96
|
+
headerName: 'Results',
|
|
97
|
+
headerGroupComponent: 'ExpandHeader', // 👈 correct prop for groups
|
|
98
|
+
headerGroupComponentParams: {
|
|
99
|
+
childColIds, // toggle these children together
|
|
100
|
+
startExpanded: false, // set true to start expanded
|
|
101
|
+
},
|
|
102
|
+
children,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return base;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Generic fallback
|
|
109
|
+
const cols = [];
|
|
110
|
+
for (const [key, val] of Object.entries(sample)) {
|
|
111
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
112
|
+
for (const subKey of Object.keys(val)) {
|
|
113
|
+
if (val[subKey] !== null && typeof val[subKey] !== 'object') {
|
|
114
|
+
cols.push({
|
|
115
|
+
headerName: `${titleCase(key)} • ${titleCase(subKey)}`,
|
|
116
|
+
field: `${key}.${subKey}`,
|
|
117
|
+
sortable: true,
|
|
118
|
+
filter: true,
|
|
119
|
+
minWidth: 140,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
cols.push({
|
|
125
|
+
headerName: titleCase(key),
|
|
126
|
+
field: key,
|
|
127
|
+
sortable: true,
|
|
128
|
+
filter: true,
|
|
129
|
+
minWidth: 120,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return cols;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// Version-safe column state helper
|
|
139
|
+
function setColumnsState(params, { state = [], defaultState = { hide: false, pinned: null } }) {
|
|
140
|
+
const columnApi =
|
|
141
|
+
params.columnApi ||
|
|
142
|
+
(params.api && typeof params.api.getColumnApi === 'function' ? params.api.getColumnApi() : null);
|
|
143
|
+
|
|
144
|
+
if (columnApi && typeof columnApi.applyColumnState === 'function') {
|
|
145
|
+
columnApi.applyColumnState({ state, defaultState });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (params.api && typeof params.api.applyColumnState === 'function') {
|
|
149
|
+
params.api.applyColumnState({ state, defaultState });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (columnApi && typeof columnApi.setColumnState === 'function') {
|
|
153
|
+
if (typeof columnApi.resetColumnState === 'function') columnApi.resetColumnState();
|
|
154
|
+
columnApi.setColumnState(state);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
console.warn('No applicable column state API found in this AG Grid version.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Per-grid responsive visibility
|
|
161
|
+
function applyResponsiveVisibility(params, el) {
|
|
162
|
+
const w = el?.getBoundingClientRect().width || el?.clientWidth || 0;
|
|
163
|
+
if (w < 520) {
|
|
164
|
+
setColumnsState(params, {
|
|
165
|
+
state: [
|
|
166
|
+
{ colId: 'domain', pinned: 'left' },
|
|
167
|
+
{ colId: 'results.rpki.status', hide: true },
|
|
168
|
+
{ colId: 'results.security_options.status', hide: true },
|
|
169
|
+
{ colId: 'results.ipv6.status', hide: true },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
} else if (w < 760) {
|
|
173
|
+
setColumnsState(params, {
|
|
174
|
+
state: [
|
|
175
|
+
{ colId: 'domain', pinned: null },
|
|
176
|
+
{ colId: 'results.rpki.status', hide: true },
|
|
177
|
+
],
|
|
178
|
+
});
|
|
179
|
+
} else {
|
|
180
|
+
setColumnsState(params, { state: [] });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
// ---- EXPAND/COLLAPSE GROUP HEADER -----------------------------------
|
|
186
|
+
class ExpandHeader {
|
|
187
|
+
init(params) {
|
|
188
|
+
this.params = params;
|
|
189
|
+
this.childColIds = params.childColIds || [];
|
|
190
|
+
this.expanded = !!params.startExpanded;
|
|
31
191
|
|
|
192
|
+
// Debug: prove we mounted
|
|
193
|
+
console.log('[ExpandHeader] init for group:', params.displayName, 'children:', this.childColIds);
|
|
32
194
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
195
|
+
// Create a full-width, clickable label (no AG classes needed)
|
|
196
|
+
const e = document.createElement('div');
|
|
197
|
+
e.className = 'expand-header';
|
|
198
|
+
e.style.cssText = 'display:flex;align-items:center;gap:6px;cursor:pointer;width:100%;';
|
|
199
|
+
e.innerHTML = `
|
|
200
|
+
<span class="expander" style="display:inline-block;transition:transform .15s">▶</span>
|
|
201
|
+
<span class="title">${params.displayName ?? 'Group'}</span>
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
e.addEventListener('click', () => {
|
|
205
|
+
this.expanded = !this.expanded;
|
|
206
|
+
this.updateIcon();
|
|
207
|
+
toggleChildColumnsVisibility(params, this.childColIds, this.expanded);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.eGui = e;
|
|
211
|
+
this.updateIcon();
|
|
212
|
+
|
|
213
|
+
// Apply initial expanded state
|
|
214
|
+
if (this.expanded) {
|
|
215
|
+
toggleChildColumnsVisibility(params, this.childColIds, true);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
updateIcon() {
|
|
219
|
+
const icon = this.eGui.querySelector('.expander');
|
|
220
|
+
if (icon) icon.style.transform = this.expanded ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
221
|
+
}
|
|
222
|
+
getGui() { return this.eGui; }
|
|
223
|
+
refresh() { return true; }
|
|
37
224
|
}
|
|
38
225
|
|
|
39
|
-
|
|
40
|
-
|
|
226
|
+
function toggleChildColumnsVisibility(params, colIds, makeVisible) {
|
|
227
|
+
const columnApi =
|
|
228
|
+
params.columnApi ||
|
|
229
|
+
(params.api && typeof params.api.getColumnApi === 'function'
|
|
230
|
+
? params.api.getColumnApi()
|
|
231
|
+
: null);
|
|
232
|
+
if (!columnApi) return;
|
|
233
|
+
columnApi.setColumnsVisible(colIds, !!makeVisible);
|
|
234
|
+
params.api?.sizeColumnsToFit?.();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Factory to create per-grid options with the correct closures
|
|
238
|
+
function makeGridOptionsFor(el) {
|
|
239
|
+
return {
|
|
240
|
+
theme: iisTheme,
|
|
241
|
+
components: { ExpandHeader },
|
|
242
|
+
defaultColDef: {
|
|
243
|
+
resizable: true,
|
|
244
|
+
sortable: true,
|
|
245
|
+
filter: true,
|
|
246
|
+
flex: 1,
|
|
247
|
+
minWidth: 150,
|
|
248
|
+
unSortIcon: true,
|
|
249
|
+
},
|
|
250
|
+
columnDefs: [],
|
|
251
|
+
rowData: [],
|
|
252
|
+
async onGridReady(params) {
|
|
253
|
+
try {
|
|
254
|
+
// dynamic JSON import based on *this* element's data attribute
|
|
255
|
+
const attr = el.dataset.json || './table.json';
|
|
256
|
+
const jsonUrl = new URL(attr, import.meta.url).toString();
|
|
257
|
+
const mod = await import(/* @vite-ignore */ jsonUrl);
|
|
258
|
+
const data = mod?.default ?? mod;
|
|
259
|
+
|
|
260
|
+
const cols = buildColumnDefsFromData(data);
|
|
261
|
+
params.api.setGridOption('columnDefs', cols);
|
|
262
|
+
params.api.setGridOption('rowData', data);
|
|
263
|
+
|
|
264
|
+
applyResponsiveVisibility(params, el);
|
|
265
|
+
params.api.sizeColumnsToFit();
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.error('Dynamic JSON import failed for grid:', el, e);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
onGridSizeChanged(params) {
|
|
271
|
+
applyResponsiveVisibility(params, el);
|
|
272
|
+
params.api.sizeColumnsToFit();
|
|
273
|
+
},
|
|
274
|
+
animateRows: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---- INIT ALL GRIDS --------------------------------------------------
|
|
279
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
280
|
+
// Use a class selector so you can have multiple grids;
|
|
281
|
+
// make sure your HTML uses: <div class="ag-theme-quartz js-ag-grid" data-json="./table1.json"></div>
|
|
282
|
+
const containers = document.querySelectorAll('.js-ag-grid');
|
|
283
|
+
|
|
284
|
+
containers.forEach((el) => {
|
|
285
|
+
// Ensure theme class + measurable size BEFORE createGrid (each grid separately)
|
|
286
|
+
if (!el.classList.contains('ag-theme-quartz')) el.classList.add('ag-theme-quartz');
|
|
287
|
+
//if (!el.style.height) el.style.height = '600px';
|
|
288
|
+
if (!el.style.width) el.style.width = '100%';
|
|
289
|
+
|
|
290
|
+
const gridOptions = makeGridOptionsFor(el);
|
|
291
|
+
const api = agGrid.createGrid(el, gridOptions);
|
|
41
292
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
293
|
+
// Per-grid ResizeObserver (don’t reuse one global API)
|
|
294
|
+
const ro = new ResizeObserver(() => {
|
|
295
|
+
gridOptions.api?.onGridSizeChanged();
|
|
296
|
+
gridOptions.api?.sizeColumnsToFit();
|
|
297
|
+
});
|
|
298
|
+
ro.observe(el);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"Category": "Modern address (IPv6)",
|
|
4
|
+
"passed": "43.11%",
|
|
5
|
+
"info": "0%",
|
|
6
|
+
"warning": "0%",
|
|
7
|
+
"failed": "56.89%",
|
|
8
|
+
"not testable": "0%",
|
|
9
|
+
"not applicable": "0%",
|
|
10
|
+
"test error": "0%"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"Category": "Signed domain name (DNSSEC)",
|
|
14
|
+
"passed": "52.96%",
|
|
15
|
+
"info": "0%",
|
|
16
|
+
"warning": "0%",
|
|
17
|
+
"failed": "47.04%",
|
|
18
|
+
"not testable": "0%",
|
|
19
|
+
"not applicable": "0%",
|
|
20
|
+
"test error": "0%"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"Category": "Secure connection following NCSC requirements (HTTPS)",
|
|
24
|
+
"passed": "19.22%",
|
|
25
|
+
"info": "0%",
|
|
26
|
+
"warning": "0%",
|
|
27
|
+
"failed": "76.6%",
|
|
28
|
+
"not testable": "0%",
|
|
29
|
+
"not applicable": "0%",
|
|
30
|
+
"test error": "4.17%"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"Category": "Security options",
|
|
34
|
+
"passed": "1.09%",
|
|
35
|
+
"info": "0%",
|
|
36
|
+
"warning": "98.91%",
|
|
37
|
+
"failed": "0%",
|
|
38
|
+
"not testable": "0%",
|
|
39
|
+
"not applicable": "0%",
|
|
40
|
+
"test error": "0%"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"Category": "Route authorisation (RPKI)",
|
|
44
|
+
"passed": "92%",
|
|
45
|
+
"info": "0%",
|
|
46
|
+
"warning": "0%",
|
|
47
|
+
"failed": "8%",
|
|
48
|
+
"not testable": "0%",
|
|
49
|
+
"not applicable": "0%",
|
|
50
|
+
"test error": "0%"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"Category": "Extra Fields",
|
|
54
|
+
"passed": "2.43%",
|
|
55
|
+
"info": "5.61%",
|
|
56
|
+
"warning": "0.99%",
|
|
57
|
+
"failed": "90.81%",
|
|
58
|
+
"not testable": "0%",
|
|
59
|
+
"not applicable": "0%",
|
|
60
|
+
"test error": "0.16%"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"Category": "A",
|
|
64
|
+
"passed": "42.16%",
|
|
65
|
+
"info": "1.12%",
|
|
66
|
+
"warning": "19.98%",
|
|
67
|
+
"failed": "55.87%",
|
|
68
|
+
"not testable": "0%",
|
|
69
|
+
"not applicable": "0%",
|
|
70
|
+
"test error": "0.87%"
|
|
71
|
+
}
|
|
72
|
+
]
|