Skip to content

Commit 67ee5f2

Browse files
committed
Adding first flat sketches for viewing details about a BSO org
1 parent e3109b9 commit 67ee5f2

12 files changed

Lines changed: 948 additions & 46 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@use "nhsuk-frontend/dist/nhsuk/core" as *;
2+
3+
.app-secondary-navigation {
4+
margin-right: #{$nhsuk-gutter-half * -1};
5+
margin-left: #{$nhsuk-gutter-half * -1};
6+
7+
@include nhsuk-responsive-margin(5, "bottom");
8+
9+
@include nhsuk-media-query($from: tablet) {
10+
margin-right: auto;
11+
margin-left: auto;
12+
}
13+
}
14+
15+
.app-secondary-navigation__link {
16+
display: flex;
17+
align-items: center;
18+
gap: 6px;
19+
padding: nhsuk-spacing(2) $nhsuk-gutter-half;
20+
21+
&:link {
22+
text-decoration: none;
23+
}
24+
25+
@include nhsuk-link-style-default;
26+
@include nhsuk-link-style-no-visited-state;
27+
28+
@include nhsuk-media-query($from: tablet) {
29+
padding: nhsuk-spacing(3) 2px;
30+
}
31+
32+
&[aria-current] {
33+
color: $nhsuk-text-colour;
34+
box-shadow: inset $nhsuk-border-width 0 nhsuk-colour("blue");
35+
text-decoration: none;
36+
37+
@include nhsuk-media-query($from: tablet) {
38+
box-shadow: inset 0 ($nhsuk-border-width * -1) nhsuk-colour("blue");
39+
}
40+
}
41+
42+
&:focus {
43+
box-shadow: inset $nhsuk-focus-width 0 $nhsuk-focus-text-colour;
44+
45+
@include nhsuk-media-query($from: tablet) {
46+
box-shadow: inset 0 ($nhsuk-focus-width * -1) $nhsuk-focus-text-colour;
47+
}
48+
}
49+
50+
.nhsuk-icon {
51+
width: 1.5rem;
52+
height: 1.5rem;
53+
}
54+
}
55+
56+
.app-secondary-navigation__link--disabled {
57+
color: $nhsuk-secondary-text-colour;
58+
cursor: not-allowed;
59+
pointer-events: none;
60+
text-decoration: none;
61+
62+
&:hover,
63+
&:focus {
64+
text-decoration: none;
65+
}
66+
}
67+
68+
.app-secondary-navigation__current {
69+
font-weight: inherit;
70+
}
71+
72+
.app-secondary-navigation__list {
73+
display: flex;
74+
75+
flex-flow: column;
76+
77+
width: 100%;
78+
margin: 0;
79+
padding: 0;
80+
81+
list-style: none;
82+
// The list uses box-shadow rather than a border to set a 1px grey line at the
83+
// bottom, so that the current item appears on top of the grey line.
84+
box-shadow: inset 0 -1px 0 $nhsuk-border-colour;
85+
86+
@include nhsuk-font(19);
87+
88+
@include nhsuk-media-query($from: tablet) {
89+
flex-flow: row wrap;
90+
gap: nhsuk-spacing(2) nhsuk-spacing(5);
91+
}
92+
}
93+
94+
.app-secondary-navigation__list-item {
95+
margin-bottom: 0;
96+
}

app/assets/sass/main.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
// Import NHS.UK frontend library
2-
@import "nhsuk-frontend/dist/nhsuk";
2+
@forward "nhsuk-frontend/dist/nhsuk/nhsuk";
3+
4+
// Stolen Manage prototype components :P
5+
// https://github.com/NHSDigital/manage-breast-screening-prototype
6+
7+
@forward "components/secondary-navigation";
38

49
// Add your custom CSS/Sass styles below.
10+
.app-card-editable {
11+
display: flex;
12+
justify-content: space-between;
13+
align-items: baseline;
14+
}

app/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module.exports = {
44
// Service name
5-
serviceName: 'Service name goes here',
5+
serviceName: 'Cohort to clinic service name',
66

77
// Port to run the prototype on locally
88
port: 3000

app/filters.js

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,45 @@
1-
/**
2-
* @param {Environment} env
3-
*/
4-
module.exports = function (env) {
5-
const filters = {}
6-
7-
/* ------------------------------------------------------------------
8-
add your methods to the filters obj below this comment block:
9-
@example:
10-
11-
filters.sayHi = function(name) {
12-
return 'Hi ' + name + '!'
13-
}
14-
15-
Which in your templates would be used as:
16-
17-
{{ 'Paul' | sayHi }} => 'Hi Paul'
18-
19-
Notice the first argument of your filters method is whatever
20-
gets 'piped' via '|' to the filter.
21-
22-
Filters can take additional arguments, for example:
23-
24-
filters.sayHi = function(name,tone) {
25-
return (tone == 'formal' ? 'Greetings' : 'Hi') + ' ' + name + '!'
26-
}
27-
28-
Which would be used like this:
29-
30-
{{ 'Joel' | sayHi('formal') }} => 'Greetings Joel!'
31-
{{ 'Gemma' | sayHi }} => 'Hi Gemma!'
32-
33-
For more on filters and how to write them see the Nunjucks
34-
documentation.
35-
36-
------------------------------------------------------------------ */
37-
38-
/* keep the following line to return your filters to the app */
39-
return filters
40-
}
41-
42-
/**
43-
* @import { Environment } from 'nunjucks'
44-
*/
1+
// app/filters.js
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
6+
module.exports = function (env) {
7+
/* eslint-disable-line func-names,no-unused-vars */
8+
/**
9+
* Instantiate object used to store the methods registered as a
10+
* 'filter' (of the same name) within nunjucks. You can override
11+
* gov.uk core filters by creating filter methods of the same name.
12+
*
13+
* @type {object}
14+
*/
15+
const filters = {}
16+
17+
// Get all files from utils directory
18+
const utilsPath = path.join(__dirname, 'lib/utils')
19+
//const filtersPath = path.join(__dirname, 'filters')
20+
21+
//const folderPaths = [utilsPath, filtersPath]
22+
const folderPaths = [utilsPath]
23+
24+
try {
25+
folderPaths.forEach((folderPath) => {
26+
const files = fs.readdirSync(folderPath)
27+
28+
files.forEach((file) => {
29+
if (path.extname(file) === '.js') {
30+
const module = require(path.join(folderPath, file))
31+
32+
Object.entries(module).forEach(([name, func]) => {
33+
if (typeof func === 'function') {
34+
filters[name] = func
35+
}
36+
})
37+
}
38+
})
39+
})
40+
} catch (err) {
41+
console.warn('Error loading filters:', err)
42+
}
43+
44+
return filters
45+
}

app/lib/utils/arrays.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// app/lib/utils/arrays.js
2+
3+
const _ = require('lodash')
4+
5+
/**
6+
* Find an object by ID in an array
7+
*
8+
* @param {Array} array - Array to search
9+
* @param {string} id - ID to find
10+
* @returns {object} Found object or undefined
11+
*/
12+
const findById = (array, id) => {
13+
if (!array || !Array.isArray(array)) return undefined
14+
return array.find((item) => item.id === id)
15+
}
16+
17+
const push = (array, item) => {
18+
const newArray = [...array]
19+
newArray.push(_.cloneDeep(item)) // clone needed to stop this mutating original
20+
return newArray
21+
}
22+
23+
/**
24+
* Check if an array includes a value
25+
*
26+
* @param {Array} array - Array to check
27+
* @param {*} value - Value to look for
28+
* @returns {boolean} True if array includes value, false otherwise
29+
*/
30+
const includes = (array, value) => {
31+
if (!array || !Array.isArray(array)) return false
32+
return array.includes(value)
33+
}
34+
35+
/**
36+
* Find first array item where the specified key matches the value
37+
*
38+
* @param {Array} array - Array to search
39+
* @param {string} key - Object key to match against
40+
* @param {any} value - Value to find
41+
* @returns {any} First matching item or undefined
42+
* @example
43+
* const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]
44+
* find(users, 'name', 'Bob') // Returns {id: 2, name: 'Bob'}
45+
*/
46+
const find = (array, key, value) => {
47+
if (!array || !Array.isArray(array)) return undefined
48+
return array.find((item) => item[key] === value)
49+
}
50+
51+
/**
52+
* Remove empty items from arrays or strings
53+
*
54+
* @param {Array|string} items - Items to filter
55+
* @returns {Array|string|undefined} Filtered items or undefined if empty
56+
*/
57+
const removeEmpty = (items) => {
58+
if (!items) return
59+
60+
if (_.isString(items)) {
61+
return items.trim() || undefined
62+
}
63+
64+
if (_.isArray(items)) {
65+
const filtered = items.filter((item) => {
66+
// Filter out falsy values and empty strings
67+
if (!item || item === '') return false
68+
69+
// Filter out empty objects
70+
if (
71+
_.isObject(item) &&
72+
!_.isArray(item) &&
73+
Object.keys(item).length === 0
74+
)
75+
return false
76+
77+
// Filter out empty arrays
78+
if (_.isArray(item) && item.length === 0) return false
79+
80+
return true
81+
})
82+
return filtered.length ? filtered : undefined
83+
}
84+
}
85+
86+
/**
87+
* Filter array to items where the specified property matches one of the comparison values
88+
*
89+
* @param {Array} array - Array to filter
90+
* @param {string} key - Object property path to match against (supports dot notation)
91+
* @param {*|Array} compare - Value or array of values to match
92+
* @returns {Array} Filtered array containing only matching items
93+
* @example
94+
* where([{type: 'dog'}, {type: 'cat'}], 'type', 'dog') // Returns [{type: 'dog'}]
95+
* where(users, 'address.postcode', ['OX1', 'OX2']) // Returns users with matching postcodes
96+
*/
97+
const where = (array, key, compare) => {
98+
if (!array || !Array.isArray(array)) return []
99+
100+
// Force comparison value to array
101+
const compareValues = Array.isArray(compare) ? compare : [compare]
102+
103+
return array.filter((item) => {
104+
const value = _.get(item, key)
105+
return compareValues.includes(value)
106+
})
107+
}
108+
109+
/**
110+
* Filter array to remove items where the specified property matches one of the comparison values
111+
*
112+
* @param {Array} array - Array to filter
113+
* @param {string} key - Object property path to match against (supports dot notation)
114+
* @param {*|Array} compare - Value or array of values to exclude
115+
* @returns {Array} Filtered array with matching items removed
116+
* @example
117+
* removeWhere([{type: 'dog'}, {type: 'cat'}], 'type', 'dog') // Returns [{type: 'cat'}]
118+
* removeWhere(users, 'status', ['inactive', 'suspended']) // Returns only active users
119+
*/
120+
const removeWhere = (array, key, compare) => {
121+
if (!array || !Array.isArray(array)) return []
122+
123+
// Force comparison value to array
124+
const compareValues = Array.isArray(compare) ? compare : [compare]
125+
126+
return array.filter((item) => {
127+
const value = _.get(item, key)
128+
return !compareValues.includes(value)
129+
})
130+
}
131+
132+
/**
133+
* Apply a filter to each element in an array
134+
*
135+
* @param {Array} array - Array to map over
136+
* @param {string} filterName - Name of the filter to apply to each element
137+
* @returns {Array} New array with filter applied to each element
138+
*/
139+
const map = function (array, filterName) {
140+
if (!array || !Array.isArray(array)) return []
141+
142+
// In Nunjucks filter context, 'this' gives us access to the environment
143+
// and we can access other filters through the environment
144+
const env = this.env
145+
146+
if (!env || !env.filters || !env.filters[filterName]) {
147+
console.warn(`Filter '${filterName}' not found`)
148+
return array
149+
}
150+
151+
const filterFunction = env.filters[filterName]
152+
153+
return array.map((item) => filterFunction.call(this, item))
154+
}
155+
156+
/**
157+
* Check if a value is an array
158+
*
159+
* @param {*} value - Value to check
160+
* @returns {boolean} True if value is an array, false otherwise
161+
* @example
162+
* isArray([1, 2, 3]) // Returns true
163+
* isArray('hello') // Returns false
164+
* isArray(null) // Returns false
165+
*/
166+
const isArray = (value) => {
167+
return Array.isArray(value)
168+
}
169+
170+
module.exports = {
171+
push,
172+
includes,
173+
find,
174+
removeEmpty,
175+
findById,
176+
where,
177+
removeWhere,
178+
map,
179+
isArray
180+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% macro appSecondaryNavigation(params) %}
2+
{%- include "./template.njk" -%}
3+
{% endmacro %}

0 commit comments

Comments
 (0)