@codeleap/web 3.16.2 → 3.18.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/package.json +1 -1
- package/src/components/ActionIcon/index.tsx +13 -10
- package/src/components/CropPicker/index.tsx +11 -1
- package/src/components/CropPicker/types.ts +1 -0
- package/src/components/CropPicker/useCropPicker.tsx +4 -0
- package/src/components/Grid/index.tsx +1 -1
- package/src/components/Modal/index.tsx +35 -2
- package/src/components/SegmentedControl/index.tsx +5 -1
- package/src/components/Switch/index.tsx +7 -3
- package/src/components/Touchable/index.tsx +4 -0
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ export type ActionIconProps = Omit<TouchableProps, 'styles' | 'variants'> & Comp
|
|
|
20
20
|
|
|
21
21
|
const defaultProps: Partial<ActionIconProps> = {
|
|
22
22
|
disabled: false,
|
|
23
|
+
tabIndex: 0,
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export const ActionIcon = (props: ActionIconProps) => {
|
|
@@ -28,7 +29,7 @@ export const ActionIcon = (props: ActionIconProps) => {
|
|
|
28
29
|
...props,
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const {
|
|
32
|
+
const {
|
|
32
33
|
icon,
|
|
33
34
|
name,
|
|
34
35
|
iconProps,
|
|
@@ -39,30 +40,31 @@ export const ActionIcon = (props: ActionIconProps) => {
|
|
|
39
40
|
children,
|
|
40
41
|
disabled,
|
|
41
42
|
debugName,
|
|
42
|
-
...touchableProps
|
|
43
|
+
...touchableProps
|
|
43
44
|
} = allProps
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
const variantStyles = useDefaultComponentStyle<'u:ActionIcon', typeof ActionIconPresets>('u:ActionIcon', {
|
|
46
47
|
responsiveVariants,
|
|
47
|
-
variants,
|
|
48
|
+
variants,
|
|
48
49
|
styles,
|
|
49
|
-
rootElement: 'touchableWrapper'
|
|
50
|
+
rootElement: 'touchableWrapper',
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
const isPressable = TypeGuards.isFunction(onPress) && !disabled
|
|
53
54
|
|
|
54
55
|
const WrapperComponent: any = isPressable ? Touchable : View
|
|
55
56
|
|
|
56
|
-
const handlePress = () => {
|
|
57
|
+
const handlePress = (e) => {
|
|
57
58
|
if (!isPressable) return
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
if (onPress && (e?.type === 'click' || e?.keyCode === 13 || e?.key === 'Enter')) {
|
|
60
|
+
onPress?.()
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
const getStyles = (key: ActionIconParts) => ({
|
|
63
65
|
...variantStyles[key],
|
|
64
66
|
...(disabled ? variantStyles[`${key}:disabled`] : {}),
|
|
65
|
-
...(isPressable ? variantStyles[`${key}:pressable`] : {})
|
|
67
|
+
...(isPressable ? variantStyles[`${key}:pressable`] : {}),
|
|
66
68
|
})
|
|
67
69
|
|
|
68
70
|
return (
|
|
@@ -72,7 +74,8 @@ export const ActionIcon = (props: ActionIconProps) => {
|
|
|
72
74
|
debugName={debugName}
|
|
73
75
|
{
|
|
74
76
|
...(isPressable && {
|
|
75
|
-
onPress: handlePress,
|
|
77
|
+
onPress: () => handlePress({ type: 'click' }),
|
|
78
|
+
onKeyDown: handlePress,
|
|
76
79
|
})
|
|
77
80
|
}
|
|
78
81
|
{...touchableProps}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { CropPickerPresets } from './styles'
|
|
7
7
|
import { CropPickerProps } from './types'
|
|
8
8
|
import { useCropPicker } from './useCropPicker'
|
|
9
|
-
import { Modal, Button, FileInput, FileInputRef } from '../components'
|
|
9
|
+
import { Modal, Button, FileInput, FileInputRef, LoadingOverlay } from '../components'
|
|
10
10
|
|
|
11
11
|
const ReactCrop: React.Component = require('react-image-crop').Component
|
|
12
12
|
import 'react-image-crop/dist/ReactCrop.css'
|
|
@@ -35,6 +35,7 @@ export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
|
|
|
35
35
|
confirmButton = 'Confirm Crop',
|
|
36
36
|
debugName,
|
|
37
37
|
handle,
|
|
38
|
+
withLoading = false,
|
|
38
39
|
...fileInputProps
|
|
39
40
|
} = allProps
|
|
40
41
|
|
|
@@ -47,6 +48,7 @@ export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
|
|
|
47
48
|
image,
|
|
48
49
|
crop,
|
|
49
50
|
setRelativeCrop,
|
|
51
|
+
isLoading,
|
|
50
52
|
handleCropChange,
|
|
51
53
|
} = handle || useCropPicker({ onFileSelect, ref, ...targetCrop })
|
|
52
54
|
|
|
@@ -98,6 +100,14 @@ export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
|
|
|
98
100
|
/>
|
|
99
101
|
</ReactCrop>
|
|
100
102
|
)}
|
|
103
|
+
{
|
|
104
|
+
withLoading ? (
|
|
105
|
+
<LoadingOverlay
|
|
106
|
+
debugName='CropPicker'
|
|
107
|
+
visible={isLoading}
|
|
108
|
+
/>
|
|
109
|
+
) : null
|
|
110
|
+
}
|
|
101
111
|
</Modal>
|
|
102
112
|
</>
|
|
103
113
|
)
|
|
@@ -23,6 +23,7 @@ export function useCropPicker({
|
|
|
23
23
|
const [image, setImage] = useState<ImageReading>(null)
|
|
24
24
|
const [crop, setCrop] = useState<Crop>()
|
|
25
25
|
const [relativeCrop, setRelativeCrop] = useState<Crop>()
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
26
27
|
const croppedPromise = usePromise<WebInputFile[]>({})
|
|
27
28
|
|
|
28
29
|
const onCancel = () => croppedPromise.resolve([])
|
|
@@ -40,6 +41,7 @@ export function useCropPicker({
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const onConfirmCrop = async () => {
|
|
44
|
+
setIsLoading(true)
|
|
43
45
|
const [preview, croppedFile] = await cropImage(image, relativeCrop)
|
|
44
46
|
onResolved([
|
|
45
47
|
{
|
|
@@ -47,6 +49,7 @@ export function useCropPicker({
|
|
|
47
49
|
preview,
|
|
48
50
|
},
|
|
49
51
|
])
|
|
52
|
+
setIsLoading(false)
|
|
50
53
|
setTimeout(() => cleanup())
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -127,6 +130,7 @@ export function useCropPicker({
|
|
|
127
130
|
setCrop,
|
|
128
131
|
relativeCrop,
|
|
129
132
|
setRelativeCrop,
|
|
133
|
+
isLoading,
|
|
130
134
|
croppedPromise,
|
|
131
135
|
handleCropChange,
|
|
132
136
|
}
|
|
@@ -185,7 +185,7 @@ export const ModalContent = (
|
|
|
185
185
|
} = modalProps
|
|
186
186
|
|
|
187
187
|
const id = useId()
|
|
188
|
-
|
|
188
|
+
const modalRef = useRef(null)
|
|
189
189
|
const variantStyles = useDefaultComponentStyle<'u:Modal', typeof ModalPresets>('u:Modal', {
|
|
190
190
|
responsiveVariants,
|
|
191
191
|
variants,
|
|
@@ -210,6 +210,39 @@ export const ModalContent = (
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const handleTabKeyPress = (e: React.KeyboardEvent<HTMLDivElement>, { firstElement, lastElement }: Record<'firstElement' |'lastElement', HTMLDivElement>) => {
|
|
214
|
+
if (e.key === 'Tab' || e?.keyCode === 9) {
|
|
215
|
+
if (e.shiftKey && document.activeElement === firstElement) {
|
|
216
|
+
e.preventDefault()
|
|
217
|
+
lastElement.focus()
|
|
218
|
+
} else if (
|
|
219
|
+
!e.shiftKey &&
|
|
220
|
+
document.activeElement === lastElement
|
|
221
|
+
) {
|
|
222
|
+
e.preventDefault()
|
|
223
|
+
firstElement.focus()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onUpdate(() => {
|
|
229
|
+
if (visible) {
|
|
230
|
+
const modalElement = modalRef.current
|
|
231
|
+
|
|
232
|
+
const focusableElements = modalElement.querySelectorAll('[tabindex]:not([tabindex="-1"])') as NodeListOf<HTMLDivElement>
|
|
233
|
+
const firstElement = focusableElements[0]
|
|
234
|
+
const lastElement = focusableElements[focusableElements.length - 1]
|
|
235
|
+
|
|
236
|
+
modalElement.addEventListener('keydown', (e) => handleTabKeyPress(e, { firstElement, lastElement }))
|
|
237
|
+
modalElement.addEventListener('keydown', closeOnEscPress)
|
|
238
|
+
|
|
239
|
+
return () => {
|
|
240
|
+
modalElement.removeEventListener('keydown', (e) => handleTabKeyPress(e, { firstElement, lastElement }))
|
|
241
|
+
modalElement.removeEventListener('keydown', closeOnEscPress)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}, [visible])
|
|
245
|
+
|
|
213
246
|
useIsomorphicEffect(() => {
|
|
214
247
|
const modal = document.getElementById(id)
|
|
215
248
|
if (modal) modal.focus()
|
|
@@ -227,6 +260,7 @@ export const ModalContent = (
|
|
|
227
260
|
|
|
228
261
|
return (
|
|
229
262
|
<View
|
|
263
|
+
ref={modalRef}
|
|
230
264
|
aria-hidden={!visible}
|
|
231
265
|
css={[
|
|
232
266
|
variantStyles.wrapper,
|
|
@@ -266,7 +300,6 @@ export const ModalContent = (
|
|
|
266
300
|
style,
|
|
267
301
|
]}
|
|
268
302
|
className='content'
|
|
269
|
-
onKeyDown={closeOnEscPress}
|
|
270
303
|
tabIndex={0}
|
|
271
304
|
id={id}
|
|
272
305
|
aria-modal={true}
|
|
@@ -135,7 +135,9 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
|
|
|
135
135
|
largestWidth,
|
|
136
136
|
]
|
|
137
137
|
|
|
138
|
-
const onSelectTab = (option: SegmentedControlOptionProps) => {
|
|
138
|
+
const onSelectTab = (option: SegmentedControlOptionProps, e?: React.KeyboardEvent<HTMLDivElement>) => {
|
|
139
|
+
if (!!e && e?.keyCode !== 13 || e?.key !== 'Enter') return null
|
|
140
|
+
|
|
139
141
|
if (!debounceEnabled || !TypeGuards.isNumber(debounce)) {
|
|
140
142
|
onValueChange(option.value)
|
|
141
143
|
return
|
|
@@ -171,6 +173,7 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
|
|
|
171
173
|
label={o.label}
|
|
172
174
|
value={o.value}
|
|
173
175
|
onPress={() => onSelectTab(o)}
|
|
176
|
+
onKeyDown={(e) => onSelectTab(o, e)}
|
|
174
177
|
key={idx}
|
|
175
178
|
icon={o.icon}
|
|
176
179
|
selected={value === o.value}
|
|
@@ -179,6 +182,7 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
|
|
|
179
182
|
disabled={disabled}
|
|
180
183
|
textProps={textProps}
|
|
181
184
|
iconProps={iconProps}
|
|
185
|
+
tabIndex={0}
|
|
182
186
|
{...props?.touchableProps}
|
|
183
187
|
/>
|
|
184
188
|
))}
|
|
@@ -89,10 +89,12 @@ export const Switch = (props: SwitchProps) => {
|
|
|
89
89
|
|
|
90
90
|
const _switchOnLeft = switchOnLeft ?? variantStyles.__props?.switchOnLeft
|
|
91
91
|
|
|
92
|
-
const handleChange = () => {
|
|
92
|
+
const handleChange = (e?: React.KeyboardEvent<HTMLDivElement>) => {
|
|
93
93
|
if (disabled) return
|
|
94
|
-
if (
|
|
95
|
-
|
|
94
|
+
if (e?.type === 'click' || e?.keyCode === 13 || e?.key === 'Enter') {
|
|
95
|
+
if (onValueChange) onValueChange?.(!value)
|
|
96
|
+
if (onChange) onChange?.(!value)
|
|
97
|
+
}
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
return <InputBase
|
|
@@ -118,6 +120,8 @@ export const Switch = (props: SwitchProps) => {
|
|
|
118
120
|
animate={trackAnimation}
|
|
119
121
|
transition={variantStyles['track:transition']}
|
|
120
122
|
onClick={handleChange}
|
|
123
|
+
onKeyDown={handleChange}
|
|
124
|
+
tabIndex={0}
|
|
121
125
|
>
|
|
122
126
|
<motion.div
|
|
123
127
|
css={[
|
|
@@ -38,6 +38,7 @@ const defaultProps: TouchableProps<'button'> = {
|
|
|
38
38
|
analyticsEnabled: false,
|
|
39
39
|
analyticsName: null,
|
|
40
40
|
analyticsData: {},
|
|
41
|
+
tabIndex: 0,
|
|
41
42
|
}
|
|
42
43
|
export const TouchableCP = <T extends NativeHTMLElement = 'button'>(
|
|
43
44
|
touchableProps: TouchableProps<T>,
|
|
@@ -105,6 +106,8 @@ export const TouchableCP = <T extends NativeHTMLElement = 'button'>(
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
const _onPress = () => {
|
|
109
|
+
if (!!event && event?.keyCode !== 13 || event?.key !== 'Enter') return null
|
|
110
|
+
|
|
108
111
|
logger.log(
|
|
109
112
|
`<${debugComponent || 'Touchable'}/> pressed`,
|
|
110
113
|
{ debugName, debugComponent },
|
|
@@ -150,6 +153,7 @@ export const TouchableCP = <T extends NativeHTMLElement = 'button'>(
|
|
|
150
153
|
{...props}
|
|
151
154
|
debugName={debugName}
|
|
152
155
|
onClick={handleClick}
|
|
156
|
+
onKeyDown={handleClick}
|
|
153
157
|
ref={ref}
|
|
154
158
|
css={_styles}
|
|
155
159
|
/>
|