@codeleap/web 3.23.2 → 3.23.3

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 (36) hide show
  1. package/package.json +1 -8
  2. package/src/components/Modal/index.tsx +1 -1
  3. package/src/components/TextInput/index.tsx +1 -1
  4. package/src/components/Touchable/index.tsx +1 -1
  5. package/src/components/View/index.tsx +1 -1
  6. package/src/index.ts +1 -3
  7. package/src/lib/hooks/index.ts +16 -0
  8. package/src/lib/{useAnimatedStyle.ts → hooks/useAnimatedStyle.ts} +3 -4
  9. package/src/lib/hooks/useAnimatedVariantStyles.ts +29 -0
  10. package/src/lib/{useBreakpointMatch.ts → hooks/useBreakpointMatch.ts} +5 -5
  11. package/src/lib/{useClick.ts → hooks/useClick.ts} +3 -3
  12. package/src/lib/hooks/useClickOutside.ts +31 -0
  13. package/src/lib/{keyboard.ts → hooks/useKeydown.ts} +6 -5
  14. package/src/lib/{utils → hooks}/useMaxContentWidth.ts +1 -3
  15. package/src/lib/hooks/useMediaQuery.ts +41 -0
  16. package/src/lib/hooks/usePageExitBlocker.ts +44 -0
  17. package/src/lib/hooks/usePagination.ts +107 -0
  18. package/src/lib/{usePopState.ts → hooks/usePopState.ts} +0 -1
  19. package/src/lib/hooks/useScrollEffect.ts +19 -0
  20. package/src/lib/hooks/useStaticAnimationStyles.ts +14 -0
  21. package/src/lib/hooks/useWindowFocus.ts +33 -0
  22. package/src/lib/hooks/useWindowSize.ts +21 -0
  23. package/src/lib/index.ts +2 -10
  24. package/src/lib/tools/index.ts +4 -0
  25. package/src/lib/{localStorage.ts → tools/localStorage.ts} +4 -4
  26. package/src/lib/tools/mediaQuery.ts +25 -0
  27. package/src/lib/utils/index.ts +1 -4
  28. package/src/types/utility.ts +5 -2
  29. package/src/lib/Toast.ts +0 -23
  30. package/src/lib/hooks.ts +0 -428
  31. package/src/lib/utils/cookies.ts +0 -13
  32. /package/src/lib/{useSearchParams.ts → hooks/useSearchParams.ts} +0 -0
  33. /package/src/lib/{modal.ts → tools/modal.ts} +0 -0
  34. /package/src/lib/{navigation → tools/navigation}/index.ts +0 -0
  35. /package/src/lib/{navigation → tools/navigation}/types.ts +0 -0
  36. /package/src/lib/{test.ts → utils/test.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeleap/web",
3
- "version": "3.23.2",
3
+ "version": "3.23.3",
4
4
  "main": "src/index.ts",
5
5
  "repository": {
6
6
  "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
@@ -12,7 +12,6 @@
12
12
  "@codeleap/config": "*",
13
13
  "@emotion/react": "link:../../apps/web/node_modules/@emotion/react",
14
14
  "@types/react-slick": "^0.23.10",
15
- "@types/react-window": "1.8.5",
16
15
  "react": "link:../../apps/web/node_modules/react"
17
16
  },
18
17
  "scripts": {
@@ -25,9 +24,7 @@
25
24
  "@radix-ui/react-tooltip": "^1.0.6",
26
25
  "@tiptap/react": "2.1.16",
27
26
  "framer-motion": "^10.10.0",
28
- "js-cookie": "^3.0.1",
29
27
  "masonic": "^3.7.0",
30
- "rc-slider": "^9.7.5",
31
28
  "react-autosize-textarea": "^7.1.0",
32
29
  "react-colorful": "^5.6.1",
33
30
  "react-datepicker": "^4.20.0",
@@ -38,11 +35,7 @@
38
35
  "react-number-format": "^5.2.1",
39
36
  "react-select": "^5.7.3",
40
37
  "react-slick": "^0.29.0",
41
- "react-toastify": "^8.2.0",
42
- "react-virtualized-auto-sizer": "^1.0.6",
43
- "react-window": "1.8.5",
44
38
  "slick-carousel": "^1.8.1",
45
- "url-parse": "^1.5.10",
46
39
  "uuid": "^8.3.2",
47
40
  "zustand": "^4.4.1",
48
41
  "@fastify/deepmerge": "1.3.0"
@@ -25,7 +25,7 @@ import { ActionIcon, ActionIconProps } from '../ActionIcon'
25
25
  import { Scroll } from '../Scroll'
26
26
  import { ComponentCommonProps } from '../../types'
27
27
  import { Touchable, TouchableProps } from '../Touchable'
28
- import { modalScrollLock, ModalStore } from '../../lib/modal'
28
+ import { modalScrollLock, ModalStore } from '../../lib/tools/modal'
29
29
 
30
30
  export * from './styles'
31
31
 
@@ -25,7 +25,7 @@ import { StylesOf, HTMLProps, ComponentWithDefaultProps } from '../../types/util
25
25
  import { InputBase, InputBaseProps, selectInputBaseProps } from '../InputBase'
26
26
  import { TextInputPresets } from './styles'
27
27
  import { getMaskInputProps, TextInputMaskingProps } from './mask'
28
- import { getTestId } from '../../lib/test'
28
+ import { getTestId } from '../../lib/utils/test'
29
29
 
30
30
  export * from './styles'
31
31
  export * from './mask'
@@ -5,7 +5,7 @@ import { View } from '../View'
5
5
  import { TouchableComposition, TouchablePresets } from './styles'
6
6
  import { CSSInterpolation } from '@emotion/css'
7
7
  import { StylesOf, NativeHTMLElement } from '../../types'
8
- import { getTestId } from '../../lib/test'
8
+ import { getTestId } from '../../lib/utils/test'
9
9
  export * from './styles'
10
10
 
11
11
  export type TouchableProps<T extends ElementType = 'button'> = ComponentPropsWithRef<T> & {
@@ -7,7 +7,7 @@ import { useMediaQuery } from '../../lib/hooks'
7
7
  import { NativeHTMLElement } from '../../types'
8
8
  import { motion } from 'framer-motion'
9
9
  import { ViewProps } from './types'
10
- import { getTestId } from '../../lib/test'
10
+ import { getTestId } from '../../lib/utils/test'
11
11
 
12
12
  export * from './styles'
13
13
  export * from './types'
package/src/index.ts CHANGED
@@ -1,5 +1,3 @@
1
1
  export * from './components/components'
2
- export * from './types/utility'
2
+ export * from './types'
3
3
  export * from './lib'
4
-
5
- export { default as Toast } from './lib/Toast'
@@ -0,0 +1,16 @@
1
+ export * from './useAnimatedStyle'
2
+ export * from './useAnimatedVariantStyles'
3
+ export * from './useBreakpointMatch'
4
+ export * from './useClick'
5
+ export * from './useClickOutside'
6
+ export * from './useMaxContentWidth'
7
+ export * from './useMediaQuery'
8
+ export * from './usePageExitBlocker'
9
+ export * from './usePagination'
10
+ export * from './usePopState'
11
+ export * from './useScrollEffect'
12
+ export * from './useSearchParams'
13
+ export * from './useStaticAnimationStyles'
14
+ export * from './useWindowFocus'
15
+ export * from './useWindowSize'
16
+ export * from './useKeydown'
@@ -1,5 +1,4 @@
1
- import React, { useRef } from 'react'
2
- import { onUpdate, useState } from '@codeleap/common'
1
+ import { useRef, useEffect, useState } from 'react'
3
2
  import { AnimationProps } from 'framer-motion'
4
3
 
5
4
  type UpdaterReturn = Omit<AnimationProps['animate'], 'transition'> & {
@@ -10,11 +9,11 @@ type UseAnimatedStyleReturn = Pick<AnimationProps, 'animate' | 'initial' | 'tran
10
9
 
11
10
  export const useAnimatedStyle = (updater: () => UpdaterReturn, deps: Array<any>): UseAnimatedStyleReturn => {
12
11
  const initialStyle = updater()
13
-
12
+
14
13
  const [animatedStyle, setAnimatedStyle] = useState(initialStyle)
15
14
  const transition = useRef(initialStyle.transition)
16
15
 
17
- onUpdate(() => {
16
+ useEffect(() => {
18
17
  const animatedStyleUpdated = updater()
19
18
 
20
19
  setAnimatedStyle(animatedStyleUpdated)
@@ -0,0 +1,29 @@
1
+ import { AnimationProps } from 'framer-motion'
2
+ import { SelectProperties } from '../../types'
3
+ import { useStaticAnimationStyles } from './useStaticAnimationStyles'
4
+ import { useState, useEffect } from 'react'
5
+
6
+ type UseAnimatedVariantStylesConfig<T extends Record<string | number | symbol, any>, K extends keyof T> = {
7
+ variantStyles: T
8
+ animatedProperties: K[]
9
+ updater: (states: SelectProperties<T, K>) => AnimationProps
10
+ dependencies?: any[]
11
+ }
12
+
13
+ export function useAnimatedVariantStyles<T extends Record<string | number | symbol, any>, K extends keyof T>(config: UseAnimatedVariantStylesConfig<T, K>) {
14
+ const { animatedProperties, updater, variantStyles, dependencies = [] } = config
15
+
16
+ const staticStyles = useStaticAnimationStyles(variantStyles, animatedProperties)
17
+
18
+ const initialState = updater(staticStyles)
19
+
20
+ const [animated, setAnimated] = useState(initialState)
21
+
22
+ useEffect(() => {
23
+ const nextState = updater(staticStyles)
24
+
25
+ setAnimated(nextState)
26
+ }, dependencies)
27
+
28
+ return animated
29
+ }
@@ -1,6 +1,6 @@
1
- import React from 'react'
1
+ import { useMemo } from 'react'
2
2
  import { TypeGuards, useCodeleapContext } from '@codeleap/common'
3
- import { useMediaQuery } from './hooks'
3
+ import { useMediaQuery } from './useMediaQuery'
4
4
 
5
5
  export type BreakpointsMatch<T extends string = string> = Record<T, any>
6
6
 
@@ -9,7 +9,7 @@ export function useBreakpointMatch<T extends string = string>(values: Partial<Br
9
9
 
10
10
  const themeBreakpoints: Record<string, number> = Theme?.breakpoints
11
11
 
12
- const breakpoints: Record<string, number> = React.useMemo(() => {
12
+ const breakpoints: Record<string, number> = useMemo(() => {
13
13
  const breaks = Object.entries(themeBreakpoints)
14
14
 
15
15
  breaks?.sort((a, b) => a?.[1] - b?.[1])
@@ -19,7 +19,7 @@ export function useBreakpointMatch<T extends string = string>(values: Partial<Br
19
19
  return sortBreakpoints
20
20
  }, [])
21
21
 
22
- const breakpointValues: Array<string> = React.useMemo(() => {
22
+ const breakpointValues: Array<string> = useMemo(() => {
23
23
  const _breakpoints = Object.keys(breakpoints)
24
24
 
25
25
  return _breakpoints.sort((a, b) => breakpoints?.[a] - breakpoints?.[b])
@@ -36,7 +36,7 @@ export function useBreakpointMatch<T extends string = string>(values: Partial<Br
36
36
 
37
37
  const currentBreakpoint = Object.keys(breakpointMatches)?.find((key) => breakpointMatches?.[key])
38
38
 
39
- const breakpoint = React.useMemo(() => {
39
+ const breakpoint = useMemo(() => {
40
40
  const validBreakpointIndex = breakpointValues?.findIndex(_breakpoint => _breakpoint === currentBreakpoint)
41
41
 
42
42
  const validBreakpoints = breakpointValues?.slice(validBreakpointIndex, 100)
@@ -1,5 +1,5 @@
1
1
  import { TypeGuards } from '@codeleap/common'
2
- import React, { useEffect } from 'react'
2
+ import React, { useEffect, useRef, useCallback } from 'react'
3
3
 
4
4
  type HandlerClickOutside = (clickedOutside: boolean) => void
5
5
 
@@ -9,9 +9,9 @@ export function useClickOutsideElement(
9
9
  handler: HandlerClickOutside,
10
10
  elements: Array<React.MutableRefObject<any>> = null,
11
11
  ): UseClickOutsideElementReturn {
12
- const elementRef = React.useRef(null)
12
+ const elementRef = useRef(null)
13
13
 
14
- const handleClickOutside = React.useCallback((event: MouseEvent) => {
14
+ const handleClickOutside = useCallback((event: MouseEvent) => {
15
15
  if (!elementRef.current) return
16
16
 
17
17
  const clickedOutsideElement = !elementRef.current.contains(event?.target)
@@ -0,0 +1,31 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ const DEFAULT_EVENTS = ['mousedown', 'touchstart']
4
+
5
+ export function useClickOutside<T extends HTMLElement = any>(
6
+ handler: () => void,
7
+ events?: string[] | null,
8
+ nodes?: HTMLElement[],
9
+ ) {
10
+ const ref = useRef<T>()
11
+
12
+ useEffect(() => {
13
+ const listener = (event: any) => {
14
+ if (Array.isArray(nodes)) {
15
+ const shouldIgnore = event?.target?.hasAttribute('data-ignore-outside-clicks')
16
+ const shouldTrigger = nodes.every((node) => !!node && !node.contains(event.target))
17
+ shouldTrigger && !shouldIgnore && handler()
18
+ } else if (ref.current && !ref.current.contains(event.target)) {
19
+ handler()
20
+ }
21
+ };
22
+
23
+ (events || DEFAULT_EVENTS).forEach((fn) => document.addEventListener(fn, listener))
24
+
25
+ return () => {
26
+ (events || DEFAULT_EVENTS).forEach((fn) => document.removeEventListener(fn, listener))
27
+ }
28
+ }, [ref, handler, nodes])
29
+
30
+ return ref
31
+ }
@@ -1,4 +1,5 @@
1
- import { onUpdate, TypeGuards } from '@codeleap/common'
1
+ import { TypeGuards } from '@codeleap/common'
2
+ import { useEffect } from 'react'
2
3
 
3
4
  export const keydownDefaultKeyOptions = {
4
5
  ArrowLeft: 'ArrowLeft',
@@ -11,11 +12,11 @@ export const keydownDefaultKeyOptions = {
11
12
  code: 'Space',
12
13
  },
13
14
  }
14
-
15
+
15
16
  export function useKeydown(
16
17
  expectedKey: keyof typeof useKeydown.keys | { key: string; code: string },
17
- handler: (event: KeyboardEvent) => void,
18
- deps: Array<any> = [],
18
+ handler: (event: KeyboardEvent) => void,
19
+ deps: Array<any> = [],
19
20
  options?: boolean | AddEventListenerOptions
20
21
  ) {
21
22
  const eventKey = TypeGuards.isString(expectedKey) ? (useKeydown?.keys?.[expectedKey] ?? expectedKey) : expectedKey
@@ -28,7 +29,7 @@ export function useKeydown(
28
29
  }
29
30
  }
30
31
 
31
- onUpdate(() => {
32
+ useEffect(() => {
32
33
  document.addEventListener('keydown', handleKeyPress, options)
33
34
 
34
35
  return () => {
@@ -1,8 +1,7 @@
1
1
  import { useCodeleapContext } from '@codeleap/common'
2
- import { useWindowSize } from '../hooks'
2
+ import { useWindowSize } from './useWindowSize'
3
3
 
4
4
  export const useMaxContentWidth = () => {
5
-
6
5
  const { Theme } = useCodeleapContext()
7
6
  const [width, height] = useWindowSize()
8
7
 
@@ -45,5 +44,4 @@ export const useMaxContentWidth = () => {
45
44
  width: maxContentWidth,
46
45
  padding,
47
46
  }
48
-
49
47
  }
@@ -0,0 +1,41 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react'
2
+ import { attachMediaListener, isMediaQuery } from '../tools'
3
+
4
+ export interface UseMediaQueryOptions {
5
+ getInitialValueInEffect?: boolean
6
+ initialValue?: boolean
7
+ }
8
+
9
+ export function useMediaQuery(
10
+ query: string,
11
+ queryOptions: UseMediaQueryOptions = {},
12
+ ) {
13
+ const {
14
+ initialValue = false,
15
+ getInitialValueInEffect = true,
16
+ } = queryOptions
17
+
18
+ const _query = useMemo(() => {
19
+ return query.trim().replace('@media screen and ', '')
20
+ }, [query])
21
+
22
+ const [matches, setMatches] = useState(
23
+ getInitialValueInEffect ? initialValue : isMediaQuery(query, initialValue),
24
+ )
25
+
26
+ const queryRef = useRef<MediaQueryList>()
27
+
28
+ useEffect(() => {
29
+ if (query.trim() === '') return
30
+
31
+ if ('matchMedia' in window) {
32
+ queryRef.current = window.matchMedia(_query)
33
+ setMatches(queryRef.current.matches)
34
+ return attachMediaListener(queryRef.current, (event) => setMatches(event.matches))
35
+ }
36
+
37
+ return undefined
38
+ }, [_query])
39
+
40
+ return matches
41
+ }
@@ -0,0 +1,44 @@
1
+ import { globalHistory } from '@reach/router'
2
+ import { useEffect } from 'react'
3
+
4
+ export const usePageExitBlocker = (
5
+ handler: (willLeavePage: boolean) => void,
6
+ deps: Array<any> = [],
7
+ message = 'Are you sure you want to leave?',
8
+ ) => {
9
+ const handleBeforeUnload = (event: BeforeUnloadEvent) => {
10
+ if (!event) return null
11
+
12
+ event?.preventDefault()
13
+ event.returnValue = ''
14
+ return
15
+ }
16
+
17
+ useEffect(() => {
18
+ if (!window) return null
19
+
20
+ window.addEventListener('beforeunload', handleBeforeUnload)
21
+
22
+ return () => {
23
+ window.removeEventListener('beforeunload', handleBeforeUnload)
24
+ }
25
+ }, deps)
26
+
27
+ useEffect(() => {
28
+ return globalHistory.listen((args) => {
29
+ if (!window) return null
30
+
31
+ const historyPathname = args?.location?.pathname
32
+ const windowPathname = window?.location?.pathname
33
+
34
+ const isPopAction = args?.action === 'POP'
35
+ const isLeaveAction = args?.action === 'PUSH' && !historyPathname?.includes(windowPathname)
36
+
37
+ if (isLeaveAction || isPopAction) {
38
+ const willLeavePage = window.confirm(message)
39
+
40
+ handler?.(willLeavePage)
41
+ }
42
+ })
43
+ }, deps)
44
+ }
@@ -0,0 +1,107 @@
1
+ import { range, useMemo, useUncontrolled } from '@codeleap/common'
2
+
3
+ export interface PaginationParams {
4
+ /** Page selected on initial render, defaults to 1 */
5
+ initialPage?: number
6
+
7
+ /** Controlled active page number */
8
+ page?: number
9
+
10
+ /** Total amount of pages */
11
+ total: number
12
+
13
+ /** Siblings amount on left/right side of selected page, defaults to 1 */
14
+ siblings?: number
15
+
16
+ /** Amount of elements visible on left/right edges, defaults to 1 */
17
+ boundaries?: number
18
+
19
+ /** Callback fired after change of each page */
20
+ onChange?: (page: number) => void
21
+ }
22
+
23
+ export function usePagination({
24
+ total,
25
+ siblings = 1,
26
+ boundaries = 1,
27
+ page,
28
+ initialPage = 1,
29
+ onChange,
30
+ }: PaginationParams) {
31
+ const [activePage, setActivePage] = useUncontrolled({
32
+ value: page,
33
+ onChange,
34
+ defaultValue: initialPage,
35
+ finalValue: initialPage,
36
+ rule: (_page) => typeof _page === 'number' && _page <= total,
37
+ })
38
+
39
+ 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
+ }
47
+ }
48
+
49
+ const next = () => setPage(activePage + 1)
50
+ const previous = () => setPage(activePage - 1)
51
+ const first = () => setPage(1)
52
+ const last = () => setPage(total)
53
+
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
59
+
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)
66
+ }
67
+
68
+ const leftSiblingIndex = Math.max(activePage - siblings, boundaries)
69
+ const rightSiblingIndex = Math.min(activePage + siblings, total - boundaries)
70
+
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)
78
+
79
+ if (!shouldShowLeftDots && shouldShowRightDots) {
80
+ const leftItemCount = siblings * 2 + boundaries + 2
81
+ return [...range(1, leftItemCount), DOTS, ...range(total - (boundaries - 1), total)]
82
+ }
83
+
84
+ if (shouldShowLeftDots && !shouldShowRightDots) {
85
+ const rightItemCount = boundaries + 1 + 2 * siblings
86
+ return [...range(1, boundaries), DOTS, ...range(total - rightItemCount, total)]
87
+ }
88
+
89
+ return [
90
+ ...range(1, boundaries),
91
+ DOTS,
92
+ ...range(leftSiblingIndex, rightSiblingIndex),
93
+ DOTS,
94
+ ...range(total - boundaries + 1, total),
95
+ ]
96
+ }, [total, siblings, activePage])
97
+
98
+ return {
99
+ range: paginationRange,
100
+ active: activePage,
101
+ setPage,
102
+ next,
103
+ previous,
104
+ first,
105
+ last,
106
+ }
107
+ }
@@ -1,7 +1,6 @@
1
1
  import { AnyFunction, useIsomorphicEffect, useUnmount } from '@codeleap/common'
2
2
 
3
3
  export const usePopState = (dependence: boolean, handler: AnyFunction) => {
4
-
5
4
  useIsomorphicEffect(() => {
6
5
  if (dependence) {
7
6
  const pathname = location.pathname
@@ -0,0 +1,19 @@
1
+ import { useEffect } from 'react'
2
+
3
+ export function useScrollEffect(
4
+ effect: (passed: boolean, current: number) => any,
5
+ breakpoint: number,
6
+ extraDependencies = [],
7
+ ) {
8
+ function handleScroll() {
9
+ const passed = window.scrollY > breakpoint
10
+ effect(passed, window.scrollY)
11
+ }
12
+
13
+ useEffect(() => {
14
+ document.addEventListener('scroll', handleScroll)
15
+ return () => {
16
+ document.removeEventListener('scroll', handleScroll)
17
+ }
18
+ }, [breakpoint, ...extraDependencies])
19
+ }
@@ -0,0 +1,14 @@
1
+ import { useRef } from 'react'
2
+ import { SelectProperties } from '../../types'
3
+
4
+ export function useStaticAnimationStyles<T extends Record<string | number | symbol, any>, K extends keyof T>(obj: T, keys: K[]) {
5
+ const styles = useRef({})
6
+
7
+ if (Object.keys(styles.current).length === 0) {
8
+ const mappedStyles = keys.map((k) => [k, { ...obj[k] }])
9
+
10
+ styles.current = Object.fromEntries(mappedStyles)
11
+ }
12
+
13
+ return styles.current as SelectProperties<T, K>
14
+ }
@@ -0,0 +1,33 @@
1
+ import { AnyFunction, TypeGuards } from '@codeleap/common'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ type UseWindowFocusOptions = {
5
+ onFocus?: AnyFunction
6
+ onBlur?: AnyFunction
7
+ }
8
+
9
+ export const useWindowFocus = (options: UseWindowFocusOptions = {}, deps: Array<any> = []): boolean => {
10
+ const [focused, setFocused] = useState(true)
11
+
12
+ const onFocus = () => {
13
+ setFocused(true)
14
+ if (TypeGuards.isFunction(options?.onFocus)) options?.onFocus()
15
+ }
16
+
17
+ const onBlur = () => {
18
+ setFocused(false)
19
+ if (TypeGuards.isFunction(options?.onBlur)) options?.onBlur()
20
+ }
21
+
22
+ useEffect(() => {
23
+ window.addEventListener('focus', onFocus)
24
+ window.addEventListener('blur', onBlur)
25
+
26
+ return () => {
27
+ window.removeEventListener('focus', onFocus)
28
+ window.removeEventListener('blur', onBlur)
29
+ }
30
+ }, deps)
31
+
32
+ return focused
33
+ }
@@ -0,0 +1,21 @@
1
+ import { useState, useEffect } from 'react'
2
+
3
+ export function useWindowSize() {
4
+ const [size, setSize] = useState([])
5
+
6
+ function handleResize() {
7
+ setSize([window.innerWidth, window.innerHeight])
8
+ }
9
+
10
+ useEffect(() => {
11
+ handleResize()
12
+
13
+ window.addEventListener('resize', handleResize)
14
+
15
+ return () => {
16
+ window.removeEventListener('resize', handleResize)
17
+ }
18
+ }, [])
19
+
20
+ return size
21
+ }
package/src/lib/index.ts CHANGED
@@ -1,12 +1,4 @@
1
- export * from './utils'
2
1
  export * from './hooks'
3
- export * from './useSearchParams'
4
- export * from './usePopState'
5
- export * from './useBreakpointMatch'
6
- export * from './useClick'
2
+ export * from './tools'
3
+ export * from './utils'
7
4
  export * from './ListMasonry'
8
- export * from './localStorage'
9
- export * from './useAnimatedStyle'
10
- export * from './navigation'
11
- export * from './modal'
12
- export * from './keyboard'
@@ -0,0 +1,4 @@
1
+ export * from './navigation'
2
+ export * from './localStorage'
3
+ export * from './mediaQuery'
4
+ export * from './modal'
@@ -1,5 +1,5 @@
1
- import React from 'react'
2
- import { onMount, TypeGuards, useState } from '@codeleap/common'
1
+ import { useEffect, useState } from 'react'
2
+ import { TypeGuards } from '@codeleap/common'
3
3
 
4
4
  export type LocalStorageHandler<T> = (key: T, event: StorageEvent, value: any) => void
5
5
  export type Key<T> = keyof T
@@ -138,7 +138,7 @@ export class LocalStorage<T extends Record<string, string>> {
138
138
  return getItemValueOnMount ? (this.getItem(key, parseValueOnGet) ?? initialValue) : initialValue
139
139
  })
140
140
 
141
- onMount(() => {
141
+ useEffect(() => {
142
142
  const handler = () => {
143
143
  let _initialValue = initialValue
144
144
  let storedValue = this.getItem(key, parseValueOnGet)
@@ -153,7 +153,7 @@ export class LocalStorage<T extends Record<string, string>> {
153
153
  handler()
154
154
 
155
155
  return disableListen ? null : this.listen(key, handler)
156
- })
156
+ }, [])
157
157
 
158
158
  const setValue = (to: S | ((prev:S) => S)) => {
159
159
  return _setValue((prev) => {
@@ -0,0 +1,25 @@
1
+ type MediaQueryCallback = (event: { matches: boolean; media: string }) => void
2
+
3
+ /**
4
+ * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia
5
+ * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent
6
+ * */
7
+ export function attachMediaListener(query: MediaQueryList, callback: MediaQueryCallback) {
8
+ try {
9
+ query.addEventListener('change', callback)
10
+ return () => query.removeEventListener('change', callback)
11
+ } catch (e) {
12
+ query.addListener(callback)
13
+ return () => query.removeListener(callback)
14
+ }
15
+ }
16
+
17
+ export function isMediaQuery(query: string, initialValue = false) {
18
+ const media = query.trim().replace('@media screen and ', '')
19
+
20
+ if (typeof window !== 'undefined' && 'matchMedia' in window) {
21
+ return window.matchMedia(media).matches
22
+ }
23
+
24
+ return initialValue
25
+ }
@@ -1,6 +1,3 @@
1
1
  export * from './pollyfils/scroll'
2
2
  export * from './stopPropagation'
3
- export * from './useMaxContentWidth'
4
- export { default as url } from 'url-parse'
5
- export { default as Cookies } from './cookies'
6
-
3
+ export * from './test'
@@ -1,6 +1,5 @@
1
- import React, { InputHTMLAttributes } from 'react'
1
+ import React from 'react'
2
2
  import { CSSInterpolation } from '@emotion/css'
3
- import { PropsOf } from '@codeleap/common'
4
3
 
5
4
  export type StylesOf<C extends string> = Partial<Record<C, CSSInterpolation>>
6
5
 
@@ -34,3 +33,7 @@ export type ComponentWithDefaultProps<P> = ((props: P) => JSX.Element) & { defau
34
33
  export type ComponentCommonProps = {
35
34
  debugName: string
36
35
  }
36
+
37
+ export type SelectProperties<T extends Record<string | number | symbol, any>, K extends keyof T> = {
38
+ [P in K]: T[K]
39
+ }
package/src/lib/Toast.ts DELETED
@@ -1,23 +0,0 @@
1
- import { toast } from 'react-toastify'
2
-
3
- type ToastArgs = {
4
- title:string
5
- } &Parameters<typeof toast.info>[1]
6
-
7
- function info({ title, ...others }:ToastArgs) {
8
- toast.info(title, others)
9
- }
10
-
11
- function success({ title, ...others }:ToastArgs) {
12
- toast.success(title, others)
13
- }
14
-
15
- function error({ title, ...others }:ToastArgs) {
16
- toast.error(title, others)
17
- }
18
-
19
- export default {
20
- info,
21
- success,
22
- error,
23
- }
package/src/lib/hooks.ts DELETED
@@ -1,428 +0,0 @@
1
- import { AnyFunction, onMount, onUpdate, range, TypeGuards, useUncontrolled } from '@codeleap/common'
2
- import React, { useCallback, useMemo, useRef, useState } from 'react'
3
- import { v4 } from 'uuid'
4
- import { easeInOut, EasingFunction, AnimationProps, useAnimate, useAnimation, animate } from 'framer-motion'
5
- import { globalHistory } from '@reach/router'
6
-
7
- export function useWindowSize() {
8
- const [size, setSize] = useState([])
9
-
10
- onMount(() => {
11
- setSize([window.innerWidth, window.innerHeight])
12
- })
13
-
14
- function handleResize() {
15
- setSize([window.innerWidth, window.innerHeight])
16
- }
17
-
18
- onUpdate(() => {
19
- window.addEventListener('resize', handleResize)
20
- return () => {
21
- window.removeEventListener('resize', handleResize)
22
- }
23
- }, [])
24
-
25
- return size
26
- }
27
-
28
- // type UseClickOutsideOpts = {
29
- // customId?: string
30
- // deps?: any[]
31
- // }
32
- // export function useClickOutside(
33
- // callback: AnyFunction,
34
- // deps?: UseClickOutsideOpts,
35
- // ) {
36
- // const id = useRef(deps?.customId || v4())
37
-
38
- // const onClick = useCallback((e: Event) => {
39
- // const element = document.getElementById(id.current)
40
- // if (!element) return
41
- // const isInside = element.contains(e.target as Node) || ((e.target as HTMLElement).id === id.current)
42
-
43
- // // const iterNodes = (el:HTMLElement|Element) => {
44
- // // if (isInside) return
45
- // // for (let i = 0; i < el.children.length; i++) {
46
- // // const node = el.children.item(i)
47
-
48
- // // if (!node) return
49
- // // const _isInside = node.contains(e.target as Node)
50
- // // if (_isInside) {
51
- // // isInside = _isInside
52
- // // }
53
- // // if (isInside) break
54
-
55
- // // if (node.hasChildNodes()) {
56
- // // iterNodes(node)
57
- // // }
58
-
59
- // // if (isInside) break
60
- // // }
61
- // // }
62
-
63
- // // if (!isInside) {
64
- // // iterNodes(element)
65
- // // }
66
-
67
- // if (!isInside) {
68
- // callback(e)
69
- // }
70
- // }, deps?.deps || [])
71
- // onUpdate(() => {
72
-
73
- // document.addEventListener('click', onClick)
74
- // return () => {
75
- // document.removeEventListener('click', onClick)
76
- // }
77
- // }, [onClick])
78
-
79
- // return id.current
80
- // }
81
-
82
- import { useEffect } from 'react'
83
-
84
- const DEFAULT_EVENTS = ['mousedown', 'touchstart']
85
-
86
- export function useClickOutside<T extends HTMLElement = any>(
87
- handler: () => void,
88
- events?: string[] | null,
89
- nodes?: HTMLElement[],
90
- ) {
91
- const ref = useRef<T>()
92
-
93
- useEffect(() => {
94
- const listener = (event: any) => {
95
- if (Array.isArray(nodes)) {
96
- const shouldIgnore = event?.target?.hasAttribute('data-ignore-outside-clicks')
97
- const shouldTrigger = nodes.every((node) => !!node && !node.contains(event.target))
98
- shouldTrigger && !shouldIgnore && handler()
99
- } else if (ref.current && !ref.current.contains(event.target)) {
100
- handler()
101
- }
102
- };
103
-
104
- (events || DEFAULT_EVENTS).forEach((fn) => document.addEventListener(fn, listener))
105
-
106
- return () => {
107
- (events || DEFAULT_EVENTS).forEach((fn) => document.removeEventListener(fn, listener))
108
- }
109
- }, [ref, handler, nodes])
110
-
111
- return ref
112
- }
113
-
114
- export function useScrollEffect(
115
- effect: (passed: boolean, current: number) => any,
116
- breakpoint: number,
117
- extraDependencies = [],
118
- ) {
119
- function handleScroll() {
120
- const passed = window.scrollY > breakpoint
121
- effect(passed, window.scrollY)
122
- }
123
-
124
- onUpdate(() => {
125
- document.addEventListener('scroll', handleScroll)
126
- return () => {
127
- document.removeEventListener('scroll', handleScroll)
128
- }
129
- }, [breakpoint, ...extraDependencies])
130
- }
131
-
132
- export const DOTS = 'dots'
133
-
134
- export interface PaginationParams {
135
- /** Page selected on initial render, defaults to 1 */
136
- initialPage?: number
137
-
138
- /** Controlled active page number */
139
- page?: number
140
-
141
- /** Total amount of pages */
142
- total: number
143
-
144
- /** Siblings amount on left/right side of selected page, defaults to 1 */
145
- siblings?: number
146
-
147
- /** Amount of elements visible on left/right edges, defaults to 1 */
148
- boundaries?: number
149
-
150
- /** Callback fired after change of each page */
151
- onChange?: (page: number) => void
152
- }
153
-
154
- export function usePagination({
155
- total,
156
- siblings = 1,
157
- boundaries = 1,
158
- page,
159
- initialPage = 1,
160
- onChange,
161
- }: PaginationParams) {
162
- const [activePage, setActivePage] = useUncontrolled({
163
- value: page,
164
- onChange,
165
- defaultValue: initialPage,
166
- finalValue: initialPage,
167
- rule: (_page) => typeof _page === 'number' && _page <= total,
168
- })
169
-
170
- const setPage = (pageNumber: number) => {
171
- if (pageNumber <= 0) {
172
- setActivePage(1)
173
- } else if (pageNumber > total) {
174
- setActivePage(total)
175
- } else {
176
- setActivePage(pageNumber)
177
- }
178
- }
179
-
180
- const next = () => setPage(activePage + 1)
181
- const previous = () => setPage(activePage - 1)
182
- const first = () => setPage(1)
183
- const last = () => setPage(total)
184
-
185
- const paginationRange = useMemo((): (number | 'dots')[] => {
186
- // Pages count is determined as siblings (left/right) + boundaries(left/right) + currentPage + 2*DOTS
187
- const totalPageNumbers = siblings * 2 + 3 + boundaries * 2
188
-
189
- /*
190
- * If the number of pages is less than the page numbers we want to show in our
191
- * paginationComponent, we return the range [1..total]
192
- */
193
- if (totalPageNumbers >= total) {
194
- return range(1, total)
195
- }
196
-
197
- const leftSiblingIndex = Math.max(activePage - siblings, boundaries)
198
- const rightSiblingIndex = Math.min(activePage + siblings, total - boundaries)
199
-
200
- /*
201
- * We do not want to show dots if there is only one position left
202
- * after/before the left/right page count as that would lead to a change if our Pagination
203
- * component size which we do not want
204
- */
205
- const shouldShowLeftDots = leftSiblingIndex > boundaries + 2
206
- const shouldShowRightDots = rightSiblingIndex < total - (boundaries + 1)
207
-
208
- if (!shouldShowLeftDots && shouldShowRightDots) {
209
- const leftItemCount = siblings * 2 + boundaries + 2
210
- return [...range(1, leftItemCount), DOTS, ...range(total - (boundaries - 1), total)]
211
- }
212
-
213
- if (shouldShowLeftDots && !shouldShowRightDots) {
214
- const rightItemCount = boundaries + 1 + 2 * siblings
215
- return [...range(1, boundaries), DOTS, ...range(total - rightItemCount, total)]
216
- }
217
-
218
- return [
219
- ...range(1, boundaries),
220
- DOTS,
221
- ...range(leftSiblingIndex, rightSiblingIndex),
222
- DOTS,
223
- ...range(total - boundaries + 1, total),
224
- ]
225
- }, [total, siblings, activePage])
226
-
227
- return {
228
- range: paginationRange,
229
- active: activePage,
230
- setPage,
231
- next,
232
- previous,
233
- first,
234
- last,
235
- }
236
- }
237
-
238
-
239
- export interface UseMediaQueryOptions {
240
- getInitialValueInEffect?: boolean
241
- initialValue?: boolean
242
- }
243
-
244
- type MediaQueryCallback = (event: { matches: boolean; media: string }) => void;
245
-
246
- /**
247
- * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia
248
- * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent
249
- * */
250
- export function attachMediaListener(query: MediaQueryList, callback: MediaQueryCallback) {
251
- try {
252
- query.addEventListener('change', callback);
253
- return () => query.removeEventListener('change', callback);
254
- } catch (e) {
255
- query.addListener(callback);
256
- return () => query.removeListener(callback);
257
- }
258
- }
259
-
260
- function getInitialValue(query: string, initialValue?: boolean) {
261
- if (typeof initialValue === 'boolean') {
262
- return initialValue;
263
- }
264
-
265
- if (typeof window !== 'undefined' && 'matchMedia' in window) {
266
- return window.matchMedia(query).matches;
267
- }
268
-
269
- return false;
270
- }
271
-
272
- export function isMediaQuery(query: string, initialValue = false) {
273
- const media = query.trim().replace('@media screen and ', '')
274
-
275
- if (typeof window !== 'undefined' && 'matchMedia' in window) {
276
- return window.matchMedia(media).matches
277
- }
278
-
279
- return initialValue
280
- }
281
-
282
- export function useMediaQuery(
283
- query: string,
284
- queryOptions: UseMediaQueryOptions = {}
285
- ) {
286
- const {
287
- initialValue = false,
288
- getInitialValueInEffect = true,
289
- } = queryOptions
290
-
291
- const _query = useMemo(() => {
292
- return query.trim().replace('@media screen and ', '')
293
- }, [query])
294
-
295
- const [matches, setMatches] = useState(
296
- getInitialValueInEffect ? initialValue : isMediaQuery(query, initialValue)
297
- )
298
-
299
- const queryRef = useRef<MediaQueryList>()
300
-
301
- useEffect(() => {
302
- if(query.trim() === '') return
303
-
304
- if ('matchMedia' in window) {
305
- queryRef.current = window.matchMedia(_query);
306
- setMatches(queryRef.current.matches);
307
- return attachMediaListener(queryRef.current, (event) => setMatches(event.matches));
308
- }
309
-
310
- return undefined
311
- }, [_query])
312
-
313
- return matches
314
- }
315
-
316
- type SelectProperties<T extends Record<string|number|symbol, any>, K extends keyof T> = {
317
- [P in K] : T[K]
318
- }
319
-
320
- export function useStaticAnimationStyles<T extends Record<string|number|symbol, any>, K extends keyof T >(obj: T, keys: K[]) {
321
- const styles = useRef({})
322
-
323
- if (Object.keys(styles.current).length === 0) {
324
- const mappedStyles = keys.map((k) => [k, { ...obj[k] }])
325
-
326
- styles.current = Object.fromEntries(mappedStyles)
327
- }
328
-
329
- return styles.current as SelectProperties<T, K>
330
- }
331
-
332
- type UseAnimatedVariantStylesConfig<T extends Record<string|number|symbol, any>, K extends keyof T > = {
333
- variantStyles: T
334
- animatedProperties: K[]
335
- updater: (states: SelectProperties<T, K>) => AnimationProps
336
- dependencies?: any[]
337
- }
338
-
339
- export function useAnimatedVariantStyles<T extends Record<string|number|symbol, any>, K extends keyof T >(config: UseAnimatedVariantStylesConfig<T, K>) {
340
- const { animatedProperties, updater, variantStyles, dependencies = [] } = config
341
-
342
- const staticStyles = useStaticAnimationStyles(variantStyles, animatedProperties)
343
-
344
- const initialState = updater(staticStyles)
345
-
346
- const [animated, setAnimated] = useState(initialState)
347
-
348
- onUpdate(() => {
349
- const nextState = updater(staticStyles)
350
-
351
- setAnimated(nextState)
352
- }, dependencies)
353
-
354
- return animated
355
- }
356
-
357
- type UseWindowFocusOptions = {
358
- onFocus?: AnyFunction
359
- onBlur?: AnyFunction
360
- }
361
-
362
- export const useWindowFocus = (options: UseWindowFocusOptions = {}, deps: Array<any> = []): boolean => {
363
- const [focused, setFocused] = useState(true)
364
-
365
- const onFocus = () => {
366
- setFocused(true)
367
- if (TypeGuards.isFunction(options?.onFocus)) options?.onFocus()
368
- }
369
-
370
- const onBlur = () => {
371
- setFocused(false)
372
- if (TypeGuards.isFunction(options?.onBlur)) options?.onBlur()
373
- }
374
-
375
- useEffect(() => {
376
- window.addEventListener('focus', onFocus)
377
- window.addEventListener('blur', onBlur)
378
-
379
- return () => {
380
- window.removeEventListener('focus', onFocus)
381
- window.removeEventListener('blur', onBlur)
382
- }
383
- }, deps)
384
-
385
- return focused
386
- }
387
-
388
- export const usePageExitBlocker = (
389
- handler: (willLeavePage: boolean) => void,
390
- deps: Array<any> = [],
391
- message: string = 'Are you sure you want to leave?'
392
- ) => {
393
- const handleBeforeUnload = (event: BeforeUnloadEvent) => {
394
- if (!event) return null
395
-
396
- event?.preventDefault()
397
- event.returnValue = ''
398
- return
399
- }
400
-
401
- React.useEffect(() => {
402
- if (!window) return null
403
-
404
- window.addEventListener('beforeunload', handleBeforeUnload)
405
-
406
- return () => {
407
- window.removeEventListener('beforeunload', handleBeforeUnload)
408
- }
409
- }, deps)
410
-
411
- React.useEffect(() => {
412
- return globalHistory.listen((args) => {
413
- if (!window) return null
414
-
415
- const historyPathname = args?.location?.pathname
416
- const windowPathname = window?.location?.pathname
417
-
418
- const isPopAction = args?.action === 'POP'
419
- const isLeaveAction = args?.action === 'PUSH' && !historyPathname?.includes(windowPathname)
420
-
421
- if (isLeaveAction || isPopAction) {
422
- const willLeavePage = window.confirm(message)
423
-
424
- handler?.(willLeavePage)
425
- }
426
- })
427
- }, deps)
428
- }
@@ -1,13 +0,0 @@
1
- import Cookies from 'js-cookie'
2
-
3
- const get = (key) => Cookies.get(key)
4
-
5
- const set = (key, value) => Cookies.set(key, value, { expires: 365 })
6
-
7
- const remove = (key) => Cookies.remove(key)
8
-
9
- export default {
10
- get,
11
- set,
12
- remove,
13
- }
File without changes
File without changes