@tamagui/accordion 1.40.0 → 1.41.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/dist/cjs/Accordion.js +387 -3
- package/dist/cjs/Accordion.js.map +2 -2
- package/dist/esm/Accordion.js +381 -2
- package/dist/esm/Accordion.js.map +2 -2
- package/dist/jsx/Accordion.js +344 -2
- package/dist/jsx/Accordion.js.map +2 -2
- package/dist/jsx/Accordion.mjs +344 -2
- package/dist/jsx/Accordion.mjs.map +2 -2
- package/package.json +10 -8
- package/src/Accordion.tsx +617 -491
- package/types/Accordion.d.ts +912 -1
- package/types/Accordion.d.ts.map +1 -1
package/src/Accordion.tsx
CHANGED
|
@@ -1,491 +1,617 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1
|
+
import { Collapsible, createCollapsibleScope } from '@tamagui/collapsible'
|
|
2
|
+
import { createCollection } from '@tamagui/collection'
|
|
3
|
+
import type { Scope } from '@tamagui/create-context'
|
|
4
|
+
import { createContextScope } from '@tamagui/create-context'
|
|
5
|
+
import { YStack } from '@tamagui/stacks'
|
|
6
|
+
import { H1, H3 } from '@tamagui/text'
|
|
7
|
+
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
8
|
+
import { useDirection } from '@tamagui/use-direction'
|
|
9
|
+
import {
|
|
10
|
+
GetProps,
|
|
11
|
+
GetRef,
|
|
12
|
+
Stack,
|
|
13
|
+
TamaguiElement,
|
|
14
|
+
composeEventHandlers,
|
|
15
|
+
isWeb,
|
|
16
|
+
styled,
|
|
17
|
+
useComposedRefs,
|
|
18
|
+
withStaticProperties,
|
|
19
|
+
} from '@tamagui/web'
|
|
20
|
+
import * as React from 'react'
|
|
21
|
+
|
|
22
|
+
type Direction = 'ltr' | 'rtl'
|
|
23
|
+
|
|
24
|
+
/* -------------------------------------------------------------------------------------------------
|
|
25
|
+
* Accordion
|
|
26
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
27
|
+
|
|
28
|
+
const ACCORDION_NAME = 'Accordion'
|
|
29
|
+
const ACCORDION_KEYS = ['Home', 'End', 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight']
|
|
30
|
+
|
|
31
|
+
const [Collection, useCollection, createCollectionScope] =
|
|
32
|
+
createCollection<AccordionTrigger>(ACCORDION_NAME)
|
|
33
|
+
|
|
34
|
+
type ScopedProps<P> = P & { __scopeAccordion?: Scope }
|
|
35
|
+
const [createAccordionContext, createAccordionScope] = createContextScope(
|
|
36
|
+
ACCORDION_NAME,
|
|
37
|
+
[createCollectionScope, createCollapsibleScope]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const useCollapsibleScope = createCollapsibleScope()
|
|
41
|
+
type AccordionElement = AccordionImplMultipleElement | AccordionImplSingleElement
|
|
42
|
+
interface AccordionSingleProps extends AccordionImplSingleProps {
|
|
43
|
+
type: 'single'
|
|
44
|
+
}
|
|
45
|
+
interface AccordionMultipleProps extends AccordionImplMultipleProps {
|
|
46
|
+
type: 'multiple'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const AccordionComponent = React.forwardRef<
|
|
50
|
+
AccordionElement,
|
|
51
|
+
AccordionSingleProps | AccordionMultipleProps
|
|
52
|
+
>((props: ScopedProps<AccordionSingleProps | AccordionMultipleProps>, forwardedRef) => {
|
|
53
|
+
const { type, ...accordionProps } = props
|
|
54
|
+
const singleProps = accordionProps as AccordionImplSingleProps
|
|
55
|
+
const multipleProps = accordionProps as AccordionImplMultipleProps
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Collection.Provider scope={props.__scopeAccordion}>
|
|
59
|
+
{type === 'multiple' ? (
|
|
60
|
+
<AccordionImplMultiple {...multipleProps} ref={forwardedRef} />
|
|
61
|
+
) : (
|
|
62
|
+
<AccordionImplSingle {...singleProps} ref={forwardedRef} />
|
|
63
|
+
)}
|
|
64
|
+
</Collection.Provider>
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
AccordionComponent.displayName = ACCORDION_NAME
|
|
69
|
+
|
|
70
|
+
AccordionComponent.propTypes = {
|
|
71
|
+
type(props) {
|
|
72
|
+
const value = props.value || props.defaultValue
|
|
73
|
+
if (props.type && !['single', 'multiple'].includes(props.type)) {
|
|
74
|
+
return new Error(
|
|
75
|
+
'Invalid prop `type` supplied to `Accordion`. Expected one of `single | multiple`.'
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
if (props.type === 'multiple' && typeof value === 'string') {
|
|
79
|
+
return new Error(
|
|
80
|
+
'Invalid prop `type` supplied to `Accordion`. Expected `single` when `defaultValue` or `value` is type `string`.'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
if (props.type === 'single' && Array.isArray(value)) {
|
|
84
|
+
return new Error(
|
|
85
|
+
'Invalid prop `type` supplied to `Accordion`. Expected `multiple` when `defaultValue` or `value` is type `string[]`.'
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return null
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
93
|
+
|
|
94
|
+
type AccordionValueContextValue = {
|
|
95
|
+
value: string[]
|
|
96
|
+
onItemOpen(value: string): void
|
|
97
|
+
onItemClose(value: string): void
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [AccordionValueProvider, useAccordionValueContext] =
|
|
101
|
+
createAccordionContext<AccordionValueContextValue>(ACCORDION_NAME)
|
|
102
|
+
|
|
103
|
+
const [AccordionCollapsibleProvider, useAccordionCollapsibleContext] =
|
|
104
|
+
createAccordionContext(ACCORDION_NAME, { collapsible: false })
|
|
105
|
+
|
|
106
|
+
type AccordionImplSingleElement = AccordionImplElement
|
|
107
|
+
|
|
108
|
+
interface AccordionImplSingleProps extends AccordionImplProps {
|
|
109
|
+
/**
|
|
110
|
+
* The controlled stateful value of the accordion item whose content is expanded.
|
|
111
|
+
*/
|
|
112
|
+
value?: string
|
|
113
|
+
/**
|
|
114
|
+
* The value of the item whose content is expanded when the accordion is initially rendered. Use
|
|
115
|
+
* `defaultValue` if you do not need to control the state of an accordion.
|
|
116
|
+
*/
|
|
117
|
+
defaultValue?: string
|
|
118
|
+
/**
|
|
119
|
+
* The callback that fires when the state of the accordion changes.
|
|
120
|
+
*/
|
|
121
|
+
onValueChange?(value: string): void
|
|
122
|
+
/**
|
|
123
|
+
* Whether an accordion item can be collapsed after it has been opened.
|
|
124
|
+
* @default false
|
|
125
|
+
*/
|
|
126
|
+
collapsible?: boolean
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const AccordionImplSingle = React.forwardRef<
|
|
130
|
+
AccordionImplSingleElement,
|
|
131
|
+
AccordionImplSingleProps
|
|
132
|
+
>((props: ScopedProps<AccordionImplSingleProps>, forwardedRef) => {
|
|
133
|
+
const {
|
|
134
|
+
value: valueProp,
|
|
135
|
+
defaultValue,
|
|
136
|
+
control,
|
|
137
|
+
onValueChange = () => {},
|
|
138
|
+
collapsible = false,
|
|
139
|
+
...accordionSingleProps
|
|
140
|
+
} = props
|
|
141
|
+
|
|
142
|
+
const [value, setValue] = useControllableState({
|
|
143
|
+
prop: valueProp,
|
|
144
|
+
defaultProp: defaultValue || '',
|
|
145
|
+
onChange: onValueChange,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<AccordionValueProvider
|
|
150
|
+
scope={props.__scopeAccordion}
|
|
151
|
+
value={value ? [value] : []}
|
|
152
|
+
onItemOpen={setValue}
|
|
153
|
+
onItemClose={React.useCallback(
|
|
154
|
+
() => collapsible && setValue(''),
|
|
155
|
+
[setValue, collapsible]
|
|
156
|
+
)}
|
|
157
|
+
>
|
|
158
|
+
<AccordionCollapsibleProvider
|
|
159
|
+
scope={props.__scopeAccordion}
|
|
160
|
+
collapsible={collapsible}
|
|
161
|
+
>
|
|
162
|
+
<AccordionImpl {...accordionSingleProps} ref={forwardedRef} />
|
|
163
|
+
</AccordionCollapsibleProvider>
|
|
164
|
+
</AccordionValueProvider>
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
169
|
+
type AccordionImplMultipleElement = AccordionImplElement
|
|
170
|
+
interface AccordionImplMultipleProps extends AccordionImplProps {
|
|
171
|
+
/**
|
|
172
|
+
* The controlled stateful value of the accordion items whose contents are expanded.
|
|
173
|
+
*/
|
|
174
|
+
value?: string[]
|
|
175
|
+
/**
|
|
176
|
+
* The value of the items whose contents are expanded when the accordion is initially rendered. Use
|
|
177
|
+
* `defaultValue` if you do not need to control the state of an accordion.
|
|
178
|
+
*/
|
|
179
|
+
defaultValue?: string[]
|
|
180
|
+
/**
|
|
181
|
+
* The callback that fires when the state of the accordion changes.
|
|
182
|
+
*/
|
|
183
|
+
onValueChange?(value: string[]): void
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const AccordionImplMultiple = React.forwardRef<
|
|
187
|
+
AccordionImplMultipleElement,
|
|
188
|
+
AccordionImplMultipleProps
|
|
189
|
+
>((props: ScopedProps<AccordionImplMultipleProps>, forwardedRef) => {
|
|
190
|
+
const {
|
|
191
|
+
value: valueProp,
|
|
192
|
+
defaultValue,
|
|
193
|
+
onValueChange = () => {},
|
|
194
|
+
...accordionMultipleProps
|
|
195
|
+
} = props
|
|
196
|
+
|
|
197
|
+
const [value, setValue] = useControllableState({
|
|
198
|
+
prop: valueProp,
|
|
199
|
+
defaultProp: defaultValue || [],
|
|
200
|
+
onChange: onValueChange,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const handleItemOpen = React.useCallback(
|
|
204
|
+
(itemValue: string) => setValue((prevValue = []) => [...prevValue, itemValue]),
|
|
205
|
+
[setValue]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const handleItemClose = React.useCallback(
|
|
209
|
+
(itemValue: string) =>
|
|
210
|
+
setValue((prevValue = []) => prevValue.filter((value) => value !== itemValue)),
|
|
211
|
+
[setValue]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<AccordionValueProvider
|
|
216
|
+
scope={props.__scopeAccordion}
|
|
217
|
+
value={value || []}
|
|
218
|
+
onItemOpen={handleItemOpen}
|
|
219
|
+
onItemClose={handleItemClose}
|
|
220
|
+
>
|
|
221
|
+
<AccordionCollapsibleProvider scope={props.__scopeAccordion} collapsible={true}>
|
|
222
|
+
<AccordionImpl {...accordionMultipleProps} ref={forwardedRef} />
|
|
223
|
+
</AccordionCollapsibleProvider>
|
|
224
|
+
</AccordionValueProvider>
|
|
225
|
+
)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
229
|
+
|
|
230
|
+
type AccordionImplContextValue = {
|
|
231
|
+
disabled?: boolean
|
|
232
|
+
direction: AccordionImplProps['dir']
|
|
233
|
+
orientation: AccordionImplProps['orientation']
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const [AccordionImplProvider, useAccordionContext] =
|
|
237
|
+
createAccordionContext<AccordionImplContextValue>(ACCORDION_NAME)
|
|
238
|
+
|
|
239
|
+
type AccordionImplElement = TamaguiElement
|
|
240
|
+
type PrimitiveDivProps = React.ComponentPropsWithoutRef<typeof Stack>
|
|
241
|
+
interface AccordionImplProps extends PrimitiveDivProps {
|
|
242
|
+
/**
|
|
243
|
+
* Whether or not an accordion is disabled from user interaction.
|
|
244
|
+
*
|
|
245
|
+
* @defaultValue false
|
|
246
|
+
*/
|
|
247
|
+
disabled?: boolean
|
|
248
|
+
/**
|
|
249
|
+
* The layout in which the Accordion operates.
|
|
250
|
+
* @default vertical
|
|
251
|
+
*/
|
|
252
|
+
orientation?: React.AriaAttributes['aria-orientation']
|
|
253
|
+
/**
|
|
254
|
+
* The language read direction.
|
|
255
|
+
*/
|
|
256
|
+
dir?: Direction
|
|
257
|
+
/**
|
|
258
|
+
* The callback that fires when the state of the accordion changes. for use with `useAccordion`
|
|
259
|
+
* @param selected - The values of the accordion items whose contents are expanded.
|
|
260
|
+
*/
|
|
261
|
+
control?(selected: string[]): void
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const AccordionImpl = React.forwardRef<AccordionImplElement, AccordionImplProps>(
|
|
265
|
+
(props: ScopedProps<AccordionImplProps>, forwardedRef) => {
|
|
266
|
+
const {
|
|
267
|
+
__scopeAccordion,
|
|
268
|
+
disabled,
|
|
269
|
+
dir,
|
|
270
|
+
orientation = 'vertical',
|
|
271
|
+
...accordionProps
|
|
272
|
+
} = props
|
|
273
|
+
|
|
274
|
+
const accordionRef = React.useRef<AccordionImplElement>(null)
|
|
275
|
+
const composedRef = useComposedRefs(accordionRef, forwardedRef)
|
|
276
|
+
const getItems = useCollection(__scopeAccordion)
|
|
277
|
+
const direction = useDirection(dir)
|
|
278
|
+
const isDirectionLTR = direction === 'ltr'
|
|
279
|
+
const handleKeyDown = composeEventHandlers(
|
|
280
|
+
(props as any).onKeyDown,
|
|
281
|
+
(event: KeyboardEvent) => {
|
|
282
|
+
if (!ACCORDION_KEYS.includes(event.key)) return
|
|
283
|
+
const target = event.target as HTMLElement
|
|
284
|
+
const triggerCollection = getItems().filter((item) => {
|
|
285
|
+
const el = item.ref.current as { disabled?: boolean } | null
|
|
286
|
+
return !el?.disabled
|
|
287
|
+
})
|
|
288
|
+
const triggerIndex = triggerCollection.findIndex(
|
|
289
|
+
(item) => item.ref.current === target
|
|
290
|
+
)
|
|
291
|
+
const triggerCount = triggerCollection.length
|
|
292
|
+
|
|
293
|
+
if (triggerIndex === -1) return
|
|
294
|
+
|
|
295
|
+
// Prevents page scroll while user is navigating
|
|
296
|
+
event.preventDefault()
|
|
297
|
+
|
|
298
|
+
let nextIndex = triggerIndex
|
|
299
|
+
const homeIndex = 0
|
|
300
|
+
const endIndex = triggerCount - 1
|
|
301
|
+
|
|
302
|
+
const moveNext = () => {
|
|
303
|
+
nextIndex = triggerIndex + 1
|
|
304
|
+
if (nextIndex > endIndex) {
|
|
305
|
+
nextIndex = homeIndex
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const movePrev = () => {
|
|
310
|
+
nextIndex = triggerIndex - 1
|
|
311
|
+
if (nextIndex < homeIndex) {
|
|
312
|
+
nextIndex = endIndex
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
switch (event.key) {
|
|
317
|
+
case 'Home':
|
|
318
|
+
nextIndex = homeIndex
|
|
319
|
+
break
|
|
320
|
+
case 'End':
|
|
321
|
+
nextIndex = endIndex
|
|
322
|
+
break
|
|
323
|
+
case 'ArrowRight':
|
|
324
|
+
if (orientation === 'horizontal') {
|
|
325
|
+
if (isDirectionLTR) {
|
|
326
|
+
moveNext()
|
|
327
|
+
} else {
|
|
328
|
+
movePrev()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
break
|
|
332
|
+
case 'ArrowDown':
|
|
333
|
+
if (orientation === 'vertical') {
|
|
334
|
+
moveNext()
|
|
335
|
+
}
|
|
336
|
+
break
|
|
337
|
+
case 'ArrowLeft':
|
|
338
|
+
if (orientation === 'horizontal') {
|
|
339
|
+
if (isDirectionLTR) {
|
|
340
|
+
movePrev()
|
|
341
|
+
} else {
|
|
342
|
+
moveNext()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
break
|
|
346
|
+
case 'ArrowUp':
|
|
347
|
+
if (orientation === 'vertical') {
|
|
348
|
+
movePrev()
|
|
349
|
+
}
|
|
350
|
+
break
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const clampedIndex = nextIndex % triggerCount
|
|
354
|
+
triggerCollection[clampedIndex].ref.current?.focus()
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<AccordionImplProvider
|
|
360
|
+
scope={__scopeAccordion}
|
|
361
|
+
disabled={disabled}
|
|
362
|
+
direction={dir}
|
|
363
|
+
orientation={orientation}
|
|
364
|
+
>
|
|
365
|
+
<Collection.Slot scope={__scopeAccordion}>
|
|
366
|
+
<YStack
|
|
367
|
+
data-orientation={orientation}
|
|
368
|
+
ref={composedRef}
|
|
369
|
+
{...accordionProps}
|
|
370
|
+
{...(isWeb && {
|
|
371
|
+
onKeyDown: handleKeyDown,
|
|
372
|
+
})}
|
|
373
|
+
/>
|
|
374
|
+
</Collection.Slot>
|
|
375
|
+
</AccordionImplProvider>
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
/* -------------------------------------------------------------------------------------------------
|
|
381
|
+
* AccordionItem
|
|
382
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
383
|
+
|
|
384
|
+
const ITEM_NAME = 'AccordionItem'
|
|
385
|
+
|
|
386
|
+
type AccordionItemContextValue = { open?: boolean; disabled?: boolean; triggerId: string }
|
|
387
|
+
const [AccordionItemProvider, useAccordionItemContext] =
|
|
388
|
+
createAccordionContext<AccordionItemContextValue>(ITEM_NAME)
|
|
389
|
+
type AccordionItemElement = React.ElementRef<typeof Collapsible>
|
|
390
|
+
type CollapsibleProps = React.ComponentPropsWithoutRef<typeof Collapsible>
|
|
391
|
+
interface AccordionItemProps
|
|
392
|
+
extends Omit<CollapsibleProps, 'open' | 'defaultOpen' | 'onOpenChange'> {
|
|
393
|
+
/**
|
|
394
|
+
* Whether or not an accordion item is disabled from user interaction.
|
|
395
|
+
*
|
|
396
|
+
* @defaultValue false
|
|
397
|
+
*/
|
|
398
|
+
disabled?: boolean
|
|
399
|
+
/**
|
|
400
|
+
* A string value for the accordion item. All items within an accordion should use a unique value.
|
|
401
|
+
*/
|
|
402
|
+
value: string
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* `AccordionItem` contains all of the parts of a collapsible section inside of an `Accordion`.
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
const AccordionItem = React.forwardRef<AccordionItemElement, AccordionItemProps>(
|
|
410
|
+
(props: ScopedProps<AccordionItemProps>, forwardedRef) => {
|
|
411
|
+
const { __scopeAccordion, value, ...accordionItemProps } = props
|
|
412
|
+
const accordionContext = useAccordionContext(ITEM_NAME, __scopeAccordion)
|
|
413
|
+
const valueContext = useAccordionValueContext(ITEM_NAME, __scopeAccordion)
|
|
414
|
+
const collapsibleScope = useCollapsibleScope(__scopeAccordion)
|
|
415
|
+
const triggerId = React.useId()
|
|
416
|
+
const open = (value && valueContext.value.includes(value)) || false
|
|
417
|
+
const disabled = accordionContext.disabled || props.disabled
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<AccordionItemProvider
|
|
421
|
+
scope={__scopeAccordion}
|
|
422
|
+
open={open}
|
|
423
|
+
disabled={disabled}
|
|
424
|
+
triggerId={triggerId}
|
|
425
|
+
>
|
|
426
|
+
<Collapsible
|
|
427
|
+
data-orientation={accordionContext.orientation}
|
|
428
|
+
data-state={open ? 'open' : 'closed'}
|
|
429
|
+
{...collapsibleScope}
|
|
430
|
+
{...accordionItemProps}
|
|
431
|
+
ref={forwardedRef}
|
|
432
|
+
disabled={disabled}
|
|
433
|
+
open={open}
|
|
434
|
+
onOpenChange={(open) => {
|
|
435
|
+
if (open) {
|
|
436
|
+
valueContext.onItemOpen(value)
|
|
437
|
+
} else {
|
|
438
|
+
valueContext.onItemClose(value)
|
|
439
|
+
}
|
|
440
|
+
}}
|
|
441
|
+
/>
|
|
442
|
+
</AccordionItemProvider>
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
AccordionItem.displayName = ITEM_NAME
|
|
448
|
+
|
|
449
|
+
/* -------------------------------------------------------------------------------------------------
|
|
450
|
+
* AccordionHeader
|
|
451
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
452
|
+
|
|
453
|
+
const HEADER_NAME = 'AccordionHeader'
|
|
454
|
+
|
|
455
|
+
type AccordionHeaderElement = React.ElementRef<typeof H3>
|
|
456
|
+
type PrimitiveHeading3Props = React.ComponentPropsWithoutRef<typeof H3>
|
|
457
|
+
type AccordionHeaderProps = PrimitiveHeading3Props
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* `AccordionHeader` contains the content for the parts of an `AccordionItem` that will be visible
|
|
461
|
+
* whether or not its content is collapsed.
|
|
462
|
+
*/
|
|
463
|
+
const AccordionHeader = React.forwardRef<AccordionHeaderElement, AccordionHeaderProps>(
|
|
464
|
+
(props: ScopedProps<AccordionHeaderProps>, forwardedRef) => {
|
|
465
|
+
const { __scopeAccordion, ...headerProps } = props
|
|
466
|
+
const accordionContext = useAccordionContext(ACCORDION_NAME, __scopeAccordion)
|
|
467
|
+
const itemContext = useAccordionItemContext(HEADER_NAME, __scopeAccordion)
|
|
468
|
+
return (
|
|
469
|
+
<H1
|
|
470
|
+
data-orientation={accordionContext.orientation}
|
|
471
|
+
data-state={getState(itemContext.open)}
|
|
472
|
+
data-disabled={itemContext.disabled ? '' : undefined}
|
|
473
|
+
{...headerProps}
|
|
474
|
+
ref={forwardedRef}
|
|
475
|
+
/>
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
AccordionHeader.displayName = HEADER_NAME
|
|
481
|
+
|
|
482
|
+
/* -------------------------------------------------------------------------------------------------
|
|
483
|
+
* AccordionTrigger
|
|
484
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
485
|
+
|
|
486
|
+
const AccordionTriggerFrame = styled(Collapsible.Trigger, {
|
|
487
|
+
variants: {
|
|
488
|
+
unstyled: {
|
|
489
|
+
false: {
|
|
490
|
+
cursor: 'pointer',
|
|
491
|
+
backgroundColor: '$background',
|
|
492
|
+
borderColor: '$borderColor',
|
|
493
|
+
borderWidth: 1,
|
|
494
|
+
padding: '$true',
|
|
495
|
+
|
|
496
|
+
hoverStyle: {
|
|
497
|
+
backgroundColor: '$backgroundHover',
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
focusStyle: {
|
|
501
|
+
backgroundColor: '$backgroundFocus',
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
pressStyle: {
|
|
505
|
+
backgroundColor: '$backgroundPress',
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
} as const,
|
|
510
|
+
|
|
511
|
+
defaultVariants: {
|
|
512
|
+
unstyled: false,
|
|
513
|
+
},
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
const TRIGGER_NAME = 'AccordionTrigger'
|
|
517
|
+
|
|
518
|
+
type AccordionTrigger = GetRef<typeof AccordionTriggerFrame>
|
|
519
|
+
type AccordionTriggerProps = GetProps<typeof AccordionTriggerFrame>
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* `AccordionTrigger` is the trigger that toggles the collapsed state of an `AccordionItem`. It
|
|
523
|
+
* should always be nested inside of an `AccordionHeader`.
|
|
524
|
+
*/
|
|
525
|
+
const AccordionTrigger = AccordionTriggerFrame.styleable(function AccordionTrigger(
|
|
526
|
+
props: ScopedProps<AccordionTriggerProps>,
|
|
527
|
+
forwardedRef
|
|
528
|
+
) {
|
|
529
|
+
const { __scopeAccordion, ...triggerProps } = props
|
|
530
|
+
const accordionContext = useAccordionContext(ACCORDION_NAME, __scopeAccordion)
|
|
531
|
+
const itemContext = useAccordionItemContext(TRIGGER_NAME, __scopeAccordion)
|
|
532
|
+
const collapsibleContext = useAccordionCollapsibleContext(
|
|
533
|
+
TRIGGER_NAME,
|
|
534
|
+
__scopeAccordion
|
|
535
|
+
)
|
|
536
|
+
const collapsibleScope = useCollapsibleScope(__scopeAccordion)
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<Collection.ItemSlot scope={__scopeAccordion}>
|
|
540
|
+
<AccordionTriggerFrame
|
|
541
|
+
aria-disabled={(itemContext.open && !collapsibleContext.collapsible) || undefined}
|
|
542
|
+
data-orientation={accordionContext.orientation}
|
|
543
|
+
id={itemContext.triggerId}
|
|
544
|
+
{...collapsibleScope}
|
|
545
|
+
{...triggerProps}
|
|
546
|
+
ref={forwardedRef}
|
|
547
|
+
/>
|
|
548
|
+
</Collection.ItemSlot>
|
|
549
|
+
)
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
/* -------------------------------------------------------------------------------------------------
|
|
553
|
+
* AccordionContent
|
|
554
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
555
|
+
|
|
556
|
+
const AccordionContentFrame = styled(Collapsible.Content, {
|
|
557
|
+
variants: {
|
|
558
|
+
unstyled: {
|
|
559
|
+
false: {
|
|
560
|
+
padding: '$true',
|
|
561
|
+
backgroundColor: '$background',
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
} as const,
|
|
565
|
+
|
|
566
|
+
defaultVariants: {
|
|
567
|
+
unstyled: false,
|
|
568
|
+
},
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
type AccordionContentProps = GetProps<typeof AccordionContentFrame>
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* `AccordionContent` contains the collapsible content for an `AccordionItem`.
|
|
575
|
+
*/
|
|
576
|
+
const AccordionContent = AccordionContentFrame.styleable(function AccordionContent(
|
|
577
|
+
props: ScopedProps<AccordionContentProps>,
|
|
578
|
+
forwardedRef
|
|
579
|
+
) {
|
|
580
|
+
const { __scopeAccordion, ...contentProps } = props
|
|
581
|
+
const accordionContext = useAccordionContext(ACCORDION_NAME, __scopeAccordion)
|
|
582
|
+
const itemContext = useAccordionItemContext('AccordionContent', __scopeAccordion)
|
|
583
|
+
const collapsibleScope = useCollapsibleScope(__scopeAccordion)
|
|
584
|
+
return (
|
|
585
|
+
<AccordionContentFrame
|
|
586
|
+
role="region"
|
|
587
|
+
aria-labelledby={itemContext.triggerId}
|
|
588
|
+
data-orientation={accordionContext.orientation}
|
|
589
|
+
{...collapsibleScope}
|
|
590
|
+
{...contentProps}
|
|
591
|
+
ref={forwardedRef}
|
|
592
|
+
/>
|
|
593
|
+
)
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
597
|
+
|
|
598
|
+
function getState(open?: boolean) {
|
|
599
|
+
return open ? 'open' : 'closed'
|
|
600
|
+
}
|
|
601
|
+
const Accordion = withStaticProperties(AccordionComponent, {
|
|
602
|
+
Trigger: AccordionTrigger,
|
|
603
|
+
Header: AccordionHeader,
|
|
604
|
+
Content: AccordionContent,
|
|
605
|
+
Item: AccordionItem,
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
export { Accordion, createAccordionScope }
|
|
609
|
+
|
|
610
|
+
export type {
|
|
611
|
+
AccordionContentProps,
|
|
612
|
+
AccordionHeaderProps,
|
|
613
|
+
AccordionItemProps,
|
|
614
|
+
AccordionMultipleProps,
|
|
615
|
+
AccordionSingleProps,
|
|
616
|
+
AccordionTriggerProps,
|
|
617
|
+
}
|