react-dropzone 11.5.2 → 11.7.1

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/src/index.spec.js CHANGED
@@ -1159,6 +1159,20 @@ describe('useDropzone() hook', () => {
1159
1159
  })
1160
1160
 
1161
1161
  describe('onClick', () => {
1162
+ let currentShowOpenFilePicker;
1163
+
1164
+ beforeEach(() => {
1165
+ currentShowOpenFilePicker = window.showOpenFilePicker
1166
+ })
1167
+
1168
+ afterEach(() => {
1169
+ if (currentShowOpenFilePicker) {
1170
+ window.showOpenFilePicker = currentShowOpenFilePicker
1171
+ } else {
1172
+ delete window.showOpenFilePicker
1173
+ }
1174
+ })
1175
+
1162
1176
  it('should proxy the click event to the input', () => {
1163
1177
  const activeRef = createRef()
1164
1178
  const active = <span ref={activeRef}>I am active</span>
@@ -1303,6 +1317,251 @@ describe('useDropzone() hook', () => {
1303
1317
  jest.useRealTimers()
1304
1318
  isIeOrEdgeSpy.mockClear()
1305
1319
  })
1320
+
1321
+ it('should not use showOpenFilePicker() if supported and {useFsAccessApi} is not true', async () => {
1322
+ jest.useFakeTimers()
1323
+
1324
+ const activeRef = createRef()
1325
+ const active = <span ref={activeRef}>I am active</span>
1326
+ const onClickSpy = jest.spyOn(HTMLInputElement.prototype, 'click')
1327
+
1328
+ const handlers = files.map(f => createFileSystemFileHandle(f))
1329
+ const showOpenFilePickerMock = jest.fn().mockReturnValue(Promise.resolve(handlers))
1330
+
1331
+ window.showOpenFilePicker = showOpenFilePickerMock
1332
+
1333
+ const onDropSpy = jest.fn()
1334
+ const onFileDialogOpenSpy = jest.fn()
1335
+
1336
+ const ui = (
1337
+ <Dropzone
1338
+ onDrop={onDropSpy}
1339
+ onFileDialogOpen={onFileDialogOpenSpy}
1340
+ accept="application/pdf"
1341
+ multiple
1342
+ >
1343
+ {({ getRootProps, getInputProps, isFileDialogActive }) => (
1344
+ <div {...getRootProps()}>
1345
+ <input {...getInputProps()} />
1346
+ {isFileDialogActive && active}
1347
+ </div>
1348
+ )}
1349
+ </Dropzone>
1350
+ )
1351
+
1352
+ const { container, rerender } = render(ui)
1353
+
1354
+ const dropzone = container.querySelector('div')
1355
+
1356
+ fireEvent.click(dropzone)
1357
+
1358
+ await flushPromises(rerender, ui)
1359
+
1360
+ dispatchEvt(document.body, 'focus')
1361
+ drainTimers()
1362
+
1363
+ expect(onFileDialogOpenSpy).toHaveBeenCalled()
1364
+
1365
+ expect(activeRef.current).toBeNull()
1366
+ expect(dropzone).not.toContainElement(activeRef.current)
1367
+ expect(onClickSpy).toHaveBeenCalled()
1368
+ expect(showOpenFilePickerMock).not.toHaveBeenCalled()
1369
+ expect(onDropSpy).not.toHaveBeenCalled()
1370
+
1371
+ jest.useRealTimers()
1372
+ })
1373
+
1374
+ it('should use showOpenFilePicker() if supported and {useFsAccessApi} is true, and not trigger click on input', async () => {
1375
+ const activeRef = createRef()
1376
+ const active = <span ref={activeRef}>I am active</span>
1377
+ const onClickSpy = jest.spyOn(HTMLInputElement.prototype, 'click')
1378
+
1379
+ const handlers = files.map(f => createFileSystemFileHandle(f))
1380
+ const thenable = createThenable()
1381
+ const showOpenFilePickerMock = jest.fn().mockReturnValue(thenable.promise)
1382
+
1383
+ window.showOpenFilePicker = showOpenFilePickerMock
1384
+
1385
+ const onDropSpy = jest.fn()
1386
+ const onFileDialogOpenSpy = jest.fn()
1387
+
1388
+ const ui = (
1389
+ <Dropzone
1390
+ onDrop={onDropSpy}
1391
+ onFileDialogOpen={onFileDialogOpenSpy}
1392
+ accept="application/pdf"
1393
+ multiple
1394
+ useFsAccessApi
1395
+ >
1396
+ {({ getRootProps, getInputProps, isFileDialogActive }) => (
1397
+ <div {...getRootProps()}>
1398
+ <input {...getInputProps()} />
1399
+ {isFileDialogActive && active}
1400
+ </div>
1401
+ )}
1402
+ </Dropzone>
1403
+ )
1404
+
1405
+ const { container, rerender } = render(ui)
1406
+
1407
+ const dropzone = container.querySelector('div')
1408
+
1409
+ fireEvent.click(dropzone)
1410
+
1411
+ await flushPromises(rerender, ui)
1412
+
1413
+ expect(activeRef.current).not.toBeNull()
1414
+ expect(dropzone).toContainElement(activeRef.current)
1415
+ expect(onFileDialogOpenSpy).toHaveBeenCalled()
1416
+
1417
+ thenable.done(handlers)
1418
+ await flushPromises(rerender, ui)
1419
+
1420
+ expect(activeRef.current).toBeNull() // We expect the dialog to be closed at this point
1421
+ expect(dropzone).not.toContainElement(activeRef.current)
1422
+ expect(onClickSpy).not.toHaveBeenCalled()
1423
+ expect(showOpenFilePickerMock).toHaveBeenCalledWith({
1424
+ multiple: true,
1425
+ types: [{
1426
+ description: 'everything',
1427
+ accept: {'application/pdf': []}
1428
+ }]
1429
+ })
1430
+ expect(onDropSpy).toHaveBeenCalledWith(files, [], null)
1431
+ })
1432
+
1433
+ test('if showOpenFilePicker() is supported and {useFsAccessApi} is true, it should work without the <input>', async () => {
1434
+ const activeRef = createRef()
1435
+ const active = <span ref={activeRef}>I am active</span>
1436
+
1437
+ const handlers = files.map(f => createFileSystemFileHandle(f))
1438
+ const showOpenFilePickerMock = jest.fn().mockReturnValue(Promise.resolve(handlers))
1439
+
1440
+ window.showOpenFilePicker = showOpenFilePickerMock
1441
+
1442
+ const onDropSpy = jest.fn()
1443
+ const onFileDialogOpenSpy = jest.fn()
1444
+
1445
+ const ui = (
1446
+ <Dropzone
1447
+ onDrop={onDropSpy}
1448
+ onFileDialogOpen={onFileDialogOpenSpy}
1449
+ useFsAccessApi
1450
+ >
1451
+ {({ getRootProps, isFileDialogActive }) => (
1452
+ <div {...getRootProps()}>
1453
+ {isFileDialogActive && active}
1454
+ </div>
1455
+ )}
1456
+ </Dropzone>
1457
+ )
1458
+
1459
+ const { container, rerender } = render(ui)
1460
+
1461
+ const dropzone = container.querySelector('div')
1462
+
1463
+ fireEvent.click(dropzone)
1464
+ await flushPromises(rerender, ui)
1465
+ const ref = activeRef.current
1466
+ expect(ref).toBeNull() // We expect the dialog to be closed at this point
1467
+ expect(dropzone).not.toContainElement(ref)
1468
+ expect(showOpenFilePickerMock).toHaveBeenCalled()
1469
+ expect(onDropSpy).toHaveBeenCalledWith(files, [], null)
1470
+ expect(onFileDialogOpenSpy).toHaveBeenCalled()
1471
+ })
1472
+
1473
+ test('if showOpenFilePicker() is supported and {useFsAccessApi} is true, and the user cancels it should call onFileDialogCancel', async () => {
1474
+ const activeRef = createRef()
1475
+ const active = <span ref={activeRef}>I am active</span>
1476
+
1477
+ const showOpenFilePickerMock = jest.fn().mockReturnValue(Promise.reject(new DOMException("user aborted request", "AbortError")))
1478
+
1479
+ window.showOpenFilePicker = showOpenFilePickerMock
1480
+
1481
+ const onDropSpy = jest.fn()
1482
+ const onFileDialogCancelSpy = jest.fn()
1483
+
1484
+ const ui = (
1485
+ <Dropzone
1486
+ onDrop={onDropSpy}
1487
+ onFileDialogCancel={onFileDialogCancelSpy}
1488
+ useFsAccessApi
1489
+ >
1490
+ {({ getRootProps, isFileDialogActive }) => (
1491
+ <div {...getRootProps()}>
1492
+ {isFileDialogActive && active}
1493
+ </div>
1494
+ )}
1495
+ </Dropzone>
1496
+ )
1497
+
1498
+ const { container, rerender } = render(ui)
1499
+
1500
+ const dropzone = container.querySelector('div')
1501
+
1502
+ fireEvent.click(dropzone)
1503
+ await flushPromises(rerender, ui)
1504
+ const ref = activeRef.current
1505
+ expect(ref).toBeNull() // We expect the dialog to be closed at this point
1506
+ expect(dropzone).not.toContainElement(ref)
1507
+ expect(showOpenFilePickerMock).toHaveBeenCalled()
1508
+ expect(onDropSpy).not.toHaveBeenCalled()
1509
+ expect(onFileDialogCancelSpy).toHaveBeenCalled()
1510
+ })
1511
+
1512
+ test('window focus evt is not bound if showOpenFilePicker() is supported and {useFsAccessApi} is true', async () => {
1513
+ jest.useFakeTimers()
1514
+
1515
+ const activeRef = createRef()
1516
+ const active = <span ref={activeRef}>I am active</span>
1517
+ const onFileDialogCancelSpy = jest.fn()
1518
+
1519
+ const thenable = createThenable()
1520
+ const showOpenFilePickerMock = jest.fn().mockReturnValue(thenable.promise)
1521
+
1522
+ window.showOpenFilePicker = showOpenFilePickerMock
1523
+
1524
+ const ui = (
1525
+ <Dropzone
1526
+ onFileDialogCancel={onFileDialogCancelSpy}
1527
+ noClick
1528
+ useFsAccessApi
1529
+ >
1530
+ {({ getRootProps, isFileDialogActive, open }) => (
1531
+ <div {...getRootProps()}>
1532
+ {isFileDialogActive && active}
1533
+ <button type="button" onClick={open}>
1534
+ Open
1535
+ </button>
1536
+ </div>
1537
+ )}
1538
+ </Dropzone>
1539
+ )
1540
+
1541
+ const { container, rerender } = render(ui)
1542
+
1543
+ const dropzone = container.querySelector('div')
1544
+ const btn = container.querySelector('button')
1545
+
1546
+ btn.click()
1547
+ drainTimers()
1548
+ await flushPromises(rerender, ui)
1549
+
1550
+ const ref = activeRef.current
1551
+ expect(ref).not.toBeNull()
1552
+ expect(dropzone).toContainElement(ref)
1553
+
1554
+ thenable.cancel(new DOMException('user aborted request', 'AbortError'))
1555
+
1556
+ dispatchEvt(document.body, 'focus')
1557
+ drainTimers()
1558
+ await flushPromises(rerender, ui)
1559
+
1560
+ expect(onFileDialogCancelSpy).toHaveBeenCalledTimes(1)
1561
+ expect(dropzone).not.toContainElement(ref)
1562
+
1563
+ jest.useRealTimers()
1564
+ })
1306
1565
  })
1307
1566
 
1308
1567
  describe('onKeyDown', () => {
@@ -2803,6 +3062,34 @@ describe('useDropzone() hook', () => {
2803
3062
  ], expect.anything())
2804
3063
  })
2805
3064
  })
3065
+
3066
+ describe('accessibility', () => {
3067
+ it('sets the role attribute to button by default on the root', () => {
3068
+ const { container } = render(
3069
+ <Dropzone>
3070
+ {({ getRootProps }) => (
3071
+ <div id="root" {...getRootProps()} />
3072
+ )}
3073
+ </Dropzone>
3074
+ )
3075
+ const root = container.querySelector('#root')
3076
+
3077
+ expect(root).toHaveAttribute('role', 'button')
3078
+ })
3079
+
3080
+ test('users can override the default role attribute on the root', () => {
3081
+ const { container } = render(
3082
+ <Dropzone>
3083
+ {({ getRootProps }) => (
3084
+ <div id="root" {...getRootProps({role: 'generic'})} />
3085
+ )}
3086
+ </Dropzone>
3087
+ )
3088
+ const root = container.querySelector('#root')
3089
+
3090
+ expect(root).toHaveAttribute('role', 'generic')
3091
+ })
3092
+ })
2806
3093
  })
2807
3094
 
2808
3095
  async function flushPromises(rerender, ui) {
@@ -2854,6 +3141,10 @@ function dispatchEvt(node, type, data) {
2854
3141
  fireEvent(node, event)
2855
3142
  }
2856
3143
 
3144
+ function createFileSystemFileHandle(file) {
3145
+ return {getFile: () => Promise.resolve(file)}
3146
+ }
3147
+
2857
3148
  function createFile(name, size, type) {
2858
3149
  const file = new File([], name, { type })
2859
3150
  Object.defineProperty(file, 'size', {
@@ -2863,3 +3154,18 @@ function createFile(name, size, type) {
2863
3154
  })
2864
3155
  return file
2865
3156
  }
3157
+
3158
+ function createThenable() {
3159
+ let done, cancel;
3160
+
3161
+ const promise = new Promise((resolve, reject) => {
3162
+ done = resolve
3163
+ cancel = reject
3164
+ })
3165
+
3166
+ return {
3167
+ promise,
3168
+ done,
3169
+ cancel
3170
+ }
3171
+ }
@@ -26,14 +26,14 @@ export const getInvalidTypeRejectionErr = accept => {
26
26
  export const getTooLargeRejectionErr = maxSize => {
27
27
  return {
28
28
  code: FILE_TOO_LARGE,
29
- message: `File is larger than ${maxSize} bytes`
29
+ message: `File is larger than ${maxSize} ${maxSize === 1 ? 'byte' : 'bytes'}`
30
30
  }
31
31
  }
32
32
 
33
33
  export const getTooSmallRejectionErr = minSize => {
34
34
  return {
35
35
  code: FILE_TOO_SMALL,
36
- message: `File is smaller than ${minSize} bytes`
36
+ message: `File is smaller than ${minSize} ${minSize === 1 ? 'byte' : 'bytes'}`
37
37
  }
38
38
  }
39
39
 
@@ -66,8 +66,8 @@ function isDefined(value) {
66
66
  return value !== undefined && value !== null
67
67
  }
68
68
 
69
- export function allFilesAccepted({ files, accept, minSize, maxSize, multiple, maxFiles }) {
70
- if ((!multiple && files.length > 1) || (multiple && maxFiles >= 1 && files.length > maxFiles) ) {
69
+ export function allFilesAccepted({files, accept, minSize, maxSize, multiple, maxFiles}) {
70
+ if ((!multiple && files.length > 1) || (multiple && maxFiles >= 1 && files.length > maxFiles)) {
71
71
  return false;
72
72
  }
73
73
 
@@ -142,3 +142,37 @@ export function composeEventHandlers(...fns) {
142
142
  return isPropagationStopped(event)
143
143
  })
144
144
  }
145
+
146
+ /**
147
+ * canUseFileSystemAccessAPI checks if the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
148
+ * is supported by the browser.
149
+ * @returns {boolean}
150
+ */
151
+ export function canUseFileSystemAccessAPI() {
152
+ return 'showOpenFilePicker' in window;
153
+ }
154
+
155
+ /**
156
+ * filePickerOptionsTypes returns the {types} option for https://developer.mozilla.org/en-US/docs/Web/API/window/showOpenFilePicker
157
+ * based on the accept attr (see https://github.com/react-dropzone/attr-accept)
158
+ * E.g: converts ['image/*', 'text/*'] to {'image/*': [], 'text/*': []}
159
+ * @param {string|string[]} accept
160
+ */
161
+ export function filePickerOptionsTypes(accept) {
162
+ accept = typeof accept === 'string' ? accept.split(',') : accept
163
+ return [{
164
+ description: 'everything',
165
+ // TODO: Need to handle filtering more elegantly than this!
166
+ accept: Array.isArray(accept)
167
+ // Accept just MIME types as per spec
168
+ // NOTE: accept can be https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers
169
+ ? accept.filter(item =>
170
+ item === 'audio/*' ||
171
+ item === 'video/*' ||
172
+ item === 'image/*' ||
173
+ item === 'text/*' ||
174
+ /\w+\/[-+.\w]+/g.test(item)
175
+ ).reduce((a, b) => ({...a, [b]: []}), {})
176
+ : {},
177
+ }];
178
+ }
@@ -2,12 +2,10 @@ beforeEach(() => {
2
2
  jest.resetModules()
3
3
  })
4
4
 
5
-
6
5
  describe('fileMatchSize()', () => {
7
6
  let utils
8
- beforeEach(async done => {
7
+ beforeEach(async () => {
9
8
  utils = await import('./index')
10
- done()
11
9
  })
12
10
 
13
11
  it('should return true if the file object doesn\'t have a {size} property', () => {
@@ -42,9 +40,8 @@ describe('fileMatchSize()', () => {
42
40
 
43
41
  describe('isIeOrEdge', () => {
44
42
  let utils
45
- beforeEach(async done => {
43
+ beforeEach(async () => {
46
44
  utils = await import('./index')
47
- done()
48
45
  })
49
46
 
50
47
  it('should return true for IE10', () => {
@@ -89,9 +86,8 @@ describe('isPropagationStopped()', () => {
89
86
  const trueFn = jest.fn(() => true)
90
87
 
91
88
  let utils
92
- beforeEach(async done => {
89
+ beforeEach(async () => {
93
90
  utils = await import('./index')
94
- done()
95
91
  })
96
92
 
97
93
  it('should return result of isPropagationStopped() if isPropagationStopped exists', () => {
@@ -109,9 +105,8 @@ describe('isPropagationStopped()', () => {
109
105
 
110
106
  describe('isEvtWithFiles()', () => {
111
107
  let utils
112
- beforeEach(async done => {
108
+ beforeEach(async () => {
113
109
  utils = await import('./index')
114
- done()
115
110
  })
116
111
 
117
112
  it('should return true if some dragged types are files', () => {
@@ -145,9 +140,8 @@ describe('isEvtWithFiles()', () => {
145
140
 
146
141
  describe('composeEventHandlers', () => {
147
142
  let utils
148
- beforeEach(async done => {
143
+ beforeEach(async () => {
149
144
  utils = await import('./index')
150
- done()
151
145
  })
152
146
 
153
147
  it('returns a fn', () => {
@@ -193,9 +187,8 @@ describe('composeEventHandlers', () => {
193
187
 
194
188
  describe('fileAccepted', () => {
195
189
  let utils
196
- beforeEach(async done => {
190
+ beforeEach(async () => {
197
191
  utils = await import('./index')
198
- done()
199
192
  })
200
193
 
201
194
  it('accepts bogus firefox file', () => {
@@ -232,9 +225,8 @@ it('rejects file when single accept criteria as array', () => {
232
225
 
233
226
  describe('allFilesAccepted()', () => {
234
227
  let utils
235
- beforeEach(async done => {
228
+ beforeEach(async () => {
236
229
  utils = await import('./index')
237
- done()
238
230
  })
239
231
  it('rejects file when multiple accept criteria', () => {
240
232
  const files = [createFile('hamster.pdf', 100, 'application/pdf'),createFile('fish.pdf', 100, 'application/pdf')];
@@ -242,29 +234,18 @@ describe('allFilesAccepted()', () => {
242
234
  expect(utils.allFilesAccepted({ files, multiple: true})).toEqual(true)
243
235
  expect(utils.allFilesAccepted({ files, multiple: true, maxFiles: 10 })).toEqual(true)
244
236
  expect(utils.allFilesAccepted({ files, multiple: false, maxFiles: 10 })).toEqual(false)
245
- expect(utils.allFilesAccepted({ files, multiple: true, accept:'image/jpeg' })).toEqual(false)
246
- expect(utils.allFilesAccepted({ files: images, multiple: true,accept:'image/*' })).toEqual(true)
237
+ expect(utils.allFilesAccepted({ files, multiple: true, accept:'image/jpeg' })).toEqual(false)
238
+ expect(utils.allFilesAccepted({ files: images, multiple: true,accept:'image/*' })).toEqual(true)
247
239
  expect(utils.allFilesAccepted({ files, multiple: true, minSize: 110 })).toEqual(false)
248
240
  expect(utils.allFilesAccepted({ files, multiple: true, maxSize: 99 })).toEqual(false)
249
241
  expect(utils.allFilesAccepted({ files, multiple: true, maxFiles: 1 })).toEqual(false)
250
242
  })
251
243
  })
252
244
 
253
- function createFile(name, size, type) {
254
- const file = new File([], name, { type })
255
- Object.defineProperty(file, 'size', {
256
- get() {
257
- return size
258
- }
259
- })
260
- return file
261
- }
262
-
263
245
  describe('ErrorCode', () => {
264
246
  let utils
265
- beforeEach(async done => {
247
+ beforeEach(async () => {
266
248
  utils = await import('./index')
267
- done()
268
249
  })
269
250
 
270
251
  it('should exist and have known error code properties', () => {
@@ -275,3 +256,85 @@ describe('ErrorCode', () => {
275
256
  })
276
257
  })
277
258
 
259
+ describe('canUseFileSystemAccessAPI()', () => {
260
+ let utils
261
+ beforeEach(async () => {
262
+ utils = await import('./index')
263
+ })
264
+
265
+ it('should return false if not', () => {
266
+ expect(utils.canUseFileSystemAccessAPI()).toBe(false)
267
+ })
268
+
269
+ it('should return true if yes', () => {
270
+ // TODO: If we use these in other tests, restore once test is done
271
+ window.showOpenFilePicker = jest.fn()
272
+ expect(utils.canUseFileSystemAccessAPI()).toBe(true)
273
+ })
274
+ })
275
+
276
+ describe('filePickerOptionsTypes()', () => {
277
+ let utils
278
+ beforeEach(async () => {
279
+ utils = await import('./index')
280
+ })
281
+
282
+ it('should return proper types when the arg is a MIME type', () => {
283
+ expect(utils.filePickerOptionsTypes('application/zip')).toEqual([{
284
+ description: 'everything',
285
+ accept: {'application/zip': []}
286
+ }])
287
+ })
288
+
289
+ it('should return proper types when the arg is an array of MIME types', () => {
290
+ expect(utils.filePickerOptionsTypes(['application/zip', 'application/json'])).toEqual([{
291
+ description: 'everything',
292
+ accept: {
293
+ 'application/zip': [],
294
+ 'application/json': []
295
+ }
296
+ }])
297
+ })
298
+
299
+ it("should exclude anything that's not a MIME type", () => {
300
+ expect(utils.filePickerOptionsTypes(['audio/*', 'video/*', 'image/*', '.txt', 'text/*'])).toEqual([{
301
+ description: 'everything',
302
+ accept: {
303
+ 'audio/*': [],
304
+ 'video/*': [],
305
+ 'image/*': [],
306
+ 'text/*': [],
307
+ }
308
+ }])
309
+ })
310
+
311
+ it("should work with comma separated string of MIME types", () => {
312
+ expect(utils.filePickerOptionsTypes('audio/*,video/*,image/*,.txt,text/*,application/zip')).toEqual([{
313
+ description: 'everything',
314
+ accept: {
315
+ 'audio/*': [],
316
+ 'video/*': [],
317
+ 'image/*': [],
318
+ 'text/*': [],
319
+ 'application/zip': []
320
+ }
321
+ }])
322
+ })
323
+
324
+ it('should return empty otherwise', () => {
325
+ expect(utils.filePickerOptionsTypes('')).toEqual([{
326
+ description: 'everything',
327
+ accept: {}
328
+ }])
329
+ })
330
+ })
331
+
332
+ function createFile(name, size, type) {
333
+ const file = new File([], name, { type })
334
+ Object.defineProperty(file, 'size', {
335
+ get() {
336
+ return size
337
+ }
338
+ })
339
+ return file
340
+ }
@@ -73,6 +73,10 @@ module.exports = {
73
73
  name: 'Class Components',
74
74
  content: 'examples/class-component/README.md'
75
75
  },
76
+ {
77
+ name: 'No JSX',
78
+ content: 'examples/no-jsx/README.md'
79
+ },
76
80
  {
77
81
  name: 'Extending Dropzone',
78
82
  content: 'examples/plugins/README.md'
@@ -0,0 +1,28 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "env": {
5
+ "es6": true,
6
+ "browser": true
7
+ },
8
+ "plugins": [
9
+ "import",
10
+ "prettier",
11
+ "react",
12
+ "react-hooks",
13
+ "@typescript-eslint"
14
+ ],
15
+ "extends": [
16
+ "eslint:recommended",
17
+ "plugin:@typescript-eslint/recommended",
18
+ "prettier"
19
+ ],
20
+ "settings": {
21
+ "react": {
22
+ "version": "detect"
23
+ }
24
+ },
25
+ "rules": {
26
+ "@typescript-eslint/no-explicit-any": "off"
27
+ }
28
+ }