@telus-uds/components-base 1.73.0 → 1.75.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/CHANGELOG.md +27 -3
- package/lib/ExpandCollapse/Panel.js +1 -1
- package/lib/Footnote/Footnote.js +328 -0
- package/lib/Footnote/FootnoteLink.js +108 -0
- package/lib/Footnote/dictionary.js +19 -0
- package/lib/Footnote/index.js +12 -0
- package/lib/Notification/Notification.js +213 -35
- package/lib/Responsive/Responsive.js +8 -0
- package/lib/Responsive/ResponsiveWithMediaQueryStyleSheet.js +6 -3
- package/lib/Typography/Typography.js +3 -1
- package/lib/index.js +8 -0
- package/lib/utils/ssr-media-query/create-stylesheet/index.js +1 -2
- package/lib-module/ExpandCollapse/Panel.js +1 -1
- package/lib-module/Footnote/Footnote.js +319 -0
- package/lib-module/Footnote/FootnoteLink.js +101 -0
- package/lib-module/Footnote/dictionary.js +12 -0
- package/lib-module/Footnote/index.js +4 -0
- package/lib-module/Notification/Notification.js +216 -38
- package/lib-module/Responsive/Responsive.js +8 -0
- package/lib-module/Responsive/ResponsiveWithMediaQueryStyleSheet.js +6 -3
- package/lib-module/Typography/Typography.js +3 -1
- package/lib-module/index.js +1 -0
- package/lib-module/utils/ssr-media-query/create-stylesheet/index.js +1 -2
- package/package.json +2 -2
- package/src/ExpandCollapse/Panel.jsx +1 -1
- package/src/Footnote/Footnote.jsx +316 -0
- package/src/Footnote/FootnoteLink.jsx +95 -0
- package/src/Footnote/dictionary.js +12 -0
- package/src/Footnote/index.js +6 -0
- package/src/Notification/Notification.jsx +213 -34
- package/src/Responsive/Responsive.jsx +8 -2
- package/src/Responsive/ResponsiveWithMediaQueryStyleSheet.jsx +6 -4
- package/src/Typography/Typography.jsx +6 -1
- package/src/index.js +1 -0
- package/src/utils/ssr-media-query/create-stylesheet/index.js +3 -2
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import React, { forwardRef, useState } from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import React, { forwardRef, useState, useRef } from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
3
|
|
|
4
4
|
import PropTypes from 'prop-types'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
applyTextStyles,
|
|
7
|
+
useTheme,
|
|
8
|
+
useThemeTokens,
|
|
9
|
+
useResponsiveThemeTokens
|
|
10
|
+
} from '../ThemeProvider'
|
|
6
11
|
import {
|
|
7
12
|
a11yProps,
|
|
8
13
|
getTokensPropType,
|
|
@@ -11,7 +16,9 @@ import {
|
|
|
11
16
|
variantProp,
|
|
12
17
|
viewProps,
|
|
13
18
|
wrapStringsInText,
|
|
14
|
-
useResponsiveProp
|
|
19
|
+
useResponsiveProp,
|
|
20
|
+
createMediaQueryStyles,
|
|
21
|
+
StyleSheet
|
|
15
22
|
} from '../utils'
|
|
16
23
|
import IconButton from '../IconButton'
|
|
17
24
|
import useCopy from '../utils/useCopy'
|
|
@@ -61,6 +68,110 @@ const selectContentContainerStyle = (maxWidth) => ({
|
|
|
61
68
|
width: maxWidth || '100%'
|
|
62
69
|
})
|
|
63
70
|
|
|
71
|
+
const getMediaQueryStyles = (themeTokens, themeOptions, viewport, mediaIdsRef, dismissible) => {
|
|
72
|
+
const transformedSelectContainerStyles = Object.entries(themeTokens).reduce(
|
|
73
|
+
(acc, [vp, viewportTokens]) => {
|
|
74
|
+
acc[vp] = selectContainerStyles({ ...viewportTokens })
|
|
75
|
+
return acc
|
|
76
|
+
},
|
|
77
|
+
{}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const selectContainerMediaQueryStyles = createMediaQueryStyles(transformedSelectContainerStyles)
|
|
81
|
+
|
|
82
|
+
const { ids: containerIds, styles: containerStyles } = StyleSheet.create({
|
|
83
|
+
container: { flexDirection: 'row', ...selectContainerMediaQueryStyles }
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const { ids: contentContainerIds, styles: contentContainerStyles } = StyleSheet.create({
|
|
87
|
+
contentContainer: {
|
|
88
|
+
flexDirection: 'row',
|
|
89
|
+
flexShrink: 1,
|
|
90
|
+
justifyContent: 'space-between',
|
|
91
|
+
...createMediaQueryStyles({
|
|
92
|
+
xs: { width: themeOptions?.contentMaxWidth.xs || '100%' },
|
|
93
|
+
md: { width: themeOptions?.contentMaxWidth.md || '100%' },
|
|
94
|
+
lg: { width: themeOptions?.contentMaxWidth.lg || '100%' },
|
|
95
|
+
sm: { width: themeOptions?.contentMaxWidth.sm || '100%' },
|
|
96
|
+
xl: { width: themeOptions?.contentMaxWidth.xl || '100%' }
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const { ids: staticContentContainerIds, styles: staticContentContainerStyles } =
|
|
102
|
+
StyleSheet.create({
|
|
103
|
+
staticContentContainer: { flexDirection: 'row', flexShrink: 1 }
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const { ids: iconContainerIds, styles: iconContainerStyles } = StyleSheet.create({
|
|
107
|
+
iconContainer: selectIconContainerStyles(themeTokens[viewport])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const { ids: dismissButtonContainerIds, styles: dismissButtonContainerStyles } =
|
|
111
|
+
StyleSheet.create({
|
|
112
|
+
dismissButtonContainer: selectDismissButtonContainerStyles(themeTokens[viewport])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const { ids: textIds, styles: textStyles } = StyleSheet.create({
|
|
116
|
+
text: selectTextStyles(themeTokens[viewport], themeOptions, dismissible)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const { styles: selectIconPropsStyles } = StyleSheet.create({
|
|
120
|
+
selectIconProps: selectIconProps(themeTokens[viewport])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const { styles: selectDismissIconPropsStyles } = StyleSheet.create({
|
|
124
|
+
selectDismissIconProps: selectDismissIconProps(themeTokens[viewport])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line no-param-reassign
|
|
128
|
+
mediaIdsRef.current = {
|
|
129
|
+
containerIds,
|
|
130
|
+
contentContainerIds,
|
|
131
|
+
staticContentContainerIds,
|
|
132
|
+
iconContainerIds,
|
|
133
|
+
dismissButtonContainerIds,
|
|
134
|
+
textIds
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
containerStyles,
|
|
139
|
+
contentContainerStyles,
|
|
140
|
+
staticContentContainerStyles,
|
|
141
|
+
iconContainerStyles,
|
|
142
|
+
dismissButtonContainerStyles,
|
|
143
|
+
textStyles,
|
|
144
|
+
selectIconPropsStyles,
|
|
145
|
+
selectDismissIconPropsStyles
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible) => ({
|
|
150
|
+
containerStyles: {
|
|
151
|
+
container: { flexDirection: 'row', ...selectContainerStyles(themeTokens) }
|
|
152
|
+
},
|
|
153
|
+
contentContainerStyles: {
|
|
154
|
+
contentContainer: {
|
|
155
|
+
flexDirection: 'row',
|
|
156
|
+
flexShrink: 1,
|
|
157
|
+
justifyContent: 'space-between',
|
|
158
|
+
...selectContentContainerStyle(maxWidth)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
staticContentContainerStyles: {
|
|
162
|
+
staticContentContainer: { flexDirection: 'row', flexShrink: 1 }
|
|
163
|
+
},
|
|
164
|
+
iconContainerStyles: { iconContainer: { ...selectIconContainerStyles(themeTokens) } },
|
|
165
|
+
dismissButtonContainerStyles: {
|
|
166
|
+
dismissButtonContainer: { ...selectDismissButtonContainerStyles(themeTokens) }
|
|
167
|
+
},
|
|
168
|
+
textStyles: { text: { ...selectTextStyles(themeTokens, themeOptions, dismissible) } },
|
|
169
|
+
selectIconPropsStyles: { selectIconProps: { ...selectIconProps(themeTokens) } },
|
|
170
|
+
selectDismissIconPropsStyles: {
|
|
171
|
+
selectDismissIconProps: { ...selectDismissIconProps(themeTokens) }
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
64
175
|
/**
|
|
65
176
|
* A banner that highlights important messages:
|
|
66
177
|
* - Status message to show there is an error or outage of services
|
|
@@ -116,43 +227,121 @@ const Notification = forwardRef(
|
|
|
116
227
|
({ children, system, dismissible, copy = 'en', tokens, variant, ...rest }, ref) => {
|
|
117
228
|
const [isDismissed, setIsDismissed] = useState(false)
|
|
118
229
|
const viewport = useViewport()
|
|
119
|
-
const themeTokens = useThemeTokens('Notification', tokens, variant, { system, viewport })
|
|
120
230
|
const getCopy = useCopy({ dictionary, copy })
|
|
231
|
+
|
|
121
232
|
const { themeOptions } = useTheme()
|
|
122
|
-
const
|
|
233
|
+
const { enableMediaQueryStyleSheet } = themeOptions
|
|
234
|
+
const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
|
|
235
|
+
const themeTokens = useTokens('Notification', tokens, variant, { system, viewport })
|
|
236
|
+
const maxWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
|
|
237
|
+
|
|
238
|
+
const notificationComponentRef = useRef({
|
|
239
|
+
containerStyles: {},
|
|
240
|
+
contentContainerStyles: {},
|
|
241
|
+
staticContentContainerStyles: {},
|
|
242
|
+
iconContainerStyles: {},
|
|
243
|
+
dismissButtonContainerStyles: {},
|
|
244
|
+
textStyles: {},
|
|
245
|
+
selectIconPropsStyles: {},
|
|
246
|
+
selectDismissIconPropsStyles: {}
|
|
247
|
+
})
|
|
248
|
+
const mediaIdsRef = useRef({
|
|
249
|
+
containerIds: {},
|
|
250
|
+
contentContainerIds: {},
|
|
251
|
+
staticContentContainerIds: {},
|
|
252
|
+
iconContainerIds: {},
|
|
253
|
+
dismissButtonContainerIds: {},
|
|
254
|
+
textIds: {},
|
|
255
|
+
selectIconPropsIds: {},
|
|
256
|
+
selectDismissIconPropsIds: {}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
if (enableMediaQueryStyleSheet) {
|
|
260
|
+
notificationComponentRef.current = getMediaQueryStyles(
|
|
261
|
+
themeTokens,
|
|
262
|
+
themeOptions,
|
|
263
|
+
viewport,
|
|
264
|
+
mediaIdsRef,
|
|
265
|
+
dismissible
|
|
266
|
+
)
|
|
267
|
+
} else {
|
|
268
|
+
notificationComponentRef.current = getDefaultStyles(
|
|
269
|
+
themeTokens,
|
|
270
|
+
themeOptions,
|
|
271
|
+
maxWidth,
|
|
272
|
+
dismissible
|
|
273
|
+
)
|
|
274
|
+
}
|
|
123
275
|
|
|
124
276
|
if (isDismissed) {
|
|
125
277
|
return null
|
|
126
278
|
}
|
|
127
279
|
|
|
128
|
-
const textStyles = selectTextStyles(themeTokens, themeOptions, dismissible)
|
|
129
|
-
|
|
130
280
|
const content = wrapStringsInText(
|
|
131
|
-
typeof children === 'function'
|
|
132
|
-
|
|
281
|
+
typeof children === 'function'
|
|
282
|
+
? children({ textStyles: notificationComponentRef.current.textStyles.text, variant })
|
|
283
|
+
: children,
|
|
284
|
+
{ style: notificationComponentRef.current.textStyles.text }
|
|
133
285
|
)
|
|
134
286
|
|
|
135
|
-
const {
|
|
287
|
+
const {
|
|
288
|
+
icon: IconComponent,
|
|
289
|
+
dismissIcon: DismissIconComponent,
|
|
290
|
+
dismissIconColor
|
|
291
|
+
} = enableMediaQueryStyleSheet === false ? themeTokens : themeTokens[viewport]
|
|
136
292
|
|
|
137
293
|
const onDismissPress = () => setIsDismissed(true)
|
|
138
294
|
|
|
139
295
|
return (
|
|
140
296
|
<View
|
|
141
297
|
ref={ref}
|
|
142
|
-
style={
|
|
298
|
+
style={notificationComponentRef.current.containerStyles.container}
|
|
299
|
+
dataSet={mediaIdsRef && { media: mediaIdsRef.current.containerIds.container }}
|
|
143
300
|
{...selectProps(rest)}
|
|
144
301
|
>
|
|
145
|
-
<View
|
|
146
|
-
|
|
302
|
+
<View
|
|
303
|
+
style={notificationComponentRef.current.contentContainerStyles.contentContainer}
|
|
304
|
+
dataSet={
|
|
305
|
+
mediaIdsRef && { media: mediaIdsRef.current.contentContainerIds.contentContainer }
|
|
306
|
+
}
|
|
307
|
+
>
|
|
308
|
+
<View
|
|
309
|
+
style={
|
|
310
|
+
notificationComponentRef.current.staticContentContainerStyles.staticContentContainer
|
|
311
|
+
}
|
|
312
|
+
dataSet={
|
|
313
|
+
mediaIdsRef && {
|
|
314
|
+
media: mediaIdsRef.current.staticContentContainerIds.staticContentContainer
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
>
|
|
147
318
|
{IconComponent && (
|
|
148
|
-
<View
|
|
149
|
-
|
|
319
|
+
<View
|
|
320
|
+
style={notificationComponentRef.current.iconContainerStyles.iconContainer}
|
|
321
|
+
dataSet={
|
|
322
|
+
mediaIdsRef && { media: mediaIdsRef.current.iconContainerIds.iconContainer }
|
|
323
|
+
}
|
|
324
|
+
>
|
|
325
|
+
<IconComponent
|
|
326
|
+
{...notificationComponentRef.current.selectIconPropsStyles.selectIconProps}
|
|
327
|
+
/>
|
|
150
328
|
</View>
|
|
151
329
|
)}
|
|
152
|
-
{content && typeof content === 'function'
|
|
330
|
+
{content && typeof content === 'function'
|
|
331
|
+
? content({ textStyles: notificationComponentRef.current.textStyles.text, variant })
|
|
332
|
+
: content}
|
|
153
333
|
</View>
|
|
154
334
|
{dismissible && DismissIconComponent && (
|
|
155
|
-
<View
|
|
335
|
+
<View
|
|
336
|
+
style={
|
|
337
|
+
notificationComponentRef.current.dismissButtonContainerStyles.dismissButtonContainer
|
|
338
|
+
}
|
|
339
|
+
dataSet={
|
|
340
|
+
mediaIdsRef && {
|
|
341
|
+
media: mediaIdsRef.current.dismissButtonContainerIds.dismissButtonContainer
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
>
|
|
156
345
|
<IconButton
|
|
157
346
|
action="close"
|
|
158
347
|
onPress={onDismissPress}
|
|
@@ -161,7 +350,12 @@ const Notification = forwardRef(
|
|
|
161
350
|
accessibilityLabel={getCopy('dismiss')}
|
|
162
351
|
variant={{ inverse: dismissIconColor === '#ffffff', size: 'small' }}
|
|
163
352
|
>
|
|
164
|
-
{() =>
|
|
353
|
+
{() => (
|
|
354
|
+
<DismissIconComponent
|
|
355
|
+
{...notificationComponentRef.current.selectDismissIconPropsStyles
|
|
356
|
+
.selectDismissIconProps}
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
165
359
|
</IconButton>
|
|
166
360
|
</View>
|
|
167
361
|
)}
|
|
@@ -198,18 +392,3 @@ Notification.propTypes = {
|
|
|
198
392
|
}
|
|
199
393
|
|
|
200
394
|
export default Notification
|
|
201
|
-
|
|
202
|
-
const staticStyles = StyleSheet.create({
|
|
203
|
-
container: {
|
|
204
|
-
flexDirection: 'row'
|
|
205
|
-
},
|
|
206
|
-
contentContainer: {
|
|
207
|
-
flexDirection: 'row',
|
|
208
|
-
flexShrink: 1
|
|
209
|
-
},
|
|
210
|
-
content: {
|
|
211
|
-
flexDirection: 'row',
|
|
212
|
-
flexShrink: 1,
|
|
213
|
-
justifyContent: 'space-between'
|
|
214
|
-
}
|
|
215
|
-
})
|
|
@@ -17,13 +17,13 @@ import ResponsiveWithMediaQueryStyleSheet from './ResponsiveWithMediaQueryStyleS
|
|
|
17
17
|
* is used to hide and show children of `Responsive` within a View.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
const Responsive = ({ min = 'xs', max, children }) => {
|
|
20
|
+
const Responsive = ({ min = 'xs', max, inheritedStyles = [], children }) => {
|
|
21
21
|
const {
|
|
22
22
|
themeOptions: { enableMediaQueryStyleSheet }
|
|
23
23
|
} = useTheme()
|
|
24
24
|
if (enableMediaQueryStyleSheet) {
|
|
25
25
|
return (
|
|
26
|
-
<ResponsiveWithMediaQueryStyleSheet min={min} max={max}>
|
|
26
|
+
<ResponsiveWithMediaQueryStyleSheet inheritedStyles={inheritedStyles} min={min} max={max}>
|
|
27
27
|
{children}
|
|
28
28
|
</ResponsiveWithMediaQueryStyleSheet>
|
|
29
29
|
)
|
|
@@ -46,6 +46,12 @@ Responsive.propTypes = {
|
|
|
46
46
|
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
47
47
|
*/
|
|
48
48
|
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
49
|
+
/**
|
|
50
|
+
* Styles to be inherited by `Responsive`.
|
|
51
|
+
* It should be an array of style property names.
|
|
52
|
+
* Note: This prop is only applicable when `enableMediaQueryStylesheet` is set to true in the `ThemeProvider`.
|
|
53
|
+
*/
|
|
54
|
+
inheritedStyles: PropTypes.arrayOf(PropTypes.string),
|
|
49
55
|
children: PropTypes.node.isRequired
|
|
50
56
|
}
|
|
51
57
|
|
|
@@ -25,12 +25,13 @@ function generateResponsiveStyles(min, max) {
|
|
|
25
25
|
})
|
|
26
26
|
return createMediaQueryStyles(styles, false)
|
|
27
27
|
}
|
|
28
|
-
const ResponsiveWithMediaQueryStyleSheet = ({ min, max, children }) => {
|
|
28
|
+
const ResponsiveWithMediaQueryStyleSheet = ({ min, max, inheritedStyles = [], children }) => {
|
|
29
29
|
const { ids, styles } = StyleSheet.create({
|
|
30
30
|
responsive: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
...inheritedStyles.reduce((acc, prop) => {
|
|
32
|
+
acc[prop] = 'inherit'
|
|
33
|
+
return acc
|
|
34
|
+
}, {}),
|
|
34
35
|
...generateResponsiveStyles(min, max)
|
|
35
36
|
}
|
|
36
37
|
})
|
|
@@ -52,6 +53,7 @@ ResponsiveWithMediaQueryStyleSheet.propTypes = {
|
|
|
52
53
|
* To hide children of `Responsive` if the current viewport is larger than `max`
|
|
53
54
|
*/
|
|
54
55
|
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
56
|
+
inheritedStyles: PropTypes.arrayOf(PropTypes.string),
|
|
55
57
|
children: PropTypes.node.isRequired
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -76,7 +76,12 @@ const Typography = forwardRef(
|
|
|
76
76
|
const { enableMediaQueryStyleSheet } = themeOptions
|
|
77
77
|
|
|
78
78
|
const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
|
|
79
|
-
const themeTokens = useTokens(
|
|
79
|
+
const themeTokens = useTokens(
|
|
80
|
+
'Typography',
|
|
81
|
+
tokens,
|
|
82
|
+
variant,
|
|
83
|
+
!enableMediaQueryStyleSheet && { viewport }
|
|
84
|
+
)
|
|
80
85
|
const maxFontSizeMultiplier = enableMediaQueryStyleSheet
|
|
81
86
|
? getMaxFontMultiplier(themeTokens[viewport])
|
|
82
87
|
: getMaxFontMultiplier(themeTokens)
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { default as ExpandCollapse, Accordion } from './ExpandCollapse'
|
|
|
17
17
|
export { default as Feedback } from './Feedback'
|
|
18
18
|
export { default as Fieldset } from './Fieldset'
|
|
19
19
|
export { default as FlexGrid } from './FlexGrid'
|
|
20
|
+
export { default as Footnote } from './Footnote'
|
|
20
21
|
export { default as HorizontalScroll } from './HorizontalScroll'
|
|
21
22
|
export * from './HorizontalScroll'
|
|
22
23
|
export { default as Icon } from './Icon'
|
|
@@ -5,13 +5,14 @@ import { isMediaOrPseudo, deepClone, createCssRule } from '../utils/common'
|
|
|
5
5
|
|
|
6
6
|
const createStyleSheet = (stylesWithQuery) => {
|
|
7
7
|
if (!stylesWithQuery) return { ids: {}, styles: {}, fullStyles: {} }
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
let ids = {}
|
|
10
|
+
const cleanStyles = deepClone(stylesWithQuery)
|
|
11
|
+
|
|
10
12
|
Object.keys(stylesWithQuery).forEach((key) => {
|
|
11
13
|
if (!stylesWithQuery?.[key]) return
|
|
12
14
|
|
|
13
15
|
const mediaQueriesAndPseudoClasses = Object.keys(stylesWithQuery[key]).filter(isMediaOrPseudo)
|
|
14
|
-
cleanStyles = deepClone(stylesWithQuery)
|
|
15
16
|
mediaQueriesAndPseudoClasses.forEach((query) => {
|
|
16
17
|
const css = createDeclarationBlock(stylesWithQuery[key][query])
|
|
17
18
|
const stringHash = `rnmq-${hash(`${key}${query}${css}`)}`
|