@codeleap/web 3.24.2 → 3.25.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.
Files changed (150) hide show
  1. package/package.json +2 -1
  2. package/src/components/ActionIcon/index.tsx +51 -52
  3. package/src/components/ActionIcon/styles.ts +1 -5
  4. package/src/components/ActionIcon/types.ts +15 -0
  5. package/src/components/ActivityIndicator/index.tsx +34 -55
  6. package/src/components/ActivityIndicator/styles.ts +0 -6
  7. package/src/components/ActivityIndicator/types.ts +12 -0
  8. package/src/components/Badge/index.tsx +43 -80
  9. package/src/components/Badge/styles.ts +1 -11
  10. package/src/components/Badge/types.ts +28 -0
  11. package/src/components/Button/index.tsx +46 -89
  12. package/src/components/Button/styles.ts +0 -5
  13. package/src/components/Button/types.ts +25 -0
  14. package/src/components/Checkbox/index.tsx +83 -97
  15. package/src/components/Checkbox/styles.ts +1 -5
  16. package/src/components/Checkbox/types.ts +15 -0
  17. package/src/components/Collapse/index.tsx +41 -83
  18. package/src/components/Collapse/styles.ts +3 -6
  19. package/src/components/Collapse/types.ts +11 -0
  20. package/src/components/ColorPicker/index.tsx +95 -48
  21. package/src/components/ColorPicker/styles.ts +11 -11
  22. package/src/components/ColorPicker/types.ts +26 -12
  23. package/src/components/CropPicker/index.tsx +100 -102
  24. package/src/components/CropPicker/styles.ts +0 -7
  25. package/src/components/CropPicker/types.ts +10 -15
  26. package/src/components/DatePicker/{defaultComponents → components}/Header.tsx +9 -17
  27. package/src/components/DatePicker/{defaultComponents → components}/OuterInput.tsx +6 -7
  28. package/src/components/DatePicker/index.tsx +110 -124
  29. package/src/components/DatePicker/styles.ts +1 -12
  30. package/src/components/DatePicker/types.ts +16 -33
  31. package/src/components/Drawer/index.tsx +133 -125
  32. package/src/components/Drawer/styles.ts +0 -5
  33. package/src/components/Drawer/types.ts +23 -0
  34. package/src/components/Dropzone/index.tsx +87 -63
  35. package/src/components/Dropzone/styles.ts +0 -6
  36. package/src/components/Dropzone/types.ts +29 -37
  37. package/src/components/EmptyPlaceholder/index.tsx +63 -83
  38. package/src/components/EmptyPlaceholder/styles.ts +0 -5
  39. package/src/components/EmptyPlaceholder/types.ts +32 -0
  40. package/src/components/FileInput/index.tsx +72 -0
  41. package/src/components/FileInput/types.ts +14 -0
  42. package/src/components/Grid/index.tsx +40 -41
  43. package/src/components/Grid/styles.ts +2 -9
  44. package/src/components/Grid/types.ts +10 -12
  45. package/src/components/Icon/index.tsx +45 -47
  46. package/src/components/Icon/styles.ts +0 -8
  47. package/src/components/Icon/types.ts +15 -0
  48. package/src/components/InputBase/index.tsx +71 -42
  49. package/src/components/InputBase/styles.ts +37 -47
  50. package/src/components/InputBase/types.ts +19 -7
  51. package/src/components/InputBase/utils.ts +3 -23
  52. package/src/components/List/ListLayout.tsx +20 -37
  53. package/src/components/List/index.tsx +36 -41
  54. package/src/components/List/styles.ts +5 -11
  55. package/src/components/List/types.ts +30 -20
  56. package/src/components/LoadingOverlay/index.tsx +31 -33
  57. package/src/components/LoadingOverlay/styles.ts +3 -8
  58. package/src/components/LoadingOverlay/types.ts +16 -0
  59. package/src/components/Modal/index.tsx +98 -160
  60. package/src/components/Modal/styles.ts +0 -5
  61. package/src/components/Modal/types.ts +55 -0
  62. package/src/components/NumberIncrement/index.tsx +67 -98
  63. package/src/components/NumberIncrement/styles.ts +0 -5
  64. package/src/components/NumberIncrement/types.ts +29 -0
  65. package/src/components/Overlay/index.tsx +37 -35
  66. package/src/components/Overlay/styles.ts +3 -5
  67. package/src/components/Overlay/types.ts +13 -0
  68. package/src/components/Pager/index.tsx +65 -81
  69. package/src/components/Pager/styles.ts +3 -9
  70. package/src/components/Pager/types.ts +35 -0
  71. package/src/components/PaginationButtons/index.tsx +173 -0
  72. package/src/components/PaginationButtons/styles.ts +7 -0
  73. package/src/components/PaginationButtons/types.ts +26 -0
  74. package/src/components/PaginationIndicator/index.tsx +69 -0
  75. package/src/components/PaginationIndicator/styles.ts +3 -0
  76. package/src/components/PaginationIndicator/types.ts +18 -0
  77. package/src/components/Progress/Bar/index.tsx +45 -50
  78. package/src/components/Progress/Bar/styles.ts +10 -0
  79. package/src/components/Progress/Bar/types.ts +26 -0
  80. package/src/components/Progress/Circle/index.tsx +45 -48
  81. package/src/components/Progress/Circle/styles.ts +1 -8
  82. package/src/components/Progress/Circle/types.ts +10 -22
  83. package/src/components/RadioInput/index.tsx +78 -124
  84. package/src/components/RadioInput/styles.ts +0 -6
  85. package/src/components/RadioInput/types.ts +29 -0
  86. package/src/components/SearchInput/index.tsx +10 -10
  87. package/src/components/SectionFilters/index.tsx +47 -36
  88. package/src/components/SectionFilters/styles.ts +1 -5
  89. package/src/components/SectionFilters/types.ts +14 -13
  90. package/src/components/SegmentedControl/index.tsx +111 -89
  91. package/src/components/SegmentedControl/styles.ts +7 -21
  92. package/src/components/SegmentedControl/types.ts +44 -0
  93. package/src/components/Select/index.tsx +92 -56
  94. package/src/components/Select/styles.ts +19 -36
  95. package/src/components/Select/types.ts +15 -10
  96. package/src/components/Slider/index.tsx +85 -93
  97. package/src/components/Slider/styles.ts +13 -6
  98. package/src/components/Slider/types.ts +29 -0
  99. package/src/components/Switch/index.tsx +63 -74
  100. package/src/components/Switch/styles.ts +1 -6
  101. package/src/components/Switch/types.ts +13 -0
  102. package/src/components/Tag/index.tsx +39 -44
  103. package/src/components/Tag/styles.ts +1 -9
  104. package/src/components/Tag/types.ts +10 -10
  105. package/src/components/Text/index.tsx +37 -48
  106. package/src/components/Text/styles.ts +0 -8
  107. package/src/components/Text/types.ts +8 -8
  108. package/src/components/TextEditor/index.tsx +49 -28
  109. package/src/components/TextEditor/styles.ts +1 -8
  110. package/src/components/TextEditor/types.ts +11 -6
  111. package/src/components/TextInput/index.tsx +58 -96
  112. package/src/components/TextInput/mask.tsx +2 -50
  113. package/src/components/TextInput/styles.ts +3 -8
  114. package/src/components/TextInput/types.ts +85 -0
  115. package/src/components/Tooltip/index.tsx +61 -84
  116. package/src/components/Tooltip/styles.ts +3 -10
  117. package/src/components/Tooltip/types.ts +46 -0
  118. package/src/components/Touchable/index.tsx +43 -86
  119. package/src/components/Touchable/styles.ts +0 -6
  120. package/src/components/Touchable/types.ts +22 -0
  121. package/src/components/View/index.tsx +36 -50
  122. package/src/components/View/styles.ts +0 -6
  123. package/src/components/View/types.ts +14 -15
  124. package/src/components/components.ts +2 -3
  125. package/src/index.ts +1 -0
  126. package/src/lib/WebStyleRegistry.ts +51 -0
  127. package/src/lib/hooks/index.ts +5 -0
  128. package/src/lib/hooks/useBreakpointMatch.ts +8 -7
  129. package/src/{components/CropPicker/useCropPicker.tsx → lib/hooks/useCropPicker.ts} +66 -13
  130. package/src/lib/hooks/useFileInput.ts +15 -0
  131. package/src/lib/hooks/useInfiniteScroll.ts +77 -0
  132. package/src/lib/hooks/useMediaQuery.ts +4 -3
  133. package/src/lib/hooks/usePagination.ts +79 -63
  134. package/src/lib/hooks/useRefresh.ts +87 -0
  135. package/src/lib/hooks/useStylesFor.ts +13 -0
  136. package/src/lib/index.ts +1 -0
  137. package/src/lib/utils/cache.ts +9 -0
  138. package/src/lib/utils/index.ts +1 -0
  139. package/src/lib/utils/test.ts +2 -2
  140. package/src/components/CropPicker/utils.ts +0 -51
  141. package/src/components/FileInput.tsx +0 -91
  142. package/src/components/List/PaginationIndicator.tsx +0 -102
  143. package/src/components/List/useInfiniteScroll.ts +0 -159
  144. package/src/components/Progress/Bar/styles.tsx +0 -7
  145. package/src/components/Progress/Bar/types.tsx +0 -30
  146. package/src/components/Scroll/index.tsx +0 -32
  147. package/src/components/Scroll/styles.ts +0 -8
  148. package/src/components/SegmentedControl/SegmentedControlOption.tsx +0 -84
  149. package/src/components/defaultStyles.ts +0 -79
  150. /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 { ViewPresets } from './styles'
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 NativeHTMLElement> =
7
- HTMLProps<T> &
8
- ComponentVariants<typeof ViewPresets> &
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?: T
12
- scroll?: boolean
10
+ component?: ElementType<AnyRecord>
13
11
  debugName?: string
14
- debug?: boolean
15
- is?: BreakpointPlaceholder
16
- not?: BreakpointPlaceholder
17
- up?: BreakpointPlaceholder
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
- } & BaseViewProps
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
@@ -1,3 +1,4 @@
1
1
  export * from './components/components'
2
2
  export * from './types'
3
3
  export * from './lib'
4
+ export * from './lib/WebStyleRegistry'
@@ -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
+ }
@@ -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, useCodeleapContext } from '@codeleap/common'
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 { Theme } = useCodeleapContext()
9
+ const theme = useTheme(store => store.current) as AppTheme<Theme>
9
10
 
10
- const themeBreakpoints: Record<string, number> = Theme?.breakpoints
11
+ const themeBreakpoints: Record<string, number> = theme?.breakpoints ?? {}
11
12
 
12
13
  const breakpoints: Record<string, number> = useMemo(() => {
13
- const breaks = Object.entries(themeBreakpoints)
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(Theme.media.down(breakpoint), { getInitialValueInEffect: false })
32
- const matchesUp = useMediaQuery(Theme.media.up(breakpoint), { getInitialValueInEffect: false })
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, UseCropPickerProps } from './types'
11
- import { Crop } from 'react-image-crop'
12
- import { cropImage, readImage } from './utils'
13
- import { FileInputRef } from '../FileInput'
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
- imageAspect >= aspect
65
- ? {
66
- width: ((naturalHeight * aspect) / naturalWidth) * 100,
67
- height: 100,
68
- }
69
- : {
70
- width: 100,
71
- height: (naturalWidth / aspect / naturalHeight) * 100,
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
- return query.trim().replace('@media screen and ', '')
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.trim() === '') return
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 interface PaginationParams {
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
- /** Siblings amount on left/right side of selected page, defaults to 1 */
14
- siblings?: number
17
+ export function usePagination(props: PaginationParams) {
15
18
 
16
- /** Amount of elements visible on left/right edges, defaults to 1 */
17
- boundaries?: number
19
+ const { isMobile } = props
18
20
 
19
- /** Callback fired after change of each page */
20
- onChange?: (page: number) => void
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
- if (pageNumber <= 0) {
41
- setActivePage(1)
42
- } else if (pageNumber > total) {
43
- setActivePage(total)
44
- } else {
45
- setActivePage(pageNumber)
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 DOTS = 'dots'
55
-
56
- const paginationRange = useMemo((): (number | 'dots')[] => {
57
- // Pages count is determined as siblings (left/right) + boundaries(left/right) + currentPage + 2*DOTS
58
- const totalPageNumbers = siblings * 2 + 3 + boundaries * 2
70
+ const status = useMemo(() => {
71
+ if (isCenterSelected) {
72
+ return 'abreviated'
73
+ }
59
74
 
60
- /*
61
- * If the number of pages is less than the page numbers we want to show in our
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
- const leftSiblingIndex = Math.max(activePage - siblings, boundaries)
69
- const rightSiblingIndex = Math.min(activePage + siblings, total - boundaries)
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 (!shouldShowLeftDots && shouldShowRightDots) {
80
- const leftItemCount = siblings * 2 + boundaries + 2
81
- return [...range(1, leftItemCount), DOTS, ...range(total - (boundaries - 1), total)]
84
+ if (!canAbreviateItems) {
85
+ return [
86
+ ...range(1, total),
87
+ ].filter(Boolean)
82
88
  }
83
89
 
84
- if (shouldShowLeftDots && !shouldShowRightDots) {
85
- const rightItemCount = boundaries + 1 + 2 * siblings
86
- return [...range(1, boundaries), DOTS, ...range(total - rightItemCount, total)]
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, boundaries),
91
- DOTS,
92
- ...range(leftSiblingIndex, rightSiblingIndex),
93
- DOTS,
94
- ...range(total - boundaries + 1, total),
95
- ]
96
- }, [total, siblings, activePage])
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
- active: activePage,
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
@@ -2,3 +2,4 @@ export * from './hooks'
2
2
  export * from './tools'
3
3
  export * from './utils'
4
4
  export * from './ListMasonry'
5
+ export * from './WebStyleRegistry'
@@ -0,0 +1,9 @@
1
+ import createCache from '@emotion/cache'
2
+
3
+ export const createCodeleapWebCache = () => {
4
+ return createCache({
5
+ key: 'codeleap-web',
6
+ })
7
+ }
8
+
9
+ export const codeleapWebCache = createCodeleapWebCache()
@@ -1,3 +1,4 @@
1
1
  export * from './pollyfils/scroll'
2
2
  export * from './stopPropagation'
3
3
  export * from './test'
4
+ export * from './cache'