@leaflink/stash 44.6.0 → 44.7.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 CHANGED
@@ -8,26 +8,26 @@
8
8
  [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
9
9
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
10
10
 
11
-
12
- Stash is a collection of primitive, product-agnostic elements that help encapsulate LeafLink's look and feel at base level. This project is intended to be used across our digital product portfolio.
11
+ Stash is a collection of primitive, product-agnostic elements that help encapsulate LeafLink's look and feel at base
12
+ level. This project is intended to be used across our digital product portfolio.
13
13
 
14
14
  <!-- START doctoc generated TOC please keep comment here to allow auto update -->
15
15
  <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
16
16
  ## Table of Contents
17
17
 
18
- - [Getting Started](#getting-started)
18
+ - [Quick Start](#quick-start)
19
19
  - [Usage](#usage)
20
20
  - [Example](#example)
21
21
  - [npm scripts](#npm-scripts)
22
- - [Styles](#styles)
23
- - [Tailwind](#tailwind)
22
+ - [Legacy Styles](#legacy-styles)
23
+ - [Tailwind](#tailwind)
24
24
  - [Configuration](#configuration)
25
- - [Design System](#design-system)
26
25
  - [Resources](#resources)
27
26
  - [Core files & Entry Points](#core-files--entry-points)
28
27
  - [à la carte](#%C3%A0-la-carte)
29
28
  - [Peer dependencies](#peer-dependencies)
30
29
  - [Testing](#testing)
30
+ - [Mocking Google Maps API when testing AddressSelect](#mocking-google-maps-api-when-testing-addressselect)
31
31
  - [Assets](#assets)
32
32
  - [Illustrations and Icons](#illustrations-and-icons)
33
33
  - [Testing `Icon`'s and `Illustration`'s](#testing-icons-and-illustrations)
@@ -88,8 +88,8 @@ export default {
88
88
 
89
89
  See the [Tailwind](#tailwind) section for more details on how to configure it in your app.
90
90
 
91
- > [!INFO]
92
- > For apps still requiring deprecated css & utility classes, you can include the backwards compat styles from Stash:
91
+ > [!NOTE] For apps still requiring deprecated css & utility classes, you can include the backwards compat styles from
92
+ > Stash:
93
93
 
94
94
  ```ts filename="main.ts"
95
95
  import '@leaflink/stash/styles/backwards-compat.css'; // Add this line before the base and components styles.
@@ -120,16 +120,20 @@ export default defineConfig(({ mode }) => {
120
120
 
121
121
  ## Usage
122
122
 
123
- `@leaflink/stash` is a Vue component library that implements [Leaflink's Stash Design System](https://stash.leaflink.com). So every one of LeafLink's colors, typography, shadows, etc. can be accessible via tailwind utility classes like `tw-text-blue-500 tw-text-sm`.
123
+ `@leaflink/stash` is a Vue component library that implements
124
+ [Leaflink's Stash Design System](https://stash.leaflink.com). So every one of LeafLink's colors, typography, shadows,
125
+ etc. can be accessible via tailwind utility classes like `tw-text-blue-500 tw-text-sm`.
124
126
 
125
- Stash is a Vue plugin that can be installed in your app. You **do not need to install the plugin in order to use the components**, but it is required if you need to configure the framework to suit your specific needs.
127
+ Stash is a Vue plugin that can be installed in your app. You **do not need to install the plugin in order to use the
128
+ components**, but it is required if you need to configure the framework to suit your specific needs.
126
129
 
127
130
  There are several options to configure the framework to suit your specific needs, and they are all optional. Any options
128
131
  you pass to the plugin will be merged with the default options & applied to the entire framework.
129
132
 
130
- > [!WARNING]
131
- > If you don't install the plugin in your app, you will need to manually setup [modals](https://stash.leaflink.com/guides/modals.html#installation), [toasts](https://stash.leaflink.com/guides/toasts.html#installation), and other features that
132
- > require some setup that's normally done for you by the Stash plugin.
133
+ > [!WARNING] If you don't install the plugin in your app, you will need to manually setup
134
+ > [modals](https://stash.leaflink.com/guides/modals.html#installation),
135
+ > [toasts](https://stash.leaflink.com/guides/toasts.html#installation), and other features that require some setup
136
+ > that's normally done for you by the Stash plugin.
133
137
 
134
138
  ```ts
135
139
  interface StashPluginOptions {
@@ -203,7 +207,7 @@ const app = createApp(App);
203
207
  app.use(stash, {
204
208
  i18n: {
205
209
  locale,
206
- t: (key, value) => i18n.t(key, value)
210
+ t: (key, value) => i18n.t(key, value),
207
211
  },
208
212
  googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API,
209
213
  });
@@ -213,18 +217,24 @@ This example will load the core i18n options and Google Maps api key.
213
217
 
214
218
  ### npm scripts
215
219
 
216
- Most operations are run using `npm run` and are defined in `package.json`. Check out the available scripts with `npm run`.
220
+ Most operations are run using `npm run` and are defined in `package.json`. Check out the available scripts with
221
+ `npm run`.
217
222
 
218
223
  A few commonly used commands are:
219
224
 
220
225
  - `npm run docs` will start the docs using Vitepress dev server on port `5180`.
221
- - `npm run lint` will lint all vue/js files with eslint & lint css with `stylelint`. Optionally you can just lint js or css with the `lint:js` and `lint:css` scripts respectively. You can run `npm run lint --fix` to auto-fix code styles.
226
+ - `npm run lint` will lint all vue/js files with eslint & lint css with `stylelint`. Optionally you can just lint js or
227
+ css with the `lint:js` and `lint:css` scripts respectively. You can run `npm run lint --fix` to auto-fix code styles.
222
228
  - `npm test <file>` when we want to run a single spec during active development.
223
- - `npm test` runs all unit and integration tests with Vitest. `--watch` is enabled by default and you can pass any other Vitest options to this script that you'd like.
224
- - `npm run test:ci` will run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if you want to run with coverage.
225
- - `npm run type-check` will run the TypeScript compiler and perform a static analysis of the code and report any type errors it finds.
229
+ - `npm test` runs all unit and integration tests with Vitest. `--watch` is enabled by default and you can pass any other
230
+ Vitest options to this script that you'd like.
231
+ - `npm run test:ci` will run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if
232
+ you want to run with coverage.
233
+ - `npm run type-check` will run the TypeScript compiler and perform a static analysis of the code and report any type
234
+ errors it finds.
226
235
  - `npm run build` will build the application and prepare it for production.
227
- - `npm run build-ts` will build the application, perform a static analysis to find any type errors and prepare it for production.
236
+ - `npm run build-ts` will build the application, perform a static analysis to find any type errors and prepare it for
237
+ production.
228
238
 
229
239
  ## Legacy Styles
230
240
 
@@ -257,8 +267,10 @@ import Button from '@leaflink/stash/Button.vue';
257
267
  import IconLabel from '@leaflink/stash/IconLabel.vue';
258
268
 
259
269
  <Button icon-label class="tw-hidden md:tw-inline tw-ml-3">
260
- <IconLabel icon="user-add" title="Add Recipient" size="dense" stacked>Add Recipient</IconLabel>
261
- </Button>
270
+ <IconLabel icon="user-add" title="Add Recipient" size="dense" stacked>
271
+ Add Recipient
272
+ </IconLabel>
273
+ </Button>;
262
274
  ```
263
275
 
264
276
  ### Configuration
@@ -280,30 +292,34 @@ export default {
280
292
 
281
293
  ## Resources
282
294
 
283
- * **index.js**: This is the "install" entry point, for use with `app.use(...)`.
284
- * **components**: All components
285
- * **composables**: Similar to mixins or React's "Hooks", but for a Vue component
286
- * **constants**: LeafLink global constants
287
- * **directives**: [Vue directives](https://vuejs.org/guide/reusability/custom-directives#custom-directives)
288
- * **plugins**: [Vue plugins](https://vuejs.org/guide/reusability/plugins.html#plugins)
289
- * **styles**: SCSS, CSS, style utils, etc.
290
- * **types**: TypeScript type declarations
291
- * **utils**: Includes various helpers for internal and external use
295
+ - **index.js**: This is the "install" entry point, for use with `app.use(...)`.
296
+ - **components**: All components
297
+ - **composables**: Similar to mixins or React's "Hooks", but for a Vue component
298
+ - **constants**: LeafLink global constants
299
+ - **directives**: [Vue directives](https://vuejs.org/guide/reusability/custom-directives#custom-directives)
300
+ - **plugins**: [Vue plugins](https://vuejs.org/guide/reusability/plugins.html#plugins)
301
+ - **styles**: SCSS, CSS, style utils, etc.
302
+ - **types**: TypeScript type declarations
303
+ - **utils**: Includes various helpers for internal and external use
292
304
 
293
305
  ## Core files & Entry Points
294
306
 
295
- `index.js` is used as the main entry point to the framework. It also exports each component individually, for an _à la carte_ build. You may pull in the default export directly and `app.use` it (to quickly get up and running w/ all components and features); or, you may wish configure it with particular options, components, or features.
307
+ `index.js` is used as the main entry point to the framework. It also exports each component individually, for an _à la
308
+ carte_ build. You may pull in the default export directly and `app.use` it (to quickly get up and running w/ all
309
+ components and features); or, you may wish configure it with particular options, components, or features.
296
310
 
297
311
  ## à la carte
298
312
 
299
- `@leaflink/stash` serves its components and directives _à la carte_, which means that instead of importing the entire library, you selectively import only the specific components and directives that you need for your project. This approach helps reduce the bundle size of your application, resulting in faster load times and improved performance.
313
+ `@leaflink/stash` serves its components and directives _à la carte_, which means that instead of importing the entire
314
+ library, you selectively import only the specific components and directives that you need for your project. This
315
+ approach helps reduce the bundle size of your application, resulting in faster load times and improved performance.
300
316
 
301
317
  ```js
302
318
  // Component.vue
303
319
 
304
- import Select from '@leaflink/stash/Select.vue'
320
+ import Select from '@leaflink/stash/Select.vue';
305
321
 
306
- <Select></Select>
322
+ <Select></Select>;
307
323
  ```
308
324
 
309
325
  ```js
@@ -311,41 +327,53 @@ import Select from '@leaflink/stash/Select.vue'
311
327
 
312
328
  import autofocus from '@leaflink/stash/autofocus';
313
329
 
314
- <button v-autofocus>
315
- Click
316
- </button>
330
+ <button v-autofocus>Click</button>;
317
331
  ```
318
332
 
319
333
  ## Peer dependencies
320
334
 
321
- Peer dependencies are specific dependencies that a package requires to work correctly, but expects the consumer of the package to provide. In other words, they are dependencies that the package relies on, but are not bundled with the package itself.
335
+ Peer dependencies are specific dependencies that a package requires to work correctly, but expects the consumer of the
336
+ package to provide. In other words, they are dependencies that the package relies on, but are not bundled with the
337
+ package itself.
322
338
 
323
339
  `@leaflink/stash` project requires some peer dependencies:
324
340
 
325
- * `postcss-preset-env`: Used for transforming CSS with PostCSS. Required by [Tailwind](https://tailwindcss.com/). Required compatibility with this package on version **9.x**.
341
+ - `postcss-preset-env`: Used for transforming CSS with PostCSS. Required by [Tailwind](https://tailwindcss.com/).
342
+ Required compatibility with this package on version **9.x**.
326
343
 
327
- * `tailwindcss`: Our utility-first CSS framework used for building our responsive and customizable components. Required compatibility with this package on version **^3.3.1** or higher.
344
+ - `tailwindcss`: Our utility-first CSS framework used for building our responsive and customizable components. Required
345
+ compatibility with this package on version **^3.3.1** or higher.
328
346
 
329
- * `typescript`: Adds static type-checking to JavaScript. Required compatibility with this package on version **^5.x** or higher.
347
+ - `typescript`: Adds static type-checking to JavaScript. Required compatibility with this package on version **^5.x** or
348
+ higher.
330
349
 
331
- * `vue-router`: The official router for Vue.js applications. Required compatibility with this package on version **^4.x** or higher.
350
+ - `vue-router`: The official router for Vue.js applications. Required compatibility with this package on version
351
+ **^4.x** or higher.
332
352
 
333
- These peer dependencies need to be installed separately by the consumer of the package, ensuring that the correct versions are used to maintain compatibility and avoid conflicts with other dependencies in the project.
353
+ These peer dependencies need to be installed separately by the consumer of the package, ensuring that the correct
354
+ versions are used to maintain compatibility and avoid conflicts with other dependencies in the project.
334
355
 
335
356
  ## Testing
336
357
 
337
- > [!TIP]
338
- > If you are contributing to `@leaflink/stash`, please refer to this contributing [testing](CONTRIBUTING.md#testing) section for more details on how to properly test your changes.
358
+ > [!TIP] If you are contributing to `@leaflink/stash`, please refer to this contributing
359
+ > [testing](CONTRIBUTING.md#testing) section for more details on how to properly test your changes.
339
360
 
340
361
  To run tests, there's multiple npm scripts available to you:
341
362
 
342
- * `npm run test` - Run all tests in watch mode.
343
- * `npm run test <file>` - Run matching spec files quickly and watch for changes.
344
- * `npm run test:ci` - Run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if you want to run with coverage.
363
+ - `npm run test` - Run all tests in watch mode.
364
+ - `npm run test <file>` - Run matching spec files quickly and watch for changes.
365
+ - `npm run test:ci` - Run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if you
366
+ want to run with coverage.
345
367
 
346
- They all run Vitest but are typically used at different times because of the default Vitest options we're passing to them inside of the npm script. All of them allow you to pass any additional Vitest cli options that you'd like, i.e. `npm run test -- --silent`.
368
+ They all run Vitest but are typically used at different times because of the default Vitest options we're passing to
369
+ them inside of the npm script. All of them allow you to pass any additional Vitest cli options that you'd like, i.e.
370
+ `npm run test -- --silent`.
347
371
 
348
- Testing Library truncates the output from tests, which can cut off large DOM elements logged to the console. This limit can be adjusted with the `DEBUG_PRINT_LIMIT` environment variable, which can be set either when running tests (`DEBUG_PRINT_LIMIT=100000 npm run test`) or added to your shell's profile (`export DEBUG_PRINT_LIMIT=100000`) to make it the default. For more on debugging with Testing Library, see [official documentation](https://testing-library.com/docs/dom-testing-library/api-debugging).
372
+ Testing Library truncates the output from tests, which can cut off large DOM elements logged to the console. This limit
373
+ can be adjusted with the `DEBUG_PRINT_LIMIT` environment variable, which can be set either when running tests
374
+ (`DEBUG_PRINT_LIMIT=100000 npm run test`) or added to your shell's profile (`export DEBUG_PRINT_LIMIT=100000`) to make
375
+ it the default. For more on debugging with Testing Library, see
376
+ [official documentation](https://testing-library.com/docs/dom-testing-library/api-debugging).
349
377
 
350
378
  Coverage HTML reports are written to `./coverage`.
351
379
 
@@ -368,7 +396,8 @@ When testing components that use the `AddressSelect` component or `useGoogleMaps
368
396
  This is because the `useGoogleMaps` composable uses the `google.maps` global object, which is not available in the
369
397
  testing environment.
370
398
 
371
- The easiest way to do this is to mock the useGoogleMaps composable and avoid trying to mock the Google Maps API directly.
399
+ The easiest way to do this is to mock the useGoogleMaps composable and avoid trying to mock the Google Maps API
400
+ directly.
372
401
 
373
402
  Create a file in the `__mocks__` directory of the `@leaflink/stash` package, and mock the `useGoogleMaps` composable.
374
403
 
@@ -390,12 +419,10 @@ export default function () {
390
419
  };
391
420
  }
392
421
 
393
-
394
422
  /* tests/setup-env.ts */
395
423
  import '@leaflink/dom-testing-utils/setup-env'; // to ensure lodash-es/debounce is mocked properly
396
424
  vi.mock('@leaflink/stash/useGoogleMaps');
397
425
 
398
-
399
426
  /* src/components/YourComponent.spec.ts */
400
427
  const user = userEvent.setup();
401
428
  // Start typing in the AddressSelect select input to trigger the useGoogleMaps mock response
@@ -405,13 +432,16 @@ await user.type(screen.getByPlaceholderText('Search'), 'type anything');
405
432
  await user.selectOptions(screen.getByLabelText('Bank address'), ['1']);
406
433
  ```
407
434
 
408
- It's also encouraged the use of [@leaflink/dom-testing-utils](https://github.com/LeafLink/dom-testing-utils) for testing utilities like global and local test setup, mocking endpoints, clean up components, get selected options and more. Checkout the [documention](https://github.com/LeafLink/dom-testing-utils) for learning more about this package.
435
+ It's also encouraged the use of [@leaflink/dom-testing-utils](https://github.com/LeafLink/dom-testing-utils) for testing
436
+ utilities like global and local test setup, mocking endpoints, clean up components, get selected options and more.
437
+ Checkout the [documention](https://github.com/LeafLink/dom-testing-utils) for learning more about this package.
409
438
 
410
439
  ## Assets
411
440
 
412
441
  When using Stash, a collection of assets are available to use, such as icons and illustrations.
413
442
 
414
- In order to configure the assets path for your project, you can do it via the `staticPath` option. By default, this property is set to the `/assets` path.
443
+ In order to configure the assets path for your project, you can do it via the `staticPath` option. By default, this
444
+ property is set to the `/assets` path.
415
445
 
416
446
  ```ts
417
447
  import { createApp } from 'vue';
@@ -420,7 +450,7 @@ import stash from '@leaflink/stash';
420
450
  const app = createApp(App);
421
451
 
422
452
  app.use(stash, {
423
- staticPath: '/my-assets-path'
453
+ staticPath: '/my-assets-path',
424
454
  });
425
455
  ```
426
456
 
@@ -458,38 +488,39 @@ export default defineConfig(({ mode }) => {
458
488
  ],
459
489
  hook: 'buildStart',
460
490
  }),
461
- ]
491
+ ],
462
492
  };
463
493
  });
464
-
465
494
  ```
466
495
 
467
496
  ## Illustrations and Icons
468
497
 
469
498
  It's encouraged to use Stash's `Illustration` and `Icon` components for these kind of data.
470
499
 
471
- 1. If your work includes a new illustration, add it here in Stash: <https://github.com/LeafLink/stash/tree/main/assets/illustrations>
500
+ 1. If your work includes a new illustration, add it here in Stash:
501
+ <https://github.com/LeafLink/stash/tree/main/assets/illustrations>
472
502
  2. Import the component:
473
503
  ```js
474
- import Illustration from "@leaflink/stash/Illustration.vue";
475
- import Icon from "@leaflink/stash/Icon.vue";
504
+ import Illustration from '@leaflink/stash/Illustration.vue';
505
+ import Icon from '@leaflink/stash/Icon.vue';
476
506
  ```
477
507
  3. Use it in your template:
478
508
  ```html
479
- <Illustration name="your-illustration-name" />
480
- <Icon name="your-icon-name" />
509
+ <Illustration name="your-illustration-name" /> <Icon name="your-icon-name" />
481
510
  ```
482
511
  4. Customize however you like: i.e:
483
512
  ```html
484
- <Illustration name="your-illustration-name" :size="58" />
485
- <Icon name="your-icon-name" :size="58" />
513
+ <Illustration name="your-illustration-name" :size="58" /> <Icon name="your-icon-name" :size="58" />
486
514
  ```
487
515
 
488
- If you're working on existing templates that use `SvgIcon` using one of the newer illustrations and you feel inclined to migrate it over to Stash, that would be helpful!
516
+ If you're working on existing templates that use `SvgIcon` using one of the newer illustrations and you feel inclined to
517
+ migrate it over to Stash, that would be helpful!
489
518
 
490
519
  ### Testing `Icon`'s and `Illustration`'s
491
520
 
492
- The `Icon` and `Illustration` components from Stash now loads SVG's asyncronously. This is fine for tests unless you're actually looking to query for an SVG. In that event, you will just need to be sure to `await findBy...` the icon before asserting on or interacting with it.
521
+ The `Icon` and `Illustration` components from Stash now loads SVG's asyncronously. This is fine for tests unless you're
522
+ actually looking to query for an SVG. In that event, you will just need to be sure to `await findBy...` the icon before
523
+ asserting on or interacting with it.
493
524
 
494
525
  **Example**
495
526
 
@@ -501,22 +532,22 @@ The `Icon` and `Illustration` components from Stash now loads SVG's asyncronousl
501
532
  ```ts
502
533
  // ❌ Fails
503
534
  renderAccountingAmounts();
504
- expect(screen.getByTestId("delete-adjustment-icon")).toBeInTheDocument();
535
+ expect(screen.getByTestId('delete-adjustment-icon')).toBeInTheDocument();
505
536
 
506
537
  // ❌ Possible false-positives
507
538
  renderAccountingAmounts();
508
- expect(screen.queryByTestId("delete-adjustment-icon")).not.toBeInTheDocument();
539
+ expect(screen.queryByTestId('delete-adjustment-icon')).not.toBeInTheDocument();
509
540
 
510
541
  // ✅ Passes
511
542
  renderAccountingAmounts();
512
- expect(await screen.findByTestId("delete-adjustment-icon")).toBeInTheDocument();
543
+ expect(await screen.findByTestId('delete-adjustment-icon')).toBeInTheDocument();
513
544
 
514
545
  // ✅ Passes
515
- import { flushPromises } from "@vue/test-utils";
546
+ import { flushPromises } from '@vue/test-utils';
516
547
 
517
548
  renderAccountingAmounts();
518
549
  await flushPromises();
519
- expect(screen.queryByTestId("delete-adjustment-icon")).not.toBeInTheDocument();
550
+ expect(screen.queryByTestId('delete-adjustment-icon')).not.toBeInTheDocument();
520
551
  ```
521
552
 
522
553
  **Details**
@@ -527,7 +558,8 @@ expect(screen.queryByTestId("delete-adjustment-icon")).not.toBeInTheDocument();
527
558
 
528
559
  ## Contributing
529
560
 
530
- Anyone can contribute to `@leaflink/stash`! Please check out the [Contribution guide](CONTRIBUTING.md) for guidelines about how to proceed.
561
+ Anyone can contribute to `@leaflink/stash`! Please check out the [Contribution guide](CONTRIBUTING.md) for guidelines
562
+ about how to proceed.
531
563
 
532
564
  Reach out in [slack](https://leaflink.slack.com/archives/C012WHER0R0) if you have other questions.
533
565
 
@@ -1 +1 @@
1
- {"version":3,"file":"FileUpload.js","sources":["../src/components/FileUpload/FileUpload.constants.ts","../src/components/FileUpload/FileUpload.vue"],"sourcesContent":["export const FILE_TYPES = {\n CSV: {\n EXTENSION: ['csv'],\n MIME_TYPES: ['text/csv', 'application/octet-stream', 'application/vnd.ms-excel'],\n ILLUSTRATION: 'csv',\n },\n PDF: {\n EXTENSION: ['pdf'],\n MIME_TYPES: ['application/pdf'],\n ILLUSTRATION: 'pdf',\n },\n PNG: {\n EXTENSION: ['png'],\n MIME_TYPES: ['image/png'],\n ILLUSTRATION: 'image',\n },\n JPEG: {\n EXTENSION: ['jpg', 'jpeg'],\n MIME_TYPES: ['image/jpeg'],\n ILLUSTRATION: 'image',\n },\n DOC: {\n EXTENSION: ['doc', 'docx'],\n MIME_TYPES: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n ILLUSTRATION: 'document',\n },\n XLS: {\n EXTENSION: ['xls', 'xlsx'],\n MIME_TYPES: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n ILLUSTRATION: 'csv',\n },\n};\n\nexport enum FileUploadSizes {\n Dense = 'dense',\n Standard = 'standard',\n}\n\nexport type FileUploadSize = `${FileUploadSizes}`;\n","<script lang=\"ts\">\n export * from './FileUpload.constants';\n</script>\n\n<script setup lang=\"ts\">\n import logger from '@leaflink/snitch';\n import { computed, inject, ref, useAttrs, useCssModule } from 'vue';\n import InlineSvg from 'vue-inline-svg';\n\n import { StashProvideState } from '../../../types/misc';\n import { t } from '../../locale';\n import Button from '../Button/Button.vue';\n import Icon from '../Icon/Icon.vue';\n import { FILE_TYPES, FileUploadSize, FileUploadSizes } from './FileUpload.constants';\n\n export type FileType = 'CSV' | 'PDF' | 'PNG' | 'JPEG' | 'DOC' | 'XLS';\n\n export interface FileUploadProps {\n /**\n * Files to display in the component\n */\n files?: File[];\n\n /**\n * Accepted file types\n */\n fileTypes?: FileType[];\n\n /**\n * Should display only the button\n */\n buttonOnly?: boolean;\n\n /**\n * Allows upload of multiple files\n */\n multiple?: boolean;\n\n /**\n * Is the input disabled\n */\n disabled?: boolean;\n\n /**\n * Component size\n */\n size?: FileUploadSize;\n }\n\n const props = withDefaults(defineProps<FileUploadProps>(), {\n files: () => [],\n fileTypes: () => ['CSV', 'PDF', 'PNG', 'JPEG', 'DOC', 'XLS'],\n buttonOnly: false,\n disabled: false,\n multiple: false,\n size: 'standard',\n });\n\n const classes = useCssModule();\n\n const emit =\n defineEmits<{\n (e: 'file-select', { files }: { files: FileUploadProps['files'] }): void;\n (e: 'file-delete', file: File): void;\n (e: 'file-error', message: string): void;\n }>();\n\n const isDraggingOver = ref(false);\n const fileUploadRef = ref<HTMLInputElement>();\n\n const stashOptions = inject<StashProvideState>('stashOptions');\n const attributes = useAttrs();\n\n const inputAttrs = computed(() => {\n const attrs = { ...attributes };\n\n delete attrs['data-test'];\n delete attrs.class;\n delete attrs.type;\n delete attrs.accept;\n\n return attrs;\n });\n\n function concatArraysToFirst(a: any[], b: any[]) {\n return a.concat(b);\n }\n\n const acceptedMimeTypes = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].MIME_TYPES).reduce(concatArraysToFirst);\n });\n\n const acceptedFileExtensions = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].EXTENSION).reduce(concatArraysToFirst);\n });\n\n const illustrationPath = computed(() => {\n return `${stashOptions?.staticPath}/illustrations/FileUpload/${FILE_TYPES[props.fileTypes[0]].ILLUSTRATION}.svg`;\n });\n\n function openFileDialog() {\n if (fileUploadRef.value) {\n fileUploadRef.value.value = '';\n fileUploadRef.value.click();\n }\n }\n\n function handleDragEnter() {\n isDraggingOver.value = true;\n }\n\n function handleDragLeave() {\n isDraggingOver.value = false;\n }\n\n function handleFileError(error: Error) {\n const message = t('ll.fileUpload.errors.incorrectFileType', {\n fileTypes: acceptedFileExtensions.value.join(', '),\n });\n\n emit('file-error', message);\n\n logger.log(error);\n }\n\n async function areFileTypesAccepted(files: File[]) {\n if (!acceptedMimeTypes.value.length) return true;\n\n const mimeTypes = await Promise.all(files.map((file) => readMimeType(file)));\n\n const allCorrectMimeTypes =\n !!mimeTypes.length && mimeTypes.every((mimeType) => acceptedMimeTypes.value.includes(mimeType));\n\n if (!allCorrectMimeTypes) {\n throw new Error('One or more files contains an unacceptable mime type.');\n }\n\n const allCorrectFileExtensions = files.every((file) => {\n const extension = file.name.split('.').pop();\n\n return extension && acceptedFileExtensions.value.includes(extension);\n });\n\n if (!allCorrectFileExtensions) {\n throw new Error('One or more files contains an unacceptable extension.');\n }\n\n return true;\n }\n\n async function processFiles(files: File[]) {\n try {\n await areFileTypesAccepted(files);\n\n emit('file-select', { files });\n } catch (error) {\n handleFileError(error as Error);\n }\n }\n\n /**\n * Sets file(s) to selected file(s) from dialogue\n * @param {Object} event - file select event that contains file(s)\n * @returns {Array} An array of files\n */\n function handleFileInput(event: Event) {\n const files = [...((event.target as HTMLInputElement)?.files || [])];\n\n processFiles(files);\n }\n\n /**\n * Sets file to dropped file if it is proper file type\n * @param {Object} event - file select event that contains file\n */\n function handleDropFile(event: DragEvent) {\n if (props.disabled) {\n return;\n }\n\n const files = [...(event.dataTransfer?.files || [])];\n\n isDraggingOver.value = false;\n\n return processFiles(files);\n }\n\n function handleFileDelete(file: File) {\n emit('file-delete', file);\n }\n\n function readMimeType(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file.type) {\n return resolve(file.type);\n } else if (window.FileReader) {\n const fileReader = new FileReader();\n\n fileReader.onload = () => {\n const mimeType =\n fileReader.result && (fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/)\n ? ((fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/) as string[])[0]\n : '';\n\n resolve(mimeType);\n };\n\n fileReader.readAsDataURL(file);\n } else {\n reject(new Error('Failed to read file.'));\n }\n });\n }\n</script>\n\n<template>\n <div class=\"stash-file-upload\" :class=\"attributes.class\" data-test=\"stash-file-upload\">\n <div v-if=\"buttonOnly\">\n <Button secondary type=\"button\" :disabled=\"disabled || null\" @click.stop.prevent=\"openFileDialog\">\n <slot name=\"submitText\">\n {{ t('ll.fileUpload.uploadFile') }}\n </slot>\n </Button>\n </div>\n <div\n v-else\n class=\"tw-rounded tw-p-6\"\n :class=\"[classes['file-dropbox'], isDraggingOver && classes['is-dragging'], disabled && classes['is-disabled']]\"\n @dragover.prevent=\"handleDragEnter\"\n @drop.prevent=\"handleDropFile\"\n @dragleave.prevent=\"handleDragLeave\"\n >\n <div\n class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-text-center\"\n :class=\"[{ 'tw-items-center md:tw-flex-row': size === FileUploadSizes.Dense }]\"\n >\n <template v-if=\"!files.length\">\n <InlineSvg v-if=\"size !== FileUploadSizes.Dense\" :src=\"illustrationPath\" name=\"file\" width=\"84\" height=\"96\" />\n <span class=\"tw-text-ice-900\">\n {{ t('ll.fileUpload.dragDropFileHere') }}\n </span>\n <span\n :class=\"\n size === FileUploadSizes.Dense\n ? 'md:tw-ml-1.5 md:tw-mr-3 md:tw-my-0 tw-my-1.5 tw-text-ice-900'\n : 'tw-mt-1.5 tw-my-1.5'\n \"\n >\n {{ t('ll.fileUpload.or') }}\n </span>\n <Button\n class=\"tw-mt-1.5\"\n secondary\n type=\"button\"\n :class=\"classes['file-select-button']\"\n :disabled=\"disabled\"\n @click.stop.prevent=\"openFileDialog\"\n >\n <!-- @slot for custom submit text -->\n <slot name=\"submitText\">{{ t('ll.fileUpload.uploadFile') }}</slot>\n </Button>\n </template>\n <template v-else>\n <div v-for=\"file in files\" :key=\"file.name\">\n <Icon name=\"file\" />\n <span>{{ file.name }}</span>\n <Button :class=\"[classes['remove-button'], classes['button']]\" @click.stop.prevent=\"handleFileDelete(file)\">\n {{ t('ll.fileUpload.remove') }}\n </Button>\n </div>\n </template>\n </div>\n <div v-if=\"$slots.hint && !files.length\" class=\"tw-mt-6 tw-text-center tw-text-xs tw-text-ice-700\">\n <!-- @slot for displaying helpful text and/or links -->\n <slot name=\"hint\"></slot>\n </div>\n </div>\n <input\n v-show=\"false\"\n v-bind=\"inputAttrs\"\n ref=\"fileUploadRef\"\n data-test=\"stash-file-upload|input\"\n type=\"file\"\n :disabled=\"disabled\"\n :accept=\"acceptedMimeTypes.join(',')\"\n :multiple=\"props.multiple\"\n @change=\"handleFileInput\"\n />\n </div>\n</template>\n\n<style module>\n .file-dropbox {\n background: var(--color-ice-200);\n background-image: url(\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='4' ry='4' stroke='%23C5C9D4FF' stroke-width='1' stroke-dasharray='5 %2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n border: theme('borderWidth.DEFAULT') solid var(--color-ice-500);\n border-color: transparent;\n }\n\n .is-dragging {\n background-image: none;\n border-color: var(--color-ice-500);\n\n & > * {\n pointer-events: none;\n }\n }\n\n .is-disabled {\n cursor: no-drop;\n }\n\n /* Constrain the upload icon for drag/drop to the required size */\n .upload-icon {\n height: 98px;\n width: 84px;\n }\n\n .remove-button.button {\n background: transparent;\n border: none;\n color: var(--color-red-500);\n\n &:hover {\n background: transparent;\n border: none;\n }\n }\n</style>\n"],"names":["FILE_TYPES","FileUploadSizes","classes","useCssModule","isDraggingOver","ref","fileUploadRef","stashOptions","inject","attributes","useAttrs","inputAttrs","computed","attrs","concatArraysToFirst","a","b","acceptedMimeTypes","props","fileType","acceptedFileExtensions","illustrationPath","openFileDialog","handleDragEnter","handleDragLeave","handleFileError","error","message","t","emit","logger","areFileTypesAccepted","files","mimeTypes","file","readMimeType","mimeType","extension","processFiles","handleFileInput","event","_a","handleDropFile","handleFileDelete","resolve","reject","fileReader"],"mappings":"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"}
1
+ {"version":3,"file":"FileUpload.js","sources":["../src/components/FileUpload/FileUpload.constants.ts","../src/components/FileUpload/FileUpload.vue"],"sourcesContent":["export const FILE_TYPES = {\n CSV: {\n EXTENSION: ['csv'],\n MIME_TYPES: ['text/csv', 'application/octet-stream', 'application/vnd.ms-excel'],\n ILLUSTRATION: 'csv',\n },\n PDF: {\n EXTENSION: ['pdf'],\n MIME_TYPES: ['application/pdf'],\n ILLUSTRATION: 'pdf',\n },\n PNG: {\n EXTENSION: ['png'],\n MIME_TYPES: ['image/png'],\n ILLUSTRATION: 'image',\n },\n JPEG: {\n EXTENSION: ['jpg', 'jpeg'],\n MIME_TYPES: ['image/jpeg'],\n ILLUSTRATION: 'image',\n },\n DOC: {\n EXTENSION: ['doc', 'docx'],\n MIME_TYPES: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n ILLUSTRATION: 'document',\n },\n XLS: {\n EXTENSION: ['xls', 'xlsx'],\n MIME_TYPES: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n ILLUSTRATION: 'csv',\n },\n};\n\nexport enum FileUploadSizes {\n Dense = 'dense',\n Standard = 'standard',\n}\n\nexport type FileUploadSize = `${FileUploadSizes}`;\n","<script lang=\"ts\">\n export * from './FileUpload.constants';\n</script>\n\n<script setup lang=\"ts\">\n import logger from '@leaflink/snitch';\n import { computed, inject, ref, useAttrs, useCssModule } from 'vue';\n import InlineSvg from 'vue-inline-svg';\n\n import { StashProvideState } from '../../../types/misc';\n import { t } from '../../locale';\n import Button from '../Button/Button.vue';\n import Icon from '../Icon/Icon.vue';\n import { FILE_TYPES, FileUploadSize, FileUploadSizes } from './FileUpload.constants';\n\n export type FileType = 'CSV' | 'PDF' | 'PNG' | 'JPEG' | 'DOC' | 'XLS';\n\n export interface FileUploadProps {\n /**\n * Files to display in the component\n */\n files?: File[];\n\n /**\n * Accepted file types\n */\n fileTypes?: FileType[];\n\n /**\n * Should display only the button\n */\n buttonOnly?: boolean;\n\n /**\n * Allows upload of multiple files\n */\n multiple?: boolean;\n\n /**\n * Is the input disabled\n */\n disabled?: boolean;\n\n /**\n * Component size\n */\n size?: FileUploadSize;\n }\n\n const props = withDefaults(defineProps<FileUploadProps>(), {\n files: () => [],\n fileTypes: () => ['CSV', 'PDF', 'PNG', 'JPEG', 'DOC', 'XLS'],\n buttonOnly: false,\n disabled: false,\n multiple: false,\n size: 'standard',\n });\n\n const classes = useCssModule();\n\n const emit =\n defineEmits<{\n (e: 'file-select', { files }: { files: FileUploadProps['files'] }): void;\n (e: 'file-delete', file: File): void;\n (e: 'file-error', message: string): void;\n }>();\n\n const isDraggingOver = ref(false);\n const fileUploadRef = ref<HTMLInputElement>();\n\n const stashOptions = inject<StashProvideState>('stashOptions');\n const attributes = useAttrs();\n\n const inputAttrs = computed(() => {\n const attrs = { ...attributes };\n\n delete attrs['data-test'];\n delete attrs.class;\n delete attrs.type;\n delete attrs.accept;\n\n return attrs;\n });\n\n function concatArraysToFirst(a: string[], b: string[]) {\n return a.concat(b);\n }\n\n const acceptedMimeTypes = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].MIME_TYPES).reduce(concatArraysToFirst);\n });\n\n const acceptedFileExtensions = computed(() => {\n return props.fileTypes.map((fileType) => FILE_TYPES[fileType].EXTENSION).reduce(concatArraysToFirst);\n });\n\n const illustrationPath = computed(() => {\n return `${stashOptions?.staticPath}/illustrations/FileUpload/${FILE_TYPES[props.fileTypes[0]].ILLUSTRATION}.svg`;\n });\n\n function openFileDialog() {\n if (fileUploadRef.value) {\n fileUploadRef.value.value = '';\n fileUploadRef.value.click();\n }\n }\n\n function handleDragEnter() {\n isDraggingOver.value = true;\n }\n\n function handleDragLeave() {\n isDraggingOver.value = false;\n }\n\n function handleFileError(error: Error) {\n const message = t('ll.fileUpload.errors.incorrectFileType', {\n fileTypes: acceptedFileExtensions.value.join(', '),\n });\n\n emit('file-error', message);\n\n logger.log(error);\n }\n\n async function areFileTypesAccepted(files: File[]) {\n if (!acceptedMimeTypes.value.length) return true;\n\n const mimeTypes = await Promise.all(files.map((file) => readMimeType(file)));\n\n const allCorrectMimeTypes =\n !!mimeTypes.length && mimeTypes.every((mimeType) => acceptedMimeTypes.value.includes(mimeType));\n\n if (!allCorrectMimeTypes) {\n throw new Error('One or more files contains an unacceptable mime type.');\n }\n\n const allCorrectFileExtensions = files.every((file) => {\n const extension = file.name.split('.').pop();\n\n return extension && acceptedFileExtensions.value.includes(extension);\n });\n\n if (!allCorrectFileExtensions) {\n throw new Error('One or more files contains an unacceptable extension.');\n }\n\n return true;\n }\n\n async function processFiles(files: File[]) {\n try {\n await areFileTypesAccepted(files);\n\n emit('file-select', { files });\n } catch (error) {\n handleFileError(error as Error);\n }\n }\n\n /**\n * Sets file(s) to selected file(s) from dialogue\n * @param {Object} event - file select event that contains file(s)\n * @returns {Array} An array of files\n */\n function handleFileInput(event: Event) {\n const files = [...((event.target as HTMLInputElement)?.files || [])];\n\n processFiles(files);\n }\n\n /**\n * Sets file to dropped file if it is proper file type\n * @param {Object} event - file select event that contains file\n */\n function handleDropFile(event: DragEvent) {\n if (props.disabled) {\n return;\n }\n\n const files = [...(event.dataTransfer?.files || [])];\n\n isDraggingOver.value = false;\n\n return processFiles(files);\n }\n\n function handleFileDelete(file: File) {\n emit('file-delete', file);\n }\n\n function readMimeType(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file.type) {\n return resolve(file.type);\n } else if (window.FileReader) {\n const fileReader = new FileReader();\n\n fileReader.onload = () => {\n const mimeType =\n fileReader.result && (fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/)\n ? ((fileReader.result as string).match(/[^:]\\w+\\/[\\w-+\\d.]+(?=;|,)/) as string[])[0]\n : '';\n\n resolve(mimeType);\n };\n\n fileReader.readAsDataURL(file);\n } else {\n reject(new Error('Failed to read file.'));\n }\n });\n }\n</script>\n\n<template>\n <div class=\"stash-file-upload\" :class=\"attributes.class\" data-test=\"stash-file-upload\">\n <div v-if=\"buttonOnly\">\n <Button secondary type=\"button\" :disabled=\"disabled || null\" @click.stop.prevent=\"openFileDialog\">\n <slot name=\"submitText\">\n {{ t('ll.fileUpload.uploadFile') }}\n </slot>\n </Button>\n </div>\n <div\n v-else\n class=\"tw-rounded tw-p-6\"\n :class=\"[classes['file-dropbox'], isDraggingOver && classes['is-dragging'], disabled && classes['is-disabled']]\"\n @dragover.prevent=\"handleDragEnter\"\n @drop.prevent=\"handleDropFile\"\n @dragleave.prevent=\"handleDragLeave\"\n >\n <div\n class=\"tw-flex tw-flex-col tw-items-center tw-justify-center tw-text-center\"\n :class=\"[{ 'tw-items-center md:tw-flex-row': size === FileUploadSizes.Dense }]\"\n >\n <template v-if=\"!files.length\">\n <InlineSvg v-if=\"size !== FileUploadSizes.Dense\" :src=\"illustrationPath\" name=\"file\" width=\"84\" height=\"96\" />\n <span class=\"tw-text-ice-900\">\n {{ t('ll.fileUpload.dragDropFileHere') }}\n </span>\n <span\n :class=\"\n size === FileUploadSizes.Dense\n ? 'md:tw-ml-1.5 md:tw-mr-3 md:tw-my-0 tw-my-1.5 tw-text-ice-900'\n : 'tw-mt-1.5 tw-my-1.5'\n \"\n >\n {{ t('ll.fileUpload.or') }}\n </span>\n <Button\n class=\"tw-mt-1.5\"\n secondary\n type=\"button\"\n :class=\"classes['file-select-button']\"\n :disabled=\"disabled\"\n @click.stop.prevent=\"openFileDialog\"\n >\n <!-- @slot for custom submit text -->\n <slot name=\"submitText\">{{ t('ll.fileUpload.uploadFile') }}</slot>\n </Button>\n </template>\n <template v-else>\n <div v-for=\"file in files\" :key=\"file.name\">\n <Icon name=\"file\" />\n <span>{{ file.name }}</span>\n <Button :class=\"[classes['remove-button'], classes['button']]\" @click.stop.prevent=\"handleFileDelete(file)\">\n {{ t('ll.fileUpload.remove') }}\n </Button>\n </div>\n </template>\n </div>\n <div v-if=\"$slots.hint && !files.length\" class=\"tw-mt-6 tw-text-center tw-text-xs tw-text-ice-700\">\n <!-- @slot for displaying helpful text and/or links -->\n <slot name=\"hint\"></slot>\n </div>\n </div>\n <input\n v-show=\"false\"\n v-bind=\"inputAttrs\"\n ref=\"fileUploadRef\"\n data-test=\"stash-file-upload|input\"\n type=\"file\"\n :disabled=\"disabled\"\n :accept=\"acceptedMimeTypes.join(',')\"\n :multiple=\"props.multiple\"\n @change=\"handleFileInput\"\n />\n </div>\n</template>\n\n<style module>\n .file-dropbox {\n background: var(--color-ice-200);\n background-image: url(\"data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='4' ry='4' stroke='%23C5C9D4FF' stroke-width='1' stroke-dasharray='5 %2c 5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n border: theme('borderWidth.DEFAULT') solid var(--color-ice-500);\n border-color: transparent;\n }\n\n .is-dragging {\n background-image: none;\n border-color: var(--color-ice-500);\n\n & > * {\n pointer-events: none;\n }\n }\n\n .is-disabled {\n cursor: no-drop;\n }\n\n /* Constrain the upload icon for drag/drop to the required size */\n .upload-icon {\n height: 98px;\n width: 84px;\n }\n\n .remove-button.button {\n background: transparent;\n border: none;\n color: var(--color-red-500);\n\n &:hover {\n background: transparent;\n border: none;\n }\n }\n</style>\n"],"names":["FILE_TYPES","FileUploadSizes","classes","useCssModule","isDraggingOver","ref","fileUploadRef","stashOptions","inject","attributes","useAttrs","inputAttrs","computed","attrs","concatArraysToFirst","a","b","acceptedMimeTypes","props","fileType","acceptedFileExtensions","illustrationPath","openFileDialog","handleDragEnter","handleDragLeave","handleFileError","error","message","t","emit","logger","areFileTypesAccepted","files","mimeTypes","file","readMimeType","mimeType","extension","processFiles","handleFileInput","event","_a","handleDropFile","handleFileDelete","resolve","reject","fileReader"],"mappings":"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"}