@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 +106 -74
- package/dist/FileUpload.js.map +1 -1
- package/dist/Image.js +114 -133
- package/dist/Image.js.map +1 -1
- package/dist/Image.vue.d.ts +28 -31
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/useValidation.d.ts +32 -8
- package/dist/useValidation.js +162 -117
- package/dist/useValidation.js.map +1 -1
- package/dist/utils/storage.js +22 -22
- package/dist/utils/storage.js.map +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -8,26 +8,26 @@
|
|
|
8
8
|
[](https://github.com/semantic-release/semantic-release)
|
|
9
9
|
[](http://commitizen.github.io/cz-cli/)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
- [
|
|
18
|
+
- [Quick Start](#quick-start)
|
|
19
19
|
- [Usage](#usage)
|
|
20
20
|
- [Example](#example)
|
|
21
21
|
- [npm scripts](#npm-scripts)
|
|
22
|
-
- [Styles](#styles)
|
|
23
|
-
|
|
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
|
-
> [!
|
|
92
|
-
>
|
|
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
|
|
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
|
|
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
|
-
>
|
|
132
|
-
>
|
|
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
|
|
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
|
|
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
|
|
224
|
-
|
|
225
|
-
- `npm run
|
|
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
|
|
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>
|
|
261
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
+
- `typescript`: Adds static type-checking to JavaScript. Required compatibility with this package on version **^5.x** or
|
|
348
|
+
higher.
|
|
330
349
|
|
|
331
|
-
|
|
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
|
|
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
|
-
>
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
475
|
-
import Icon from
|
|
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
|
|
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
|
|
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(
|
|
535
|
+
expect(screen.getByTestId('delete-adjustment-icon')).toBeInTheDocument();
|
|
505
536
|
|
|
506
537
|
// ❌ Possible false-positives
|
|
507
538
|
renderAccountingAmounts();
|
|
508
|
-
expect(screen.queryByTestId(
|
|
539
|
+
expect(screen.queryByTestId('delete-adjustment-icon')).not.toBeInTheDocument();
|
|
509
540
|
|
|
510
541
|
// ✅ Passes
|
|
511
542
|
renderAccountingAmounts();
|
|
512
|
-
expect(await screen.findByTestId(
|
|
543
|
+
expect(await screen.findByTestId('delete-adjustment-icon')).toBeInTheDocument();
|
|
513
544
|
|
|
514
545
|
// ✅ Passes
|
|
515
|
-
import { flushPromises } from
|
|
546
|
+
import { flushPromises } from '@vue/test-utils';
|
|
516
547
|
|
|
517
548
|
renderAccountingAmounts();
|
|
518
549
|
await flushPromises();
|
|
519
|
-
expect(screen.queryByTestId(
|
|
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
|
|
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
|
|
package/dist/FileUpload.js.map
CHANGED
|
@@ -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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"}
|