@codeleap/web 3.1.2 → 3.2.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/package.json +13 -10
- package/src/components/ActionIcon/index.tsx +24 -15
- package/src/components/ActivityIndicator/index.tsx +39 -46
- package/src/components/ActivityIndicator/styles.ts +7 -11
- package/src/components/Button/index.tsx +17 -21
- package/src/components/Checkbox/index.tsx +103 -96
- package/src/components/Collapse/index.tsx +4 -3
- package/src/components/Drawer/index.tsx +25 -17
- package/src/components/FileInput.tsx +60 -32
- package/src/components/Icon/index.tsx +6 -5
- package/src/components/InputBase/index.tsx +9 -4
- package/src/components/InputBase/types.ts +1 -0
- package/src/components/List/index.tsx +3 -0
- package/src/components/LoadingOverlay/index.tsx +36 -23
- package/src/components/Modal/index.tsx +260 -88
- package/src/components/Modal/styles.ts +3 -4
- package/src/components/NumberIncrement/index.tsx +2 -0
- package/src/components/Overlay/index.tsx +11 -10
- package/src/components/Pager/index.tsx +166 -0
- package/src/components/Pager/styles.ts +14 -0
- package/src/components/RadioInput/index.tsx +21 -19
- package/src/components/Scroll/index.tsx +6 -3
- package/src/components/SegmentedControl/SegmentedControlOption.tsx +78 -0
- package/src/components/SegmentedControl/index.tsx +161 -0
- package/src/components/SegmentedControl/styles.ts +26 -0
- package/src/components/Select/index.tsx +13 -11
- package/src/components/Select/types.ts +23 -23
- package/src/components/Slider/index.tsx +77 -72
- package/src/components/Switch/index.tsx +96 -93
- package/src/components/Text/index.tsx +18 -33
- package/src/components/TextInput/index.tsx +14 -8
- package/src/components/Tooltip/index.tsx +1 -1
- package/src/components/Touchable/index.tsx +16 -18
- package/src/components/View/index.tsx +49 -25
- package/src/components/View/styles.ts +0 -1
- package/src/components/components.ts +2 -2
- package/src/components/defaultStyles.ts +16 -3
- package/src/lib/OSAlert.tsx +1 -1
- package/src/types/utility.ts +31 -2
- package/src/components/Link/index.tsx +0 -69
- package/src/components/Link/styles.ts +0 -11
|
@@ -1,65 +1,173 @@
|
|
|
1
|
-
/** @jsx jsx */
|
|
2
|
-
import { jsx } from '@emotion/react'
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
2
|
AnyFunction,
|
|
6
3
|
ComponentVariants,
|
|
7
4
|
IconPlaceholder,
|
|
5
|
+
TypeGuards,
|
|
6
|
+
onMount,
|
|
8
7
|
onUpdate,
|
|
9
8
|
useDefaultComponentStyle,
|
|
10
9
|
useNestedStylesByKey,
|
|
10
|
+
useUnmount,
|
|
11
|
+
StylesOf,
|
|
12
|
+
PropsOf,
|
|
13
|
+
useIsomorphicEffect,
|
|
11
14
|
} from '@codeleap/common'
|
|
12
15
|
|
|
13
|
-
import
|
|
16
|
+
import React, { useId, useRef } from 'react'
|
|
14
17
|
import ReactDOM from 'react-dom'
|
|
15
|
-
|
|
16
18
|
import { v4 } from 'uuid'
|
|
17
|
-
|
|
18
|
-
import { StylesOf } from '../../types/utility'
|
|
19
|
-
import { Button } from '../Button'
|
|
20
19
|
import { View } from '../View'
|
|
21
20
|
import { Text } from '../Text'
|
|
22
|
-
import { Overlay } from '../Overlay'
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
21
|
+
import { Overlay, OverlayProps } from '../Overlay'
|
|
22
|
+
import { ModalComposition, ModalPresets } from './styles'
|
|
23
|
+
import { ActionIcon, ActionIconProps } from '../ActionIcon'
|
|
24
|
+
import { Scroll } from '../Scroll'
|
|
26
25
|
|
|
27
26
|
export * from './styles'
|
|
28
27
|
|
|
29
|
-
export type ModalProps =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
export type ModalProps =
|
|
29
|
+
{
|
|
30
|
+
visible: boolean
|
|
31
|
+
children?: React.ReactNode
|
|
32
|
+
title?: React.ReactNode | string
|
|
33
|
+
description?: React.ReactNode | string
|
|
34
|
+
renderModalBody?: (props: ModalBodyProps) => React.ReactElement
|
|
35
|
+
toggle: AnyFunction
|
|
36
|
+
styles?: StylesOf<ModalComposition>
|
|
37
|
+
style?: React.CSSProperties
|
|
38
|
+
accessible?: boolean
|
|
39
|
+
showClose?: boolean
|
|
40
|
+
closable?: boolean
|
|
41
|
+
dismissOnBackdrop?: boolean
|
|
42
|
+
scroll?: boolean
|
|
43
|
+
header?: React.ReactElement
|
|
44
|
+
footer?: React.ReactNode
|
|
45
|
+
withOverlay?: boolean
|
|
46
|
+
closeIconName?: IconPlaceholder
|
|
47
|
+
keepMounted?: boolean
|
|
48
|
+
renderHeader?: (props: ModalHeaderProps) => React.ReactElement
|
|
49
|
+
debugName?: string
|
|
50
|
+
closeButtonProps?: Partial<ActionIconProps>
|
|
51
|
+
closeOnEscape?: boolean
|
|
52
|
+
onClose?: () => void
|
|
53
|
+
overlayProps?: Partial<OverlayProps>
|
|
54
|
+
zIndex?: number
|
|
55
|
+
scrollable?: boolean
|
|
56
|
+
} & ComponentVariants<typeof ModalPresets>
|
|
57
|
+
|
|
42
58
|
function focusModal(event: FocusEvent, id: string) {
|
|
43
59
|
event.preventDefault()
|
|
44
60
|
const modal = document.getElementById(id)
|
|
45
|
-
if (modal)
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
if (modal) modal.focus()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type ModalBodyProps = {
|
|
65
|
+
id: string
|
|
66
|
+
variantStyles: PropsOf<ModalComposition>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type ModalHeaderProps = Partial<Omit<ModalProps, 'children'>> & {
|
|
70
|
+
id: string
|
|
71
|
+
variantStyles: PropsOf<ModalComposition>
|
|
72
|
+
onPressClose: () => void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ModalDefaultHeader = (props: ModalHeaderProps) => {
|
|
76
|
+
const {
|
|
77
|
+
id,
|
|
78
|
+
variantStyles,
|
|
79
|
+
title,
|
|
80
|
+
showClose,
|
|
81
|
+
closable,
|
|
82
|
+
closeIconName,
|
|
83
|
+
onPressClose,
|
|
84
|
+
closeButtonProps = {},
|
|
85
|
+
description,
|
|
86
|
+
} = props
|
|
87
|
+
|
|
88
|
+
const closeButtonStyles = useNestedStylesByKey('closeButton', variantStyles)
|
|
89
|
+
|
|
90
|
+
const showCloseButton = showClose && closable
|
|
91
|
+
|
|
92
|
+
const hasHeader = !!title || !!description || showCloseButton
|
|
93
|
+
|
|
94
|
+
if (!hasHeader) return null
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View
|
|
98
|
+
id={`${id}-header`}
|
|
99
|
+
component='header'
|
|
100
|
+
css={variantStyles.header}
|
|
101
|
+
className='modal-header header'
|
|
102
|
+
>
|
|
103
|
+
<View id={`${id}-title`} css={variantStyles.titleWrapper}>
|
|
104
|
+
{TypeGuards.isString(title) ? (
|
|
105
|
+
<Text text={title} css={variantStyles.title} />
|
|
106
|
+
) : (
|
|
107
|
+
title
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{showCloseButton && (
|
|
111
|
+
<ActionIcon
|
|
112
|
+
icon={closeIconName as IconPlaceholder}
|
|
113
|
+
onPress={onPressClose}
|
|
114
|
+
{...closeButtonProps}
|
|
115
|
+
styles={closeButtonStyles}
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
118
|
+
</View>
|
|
119
|
+
|
|
120
|
+
{TypeGuards.isString(description) ? (
|
|
121
|
+
<Text text={description} style={variantStyles.description} />
|
|
122
|
+
) : (
|
|
123
|
+
description
|
|
124
|
+
)}
|
|
125
|
+
</View>
|
|
126
|
+
)
|
|
48
127
|
}
|
|
128
|
+
|
|
129
|
+
const defaultProps: Partial<ModalProps> = {
|
|
130
|
+
title: '',
|
|
131
|
+
closeIconName: 'close' as IconPlaceholder,
|
|
132
|
+
closable: true,
|
|
133
|
+
withOverlay: true,
|
|
134
|
+
showClose: true,
|
|
135
|
+
scroll: false,
|
|
136
|
+
closeOnEscape: true,
|
|
137
|
+
renderHeader: ModalDefaultHeader,
|
|
138
|
+
keepMounted: true,
|
|
139
|
+
dismissOnBackdrop: true,
|
|
140
|
+
zIndex: null,
|
|
141
|
+
description: null,
|
|
142
|
+
scrollable: false,
|
|
143
|
+
}
|
|
144
|
+
|
|
49
145
|
export const ModalContent: React.FC<ModalProps & { id: string }> = (
|
|
50
146
|
modalProps,
|
|
51
147
|
) => {
|
|
52
148
|
const {
|
|
53
149
|
children,
|
|
54
|
-
closable = true,
|
|
55
150
|
visible,
|
|
56
|
-
title
|
|
151
|
+
title,
|
|
57
152
|
toggle,
|
|
58
|
-
|
|
59
|
-
variants,
|
|
153
|
+
variants = [],
|
|
60
154
|
styles,
|
|
61
|
-
showClose = true,
|
|
62
155
|
footer,
|
|
156
|
+
style = {},
|
|
157
|
+
renderHeader: ModalHeader,
|
|
158
|
+
closable,
|
|
159
|
+
withOverlay,
|
|
160
|
+
showClose,
|
|
161
|
+
responsiveVariants = {},
|
|
162
|
+
closeIconName,
|
|
163
|
+
scroll,
|
|
164
|
+
renderModalBody,
|
|
165
|
+
closeOnEscape,
|
|
166
|
+
onClose,
|
|
167
|
+
overlayProps = {},
|
|
168
|
+
dismissOnBackdrop,
|
|
169
|
+
zIndex,
|
|
170
|
+
scrollable,
|
|
63
171
|
...props
|
|
64
172
|
} = modalProps
|
|
65
173
|
|
|
@@ -71,45 +179,84 @@ export const ModalContent: React.FC<ModalProps & { id: string }> = (
|
|
|
71
179
|
styles,
|
|
72
180
|
})
|
|
73
181
|
|
|
182
|
+
const toggleAndReturn = () => {
|
|
183
|
+
toggle?.()
|
|
184
|
+
window.history.back()
|
|
185
|
+
|
|
186
|
+
if (TypeGuards.isFunction(onClose)) onClose()
|
|
187
|
+
}
|
|
188
|
+
|
|
74
189
|
onUpdate(() => {
|
|
75
190
|
if (visible) {
|
|
76
191
|
document.body.style.overflow = 'hidden'
|
|
192
|
+
window.history.pushState(null, null, window.location.pathname)
|
|
193
|
+
window.addEventListener('popstate', toggle)
|
|
77
194
|
} else {
|
|
78
195
|
document.body.style.overflow = 'auto'
|
|
79
|
-
|
|
196
|
+
window.removeEventListener('popstate', toggle)
|
|
80
197
|
}
|
|
81
198
|
}, [visible])
|
|
82
199
|
|
|
200
|
+
useUnmount(() => {
|
|
201
|
+
window.removeEventListener('popstate', toggle)
|
|
202
|
+
})
|
|
203
|
+
|
|
83
204
|
function closeOnEscPress(e: React.KeyboardEvent<HTMLDivElement>) {
|
|
84
|
-
if (
|
|
85
|
-
|
|
205
|
+
if (!closeOnEscape) return null
|
|
206
|
+
|
|
207
|
+
if (e?.key === 'Escape' || e?.keyCode === 27) {
|
|
208
|
+
toggleAndReturn()
|
|
86
209
|
}
|
|
87
210
|
}
|
|
88
211
|
|
|
89
|
-
|
|
212
|
+
useIsomorphicEffect(() => {
|
|
90
213
|
const modal = document.getElementById(id)
|
|
91
|
-
if (modal)
|
|
92
|
-
modal.focus()
|
|
93
|
-
}
|
|
214
|
+
if (modal) modal.focus()
|
|
94
215
|
}, [id])
|
|
95
216
|
|
|
96
|
-
const
|
|
97
|
-
|
|
217
|
+
const close = (closable && dismissOnBackdrop) ? toggleAndReturn : () => {}
|
|
218
|
+
|
|
219
|
+
const ModalBody = renderModalBody || (scroll ? Scroll : View)
|
|
220
|
+
|
|
221
|
+
const ModalArea = scrollable ? Scroll : View
|
|
222
|
+
|
|
223
|
+
const _zIndex = React.useMemo(() => {
|
|
224
|
+
return TypeGuards.isNumber(zIndex) ? { zIndex } : {}
|
|
225
|
+
}, [zIndex])
|
|
226
|
+
|
|
98
227
|
return (
|
|
99
|
-
<
|
|
228
|
+
<ModalArea
|
|
100
229
|
aria-hidden={!visible}
|
|
101
|
-
css={[
|
|
230
|
+
css={[
|
|
231
|
+
variantStyles.wrapper,
|
|
232
|
+
_zIndex,
|
|
233
|
+
visible
|
|
234
|
+
? variantStyles['wrapper:visible']
|
|
235
|
+
: variantStyles['wrapper:hidden'],
|
|
236
|
+
]}
|
|
102
237
|
>
|
|
103
238
|
<Overlay
|
|
104
|
-
visible={visible}
|
|
105
|
-
|
|
106
|
-
|
|
239
|
+
visible={withOverlay ? visible : false}
|
|
240
|
+
css={[
|
|
241
|
+
variantStyles.backdrop,
|
|
242
|
+
visible
|
|
243
|
+
? variantStyles['backdrop:visible']
|
|
244
|
+
: variantStyles['backdrop:hidden'],
|
|
245
|
+
]}
|
|
246
|
+
{...overlayProps}
|
|
107
247
|
/>
|
|
108
|
-
|
|
109
|
-
|
|
248
|
+
|
|
249
|
+
<View css={variantStyles.innerWrapper}>
|
|
250
|
+
<View css={variantStyles.backdropPressable} onClick={close} />
|
|
110
251
|
<View
|
|
111
252
|
component='section'
|
|
112
|
-
css={[
|
|
253
|
+
css={[
|
|
254
|
+
variantStyles.box,
|
|
255
|
+
visible
|
|
256
|
+
? variantStyles['box:visible']
|
|
257
|
+
: variantStyles['box:hidden'],
|
|
258
|
+
style,
|
|
259
|
+
]}
|
|
113
260
|
className='content'
|
|
114
261
|
onKeyDown={closeOnEscPress}
|
|
115
262
|
tabIndex={0}
|
|
@@ -117,30 +264,24 @@ export const ModalContent: React.FC<ModalProps & { id: string }> = (
|
|
|
117
264
|
aria-modal={true}
|
|
118
265
|
role='dialog'
|
|
119
266
|
aria-describedby={`${id}-title`}
|
|
120
|
-
aria-label='Close the modal by
|
|
267
|
+
aria-label='Close the modal by pressing Escape key'
|
|
121
268
|
{...props}
|
|
122
269
|
>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
styles={closeButtonStyles}
|
|
138
|
-
/>
|
|
139
|
-
)}
|
|
140
|
-
</View>
|
|
141
|
-
)}
|
|
270
|
+
<ModalHeader
|
|
271
|
+
{...modalProps}
|
|
272
|
+
variantStyles={variantStyles}
|
|
273
|
+
id={id}
|
|
274
|
+
onPressClose={toggleAndReturn}
|
|
275
|
+
/>
|
|
276
|
+
|
|
277
|
+
<ModalBody
|
|
278
|
+
css={variantStyles.body}
|
|
279
|
+
variantStyles={variantStyles}
|
|
280
|
+
id={id}
|
|
281
|
+
>
|
|
282
|
+
{children}
|
|
283
|
+
</ModalBody>
|
|
142
284
|
|
|
143
|
-
<View css={variantStyles.body}>{children}</View>
|
|
144
285
|
{footer && (
|
|
145
286
|
<View component='footer' css={variantStyles.footer}>
|
|
146
287
|
{footer}
|
|
@@ -148,42 +289,73 @@ export const ModalContent: React.FC<ModalProps & { id: string }> = (
|
|
|
148
289
|
)}
|
|
149
290
|
</View>
|
|
150
291
|
</View>
|
|
151
|
-
</
|
|
292
|
+
</ModalArea>
|
|
152
293
|
)
|
|
153
294
|
}
|
|
154
295
|
|
|
155
|
-
export const Modal: React.FC<ModalProps> = (
|
|
296
|
+
export const Modal: React.FC<ModalProps> = (props) => {
|
|
297
|
+
const allProps = {
|
|
298
|
+
...Modal.defaultProps,
|
|
299
|
+
...props,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const {
|
|
303
|
+
accessible,
|
|
304
|
+
keepMounted,
|
|
305
|
+
visible,
|
|
306
|
+
} = allProps
|
|
307
|
+
|
|
156
308
|
const modalId = useRef(v4())
|
|
309
|
+
const [renderStatus, setRenderStatus] = React.useState(
|
|
310
|
+
keepMounted ? 'mounted' : 'unmounted',
|
|
311
|
+
)
|
|
157
312
|
|
|
158
|
-
|
|
313
|
+
// onUpdate(() => {
|
|
314
|
+
// if (!keepMounted) {
|
|
315
|
+
// if (visible) {
|
|
316
|
+
// setRenderStatus('mounted')
|
|
317
|
+
// } else {
|
|
318
|
+
// setTimeout(() => setRenderStatus('unmounted'), 500)
|
|
319
|
+
// }
|
|
320
|
+
// }
|
|
321
|
+
// }, [keepMounted, visible])
|
|
322
|
+
|
|
323
|
+
onMount(() => {
|
|
159
324
|
if (accessible) {
|
|
160
|
-
const currentId = modalId
|
|
325
|
+
const currentId = modalId
|
|
161
326
|
const appRoot = document.body
|
|
162
327
|
appRoot.addEventListener('focusin', (e) => focusModal(e, currentId))
|
|
163
328
|
return () => appRoot.removeEventListener('focusin', (e) => focusModal(e, currentId))
|
|
164
329
|
}
|
|
165
|
-
}
|
|
330
|
+
})
|
|
166
331
|
|
|
167
|
-
|
|
332
|
+
onUpdate(() => {
|
|
168
333
|
if (accessible) {
|
|
169
334
|
const appRoot = document.body
|
|
170
|
-
appRoot.setAttribute('aria-hidden', `${
|
|
335
|
+
appRoot.setAttribute('aria-hidden', `${visible}`)
|
|
171
336
|
appRoot.setAttribute('tabindex', `${-1}`)
|
|
172
337
|
}
|
|
173
|
-
}, [
|
|
338
|
+
}, [visible])
|
|
174
339
|
|
|
175
|
-
|
|
176
|
-
if (
|
|
340
|
+
onUpdate(() => {
|
|
341
|
+
if (visible) {
|
|
177
342
|
document.body.style.overflow = 'hidden'
|
|
178
|
-
return ReactDOM.createPortal(
|
|
179
|
-
<ModalContent {...props} id={modalId.current} />,
|
|
180
|
-
document.body,
|
|
181
|
-
)
|
|
182
343
|
} else {
|
|
183
344
|
document.body.style.overflow = 'visible'
|
|
184
|
-
return null
|
|
185
345
|
}
|
|
186
|
-
}
|
|
346
|
+
}, [visible])
|
|
347
|
+
|
|
348
|
+
const content = <ModalContent {...props} id={modalId.current} />
|
|
349
|
+
|
|
350
|
+
// if (renderStatus === 'unmounted') return null
|
|
351
|
+
|
|
352
|
+
if (typeof window === 'undefined') return content
|
|
353
|
+
|
|
354
|
+
return ReactDOM.createPortal(
|
|
355
|
+
content,
|
|
356
|
+
document.body,
|
|
357
|
+
)
|
|
187
358
|
|
|
188
|
-
return <ModalContent {...props} id={modalId.current} />
|
|
189
359
|
}
|
|
360
|
+
|
|
361
|
+
Modal.defaultProps = defaultProps
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createDefaultVariantFactory,
|
|
3
|
-
includePresets,
|
|
4
|
-
} from '@codeleap/common'
|
|
1
|
+
import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
|
|
5
2
|
import { ActionIconComposition } from '../ActionIcon'
|
|
6
3
|
|
|
7
4
|
export type AnimatableParts = 'box' | 'backdrop' | 'wrapper'
|
|
@@ -14,6 +11,8 @@ export type ModalParts =
|
|
|
14
11
|
| 'title'
|
|
15
12
|
| 'innerWrapper'
|
|
16
13
|
| 'backdropPressable'
|
|
14
|
+
| 'description'
|
|
15
|
+
| 'titleWrapper'
|
|
17
16
|
| `closeButton${Capitalize<ActionIconComposition>}`
|
|
18
17
|
|
|
19
18
|
export type ModalComposition =
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
import React from 'react'
|
|
1
2
|
import {
|
|
2
3
|
ComponentVariants,
|
|
3
|
-
SmartOmit,
|
|
4
4
|
StylesOf,
|
|
5
5
|
useDefaultComponentStyle,
|
|
6
6
|
} from '@codeleap/common'
|
|
7
7
|
import { Touchable, TouchableProps } from '../Touchable'
|
|
8
8
|
import { View, ViewProps } from '../View'
|
|
9
9
|
import { OverlayComposition, OverlayPresets } from './styles'
|
|
10
|
+
import { NativeHTMLElement } from '../../types'
|
|
10
11
|
|
|
11
|
-
export type OverlayProps = {
|
|
12
|
+
export type OverlayProps<T extends NativeHTMLElement = 'div'> = {
|
|
12
13
|
visible?: boolean
|
|
13
14
|
styles?: StylesOf<OverlayComposition>
|
|
14
15
|
onPress?: TouchableProps<'div'>['onClick']
|
|
15
|
-
} & ComponentVariants<typeof OverlayPresets> &
|
|
16
|
-
Partial<SmartOmit<ViewProps<'div'>, 'variants' | 'responsiveVariants'>>
|
|
16
|
+
} & ComponentVariants<typeof OverlayPresets> & Omit<ViewProps<T>, 'variants' | 'responsiveVariants'>
|
|
17
17
|
|
|
18
18
|
export * from './styles'
|
|
19
19
|
|
|
20
|
-
export const Overlay
|
|
20
|
+
export const Overlay = <T extends NativeHTMLElement>(overlayProps:OverlayProps<T>) => {
|
|
21
21
|
const { visible, responsiveVariants, variants, styles, ...props } =
|
|
22
22
|
overlayProps
|
|
23
23
|
|
|
@@ -30,12 +30,13 @@ export const Overlay: React.FC<OverlayProps> = (overlayProps) => {
|
|
|
30
30
|
const Component = props.onClick || props.onPress ? Touchable : View
|
|
31
31
|
|
|
32
32
|
return (
|
|
33
|
+
// @ts-ignore
|
|
33
34
|
<Component
|
|
34
|
-
css={
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
35
|
+
css={[
|
|
36
|
+
{ transition: 'opacity 0.2s ease' },
|
|
37
|
+
variantStyles.wrapper,
|
|
38
|
+
visible ? variantStyles['wrapper:visible'] : {},
|
|
39
|
+
]}
|
|
39
40
|
{...props}
|
|
40
41
|
/>
|
|
41
42
|
)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentVariants,
|
|
3
|
+
StylesOf,
|
|
4
|
+
onUpdate,
|
|
5
|
+
useDefaultComponentStyle,
|
|
6
|
+
} from '@codeleap/common'
|
|
7
|
+
import Slider, { Settings } from 'react-slick'
|
|
8
|
+
import { PagerComposition, PagerPresets } from './styles'
|
|
9
|
+
import React, {
|
|
10
|
+
forwardRef,
|
|
11
|
+
useCallback,
|
|
12
|
+
useImperativeHandle,
|
|
13
|
+
useRef,
|
|
14
|
+
ReactNode,
|
|
15
|
+
ReactElement,
|
|
16
|
+
CSSProperties,
|
|
17
|
+
} from 'react'
|
|
18
|
+
|
|
19
|
+
import 'slick-carousel/slick/slick.css'
|
|
20
|
+
import 'slick-carousel/slick/slick-theme.css'
|
|
21
|
+
import { View, ViewProps } from '../View'
|
|
22
|
+
import { Touchable } from '../Touchable'
|
|
23
|
+
|
|
24
|
+
export type PagerRef = {
|
|
25
|
+
goTo: (page: number) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type PagerProps = Settings &
|
|
29
|
+
ComponentVariants<typeof PagerPresets> & {
|
|
30
|
+
styles?: StylesOf<PagerComposition>
|
|
31
|
+
page?: number
|
|
32
|
+
style?: CSSProperties
|
|
33
|
+
children: ReactNode
|
|
34
|
+
onChange?: (page: number) => void
|
|
35
|
+
renderPageWrapper?: React.FC
|
|
36
|
+
footer?: ReactElement
|
|
37
|
+
dotsProps?: DotsProps
|
|
38
|
+
pageWrapperProps?: ViewProps<'div'>
|
|
39
|
+
dotsDisabled?:boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type DotsProps = Pick<PagerProps, 'page' | 'dotsDisabled'> & {
|
|
43
|
+
childArray: ReactNode[]
|
|
44
|
+
onPress?: (index: number) => void
|
|
45
|
+
variantStyles: StylesOf<PagerComposition>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Dots = ({ page, childArray, onPress, variantStyles, dotsDisabled }: DotsProps) => {
|
|
49
|
+
return (
|
|
50
|
+
<View style={variantStyles.dots}>
|
|
51
|
+
{childArray.map((_, index) => {
|
|
52
|
+
const isSelected = index === page
|
|
53
|
+
const css = [
|
|
54
|
+
variantStyles[isSelected ? 'dot:selected' : 'dot'],
|
|
55
|
+
dotsDisabled && variantStyles['dot:disabled'],
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Touchable
|
|
60
|
+
key={index}
|
|
61
|
+
onPress={() => onPress?.(index)}
|
|
62
|
+
css={css}
|
|
63
|
+
disabled={dotsDisabled}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
})}
|
|
67
|
+
</View>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const PagerComponent = (
|
|
72
|
+
props: PagerProps,
|
|
73
|
+
ref: React.ForwardedRef<PagerRef>,
|
|
74
|
+
) => {
|
|
75
|
+
const sliderRef = useRef<Slider>()
|
|
76
|
+
const {
|
|
77
|
+
styles,
|
|
78
|
+
style,
|
|
79
|
+
variants,
|
|
80
|
+
children,
|
|
81
|
+
renderPageWrapper,
|
|
82
|
+
responsiveVariants,
|
|
83
|
+
page,
|
|
84
|
+
dots = false,
|
|
85
|
+
dotsDisabled = false,
|
|
86
|
+
infinite = false,
|
|
87
|
+
onChange,
|
|
88
|
+
footer,
|
|
89
|
+
dotsProps,
|
|
90
|
+
pageWrapperProps,
|
|
91
|
+
...rest
|
|
92
|
+
} = props
|
|
93
|
+
|
|
94
|
+
const variantStyles = useDefaultComponentStyle<
|
|
95
|
+
'u:Pager',
|
|
96
|
+
typeof PagerPresets
|
|
97
|
+
>('u:Pager', {
|
|
98
|
+
variants,
|
|
99
|
+
responsiveVariants,
|
|
100
|
+
styles,
|
|
101
|
+
rootElement: 'wrapper',
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const childArray = React.Children.toArray(children)
|
|
105
|
+
const PageWrapper = renderPageWrapper || View
|
|
106
|
+
|
|
107
|
+
const goTo = useCallback(
|
|
108
|
+
(page: number) => {
|
|
109
|
+
if (sliderRef.current) sliderRef.current.slickGoTo(page)
|
|
110
|
+
},
|
|
111
|
+
[sliderRef?.current],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
useImperativeHandle(ref, () => ({
|
|
115
|
+
goTo,
|
|
116
|
+
}))
|
|
117
|
+
|
|
118
|
+
onUpdate(() => {
|
|
119
|
+
goTo(page)
|
|
120
|
+
}, [page])
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<View css={[variantStyles.wrapper, style]}>
|
|
124
|
+
<Slider
|
|
125
|
+
{...rest}
|
|
126
|
+
arrows={false}
|
|
127
|
+
ref={sliderRef}
|
|
128
|
+
dots={false}
|
|
129
|
+
infinite={infinite}
|
|
130
|
+
accessibility={false}
|
|
131
|
+
afterChange={onChange}
|
|
132
|
+
>
|
|
133
|
+
{childArray.map((child, index) => {
|
|
134
|
+
return (
|
|
135
|
+
<PageWrapper
|
|
136
|
+
key={index}
|
|
137
|
+
css={variantStyles.pageWrapper}
|
|
138
|
+
{...pageWrapperProps}
|
|
139
|
+
>
|
|
140
|
+
{child}
|
|
141
|
+
</PageWrapper>
|
|
142
|
+
)
|
|
143
|
+
})}
|
|
144
|
+
</Slider>
|
|
145
|
+
|
|
146
|
+
<View css={variantStyles.footerWrapper}>
|
|
147
|
+
{footer}
|
|
148
|
+
|
|
149
|
+
{dots && (
|
|
150
|
+
<Dots
|
|
151
|
+
page={page}
|
|
152
|
+
onPress={onChange}
|
|
153
|
+
childArray={childArray}
|
|
154
|
+
variantStyles={variantStyles}
|
|
155
|
+
dotsDisabled={dotsDisabled}
|
|
156
|
+
{...dotsProps}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
</View>
|
|
160
|
+
</View>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const Pager = forwardRef(PagerComponent)
|
|
165
|
+
|
|
166
|
+
export * from './styles'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
|
|
2
|
+
|
|
3
|
+
export type PagerComposition =
|
|
4
|
+
| 'wrapper'
|
|
5
|
+
| 'dot'
|
|
6
|
+
| 'dot:selected'
|
|
7
|
+
| 'dots'
|
|
8
|
+
| 'pageWrapper'
|
|
9
|
+
| 'footerWrapper'
|
|
10
|
+
|
|
11
|
+
const createPagerStyle = createDefaultVariantFactory<PagerComposition>()
|
|
12
|
+
|
|
13
|
+
export const PagerPresets = includePresets((styles) => createPagerStyle(() => ({ wrapper: styles })),
|
|
14
|
+
)
|