@forwardimpact/pathway 0.21.0 → 0.23.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/README.md +3 -3
- package/bin/fit-pathway.js +22 -22
- package/package.json +4 -3
- package/src/commands/agent.js +14 -10
- package/src/commands/behaviour.js +11 -1
- package/src/commands/build.js +11 -2
- package/src/commands/command-factory.js +4 -2
- package/src/commands/dev.js +9 -2
- package/src/commands/discipline.js +19 -2
- package/src/commands/driver.js +11 -1
- package/src/commands/index.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/interview.js +8 -8
- package/src/commands/job.js +41 -28
- package/src/commands/level.js +76 -0
- package/src/commands/progress.js +20 -20
- package/src/commands/questions.js +3 -3
- package/src/commands/skill.js +11 -1
- package/src/commands/stage.js +11 -1
- package/src/commands/tool.js +4 -3
- package/src/commands/track.js +11 -1
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/card.js +8 -104
- package/src/components/comparison-radar.js +4 -4
- package/src/components/detail.js +18 -120
- package/src/components/error-page.js +8 -68
- package/src/components/grid.js +12 -106
- package/src/components/list.js +7 -116
- package/src/components/nav.js +7 -60
- package/src/components/radar-chart.js +3 -3
- package/src/components/skill-matrix.js +7 -7
- package/src/css/bundles/app.css +25 -21
- package/src/css/bundles/handout.css +33 -33
- package/src/css/bundles/slides.css +25 -25
- package/src/css/pages/landing.css +5 -5
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/shared.js +23 -23
- package/src/formatters/job/description.js +18 -18
- package/src/formatters/job/dom.js +12 -12
- package/src/formatters/job/markdown.js +7 -7
- package/src/formatters/json-ld.js +24 -24
- package/src/formatters/{grade → level}/dom.js +31 -27
- package/src/formatters/{grade → level}/markdown.js +19 -28
- package/src/formatters/{grade → level}/microdata.js +28 -38
- package/src/formatters/level/shared.js +86 -0
- package/src/formatters/progress/markdown.js +2 -2
- package/src/formatters/progress/shared.js +51 -51
- package/src/formatters/questions/markdown.js +8 -6
- package/src/formatters/questions/shared.js +7 -7
- package/src/formatters/skill/dom.js +4 -4
- package/src/formatters/skill/markdown.js +1 -1
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +3 -3
- package/src/formatters/track/shared.js +1 -1
- package/src/handout-main.js +12 -12
- package/src/handout.html +32 -13
- package/src/index.html +33 -14
- package/src/lib/card-mappers.js +16 -16
- package/src/lib/cli-command.js +3 -3
- package/src/lib/cli-output.js +2 -2
- package/src/lib/error-boundary.js +3 -66
- package/src/lib/errors.js +7 -45
- package/src/lib/job-cache.js +12 -12
- package/src/lib/markdown.js +2 -109
- package/src/lib/reactive.js +7 -73
- package/src/lib/render.js +53 -201
- package/src/lib/router-core.js +2 -156
- package/src/lib/router-pages.js +2 -11
- package/src/lib/router-slides.js +2 -197
- package/src/lib/state.js +16 -65
- package/src/lib/utils.js +3 -10
- package/src/lib/yaml-loader.js +22 -80
- package/src/main.js +10 -10
- package/src/pages/agent-builder.js +12 -12
- package/src/pages/assessment-results.js +28 -24
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +8 -8
- package/src/pages/job-builder.js +7 -7
- package/src/pages/job.js +8 -8
- package/src/pages/landing.js +8 -8
- package/src/pages/level.js +122 -0
- package/src/pages/progress-builder.js +8 -8
- package/src/pages/progress.js +74 -74
- package/src/pages/self-assessment.js +7 -7
- package/src/pages/skill.js +1 -1
- package/src/slide-main.js +23 -23
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +11 -11
- package/src/slides/interview.js +2 -2
- package/src/slides/job.js +4 -4
- package/src/slides/level.js +32 -0
- package/src/slides/overview.js +10 -10
- package/src/slides/progress.js +13 -13
- package/src/slides.html +32 -13
- package/src/types.js +1 -1
- package/templates/job.template.md +2 -2
- package/src/commands/grade.js +0 -60
- package/src/css/base.css +0 -56
- package/src/css/components/badges.css +0 -232
- package/src/css/components/buttons.css +0 -101
- package/src/css/components/forms.css +0 -191
- package/src/css/components/layout.css +0 -218
- package/src/css/components/nav.css +0 -206
- package/src/css/components/progress.css +0 -166
- package/src/css/components/states.css +0 -82
- package/src/css/components/surfaces.css +0 -347
- package/src/css/components/tables.css +0 -362
- package/src/css/components/top-bar.css +0 -180
- package/src/css/components/typography.css +0 -121
- package/src/css/components/utilities.css +0 -41
- package/src/css/pages/detail.css +0 -119
- package/src/css/reset.css +0 -50
- package/src/css/tokens.css +0 -162
- package/src/css/views/handout.css +0 -30
- package/src/css/views/print.css +0 -634
- package/src/css/views/slide-animations.css +0 -113
- package/src/css/views/slide-base.css +0 -331
- package/src/css/views/slide-sections.css +0 -597
- package/src/css/views/slide-tables.css +0 -275
- package/src/formatters/grade/shared.js +0 -86
- package/src/pages/grade.js +0 -122
- package/src/slides/grade.js +0 -32
package/src/components/detail.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Detail view components
|
|
3
|
+
*
|
|
4
|
+
* Re-exports generic components from @forwardimpact/libui/components/detail
|
|
5
|
+
* and provides domain-specific level display components.
|
|
3
6
|
*/
|
|
4
7
|
|
|
8
|
+
export {
|
|
9
|
+
createDetailHeader,
|
|
10
|
+
createDetailSection,
|
|
11
|
+
createLinksList,
|
|
12
|
+
createTagsList,
|
|
13
|
+
createDetailItem,
|
|
14
|
+
} from "@forwardimpact/libui/components/detail";
|
|
15
|
+
|
|
5
16
|
import {
|
|
6
17
|
div,
|
|
7
|
-
h1,
|
|
8
|
-
h2,
|
|
9
|
-
p,
|
|
10
|
-
a,
|
|
11
18
|
span,
|
|
12
19
|
table,
|
|
13
20
|
thead,
|
|
@@ -15,63 +22,13 @@ import {
|
|
|
15
22
|
tr,
|
|
16
23
|
th,
|
|
17
24
|
td,
|
|
18
|
-
|
|
19
|
-
} from "
|
|
20
|
-
import { createBackLink } from "./nav.js";
|
|
21
|
-
import { createTag } from "./card.js";
|
|
22
|
-
import { formatLevel } from "../lib/render.js";
|
|
25
|
+
formatLevel,
|
|
26
|
+
} from "@forwardimpact/libui/render";
|
|
23
27
|
import {
|
|
24
|
-
|
|
28
|
+
SKILL_PROFICIENCY_ORDER,
|
|
25
29
|
BEHAVIOUR_MATURITY_ORDER,
|
|
26
30
|
} from "@forwardimpact/map/levels";
|
|
27
31
|
|
|
28
|
-
/**
|
|
29
|
-
* Create a detail page header
|
|
30
|
-
* @param {Object} options
|
|
31
|
-
* @param {string} options.title
|
|
32
|
-
* @param {string} [options.description]
|
|
33
|
-
* @param {string} options.backLink - Path to go back to
|
|
34
|
-
* @param {string} [options.backText] - Back link text
|
|
35
|
-
* @param {HTMLElement[]} [options.badges]
|
|
36
|
-
* @param {HTMLElement[]} [options.actions] - Action buttons
|
|
37
|
-
* @returns {HTMLElement}
|
|
38
|
-
*/
|
|
39
|
-
export function createDetailHeader({
|
|
40
|
-
title,
|
|
41
|
-
description,
|
|
42
|
-
backLink,
|
|
43
|
-
backText = "← Back to list",
|
|
44
|
-
badges = [],
|
|
45
|
-
actions = [],
|
|
46
|
-
}) {
|
|
47
|
-
return div(
|
|
48
|
-
{ className: "page-header" },
|
|
49
|
-
createBackLink(backLink, backText),
|
|
50
|
-
div(
|
|
51
|
-
{ className: "card-header" },
|
|
52
|
-
h1({ className: "page-title" }, title),
|
|
53
|
-
badges.length > 0 ? div({ className: "page-meta" }, ...badges) : null,
|
|
54
|
-
),
|
|
55
|
-
description ? p({ className: "page-description" }, description) : null,
|
|
56
|
-
actions.length > 0 ? div({ className: "page-actions" }, ...actions) : null,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create a detail section
|
|
62
|
-
* @param {Object} options
|
|
63
|
-
* @param {string} options.title
|
|
64
|
-
* @param {HTMLElement} options.content
|
|
65
|
-
* @returns {HTMLElement}
|
|
66
|
-
*/
|
|
67
|
-
export function createDetailSection({ title, content }) {
|
|
68
|
-
return section(
|
|
69
|
-
{ className: "section section-detail" },
|
|
70
|
-
h2({ className: "section-title" }, title),
|
|
71
|
-
content,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
32
|
/**
|
|
76
33
|
* Create a level descriptions table
|
|
77
34
|
* @param {Object} descriptions - Level descriptions object
|
|
@@ -80,7 +37,7 @@ export function createDetailSection({ title, content }) {
|
|
|
80
37
|
*/
|
|
81
38
|
export function createLevelTable(descriptions, type = "skill") {
|
|
82
39
|
const levels =
|
|
83
|
-
type === "skill" ?
|
|
40
|
+
type === "skill" ? SKILL_PROFICIENCY_ORDER : BEHAVIOUR_MATURITY_ORDER;
|
|
84
41
|
|
|
85
42
|
const levelLabels = Object.fromEntries(
|
|
86
43
|
levels.map((level, index) => [level, String(index + 1)]),
|
|
@@ -89,7 +46,7 @@ export function createLevelTable(descriptions, type = "skill") {
|
|
|
89
46
|
const maxLevels = levels.length;
|
|
90
47
|
|
|
91
48
|
const rows = levels.map((level) => {
|
|
92
|
-
const description = descriptions?.[level] || "
|
|
49
|
+
const description = descriptions?.[level] || "\u2014";
|
|
93
50
|
const levelIndex = parseInt(levelLabels[level]);
|
|
94
51
|
return tr(
|
|
95
52
|
{},
|
|
@@ -147,66 +104,7 @@ export function createLevelCell(levelIndex, maxLevels, levelName) {
|
|
|
147
104
|
export function createEmptyLevelCell() {
|
|
148
105
|
return td(
|
|
149
106
|
{ className: "level-cell" },
|
|
150
|
-
span({ className: "level-label text-muted" }, "
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Create a links list
|
|
156
|
-
* @param {Array<{id: string, name: string}>} items
|
|
157
|
-
* @param {string} basePath - Base path for links (e.g., '/skill')
|
|
158
|
-
* @param {string} [emptyMessage='None']
|
|
159
|
-
* @param {Function} [getDisplayName] - Optional function to get display name
|
|
160
|
-
* @returns {HTMLElement}
|
|
161
|
-
*/
|
|
162
|
-
export function createLinksList(
|
|
163
|
-
items,
|
|
164
|
-
basePath,
|
|
165
|
-
emptyMessage = "None",
|
|
166
|
-
getDisplayName,
|
|
167
|
-
) {
|
|
168
|
-
if (!items || items.length === 0) {
|
|
169
|
-
return p({ className: "text-muted" }, emptyMessage);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const displayFn = getDisplayName || ((item) => item.name);
|
|
173
|
-
const links = items.map((item) =>
|
|
174
|
-
a({ href: `#${basePath}/${item.id}` }, displayFn(item)),
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
return div({ className: "links-list" }, ...links);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Create a tags list
|
|
182
|
-
* @param {string[]} tags
|
|
183
|
-
* @param {string} [emptyMessage='None']
|
|
184
|
-
* @returns {HTMLElement}
|
|
185
|
-
*/
|
|
186
|
-
export function createTagsList(tags, emptyMessage = "None") {
|
|
187
|
-
if (!tags || tags.length === 0) {
|
|
188
|
-
return p({ className: "text-muted" }, emptyMessage);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return div({ className: "info-tags" }, ...tags.map((tag) => createTag(tag)));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Create a detail grid item
|
|
196
|
-
* @param {string} label
|
|
197
|
-
* @param {string|HTMLElement} value
|
|
198
|
-
* @returns {HTMLElement}
|
|
199
|
-
*/
|
|
200
|
-
export function createDetailItem(label, value) {
|
|
201
|
-
const valueEl =
|
|
202
|
-
typeof value === "string"
|
|
203
|
-
? div({ className: "detail-item-value" }, value)
|
|
204
|
-
: value;
|
|
205
|
-
|
|
206
|
-
return div(
|
|
207
|
-
{ className: "detail-item" },
|
|
208
|
-
div({ className: "detail-item-label" }, label),
|
|
209
|
-
valueEl,
|
|
107
|
+
span({ className: "level-label text-muted" }, "\u2014"),
|
|
210
108
|
);
|
|
211
109
|
}
|
|
212
110
|
|
|
@@ -1,72 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error page components
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from @forwardimpact/libui/components/error-page.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @param {string} options.entityId - ID that was not found
|
|
12
|
-
* @param {string} options.backPath - Path to navigate back to
|
|
13
|
-
* @param {string} options.backText - Text for back link
|
|
14
|
-
*/
|
|
15
|
-
export function renderNotFound({ entityType, entityId, backPath, backText }) {
|
|
16
|
-
render(
|
|
17
|
-
div(
|
|
18
|
-
{ className: "error-message" },
|
|
19
|
-
h1({}, `${entityType} Not Found`),
|
|
20
|
-
p({}, `No ${entityType.toLowerCase()} found with ID: ${entityId}`),
|
|
21
|
-
a({ href: `#${backPath}` }, backText),
|
|
22
|
-
),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create a not found error element (without rendering)
|
|
28
|
-
* @param {Object} options - Configuration options
|
|
29
|
-
* @param {string} options.entityType - Type of entity not found
|
|
30
|
-
* @param {string} options.entityId - ID that was not found
|
|
31
|
-
* @param {string} options.backPath - Path to navigate back to
|
|
32
|
-
* @param {string} options.backText - Text for back link
|
|
33
|
-
* @returns {HTMLElement}
|
|
34
|
-
*/
|
|
35
|
-
export function createNotFound({ entityType, entityId, backPath, backText }) {
|
|
36
|
-
return div(
|
|
37
|
-
{ className: "error-message" },
|
|
38
|
-
h1({}, `${entityType} Not Found`),
|
|
39
|
-
p({}, `No ${entityType.toLowerCase()} found with ID: ${entityId}`),
|
|
40
|
-
a({ href: `#${backPath}` }, backText),
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create an invalid state error element
|
|
46
|
-
* @param {Object} options - Configuration options
|
|
47
|
-
* @param {string} options.title - Error title
|
|
48
|
-
* @param {string} options.message - Error message
|
|
49
|
-
* @param {string} options.backPath - Path to navigate back to
|
|
50
|
-
* @param {string} options.backText - Text for back link
|
|
51
|
-
* @returns {HTMLElement}
|
|
52
|
-
*/
|
|
53
|
-
export function createErrorMessage({ title, message, backPath, backText }) {
|
|
54
|
-
return div(
|
|
55
|
-
{ className: "error-message" },
|
|
56
|
-
h1({}, title),
|
|
57
|
-
p({}, message),
|
|
58
|
-
a({ href: `#${backPath}` }, backText),
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Render an invalid state error page
|
|
64
|
-
* @param {Object} options - Configuration options
|
|
65
|
-
* @param {string} options.title - Error title
|
|
66
|
-
* @param {string} options.message - Error message
|
|
67
|
-
* @param {string} options.backPath - Path to navigate back to
|
|
68
|
-
* @param {string} options.backText - Text for back link
|
|
69
|
-
*/
|
|
70
|
-
export function renderError({ title, message, backPath, backText }) {
|
|
71
|
-
render(createErrorMessage({ title, message, backPath, backText }));
|
|
72
|
-
}
|
|
7
|
+
export {
|
|
8
|
+
renderNotFound,
|
|
9
|
+
createNotFound,
|
|
10
|
+
createErrorMessage,
|
|
11
|
+
renderError,
|
|
12
|
+
} from "@forwardimpact/libui/components/error-page";
|
package/src/components/grid.js
CHANGED
|
@@ -1,109 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grid components and utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Grid size options
|
|
18
|
-
* @typedef {'xs' | 'sm' | 'md' | 'lg'} GridSize
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Gap size options
|
|
23
|
-
* @typedef {'sm' | 'md' | 'lg' | 'xl'} GapSize
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create an auto-fit grid container
|
|
28
|
-
* @param {GridSize} size - Grid size variant (xs, sm, md, lg)
|
|
29
|
-
* @param {HTMLElement[]} children - Child elements to place in grid
|
|
30
|
-
* @param {Object} [options] - Optional configuration
|
|
31
|
-
* @param {string} [options.className] - Additional CSS classes
|
|
32
|
-
* @param {GapSize} [options.gap] - Override default gap (sm, md, lg, xl)
|
|
33
|
-
* @returns {HTMLElement}
|
|
34
|
-
*/
|
|
35
|
-
export function createAutoGrid(size, children, options = {}) {
|
|
36
|
-
const { className = "", gap } = options;
|
|
37
|
-
const classes = [`auto-grid-${size}`, gap ? `gap-${gap}` : "", className]
|
|
38
|
-
.filter(Boolean)
|
|
39
|
-
.join(" ");
|
|
40
|
-
|
|
41
|
-
return div({ className: classes }, ...children);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create a fixed-column grid container
|
|
46
|
-
* @param {2 | 3 | 6} columns - Number of columns
|
|
47
|
-
* @param {HTMLElement[]} children - Child elements to place in grid
|
|
48
|
-
* @param {Object} [options] - Optional configuration
|
|
49
|
-
* @param {string} [options.className] - Additional CSS classes
|
|
50
|
-
* @returns {HTMLElement}
|
|
51
|
-
*/
|
|
52
|
-
export function createFixedGrid(columns, children, options = {}) {
|
|
53
|
-
const { className = "" } = options;
|
|
54
|
-
const classes = ["grid", `grid-${columns}`, className]
|
|
55
|
-
.filter(Boolean)
|
|
56
|
-
.join(" ");
|
|
57
|
-
|
|
58
|
-
return div({ className: classes }, ...children);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Create a grid for form selectors (discipline/grade/track dropdowns)
|
|
63
|
-
* Uses auto-grid-sm (200px min)
|
|
64
|
-
* @param {HTMLElement[]} children - Form control elements
|
|
65
|
-
* @returns {HTMLElement}
|
|
66
|
-
*/
|
|
67
|
-
export function createSelectorGrid(children) {
|
|
68
|
-
return createAutoGrid("sm", children, { gap: "lg" });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create a grid for detail items (key-value pairs)
|
|
73
|
-
* Uses auto-grid-md (300px min)
|
|
74
|
-
* @param {HTMLElement[]} children - Detail item elements
|
|
75
|
-
* @returns {HTMLElement}
|
|
76
|
-
*/
|
|
77
|
-
export function createDetailGrid(children) {
|
|
78
|
-
return createAutoGrid("md", children);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a grid for radar charts or large content
|
|
83
|
-
* Uses auto-grid-lg (400px min)
|
|
84
|
-
* @param {HTMLElement[]} children - Large content elements
|
|
85
|
-
* @returns {HTMLElement}
|
|
86
|
-
*/
|
|
87
|
-
export function createRadarGrid(children) {
|
|
88
|
-
return createAutoGrid("lg", children);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Create a grid for compact stats
|
|
93
|
-
* Uses auto-grid-xs (150px min)
|
|
94
|
-
* @param {HTMLElement[]} children - Stat elements
|
|
95
|
-
* @returns {HTMLElement}
|
|
96
|
-
*/
|
|
97
|
-
export function createStatsGrid(children) {
|
|
98
|
-
return createAutoGrid("xs", children);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Create a grid for card-like items (tips, expectations)
|
|
103
|
-
* Uses auto-grid-sm (200px min)
|
|
104
|
-
* @param {HTMLElement[]} children - Card elements
|
|
105
|
-
* @returns {HTMLElement}
|
|
106
|
-
*/
|
|
107
|
-
export function createCardGrid(children) {
|
|
108
|
-
return createAutoGrid("sm", children);
|
|
109
|
-
}
|
|
4
|
+
* Re-exports from @forwardimpact/libui/components/grid.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
createAutoGrid,
|
|
9
|
+
createFixedGrid,
|
|
10
|
+
createSelectorGrid,
|
|
11
|
+
createDetailGrid,
|
|
12
|
+
createRadarGrid,
|
|
13
|
+
createStatsGrid,
|
|
14
|
+
createCardGrid,
|
|
15
|
+
} from "@forwardimpact/libui/components/grid";
|
package/src/components/list.js
CHANGED
|
@@ -1,120 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reusable list component
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from @forwardimpact/libui/components/list.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @param {Object} options
|
|
11
|
-
* @param {Function} options.onSearch - Search callback
|
|
12
|
-
* @param {Array<{value: string, label: string}>} [options.filterOptions] - Filter dropdown options
|
|
13
|
-
* @param {Function} [options.onFilter] - Filter callback
|
|
14
|
-
* @param {string} [options.searchPlaceholder='Search...']
|
|
15
|
-
* @param {string} [options.filterPlaceholder='All']
|
|
16
|
-
* @returns {HTMLElement}
|
|
17
|
-
*/
|
|
18
|
-
export function createSearchBar({
|
|
19
|
-
onSearch,
|
|
20
|
-
filterOptions,
|
|
21
|
-
onFilter,
|
|
22
|
-
searchPlaceholder = "Search...",
|
|
23
|
-
filterPlaceholder = "All",
|
|
24
|
-
}) {
|
|
25
|
-
const searchInput = input({
|
|
26
|
-
type: "text",
|
|
27
|
-
className: "form-input",
|
|
28
|
-
placeholder: searchPlaceholder,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
searchInput.addEventListener("input", (e) => {
|
|
32
|
-
onSearch(e.target.value);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const children = [searchInput];
|
|
36
|
-
|
|
37
|
-
if (filterOptions && onFilter) {
|
|
38
|
-
const filterSelect = select(
|
|
39
|
-
{ className: "form-select" },
|
|
40
|
-
option({ value: "" }, filterPlaceholder),
|
|
41
|
-
...filterOptions.map((opt) => option({ value: opt.value }, opt.label)),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
filterSelect.addEventListener("change", (e) => {
|
|
45
|
-
onFilter(e.target.value);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
children.push(filterSelect);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return div({ className: "search-bar" }, ...children);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Create a list of cards
|
|
56
|
-
* @param {Array} items - Items to render
|
|
57
|
-
* @param {Function} renderItem - Function to render each item as a card config
|
|
58
|
-
* @param {string} [emptyMessage='No items found']
|
|
59
|
-
* @returns {HTMLElement}
|
|
60
|
-
*/
|
|
61
|
-
export function createCardList(
|
|
62
|
-
items,
|
|
63
|
-
renderItem,
|
|
64
|
-
emptyMessage = "No items found",
|
|
65
|
-
) {
|
|
66
|
-
if (!items || items.length === 0) {
|
|
67
|
-
return div(
|
|
68
|
-
{ className: "empty-state" },
|
|
69
|
-
div({ className: "empty-message" }, emptyMessage),
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const cards = items.map((item) => {
|
|
74
|
-
const config = renderItem(item);
|
|
75
|
-
return createCard(config);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return div({ className: "grid grid-3" }, ...cards);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a grouped list (like skills by capability)
|
|
83
|
-
* @param {Object} groups - Object with group names as keys and arrays as values
|
|
84
|
-
* @param {Function} renderItem - Function to render each item
|
|
85
|
-
* @param {Function} [renderGroupHeader] - Function to render group header
|
|
86
|
-
* @returns {HTMLElement}
|
|
87
|
-
*/
|
|
88
|
-
export function createGroupedList(groups, renderItem, renderGroupHeader) {
|
|
89
|
-
const sections = Object.entries(groups).map(([groupName, items]) => {
|
|
90
|
-
const header = renderGroupHeader
|
|
91
|
-
? renderGroupHeader(groupName, items.length)
|
|
92
|
-
: div(
|
|
93
|
-
{ className: "capability-header" },
|
|
94
|
-
h2({ className: "capability-title" }, formatGroupName(groupName)),
|
|
95
|
-
createBadge(`${items.length}`, "default"),
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const cards = items.map((item) => {
|
|
99
|
-
const config = renderItem(item);
|
|
100
|
-
return createCard(config);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return div(
|
|
104
|
-
{ className: "capability-group" },
|
|
105
|
-
header,
|
|
106
|
-
div({ className: "grid grid-3" }, ...cards),
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return div({ className: "grouped-list" }, ...sections);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Format a group name for display
|
|
115
|
-
* @param {string} name
|
|
116
|
-
* @returns {string}
|
|
117
|
-
*/
|
|
118
|
-
function formatGroupName(name) {
|
|
119
|
-
return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
120
|
-
}
|
|
7
|
+
export {
|
|
8
|
+
createSearchBar,
|
|
9
|
+
createCardList,
|
|
10
|
+
createGroupedList,
|
|
11
|
+
} from "@forwardimpact/libui/components/list";
|
package/src/components/nav.js
CHANGED
|
@@ -1,64 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Navigation component helpers
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from @forwardimpact/libui/components/nav.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
export function updateActiveNav(path) {
|
|
12
|
-
const links = document.querySelectorAll("#drawer-nav a");
|
|
13
|
-
links.forEach((link) => {
|
|
14
|
-
const href = link.getAttribute("href").slice(1); // Remove #
|
|
15
|
-
const isActive = path === href || (href !== "/" && path.startsWith(href));
|
|
16
|
-
link.classList.toggle("active", isActive);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a back link
|
|
22
|
-
* @param {string} href - Link destination
|
|
23
|
-
* @param {string} [text='Back'] - Link text
|
|
24
|
-
* @returns {HTMLElement}
|
|
25
|
-
*/
|
|
26
|
-
export function createBackLink(href, text = "← Back") {
|
|
27
|
-
return a({ href: `#${href}`, className: "back-link" }, text);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create breadcrumbs
|
|
32
|
-
* @param {Array<{label: string, href?: string}>} items
|
|
33
|
-
* @returns {HTMLElement}
|
|
34
|
-
*/
|
|
35
|
-
export function createBreadcrumbs(items) {
|
|
36
|
-
const crumbs = items.map((item, index) => {
|
|
37
|
-
const isLast = index === items.length - 1;
|
|
38
|
-
if (isLast || !item.href) {
|
|
39
|
-
return span({ className: "breadcrumb-item" }, item.label);
|
|
40
|
-
}
|
|
41
|
-
return a(
|
|
42
|
-
{ href: `#${item.href}`, className: "breadcrumb-item" },
|
|
43
|
-
item.label,
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const separator = " / ";
|
|
48
|
-
const children = [];
|
|
49
|
-
crumbs.forEach((crumb, i) => {
|
|
50
|
-
children.push(crumb);
|
|
51
|
-
if (i < crumbs.length - 1) {
|
|
52
|
-
children.push(document.createTextNode(separator));
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return div({ className: "breadcrumbs" }, ...children);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function span(attrs, text) {
|
|
60
|
-
const el = document.createElement("span");
|
|
61
|
-
if (attrs.className) el.className = attrs.className;
|
|
62
|
-
if (text) el.textContent = text;
|
|
63
|
-
return el;
|
|
64
|
-
}
|
|
7
|
+
export {
|
|
8
|
+
updateActiveNav,
|
|
9
|
+
createBackLink,
|
|
10
|
+
createBreadcrumbs,
|
|
11
|
+
} from "@forwardimpact/libui/components/nav";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { RadarChart } from "../lib/radar.js";
|
|
9
9
|
import { div, h3 } from "../lib/render.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
getSkillProficiencyIndex,
|
|
12
12
|
getBehaviourMaturityIndex,
|
|
13
13
|
formatLevel,
|
|
14
14
|
} from "../lib/render.js";
|
|
@@ -33,9 +33,9 @@ export function createSkillRadar(skillMatrix, options = {}) {
|
|
|
33
33
|
|
|
34
34
|
const data = skillMatrix.map((skill) => ({
|
|
35
35
|
label: skill.skillName,
|
|
36
|
-
value:
|
|
36
|
+
value: getSkillProficiencyIndex(skill.proficiency),
|
|
37
37
|
maxValue: 5,
|
|
38
|
-
description: `${formatLevel(skill.type)} skill - ${formatLevel(skill.
|
|
38
|
+
description: `${formatLevel(skill.type)} skill - ${formatLevel(skill.proficiency)}`,
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
41
|
const chart = new RadarChart({
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
td,
|
|
16
16
|
a,
|
|
17
17
|
} from "../lib/render.js";
|
|
18
|
-
import {
|
|
18
|
+
import { getSkillProficiencyIndex } from "../lib/render.js";
|
|
19
19
|
import { createLevelCell } from "./detail.js";
|
|
20
20
|
import { createBadge } from "./card.js";
|
|
21
|
-
import {
|
|
21
|
+
import { SKILL_PROFICIENCY_ORDER } from "@forwardimpact/map/levels";
|
|
22
22
|
import { truncate } from "../formatters/shared.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -28,8 +28,8 @@ import { truncate } from "../formatters/shared.js";
|
|
|
28
28
|
*/
|
|
29
29
|
function sortByLevelDescending(skills) {
|
|
30
30
|
return [...skills].sort((a, b) => {
|
|
31
|
-
const levelA =
|
|
32
|
-
const levelB =
|
|
31
|
+
const levelA = SKILL_PROFICIENCY_ORDER.indexOf(a.level);
|
|
32
|
+
const levelB = SKILL_PROFICIENCY_ORDER.indexOf(b.level);
|
|
33
33
|
if (levelB !== levelA) {
|
|
34
34
|
return levelB - levelA;
|
|
35
35
|
}
|
|
@@ -50,7 +50,7 @@ export function createSkillMatrix(skillMatrix) {
|
|
|
50
50
|
const sortedSkills = sortByLevelDescending(skillMatrix);
|
|
51
51
|
|
|
52
52
|
const rows = sortedSkills.map((skill) => {
|
|
53
|
-
const levelIndex =
|
|
53
|
+
const levelIndex = getSkillProficiencyIndex(skill.proficiency);
|
|
54
54
|
|
|
55
55
|
return tr(
|
|
56
56
|
{ className: skill.isHumanOnly ? "human-only-row" : "" },
|
|
@@ -69,10 +69,10 @@ export function createSkillMatrix(skillMatrix) {
|
|
|
69
69
|
: null,
|
|
70
70
|
),
|
|
71
71
|
td({}, createBadge(skill.capability, skill.capability)),
|
|
72
|
-
createLevelCell(levelIndex, 5, skill.
|
|
72
|
+
createLevelCell(levelIndex, 5, skill.proficiency),
|
|
73
73
|
td(
|
|
74
74
|
{ className: "skill-description" },
|
|
75
|
-
truncate(skill.
|
|
75
|
+
truncate(skill.proficiencyDescription, 80),
|
|
76
76
|
),
|
|
77
77
|
);
|
|
78
78
|
});
|