@codeleap/web 3.24.3 → 4.0.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 +2 -1
- package/src/components/ActionIcon/index.tsx +51 -52
- package/src/components/ActionIcon/styles.ts +1 -5
- package/src/components/ActionIcon/types.ts +15 -0
- package/src/components/ActivityIndicator/index.tsx +34 -55
- package/src/components/ActivityIndicator/styles.ts +0 -6
- package/src/components/ActivityIndicator/types.ts +12 -0
- package/src/components/Badge/index.tsx +43 -80
- package/src/components/Badge/styles.ts +1 -11
- package/src/components/Badge/types.ts +28 -0
- package/src/components/Button/index.tsx +46 -89
- package/src/components/Button/styles.ts +0 -5
- package/src/components/Button/types.ts +25 -0
- package/src/components/Checkbox/index.tsx +83 -97
- package/src/components/Checkbox/styles.ts +1 -5
- package/src/components/Checkbox/types.ts +15 -0
- package/src/components/Collapse/index.tsx +41 -83
- package/src/components/Collapse/styles.ts +3 -6
- package/src/components/Collapse/types.ts +11 -0
- package/src/components/ColorPicker/index.tsx +95 -48
- package/src/components/ColorPicker/styles.ts +11 -11
- package/src/components/ColorPicker/types.ts +26 -12
- package/src/components/CropPicker/index.tsx +100 -104
- package/src/components/CropPicker/styles.ts +0 -7
- package/src/components/CropPicker/types.ts +8 -15
- package/src/components/DatePicker/{defaultComponents → components}/Header.tsx +9 -17
- package/src/components/DatePicker/{defaultComponents → components}/OuterInput.tsx +6 -7
- package/src/components/DatePicker/index.tsx +110 -124
- package/src/components/DatePicker/styles.ts +1 -12
- package/src/components/DatePicker/types.ts +16 -33
- package/src/components/Drawer/index.tsx +133 -125
- package/src/components/Drawer/styles.ts +0 -5
- package/src/components/Drawer/types.ts +23 -0
- package/src/components/Dropzone/index.tsx +87 -63
- package/src/components/Dropzone/styles.ts +0 -6
- package/src/components/Dropzone/types.ts +29 -37
- package/src/components/EmptyPlaceholder/index.tsx +63 -83
- package/src/components/EmptyPlaceholder/styles.ts +0 -5
- package/src/components/EmptyPlaceholder/types.ts +32 -0
- package/src/components/FileInput/index.tsx +72 -0
- package/src/components/FileInput/types.ts +14 -0
- package/src/components/Grid/index.tsx +40 -41
- package/src/components/Grid/styles.ts +2 -9
- package/src/components/Grid/types.ts +10 -12
- package/src/components/Icon/index.tsx +45 -47
- package/src/components/Icon/styles.ts +0 -8
- package/src/components/Icon/types.ts +15 -0
- package/src/components/InputBase/index.tsx +71 -42
- package/src/components/InputBase/styles.ts +37 -47
- package/src/components/InputBase/types.ts +19 -7
- package/src/components/InputBase/utils.ts +3 -23
- package/src/components/List/ListLayout.tsx +20 -37
- package/src/components/List/index.tsx +36 -41
- package/src/components/List/styles.ts +5 -11
- package/src/components/List/types.ts +30 -20
- package/src/components/LoadingOverlay/index.tsx +31 -33
- package/src/components/LoadingOverlay/styles.ts +3 -8
- package/src/components/LoadingOverlay/types.ts +16 -0
- package/src/components/Modal/index.tsx +98 -160
- package/src/components/Modal/styles.ts +0 -5
- package/src/components/Modal/types.ts +55 -0
- package/src/components/NumberIncrement/index.tsx +67 -98
- package/src/components/NumberIncrement/styles.ts +0 -5
- package/src/components/NumberIncrement/types.ts +29 -0
- package/src/components/Overlay/index.tsx +37 -35
- package/src/components/Overlay/styles.ts +3 -5
- package/src/components/Overlay/types.ts +13 -0
- package/src/components/Pager/index.tsx +65 -81
- package/src/components/Pager/styles.ts +3 -9
- package/src/components/Pager/types.ts +35 -0
- package/src/components/PaginationButtons/index.tsx +173 -0
- package/src/components/PaginationButtons/styles.ts +7 -0
- package/src/components/PaginationButtons/types.ts +26 -0
- package/src/components/PaginationIndicator/index.tsx +69 -0
- package/src/components/PaginationIndicator/styles.ts +3 -0
- package/src/components/PaginationIndicator/types.ts +18 -0
- package/src/components/Progress/Bar/index.tsx +45 -50
- package/src/components/Progress/Bar/styles.ts +10 -0
- package/src/components/Progress/Bar/types.ts +26 -0
- package/src/components/Progress/Circle/index.tsx +45 -48
- package/src/components/Progress/Circle/styles.ts +1 -8
- package/src/components/Progress/Circle/types.ts +10 -22
- package/src/components/RadioInput/index.tsx +78 -124
- package/src/components/RadioInput/styles.ts +0 -6
- package/src/components/RadioInput/types.ts +29 -0
- package/src/components/SearchInput/index.tsx +10 -10
- package/src/components/SectionFilters/index.tsx +47 -36
- package/src/components/SectionFilters/styles.ts +1 -5
- package/src/components/SectionFilters/types.ts +14 -13
- package/src/components/SegmentedControl/index.tsx +111 -89
- package/src/components/SegmentedControl/styles.ts +7 -21
- package/src/components/SegmentedControl/types.ts +44 -0
- package/src/components/Select/index.tsx +92 -56
- package/src/components/Select/styles.ts +19 -36
- package/src/components/Select/types.ts +15 -10
- package/src/components/Slider/index.tsx +85 -93
- package/src/components/Slider/styles.ts +13 -6
- package/src/components/Slider/types.ts +29 -0
- package/src/components/Switch/index.tsx +63 -74
- package/src/components/Switch/styles.ts +1 -6
- package/src/components/Switch/types.ts +13 -0
- package/src/components/Tag/index.tsx +39 -44
- package/src/components/Tag/styles.ts +1 -9
- package/src/components/Tag/types.ts +10 -10
- package/src/components/Text/index.tsx +37 -48
- package/src/components/Text/styles.ts +0 -8
- package/src/components/Text/types.ts +8 -8
- package/src/components/TextEditor/index.tsx +49 -28
- package/src/components/TextEditor/styles.ts +1 -8
- package/src/components/TextEditor/types.ts +11 -6
- package/src/components/TextInput/index.tsx +58 -96
- package/src/components/TextInput/mask.tsx +2 -50
- package/src/components/TextInput/styles.ts +3 -8
- package/src/components/TextInput/types.ts +85 -0
- package/src/components/Tooltip/index.tsx +61 -84
- package/src/components/Tooltip/styles.ts +3 -10
- package/src/components/Tooltip/types.ts +46 -0
- package/src/components/Touchable/index.tsx +43 -86
- package/src/components/Touchable/styles.ts +0 -6
- package/src/components/Touchable/types.ts +22 -0
- package/src/components/View/index.tsx +36 -50
- package/src/components/View/styles.ts +0 -6
- package/src/components/View/types.ts +14 -15
- package/src/components/components.ts +2 -3
- package/src/index.ts +1 -0
- package/src/lib/WebStyleRegistry.ts +51 -0
- package/src/lib/hooks/index.ts +5 -0
- package/src/lib/hooks/useBreakpointMatch.ts +8 -7
- package/src/{components/CropPicker/useCropPicker.tsx → lib/hooks/useCropPicker.ts} +66 -13
- package/src/lib/hooks/useFileInput.ts +15 -0
- package/src/lib/hooks/useInfiniteScroll.ts +77 -0
- package/src/lib/hooks/useMediaQuery.ts +4 -3
- package/src/lib/hooks/usePagination.ts +79 -63
- package/src/lib/hooks/useRefresh.ts +87 -0
- package/src/lib/hooks/useStylesFor.ts +13 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/utils/cache.ts +9 -0
- package/src/lib/utils/index.ts +1 -0
- package/src/lib/utils/test.ts +2 -2
- package/src/components/CropPicker/utils.ts +0 -51
- package/src/components/FileInput.tsx +0 -91
- package/src/components/List/PaginationIndicator.tsx +0 -102
- package/src/components/List/useInfiniteScroll.ts +0 -159
- package/src/components/Progress/Bar/styles.tsx +0 -7
- package/src/components/Progress/Bar/types.tsx +0 -30
- package/src/components/Scroll/index.tsx +0 -32
- package/src/components/Scroll/styles.ts +0 -8
- package/src/components/SegmentedControl/SegmentedControlOption.tsx +0 -84
- package/src/components/defaultStyles.ts +0 -79
- /package/src/components/DatePicker/{defaultComponents → components}/index.tsx +0 -0
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { BaseViewProps, BreakpointPlaceholder, ComponentVariants } from '@codeleap/common'
|
|
2
|
-
import { HTMLProps, NativeHTMLElement } from '../../types'
|
|
3
1
|
import { AnimationProps, MotionProps } from 'framer-motion'
|
|
4
|
-
import {
|
|
2
|
+
import { AnyRecord, StyledProp } from '@codeleap/styles'
|
|
3
|
+
import { ViewComposition } from './styles'
|
|
4
|
+
import { ComponentPropsWithRef, ElementType } from 'react'
|
|
5
5
|
|
|
6
|
-
export type ViewProps<T extends
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Omit<AnimationProps, 'variants'> &
|
|
6
|
+
export type ViewProps<T extends ElementType = 'div'> =
|
|
7
|
+
Omit<ComponentPropsWithRef<T>, 'style'> &
|
|
8
|
+
Omit<AnimationProps, 'style'> &
|
|
10
9
|
{
|
|
11
|
-
component?:
|
|
12
|
-
scroll?: boolean
|
|
10
|
+
component?: ElementType<AnyRecord>
|
|
13
11
|
debugName?: string
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
down?: BreakpointPlaceholder
|
|
12
|
+
is?: any
|
|
13
|
+
not?: any
|
|
14
|
+
up?: any
|
|
15
|
+
down?: any
|
|
19
16
|
onHover?: (isMouseOverElement: boolean) => void
|
|
20
17
|
animated?: boolean
|
|
21
18
|
animatedProps?: Partial<MotionProps>
|
|
22
|
-
|
|
19
|
+
style?: StyledProp<ViewComposition>
|
|
20
|
+
children?: React.ReactNode
|
|
21
|
+
}
|
|
@@ -3,7 +3,6 @@ export * from './Icon'
|
|
|
3
3
|
export * from './Touchable'
|
|
4
4
|
export * from './Text'
|
|
5
5
|
export * from './Slider'
|
|
6
|
-
export * from './Scroll'
|
|
7
6
|
export * from './List'
|
|
8
7
|
export * from './ActivityIndicator'
|
|
9
8
|
export * from './Button'
|
|
@@ -37,6 +36,6 @@ export * from './Progress'
|
|
|
37
36
|
export * from './Tag'
|
|
38
37
|
export * from './TextEditor'
|
|
39
38
|
export * from './ColorPicker'
|
|
39
|
+
export * from './PaginationButtons'
|
|
40
40
|
export * from './SectionFilters'
|
|
41
|
-
|
|
42
|
-
export * from './defaultStyles'
|
|
41
|
+
export * from './PaginationIndicator'
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AnyStyledComponent, CodeleapStyleRegistry, ICSS, StylePersistor } from '@codeleap/styles'
|
|
2
|
+
|
|
3
|
+
const persistor = new StylePersistor({
|
|
4
|
+
set(key, value) {
|
|
5
|
+
if (typeof window === 'undefined') return null
|
|
6
|
+
return localStorage?.setItem(key, value)
|
|
7
|
+
},
|
|
8
|
+
get(key) {
|
|
9
|
+
if (typeof window === 'undefined') return null
|
|
10
|
+
return localStorage?.getItem(key)
|
|
11
|
+
},
|
|
12
|
+
del(key) {
|
|
13
|
+
if (typeof window === 'undefined') return null
|
|
14
|
+
return localStorage?.removeItem(key)
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
let instance: WebStyleRegistry
|
|
19
|
+
|
|
20
|
+
const components: CodeleapStyleRegistry['components'][string][] = []
|
|
21
|
+
|
|
22
|
+
export class WebStyleRegistry extends CodeleapStyleRegistry {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(persistor)
|
|
25
|
+
|
|
26
|
+
components.forEach((component) => {
|
|
27
|
+
this.registerComponent(component)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (!instance) {
|
|
31
|
+
instance = this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return instance
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createStyle(css: ICSS): ICSS {
|
|
38
|
+
return css
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static get current() {
|
|
42
|
+
return instance
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static registerComponent(component: AnyStyledComponent) {
|
|
46
|
+
components.push(component)
|
|
47
|
+
if (instance) {
|
|
48
|
+
instance.registerComponent(component)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/lib/hooks/index.ts
CHANGED
|
@@ -14,3 +14,8 @@ export * from './useStaticAnimationStyles'
|
|
|
14
14
|
export * from './useWindowFocus'
|
|
15
15
|
export * from './useWindowSize'
|
|
16
16
|
export * from './useKeydown'
|
|
17
|
+
export * from './useInfiniteScroll'
|
|
18
|
+
export * from './useRefresh'
|
|
19
|
+
export * from './useCropPicker'
|
|
20
|
+
export * from './useFileInput'
|
|
21
|
+
export * from './useStylesFor'
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
|
-
import { TypeGuards
|
|
2
|
+
import { TypeGuards } from '@codeleap/common'
|
|
3
|
+
import { AppTheme, Theme, useTheme } from '@codeleap/styles'
|
|
3
4
|
import { useMediaQuery } from './useMediaQuery'
|
|
4
5
|
|
|
5
6
|
export type BreakpointsMatch<T extends string = string> = Record<T, any>
|
|
6
7
|
|
|
7
8
|
export function useBreakpointMatch<T extends string = string>(values: Partial<BreakpointsMatch<T>>) {
|
|
8
|
-
const
|
|
9
|
+
const theme = useTheme(store => store.current) as AppTheme<Theme>
|
|
9
10
|
|
|
10
|
-
const themeBreakpoints: Record<string, number> =
|
|
11
|
+
const themeBreakpoints: Record<string, number> = theme?.breakpoints ?? {}
|
|
11
12
|
|
|
12
13
|
const breakpoints: Record<string, number> = useMemo(() => {
|
|
13
|
-
|
|
14
|
+
let breaks = Object.entries(themeBreakpoints)
|
|
14
15
|
|
|
15
|
-
breaks?.sort((a, b) => a?.[1] - b?.[1])
|
|
16
|
+
breaks = breaks?.sort((a, b) => a?.[1] - b?.[1])
|
|
16
17
|
|
|
17
18
|
const sortBreakpoints = Object.fromEntries(breaks)
|
|
18
19
|
|
|
@@ -28,8 +29,8 @@ export function useBreakpointMatch<T extends string = string>(values: Partial<Br
|
|
|
28
29
|
const breakpointMatches = {}
|
|
29
30
|
|
|
30
31
|
for (const breakpoint in breakpoints) {
|
|
31
|
-
const matchesDown = useMediaQuery(
|
|
32
|
-
const matchesUp = useMediaQuery(
|
|
32
|
+
const matchesDown = useMediaQuery(theme?.media?.down(breakpoint as never), { getInitialValueInEffect: false })
|
|
33
|
+
const matchesUp = useMediaQuery(theme?.media?.up(breakpoint as never), { getInitialValueInEffect: false })
|
|
33
34
|
|
|
34
35
|
breakpointMatches[breakpoint] = !matchesUp && matchesDown
|
|
35
36
|
}
|
|
@@ -7,10 +7,63 @@ import {
|
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
9
|
} from '@codeleap/common'
|
|
10
|
-
import { ImageReading
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
10
|
+
import { ImageReading } from '../../components/CropPicker'
|
|
11
|
+
import { FileInputProps, FileInputRef } from '../../components/FileInput'
|
|
12
|
+
import { Crop, ReactCropProps } from 'react-image-crop'
|
|
13
|
+
|
|
14
|
+
export type UseCropPickerProps = Partial<ReactCropProps> & {
|
|
15
|
+
onFileSelect: FileInputProps['onFileSelect']
|
|
16
|
+
ref: React.MutableRefObject<FileInputRef> | React.Ref<FileInputRef>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readImage(file: File | Blob): Promise<ImageReading> {
|
|
20
|
+
const reader = new FileReader()
|
|
21
|
+
return new Promise<ImageReading>((resolve) => {
|
|
22
|
+
reader.onload = () => {
|
|
23
|
+
const image = new Image()
|
|
24
|
+
image.onload = () => resolve(image)
|
|
25
|
+
image.src = reader.result as string
|
|
26
|
+
}
|
|
27
|
+
reader.readAsDataURL(file)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function cropImage(image: ImageReading, crop: Crop): Promise<[string, Blob]> {
|
|
32
|
+
const canvas = document.createElement('canvas')
|
|
33
|
+
const ctx = canvas.getContext('2d', { alpha: true })
|
|
34
|
+
|
|
35
|
+
if (!ctx) throw new Error('No 2d context')
|
|
36
|
+
|
|
37
|
+
canvas.width = image.naturalWidth * (crop.width / 100)
|
|
38
|
+
canvas.height = image.naturalHeight * (crop.height / 100)
|
|
39
|
+
|
|
40
|
+
const x = image.naturalWidth * (crop.x / 100)
|
|
41
|
+
const y = image.naturalHeight * (crop.y / 100)
|
|
42
|
+
|
|
43
|
+
ctx.drawImage(
|
|
44
|
+
image,
|
|
45
|
+
x,
|
|
46
|
+
y,
|
|
47
|
+
canvas.width,
|
|
48
|
+
canvas.height,
|
|
49
|
+
0,
|
|
50
|
+
0,
|
|
51
|
+
canvas.width,
|
|
52
|
+
canvas.height,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return new Promise<[string, Blob]>((resolve, reject) => {
|
|
56
|
+
canvas.toBlob(blob => {
|
|
57
|
+
if (!blob) {
|
|
58
|
+
reject(new Error('Canvas is empty'))
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
readImage(blob).then(cropped => {
|
|
62
|
+
resolve([cropped.src, blob])
|
|
63
|
+
}).catch(reject)
|
|
64
|
+
}, 'image/png')
|
|
65
|
+
})
|
|
66
|
+
}
|
|
14
67
|
|
|
15
68
|
export function useCropPicker({
|
|
16
69
|
onFileSelect,
|
|
@@ -61,15 +114,15 @@ export function useCropPicker({
|
|
|
61
114
|
const { naturalWidth, naturalHeight } = imageData
|
|
62
115
|
const imageAspect = naturalWidth / naturalHeight
|
|
63
116
|
const v =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
imageAspect >= aspect
|
|
118
|
+
? {
|
|
119
|
+
width: ((naturalHeight * aspect) / naturalWidth) * 100,
|
|
120
|
+
height: 100,
|
|
121
|
+
}
|
|
122
|
+
: {
|
|
123
|
+
width: 100,
|
|
124
|
+
height: (naturalWidth / aspect / naturalHeight) * 100,
|
|
125
|
+
}
|
|
73
126
|
const initialCrop: Crop = {
|
|
74
127
|
...v,
|
|
75
128
|
x: (100 - v.width) / 2,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
import { FileInputRef } from '../../components/FileInput/types'
|
|
3
|
+
|
|
4
|
+
export const useFileInput = () => {
|
|
5
|
+
const inputRef = useRef<FileInputRef | null>(null)
|
|
6
|
+
|
|
7
|
+
const openFilePicker = () => {
|
|
8
|
+
return inputRef.current?.openFilePicker()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
openFilePicker,
|
|
13
|
+
ref: inputRef,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { AnyFunction, TypeGuards } from '@codeleap/common'
|
|
3
|
+
import { LoadMoreItemsCallback, UseInfiniteLoaderOptions, useInfiniteLoader } from 'masonic'
|
|
4
|
+
import { ListProps } from '../../components/List'
|
|
5
|
+
import { GridProps } from '../../components/Grid'
|
|
6
|
+
import { useRefresh } from './useRefresh'
|
|
7
|
+
|
|
8
|
+
export type UseInfiniteScrollArgs<Item extends Element = any> = {
|
|
9
|
+
threshold?: number
|
|
10
|
+
onLoadMore?: AnyFunction
|
|
11
|
+
loadMoreOptions?: Partial<UseInfiniteLoaderOptions<Item>>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type UseInfiniteScrollProps<Item extends Element = any> =
|
|
15
|
+
Partial<ListProps> &
|
|
16
|
+
Partial<GridProps> &
|
|
17
|
+
UseInfiniteScrollArgs<Item>
|
|
18
|
+
|
|
19
|
+
export type UseInfiniteScrollReturn<Item extends Element = any> = {
|
|
20
|
+
onLoadMore: LoadMoreItemsCallback<Item>
|
|
21
|
+
isRefresh: boolean
|
|
22
|
+
layoutProps: {
|
|
23
|
+
isEmpty: boolean
|
|
24
|
+
refreshing: boolean
|
|
25
|
+
scrollableRef: React.MutableRefObject<undefined>
|
|
26
|
+
}
|
|
27
|
+
onRefreshItems: AnyFunction
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useInfiniteScroll<Item extends Element = any>(props: UseInfiniteScrollProps<Item>): UseInfiniteScrollReturn<Item> {
|
|
31
|
+
const {
|
|
32
|
+
onRefresh,
|
|
33
|
+
data,
|
|
34
|
+
hasNextPage,
|
|
35
|
+
refresh: refreshEnabled,
|
|
36
|
+
fetchNextPage,
|
|
37
|
+
refreshThreshold,
|
|
38
|
+
refreshDebounce,
|
|
39
|
+
loadMoreOptions = {},
|
|
40
|
+
onLoadMore,
|
|
41
|
+
threshold = 16,
|
|
42
|
+
} = props
|
|
43
|
+
|
|
44
|
+
const infiniteLoader = useInfiniteLoader(
|
|
45
|
+
async (args) => {
|
|
46
|
+
if (hasNextPage) await fetchNextPage?.()
|
|
47
|
+
if (TypeGuards.isFunction(onLoadMore)) await onLoadMore?.(args)
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
isItemLoaded: (index, items) => !!items?.[index],
|
|
51
|
+
threshold: threshold,
|
|
52
|
+
...loadMoreOptions,
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const refreshHookReturn = useRefresh(
|
|
57
|
+
onRefresh,
|
|
58
|
+
{
|
|
59
|
+
threshold: refreshThreshold,
|
|
60
|
+
debounce: refreshDebounce,
|
|
61
|
+
enabled: refreshEnabled,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const isEmpty = React.useMemo(() => (!data || !data?.length), [data?.length])
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
onLoadMore: infiniteLoader,
|
|
69
|
+
isRefresh: refreshHookReturn.refresh,
|
|
70
|
+
layoutProps: {
|
|
71
|
+
scrollableRef: refreshHookReturn.scrollableRef,
|
|
72
|
+
refreshing: refreshHookReturn.refresh,
|
|
73
|
+
isEmpty,
|
|
74
|
+
},
|
|
75
|
+
onRefreshItems: refreshHookReturn.refresher,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -16,17 +16,18 @@ export function useMediaQuery(
|
|
|
16
16
|
} = queryOptions
|
|
17
17
|
|
|
18
18
|
const _query = useMemo(() => {
|
|
19
|
-
|
|
19
|
+
if (!query) return ''
|
|
20
|
+
return query?.trim()?.replace('@media screen and ', '')
|
|
20
21
|
}, [query])
|
|
21
22
|
|
|
22
23
|
const [matches, setMatches] = useState(
|
|
23
|
-
getInitialValueInEffect ? initialValue : isMediaQuery(query, initialValue),
|
|
24
|
+
(getInitialValueInEffect || !query) ? initialValue : isMediaQuery(query, initialValue),
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
const queryRef = useRef<MediaQueryList>()
|
|
27
28
|
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
if (query
|
|
30
|
+
if (query?.trim() === '' || !query) return
|
|
30
31
|
|
|
31
32
|
if ('matchMedia' in window) {
|
|
32
33
|
queryRef.current = window.matchMedia(_query)
|
|
@@ -1,49 +1,65 @@
|
|
|
1
1
|
import { range, useMemo, useUncontrolled } from '@codeleap/common'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
/** Page selected on initial render, defaults to 1 */
|
|
3
|
+
export type PaginationParams = {
|
|
5
4
|
initialPage?: number
|
|
6
|
-
|
|
7
|
-
/** Controlled active page number */
|
|
8
5
|
page?: number
|
|
9
|
-
|
|
10
|
-
/** Total amount of pages */
|
|
11
6
|
total: number
|
|
7
|
+
boundaries?: number
|
|
8
|
+
onChangePage?: (page: number) => void
|
|
9
|
+
shouldAbbreviate?: boolean
|
|
10
|
+
abbreviationMinimumAmount?: number
|
|
11
|
+
displayLeftArrow?: boolean
|
|
12
|
+
displayRightArrow?: boolean
|
|
13
|
+
isMobile?: boolean
|
|
14
|
+
abbreviationSymbol?: any
|
|
15
|
+
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
siblings?: number
|
|
17
|
+
export function usePagination(props: PaginationParams) {
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
boundaries?: number
|
|
19
|
+
const { isMobile } = props
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const {
|
|
22
|
+
total,
|
|
23
|
+
boundaries = 2,
|
|
24
|
+
initialPage = 1,
|
|
25
|
+
page,
|
|
26
|
+
onChangePage,
|
|
27
|
+
shouldAbbreviate = true,
|
|
28
|
+
abbreviationMinimumAmount = isMobile ? 5 : 10,
|
|
29
|
+
displayLeftArrow = true,
|
|
30
|
+
displayRightArrow = true,
|
|
31
|
+
abbreviationSymbol,
|
|
32
|
+
} = props
|
|
22
33
|
|
|
23
|
-
export function usePagination({
|
|
24
|
-
total,
|
|
25
|
-
siblings = 1,
|
|
26
|
-
boundaries = 1,
|
|
27
|
-
page,
|
|
28
|
-
initialPage = 1,
|
|
29
|
-
onChange,
|
|
30
|
-
}: PaginationParams) {
|
|
31
34
|
const [activePage, setActivePage] = useUncontrolled({
|
|
32
35
|
value: page,
|
|
33
|
-
onChange,
|
|
36
|
+
onChange: onChangePage,
|
|
34
37
|
defaultValue: initialPage,
|
|
35
38
|
finalValue: initialPage,
|
|
36
39
|
rule: (_page) => typeof _page === 'number' && _page <= total,
|
|
37
40
|
})
|
|
38
41
|
|
|
42
|
+
const _boundaries = isMobile ? 2 : boundaries
|
|
43
|
+
|
|
44
|
+
const canAbreviateItems = (shouldAbbreviate || isMobile) && total > abbreviationMinimumAmount
|
|
45
|
+
const displayLastNumbers = activePage + _boundaries + (isMobile ? 0 : 2) >= total
|
|
46
|
+
const isCenterSelected = canAbreviateItems && activePage > _boundaries && !displayLastNumbers
|
|
47
|
+
|
|
48
|
+
const dotsDisplay = isCenterSelected ? abbreviationSymbol : null
|
|
49
|
+
|
|
39
50
|
const setPage = (pageNumber: number) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
|
|
52
|
+
const isPreviousArrow = pageNumber === 0
|
|
53
|
+
const isNextArrow = pageNumber === total + 1
|
|
54
|
+
|
|
55
|
+
const nonSelectableItems = [
|
|
56
|
+
displayLeftArrow && isPreviousArrow,
|
|
57
|
+
displayRightArrow && isNextArrow,
|
|
58
|
+
].some(x => x)
|
|
59
|
+
|
|
60
|
+
if (nonSelectableItems) return activePage
|
|
61
|
+
|
|
62
|
+
setActivePage(pageNumber)
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
const next = () => setPage(activePage + 1)
|
|
@@ -51,54 +67,54 @@ export function usePagination({
|
|
|
51
67
|
const first = () => setPage(1)
|
|
52
68
|
const last = () => setPage(total)
|
|
53
69
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const totalPageNumbers = siblings * 2 + 3 + boundaries * 2
|
|
70
|
+
const status = useMemo(() => {
|
|
71
|
+
if (isCenterSelected) {
|
|
72
|
+
return 'abreviated'
|
|
73
|
+
}
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* paginationComponent, we return the range [1..total]
|
|
63
|
-
*/
|
|
64
|
-
if (totalPageNumbers >= total) {
|
|
65
|
-
return range(1, total)
|
|
75
|
+
if (displayLastNumbers) {
|
|
76
|
+
return 'end'
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
return 'initial'
|
|
80
|
+
}, [isCenterSelected, displayLastNumbers])
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
* We do not want to show dots if there is only one position left
|
|
73
|
-
* after/before the left/right page count as that would lead to a change if our Pagination
|
|
74
|
-
* component size which we do not want
|
|
75
|
-
*/
|
|
76
|
-
const shouldShowLeftDots = leftSiblingIndex > boundaries + 2
|
|
77
|
-
const shouldShowRightDots = rightSiblingIndex < total - (boundaries + 1)
|
|
82
|
+
const paginationRange = useMemo((): (number | string | '...')[] => {
|
|
78
83
|
|
|
79
|
-
if (!
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
if (!canAbreviateItems) {
|
|
85
|
+
return [
|
|
86
|
+
...range(1, total),
|
|
87
|
+
].filter(Boolean)
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
if (displayLastNumbers) {
|
|
91
|
+
|
|
92
|
+
const extraItems = [
|
|
93
|
+
1,
|
|
94
|
+
abbreviationSymbol,
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
return [
|
|
98
|
+
...extraItems,
|
|
99
|
+
...range(total - (_boundaries + extraItems?.length - (isMobile ? 2 : 0)), total),
|
|
100
|
+
].filter(Boolean)
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
return [
|
|
90
|
-
...range(1,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
...range(total -
|
|
95
|
-
]
|
|
96
|
-
|
|
104
|
+
...range(1, isCenterSelected ? _boundaries - 1 : _boundaries),
|
|
105
|
+
dotsDisplay,
|
|
106
|
+
isCenterSelected ? activePage : abbreviationSymbol,
|
|
107
|
+
dotsDisplay,
|
|
108
|
+
...range(total - _boundaries + (isCenterSelected ? 2 : 1), total),
|
|
109
|
+
].filter(Boolean)
|
|
110
|
+
|
|
111
|
+
}, [total, activePage, displayLastNumbers, isCenterSelected, canAbreviateItems])
|
|
97
112
|
|
|
98
113
|
return {
|
|
99
114
|
range: paginationRange,
|
|
100
|
-
|
|
115
|
+
page: Number(activePage),
|
|
101
116
|
setPage,
|
|
117
|
+
status,
|
|
102
118
|
next,
|
|
103
119
|
previous,
|
|
104
120
|
first,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { AnyFunction, useEffect } from '@codeleap/common'
|
|
3
|
+
|
|
4
|
+
type UseRefreshOptions = {
|
|
5
|
+
threshold: number
|
|
6
|
+
debounce: number
|
|
7
|
+
enabled: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const scrollDebounce = (func, delay) => {
|
|
11
|
+
const timerRef = React.useRef(null)
|
|
12
|
+
|
|
13
|
+
const scrollDebounce = (...args) => {
|
|
14
|
+
clearTimeout(timerRef.current)
|
|
15
|
+
timerRef.current = setTimeout(() => {
|
|
16
|
+
func(...args)
|
|
17
|
+
}, delay)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return scrollDebounce
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useRefresh = (onRefresh = () => null, options: UseRefreshOptions) => {
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
threshold,
|
|
27
|
+
debounce,
|
|
28
|
+
enabled,
|
|
29
|
+
} = options
|
|
30
|
+
|
|
31
|
+
const [refresh, setRefresh] = React.useState(false)
|
|
32
|
+
|
|
33
|
+
const pushToTopRef = React.useRef(0)
|
|
34
|
+
|
|
35
|
+
const refresher = React.useCallback(async (_onRefresh: AnyFunction) => {
|
|
36
|
+
setRefresh(true)
|
|
37
|
+
await _onRefresh?.()
|
|
38
|
+
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
setRefresh(false)
|
|
41
|
+
pushToTopRef.current = 0
|
|
42
|
+
}, 2500)
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
const containerRef = React.useRef(null)
|
|
46
|
+
|
|
47
|
+
const onScroll = scrollDebounce(() => {
|
|
48
|
+
if (containerRef.current) {
|
|
49
|
+
const rect = containerRef.current?.getBoundingClientRect()
|
|
50
|
+
const scrollTop = window?.pageYOffset || document?.documentElement?.scrollTop
|
|
51
|
+
|
|
52
|
+
const containerTop = rect.top + scrollTop
|
|
53
|
+
const containerHeight = rect.height
|
|
54
|
+
|
|
55
|
+
const distanceFromTop = Math.max(0, scrollTop - containerTop)
|
|
56
|
+
const distanceFromBottom = Math.max(0, containerTop + containerHeight - scrollTop)
|
|
57
|
+
|
|
58
|
+
const totalDistance = containerHeight + distanceFromTop + distanceFromBottom
|
|
59
|
+
const percentage = (distanceFromTop / totalDistance) * 100
|
|
60
|
+
|
|
61
|
+
if (percentage < threshold) {
|
|
62
|
+
if (pushToTopRef.current === 2) {
|
|
63
|
+
refresher(onRefresh)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pushToTopRef.current = pushToTopRef.current + 1
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, debounce)
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (enabled) {
|
|
73
|
+
window.addEventListener('scroll', onScroll)
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
window.removeEventListener('scroll', onScroll)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [enabled])
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
refresh,
|
|
83
|
+
scrollableRef: containerRef,
|
|
84
|
+
refresher,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StyleProp, useStyleObserver } from '@codeleap/styles'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { WebStyleRegistry } from '../WebStyleRegistry'
|
|
4
|
+
|
|
5
|
+
export const useStylesFor = <T = unknown>(componentName: string, style: StyleProp<T, string>): T => {
|
|
6
|
+
const styleObserver = useStyleObserver(style)
|
|
7
|
+
|
|
8
|
+
const styles = useMemo(() => {
|
|
9
|
+
return WebStyleRegistry.current.styleFor(componentName, style)
|
|
10
|
+
}, [styleObserver])
|
|
11
|
+
|
|
12
|
+
return styles
|
|
13
|
+
}
|
package/src/lib/index.ts
CHANGED
package/src/lib/utils/index.ts
CHANGED