|
| 1 | +<script module> |
| 2 | + import BaseNameAndStatus from "$lib/package-wrapping/BaseNameAndStatus.svelte"; |
| 3 | + import BaseInformation from "$lib/package-wrapping/BaseInformation.svelte"; |
| 4 | + export { WrapperNameAndStatus, WrapperInformation }; |
| 5 | +
|
| 6 | + /** |
| 7 | + * ! More documentation is provided on the component library's user guide page. |
| 8 | + */ |
| 9 | +
|
| 10 | + /** |
| 11 | + * ! STEP 1 - Adding component metadata |
| 12 | + * CUSTOMISETHIS Update the status for this component. |
| 13 | + * && statusObject.progress determines which pill is shown against the component's name. |
| 14 | + * ? progress must be one of: |
| 15 | + * ? 1. 'To be developed' - This is the inital status, when the component files have been generated but the work to actually build out the code for the component has not started. |
| 16 | + * ? 2. 'In progress' - This is the status while the component is being built. This lets developers know that the full fuctionality of the component has not been completed and that it may change in the future. |
| 17 | + * ? 3. 'Baseline completed' - This means the core functionality of the component has been completed and it is ready for use. However, small changes to the component may still occur in the future. |
| 18 | + * ? 4. 'In use' - This means the component is completed and being using in products. Therefore, developers need to be mindful of its existing uses when making any changes. |
| 19 | + * && statusObject.statusRow determines the sets of ticks/crosses shown below the component name. |
| 20 | + * && The ticks/crosses are separated into rows with one row for each entry of the statusRows array. |
| 21 | + * && Any entries can be included, but by default the following are provided, initally all set to false: |
| 22 | + * ? Accessible - The component has been developed with reference to the WCAG guidelines. The component has been checked against our accessibility checklist, including testing it on screen readers. |
| 23 | + * ? Responsive - The component has been checked against our mobile design checklist. The component has been tested on multiple mobile devices. |
| 24 | + * ? Prog. enhanced - Potential progressive enhancements have been considered, and if appropriate, implemented for this component. |
| 25 | + * ? Reviewed - The component requirements, functionality and code have been reviewed and approved. |
| 26 | + * ? Tested - The component's use within products or prototyping (i.e. in a real-use example, using real props) has been tested and approved. |
| 27 | + */ |
| 28 | + let statusObject = { |
| 29 | + progress: "To be developed", |
| 30 | + statusRows: [ |
| 31 | + { |
| 32 | + obj: { Accessible: false, Responsive: false, "Prog. enhanced": false }, |
| 33 | + visibleOnHompepage: false, |
| 34 | + }, |
| 35 | + { |
| 36 | + obj: { Reviewed: false, Tested: false }, |
| 37 | + visibleOnHomepage: false, |
| 38 | + }, |
| 39 | + ], |
| 40 | + }; |
| 41 | +
|
| 42 | + /** |
| 43 | + * CUSTOMISETHIS Update detailsArray to provide description of what this component does and when it should be used. |
| 44 | + * && By default the detailsArray includes description and context. The description is intended to explain what the component does, the context is intended to explain when the component will be used. |
| 45 | + * ? Within the array, each object has an optional markdown (default = false) parameter. When set to true, it uses the @html tag to render the content (e.g. this can be used for including links to other pages). |
| 46 | + * ? You can add other categories to the detailsArray or, if you need a more flexible solution, edit the WrapperInformation snippet directly. |
| 47 | + * |
| 48 | + */ |
| 49 | + let descriptionArray = ["Explain here what the component does."]; |
| 50 | +
|
| 51 | + let contextArray = [ |
| 52 | + "Explain here the different contexts in which the component should be used.", |
| 53 | + ]; |
| 54 | +
|
| 55 | + let detailsArray = [ |
| 56 | + { |
| 57 | + label: "Description", |
| 58 | + arr: descriptionArray, |
| 59 | + visibleOnHomepage: true, |
| 60 | + markdown: true, |
| 61 | + }, |
| 62 | + { |
| 63 | + label: "Context", |
| 64 | + arr: contextArray, |
| 65 | + visibleOnHomepage: false, |
| 66 | + markdown: true, |
| 67 | + }, |
| 68 | + ]; |
| 69 | +
|
| 70 | + /** |
| 71 | + * CUSTOMISETHIS Update connectedComponentsArray to provide links to any children, parent or related components. |
| 72 | + */ |
| 73 | + let connectedComponentsArray = []; |
| 74 | +</script> |
| 75 | + |
| 76 | +<script> |
| 77 | + //@ts-nocheck |
| 78 | + import { page } from "$app/state"; |
| 79 | + import { browser } from "$app/environment"; |
| 80 | +
|
| 81 | + import WrapperDetailsUpdate from "$lib/package-wrapping/WrapperDetailsUpdate.svelte"; |
| 82 | + import ParsingErrorToastsContainer from "$lib/package-wrapping/ParsingErrorToastsContainer.svelte"; |
| 83 | + import ComponentDemo from "$lib/package-wrapping/ComponentDemo.svelte"; |
| 84 | +
|
| 85 | + import { kebabToPascalCase } from "$lib/utils/text-string-conversion/textStringConversion.js"; |
| 86 | + import { addIndexAndInitalValue } from "$lib/utils/package-wrapping-specific/addIndexAndInitialValue.js"; |
| 87 | + import { createParametersObject } from "$lib/utils/package-wrapping-specific/createParametersObject.js"; |
| 88 | + import { trackVisibleParameters } from "$lib/utils/package-wrapping-specific/trackVisibleParameters.js"; |
| 89 | + import { createBindableParametersValuesArray } from "$lib/utils/package-wrapping-specific/createBindableParametersValuesArray.js"; |
| 90 | + import { getValueFromParametersArray } from "$lib/utils/data-transformations/getValueFromParametersArray.js"; |
| 91 | +
|
| 92 | + import { defaultScreenWidthBreakpoints } from "$lib/config.js"; |
| 93 | +
|
| 94 | + import LineChart from "$lib/components/data-vis/line-chart/LineChart.svelte"; |
| 95 | + import Examples from "./line chart/Examples.svelte"; |
| 96 | +
|
| 97 | + let { data } = $props(); |
| 98 | +
|
| 99 | + /** |
| 100 | + * DONOTTOUCH * |
| 101 | + * ? uses the page url to identify the name of the component and the folder it belongs to (folder is only used by snippets exported to the homepage to link back to this page). |
| 102 | + */ |
| 103 | + let pageInfo = page.url.pathname.split("/"); |
| 104 | + let pageName = kebabToPascalCase(pageInfo[pageInfo.length - 1]); |
| 105 | +
|
| 106 | + /** |
| 107 | + * DONOTTOUCH * |
| 108 | + * ? demoScreenWidth is a reactive variable which tracks which screen size the user has selected for demoing the component |
| 109 | + */ |
| 110 | + let demoScreenWidth = $state(defaultScreenWidthBreakpoints.md); |
| 111 | +
|
| 112 | + /** |
| 113 | + * ! Step 2 - Adding binded props |
| 114 | + * CUSTOMISETHIS Define any binded props |
| 115 | + * && Any props which are updated inside the component but accessed outside should be declared here using the $state() rune. They can then be added to the parameterSourceArray below. |
| 116 | + * && Also note that they must also be passed to component using the bind: directive (e.g. <ExampleComponent bind:exampleBindableProp>) |
| 117 | + */ |
| 118 | +
|
| 119 | + /** |
| 120 | + * ! Step 3 - Add your props |
| 121 | + * CUSTOMISETHIS Add your parameters to the array. |
| 122 | + * && parametersSourceArray is where you define any props for the component. All props should be listed in the parametersSourceArray. |
| 123 | + * && Initial values should be provided for all props which either (i) are binded, or (ii) can be modified using the demo UI. |
| 124 | + * && Values should also be provided for functions and svelte snippets - which cannot be modified by the user to prevent security issues. |
| 125 | + * && Some props may be derived from other props. These should still be listed in the parametersSourceArray, but their value should be left empty. Their value can then be defined further down the page. |
| 126 | + * ? For each prop, the following fields are available: |
| 127 | + * ? <name> (required, must be unique) - Name of the parameter which is passed to the component. The name can also be referenced in the calculation of derived parameters which depend on this value. |
| 128 | + * ? <category> - (required) - Used for separating parameters into different groups. |
| 129 | + * |
| 130 | + * ? <isBinded> - (optional, default = false) - Should be set to true, if the prop is utilising the bind:directive. Note that for bind to work, the prop must also be defined in step 1 using the $state() rune and passed to the component separately as bindable (e.g. bind:thisProp). |
| 131 | + * |
| 132 | + * |
| 133 | + * ? <options> - (required for $state() props which can be modified using dropwdown or radio inputs). <options> should contain an array of strings. If <value> is null or absent, the initial value will be taken from the first entry in the options array. |
| 134 | + * ? <value> - (Should be null or absent for derived props. For all others props it's required, unless there is an options field). Used to set the initial value of the prop. |
| 135 | + * |
| 136 | + * ? <propType> - (optional) - Has two use cases: |
| 137 | + * ? (i) set to 'fixed' to prevent users editing the value. |
| 138 | + * ? (i) set to 'radio' to have options selectable via the radio input (uses dropdown by default). |
| 139 | + * |
| 140 | + * ? <visible> - (optional, default = true). Hides prop in the UI if certain conditions are not met. Specify an object with a name - the parameter that you want to check against and a value - the value that the named parameters need to equal for this input to be visible. |
| 141 | + * ? The Line component provides an example of the <visible> field in action, showing and hiding props based on <includeMarkers> |
| 142 | + * ? If you want the form to be visible only if multiple conditions are met, you can provide an array of conditional objects instead. |
| 143 | + * |
| 144 | + * ? <rows> - (optional, default = 1, only use with $state() where typeof value === "string"). Sets the numbers of rows used by the textArea input. |
| 145 | + * |
| 146 | + * ? <functionElements> - (optional, only used where typeof value === "function") - <functionElements> can have three optional properties: |
| 147 | + * ? (i) 'functionAsString': Should be set as a string copy of the function itself. Used to display the function in the demo UI. |
| 148 | + * ? (ii) 'counter': For use with event handler functions. Should be set to 0. Then putting 'this.functionElements.counter += 1;' in the function body will cause the counter to update each time the function is triggered. |
| 149 | + * ? (iii) 'dataset': For use with event handler fuctions. Should be an object - then putting 'Object.keys(this.functionElements.dataset).forEach((el) => { this.functionElements.dataset[el] = event.currentTarget.dataset[el]; });' in the function body will cause the object to update each time the function is triggered. |
| 150 | + * |
| 151 | + * ? <description> - (optional, but strongly encouraged). Describes what the parameter does and best practice uses for it. The description can be a string, an object with a markdown field (true or false) and arr field, or a svelte snippet. |
| 152 | + * |
| 153 | + * ? <isProp> - (optional, default = true) - Should be set to false for paramters which are not actually passed to the component. |
| 154 | + * ? <isRequired> - (optional, default = false) - Should be set to true for any props which the component will not functionally properly without (e.g. props with no default value, props which will cause erros if undefined). |
| 155 | + * |
| 156 | + */ |
| 157 | + let parametersSourceArray = $derived( |
| 158 | + addIndexAndInitalValue([ |
| 159 | + { |
| 160 | + name: "selectedMetric", |
| 161 | + category: "Data", |
| 162 | + visible: true, |
| 163 | + options: [ |
| 164 | + "Household waste recycling rate", |
| 165 | + "Recycling contamination rate", |
| 166 | + "Residual household waste", |
| 167 | + ], |
| 168 | + }, |
| 169 | + { |
| 170 | + name: "lineChartData", |
| 171 | + category: "Data", |
| 172 | + visible: true, |
| 173 | + isProp: true, |
| 174 | + }, |
| 175 | + { |
| 176 | + name: "showAllData", |
| 177 | + category: "Data", |
| 178 | + visible: true, |
| 179 | + isProp: true, |
| 180 | + value: true, |
| 181 | + }, |
| 182 | + { |
| 183 | + name: "interactiveLines", |
| 184 | + category: "Interaction", |
| 185 | + visible: true, |
| 186 | + isProp: true, |
| 187 | + value: ["primary", "secondary"], |
| 188 | + }, |
| 189 | + { |
| 190 | + name: "chartBackgroundColor", |
| 191 | + category: "Aesthetics", |
| 192 | + visible: true, |
| 193 | + isProp: true, |
| 194 | + value: "#f5f5f5", |
| 195 | + }, |
| 196 | + ]), |
| 197 | + ); |
| 198 | + /** |
| 199 | + * DONOTTOUCH * |
| 200 | + * && Defining functions. generateValuesArray is used to create our arrays which track the $state() and $derived() props. getValue can used to access a reactive value from the $state() based on the prop name. |
| 201 | + */ |
| 202 | + let generateValuesArray = function ( |
| 203 | + parametersSourceArray, |
| 204 | + isEditableBoolean, |
| 205 | + derivedParametersObject, |
| 206 | + ) { |
| 207 | + return parametersSourceArray.map((el) => { |
| 208 | + let value = derivedParametersObject[el.name] ?? el.value; |
| 209 | +
|
| 210 | + return el.isEditable === isEditableBoolean && value != null |
| 211 | + ? typeof value === "object" |
| 212 | + ? JSON.stringify(value, null, 2) |
| 213 | + : value |
| 214 | + : null; |
| 215 | + }); |
| 216 | + }; |
| 217 | +
|
| 218 | + let getValue = function (fieldName) { |
| 219 | + return statedParametersValuesArray[ |
| 220 | + parametersSourceArray?.findIndex((el) => el.name === fieldName) |
| 221 | + ]; |
| 222 | + }; |
| 223 | +
|
| 224 | + /** |
| 225 | + * DONOTTOUCH * |
| 226 | + * && statedParametersValuesArray tracks the values of $state() props as they are modified by the user using the demo UI. |
| 227 | + */ |
| 228 | + let statedParametersValuesArray = $state( |
| 229 | + generateValuesArray(parametersSourceArray, true, {}), |
| 230 | + ); |
| 231 | +
|
| 232 | + /** |
| 233 | + * ! Step 4 - Define values for derived parameters, and add them to. |
| 234 | + * CUSTOMISETHIS Add any additional parameters which are calculated based on other parameters. |
| 235 | + * && Here you can define calculations for any additional component parameters which - rather than being set by the user - are calculated based on the value of other parameters. |
| 236 | + * && Next, add the variables to the derivedParametersObject. |
| 237 | + * |
| 238 | + * && (e.g. let derivedProp = $derived(...code for calculating value here), then derivedParametersObject = $derived({ derivedProp })) |
| 239 | + * |
| 240 | + * && Note that these parameters still need to be listed in the source array (with a null or absent value). |
| 241 | + * && You must then also combine them into the derivedParametersObject below so that they are passed to the component. |
| 242 | + * && The getValue() function can be helpful for deriving props based on the value of $state() prop. |
| 243 | + */ |
| 244 | + let lineChartData = $derived( |
| 245 | + data.dataInFormatForLineChart.find( |
| 246 | + (el) => el.metric === getValue("selectedMetric"), |
| 247 | + ), |
| 248 | + ); |
| 249 | + let derivedParametersObject = $derived({ lineChartData }); |
| 250 | +
|
| 251 | + /** |
| 252 | + * DONOTTOUCH * |
| 253 | + * && derivedParametersValuesArray tracks the values of $derived() and fixed props. |
| 254 | + */ |
| 255 | + let derivedParametersValuesArray = $derived( |
| 256 | + generateValuesArray(parametersSourceArray, false, derivedParametersObject), |
| 257 | + ); |
| 258 | +
|
| 259 | + /** |
| 260 | + * DONOTTOUCH * |
| 261 | + * && parametersValuesArray's is a one-to-one mapping to the source array which tracks whether a parameter should be visible in the demo UI. |
| 262 | + */ |
| 263 | + let parametersVisibleArray = $derived( |
| 264 | + trackVisibleParameters(parametersSourceArray, statedParametersValuesArray), |
| 265 | + ); |
| 266 | +
|
| 267 | + /** |
| 268 | + * DONOTTOUCH * |
| 269 | + * && parametersObject takes the props to be passed to the component and converts them into a (parameterName: parameterValue) pattern |
| 270 | + * && parametersParsingErrorsArray tracks any errors due to attempting to use JSON.parse() on strings which do not convert to valid JavaScript objects. |
| 271 | + * && $effect() is then use to update parametersParsingErrorsObject, which tracks when errors and fixes occur. |
| 272 | + */ |
| 273 | +
|
| 274 | + let [parametersObject, parametersParsingErrorsArray] = $derived( |
| 275 | + createParametersObject( |
| 276 | + parametersSourceArray, |
| 277 | + statedParametersValuesArray, |
| 278 | + derivedParametersValuesArray, |
| 279 | + ), |
| 280 | + ); |
| 281 | +
|
| 282 | + let parametersParsingErrorsObject = $state({}); |
| 283 | +
|
| 284 | + $effect(() => { |
| 285 | + parametersParsingErrorsArray.forEach((el) => { |
| 286 | + parametersParsingErrorsObject[el] = true; |
| 287 | + }); |
| 288 | +
|
| 289 | + Object.keys(parametersParsingErrorsObject).forEach((el) => { |
| 290 | + if (!parametersParsingErrorsArray.includes(el)) { |
| 291 | + parametersParsingErrorsObject[el] = false; |
| 292 | + } |
| 293 | + }); |
| 294 | + }); |
| 295 | +
|
| 296 | + /** |
| 297 | + * DONOTTOUCH * |
| 298 | + * && copyParametersToClipboard simply takes the set of props being passed to the component, and replaces any function with their functionAsString property. This is necessary because actual functions cannot be written to parsed into a string, and therefore can't be copied to the clipboard. |
| 299 | + */ |
| 300 | +
|
| 301 | + let copyParametersToClipboardObject = $derived( |
| 302 | + Object.fromEntries( |
| 303 | + Object.entries(parametersObject).map(([key, value]) => [ |
| 304 | + key, |
| 305 | + typeof value === "function" |
| 306 | + ? parametersSourceArray.find((el) => el.name === key) |
| 307 | + ?.functionElements?.functionAsString |
| 308 | + : value, |
| 309 | + ]), |
| 310 | + ), |
| 311 | + ); |
| 312 | +</script> |
| 313 | +
|
| 314 | +<!-- |
| 315 | +&& WrapperNameAndStatus and WrapperInformation are passed to the WrapperDetails component. They are also exported and then imported on the homepage, and then used (again by the WrapperDetails component) to provide a link and info to this component. |
| 316 | + --> |
| 317 | +
|
| 318 | +{#snippet WrapperNameAndStatus(name, folder, subFolder, homepage)} |
| 319 | + <BaseNameAndStatus |
| 320 | + {name} |
| 321 | + {folder} |
| 322 | + {subFolder} |
| 323 | + {homepage} |
| 324 | + {statusObject} |
| 325 | + parentFolder="components-update" |
| 326 | + ></BaseNameAndStatus> |
| 327 | +{/snippet} |
| 328 | +
|
| 329 | +{#snippet WrapperInformation(homepage)} |
| 330 | + <BaseInformation {homepage} {detailsArray} {connectedComponentsArray} |
| 331 | + ></BaseInformation> |
| 332 | +{/snippet} |
| 333 | +
|
| 334 | +<!-- |
| 335 | + ! Step 5 - Create a context for the component and pass in any binded props using the bind:directive |
| 336 | + CUSTOMISETHIS Create a context in which your component is commonly used (e.g. wrap chart components within SVGs). Pass through binded props separately (e.g. <Component {...parametersOnject} bind:bindedProp></Component>) |
| 337 | + --> |
| 338 | +{#snippet Component()} |
| 339 | + <div class="p-8"> |
| 340 | + <LineChart {...parametersObject}></LineChart> |
| 341 | + </div> |
| 342 | +{/snippet} |
| 343 | +
|
| 344 | +<!-- |
| 345 | +DONOTTOUCH * |
| 346 | +&& Uses snippets to render metadata for the component. |
| 347 | +--> |
| 348 | +<WrapperDetailsUpdate |
| 349 | + wrapper={{ |
| 350 | + component: { WrapperInformation, WrapperNameAndStatus }, |
| 351 | + name: pageName, |
| 352 | + }} |
| 353 | + homepage={false} |
| 354 | +></WrapperDetailsUpdate> |
| 355 | +
|
| 356 | +<!-- |
| 357 | + DONOTTOUCH * |
| 358 | + && Renders toast components based on tracking parsing errors and fixes. |
| 359 | + --> |
| 360 | +<ParsingErrorToastsContainer |
| 361 | + {parametersParsingErrorsArray} |
| 362 | + {parametersParsingErrorsObject} |
| 363 | + onCloseFunction={(key) => |
| 364 | + parametersParsingErrorsArray.filter((el) => el != key)} |
| 365 | +></ParsingErrorToastsContainer> |
| 366 | +
|
| 367 | +<!-- |
| 368 | + DONOTTOUCH * |
| 369 | + && Renders the demo UI and the component itself. |
| 370 | +--> |
| 371 | +<ComponentDemo |
| 372 | + {Component} |
| 373 | + bind:demoScreenWidth |
| 374 | + {parametersSourceArray} |
| 375 | + bind:statedParametersValuesArray |
| 376 | + {derivedParametersValuesArray} |
| 377 | + {parametersVisibleArray} |
| 378 | + {parametersParsingErrorsObject} |
| 379 | + {copyParametersToClipboardObject} |
| 380 | +></ComponentDemo> |
| 381 | +
|
| 382 | +<!-- |
| 383 | + DONOTTOUCH * |
| 384 | + && Creates a list of examples where the component is used (if any examples exist). |
| 385 | +--> |
| 386 | +<div id="examples" data-role="examples-section" class="px-5"> |
| 387 | + <Examples></Examples> |
| 388 | +</div> |
0 commit comments