@codeleap/web 3.8.0 → 3.10.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeleap/web",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "main": "src/index.ts",
5
5
  "repository": {
6
6
  "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
@@ -34,7 +34,7 @@ const defaultProps: Partial<GridProps> = {
34
34
  ListSeparatorComponent: RenderSeparator,
35
35
  refreshDebounce: 3000,
36
36
  refreshSize: 40,
37
- refreshThreshold: 1,
37
+ refreshThreshold: 0.5,
38
38
  refreshPosition: 2,
39
39
  refresh: true,
40
40
  numColumns: 2,
@@ -14,6 +14,8 @@ export * from './useInfiniteScroll'
14
14
  export * from './types'
15
15
  export * from './ListLayout'
16
16
 
17
+ export type ListComponentType = <T extends any[] = any[]>(props: ListProps<T>) => React.ReactElement
18
+
17
19
  const RenderSeparator = (props: { separatorStyles: ViewProps<'div'>['css'] }) => {
18
20
  return (
19
21
  <View css={[props?.separatorStyles]}></View>
@@ -29,14 +31,14 @@ const defaultProps: Partial<ListProps> = {
29
31
  ListSeparatorComponent: RenderSeparator,
30
32
  refreshDebounce: 3000,
31
33
  refreshSize: 40,
32
- refreshThreshold: 1,
34
+ refreshThreshold: 0.5,
33
35
  refreshPosition: 2,
34
36
  refresh: true,
35
37
  }
36
38
 
37
- const ListCP = React.forwardRef<'div', ListProps>((flatListProps, ref) => {
38
- const allProps = {
39
- ...defaultProps,
39
+ export const List: ListComponentType = React.forwardRef<'div', ListProps>((flatListProps, ref) => {
40
+ const allProps = { // @ts-ignore
41
+ ...List.defaultProps,
40
42
  ...flatListProps,
41
43
  }
42
44
 
@@ -114,6 +116,5 @@ const ListCP = React.forwardRef<'div', ListProps>((flatListProps, ref) => {
114
116
  )
115
117
  })
116
118
 
117
- export type ListComponentType = <T extends any[] = any[]>(props: ListProps<T>) => React.ReactElement
118
-
119
- export const List = ListCP as unknown as ListComponentType
119
+ // @ts-ignore
120
+ List.defaultProps = defaultProps
@@ -219,13 +219,17 @@ export const Select = forwardRef<HTMLInputElement, SelectProps>(
219
219
  filterItems = null,
220
220
  itemProps = {},
221
221
  loadingIndicatorSize,
222
+ selectedOption: _selectedOption,
223
+ setSelectedOption: _setSelectedOption,
222
224
  ...otherProps
223
225
  } = selectProps
224
226
 
225
227
  const innerInputRef = useRef<any>(null)
226
228
  const innerWrapperRef = useRef(null)
227
229
 
228
- const [selectedOption, setSelectedOption] = useState(value)
230
+ const hasSelectedOptionState = !TypeGuards.isNil(_selectedOption) && TypeGuards.isFunction(_setSelectedOption)
231
+
232
+ const [selectedOption, setSelectedOption] = hasSelectedOptionState ? [_selectedOption, _setSelectedOption] : useState(value)
229
233
 
230
234
  const [_isFocused, setIsFocused] = useState(false)
231
235
 
@@ -93,6 +93,8 @@ export type SelectProps<T = any, Multi extends boolean = false> = React.PropsWit
93
93
  itemProps?: ButtonProps
94
94
  loadingIndicatorSize?: number
95
95
  limit?: number
96
+ selectedOption?: ReactSelectProps<T>['value']
97
+ setSelectedOption?: ReactSelectProps<T>['onValueChange']
96
98
  } & Omit<
97
99
  ReactSelectProps<T, Multi>,
98
100
  'isSearchable' | 'isClearable' | 'isDisabled' | 'loadingMessage' | 'filterOption' |
@@ -18,6 +18,7 @@ import { AnyFunction, ComponentVariants, StylesOf, TypeGuards, useDefaultCompone
18
18
  import { TooltipComposition, TooltipPresets } from './styles'
19
19
  import { ComponentCommonProps, ComponentWithDefaultProps } from '../../types/utility'
20
20
  import { View, ViewProps } from '../View'
21
+ import { useClickOutsideElement } from '../../lib'
21
22
 
22
23
  type TooltipComponentProps = {
23
24
  contentProps?: TooltipContentProps
@@ -39,6 +40,7 @@ export type TooltipProps = PrimitiveTooltipProps & TooltipComponentProps & {
39
40
  openOnHover?: boolean
40
41
  disabled?: boolean
41
42
  delayDuration?: number
43
+ closeOnClickOutside?: boolean
42
44
  onOpen?: AnyFunction
43
45
  onClose?: AnyFunction
44
46
  onValueChange?: (value: boolean) => void
@@ -52,6 +54,7 @@ const defaultProps: Partial<TooltipProps> = {
52
54
  openOnHover: true,
53
55
  disabled: false,
54
56
  delayDuration: 0,
57
+ closeOnClickOutside: false,
55
58
  side: 'bottom',
56
59
  triggerWrapper: View,
57
60
  }
@@ -87,6 +90,7 @@ export const Tooltip: ComponentWithDefaultProps<TooltipProps> = (props: TooltipP
87
90
  variants = [],
88
91
  responsiveVariants = {},
89
92
  styles = {},
93
+ closeOnClickOutside,
90
94
  ...rest
91
95
  } = allProps
92
96
 
@@ -142,6 +146,14 @@ export const Tooltip: ComponentWithDefaultProps<TooltipProps> = (props: TooltipP
142
146
  if (TypeGuards.isFunction(onPress)) onPress?.(value)
143
147
  }
144
148
 
149
+ const triggerRef = React.useRef(null)
150
+
151
+ const contentRef = useClickOutsideElement((isOutside) => {
152
+ if (isOutside && closeOnClickOutside && !openOnHover && visible) {
153
+ handleToggle(false)
154
+ }
155
+ }, [triggerRef])
156
+
145
157
  return (
146
158
  <TooltipContainer {...providerProps}>
147
159
  <TooltipWrapper
@@ -155,6 +167,7 @@ export const Tooltip: ComponentWithDefaultProps<TooltipProps> = (props: TooltipP
155
167
  onMouseEnter={() => _onHover('enter')}
156
168
  onMouseLeave={() => _onHover('leave')}
157
169
  asChild
170
+ ref={triggerRef}
158
171
  {...triggerProps}
159
172
  >
160
173
  <TriggerWrapper {...allProps as any} {...triggerWrapperProps}>
@@ -162,7 +175,7 @@ export const Tooltip: ComponentWithDefaultProps<TooltipProps> = (props: TooltipP
162
175
  </TriggerWrapper>
163
176
  </TooltipTrigger>
164
177
  <TooltipPortal {...portalProps}>
165
- <TooltipContent css={[tooltipDirectionStyle, variantsStyles.wrapper]} sideOffset={2} side={side} {...contentProps}>
178
+ <TooltipContent ref={contentRef} css={[tooltipDirectionStyle, variantsStyles.wrapper]} sideOffset={2} side={side} {...contentProps}>
166
179
  {
167
180
  TypeGuards.isFunction(Content)
168
181
  ? <Content
package/src/lib/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './hooks'
3
3
  export * from './useSearchParams'
4
4
  export * from './usePopState'
5
5
  export * from './useBreakpointMatch'
6
+ export * from './useClick'
@@ -1,15 +1,16 @@
1
- import { useCodeleapContext, useMemo } from '@codeleap/common'
1
+ import React from 'react'
2
+ import { TypeGuards, useCodeleapContext } from '@codeleap/common'
2
3
  import { useMediaQuery } from './hooks'
3
4
 
4
- export type BreakpointsMatch = Record<string, any> & {
5
- defaultMedia: string
6
- }
5
+ export type BreakpointsMatch<T extends string = string> = Record<T, any>
7
6
 
8
- export const useBreakpointMatch = (values: BreakpointsMatch) => {
7
+ export function useBreakpointMatch<T extends string = string>(values: BreakpointsMatch<T>) {
9
8
  const { Theme } = useCodeleapContext()
10
9
 
11
- const breakpoints = useMemo(() => {
12
- const breaks = Object.entries(Theme.breakpoints as Record<string, number>)
10
+ const themeBreakpoints: Record<string, number> = Theme?.breakpoints
11
+
12
+ const breakpoints: Record<string, number> = React.useMemo(() => {
13
+ const breaks = Object.entries(themeBreakpoints)
13
14
 
14
15
  breaks?.sort((a, b) => a?.[1] - b?.[1])
15
16
 
@@ -18,16 +19,42 @@ export const useBreakpointMatch = (values: BreakpointsMatch) => {
18
19
  return sortBreakpoints
19
20
  }, [])
20
21
 
22
+ const breakpointValues: Array<string> = React.useMemo(() => {
23
+ const _breakpoints = Object.keys(breakpoints)
24
+
25
+ return _breakpoints.sort((a, b) => breakpoints?.[a] - breakpoints?.[b])
26
+ }, [])
27
+
21
28
  const breakpointMatches = {}
22
29
 
23
30
  for (const breakpoint in breakpoints) {
24
- const matchesDown = useMediaQuery(Theme.media.down(breakpoint))
25
- const matchesUp = useMediaQuery(Theme.media.up(breakpoint))
31
+ const matchesDown = useMediaQuery(Theme.media.down(breakpoint), { getInitialValueInEffect: false })
32
+ const matchesUp = useMediaQuery(Theme.media.up(breakpoint), { getInitialValueInEffect: false })
26
33
 
27
34
  breakpointMatches[breakpoint] = !matchesUp && matchesDown
28
35
  }
29
36
 
30
- const breakpoint = Object.keys(breakpointMatches).find((key) => breakpointMatches[key])
37
+ const currentBreakpoint = Object.keys(breakpointMatches)?.find((key) => breakpointMatches?.[key])
38
+
39
+ const breakpoint = React.useMemo(() => {
40
+ const validBreakpointIndex = breakpointValues?.findIndex(_breakpoint => _breakpoint === currentBreakpoint)
41
+
42
+ const validBreakpoints = breakpointValues?.slice(validBreakpointIndex, 100)
43
+
44
+ let validBreakpoint = null
45
+
46
+ validBreakpoint = validBreakpoints?.find((currentValue) => {
47
+ if (Object?.keys(values)?.includes(currentValue)) {
48
+ return currentValue
49
+ }
50
+ })
51
+
52
+ if (TypeGuards.isNil(validBreakpoint)) {
53
+ validBreakpoint = breakpointValues?.reverse()?.find(_breakpoint => !!values?.[_breakpoint])
54
+ }
55
+
56
+ return validBreakpoint
57
+ }, [currentBreakpoint])
31
58
 
32
- return values[breakpoint] ?? values[values.defaultMedia]
59
+ return values?.[breakpoint]
33
60
  }
@@ -0,0 +1,44 @@
1
+ import { TypeGuards } from '@codeleap/common'
2
+ import React, { useEffect } from 'react'
3
+
4
+ type HandlerClickOutside = (clickedOutside: boolean) => void
5
+
6
+ type UseClickOutsideElementReturn = React.MutableRefObject<any>
7
+
8
+ export function useClickOutsideElement(
9
+ handler: HandlerClickOutside,
10
+ elements: Array<React.MutableRefObject<any>> = null,
11
+ ): UseClickOutsideElementReturn {
12
+ const elementRef = React.useRef(null)
13
+
14
+ const handleClickOutside = React.useCallback((event: MouseEvent) => {
15
+ if (!elementRef.current) return
16
+
17
+ const clickedOutsideElement = !elementRef.current.contains(event?.target)
18
+ const clickedOutsideElements = [true]
19
+
20
+ if (TypeGuards?.isArray(elements)) {
21
+ elements?.forEach(element => {
22
+ if (element.current) {
23
+ clickedOutsideElements.push(!element.current.contains(event?.target))
24
+ }
25
+ })
26
+ }
27
+
28
+ if (clickedOutsideElement && clickedOutsideElements.every((clickedOutside) => !!clickedOutside)) {
29
+ handler(true)
30
+ } else {
31
+ handler(false)
32
+ }
33
+ }, [handler])
34
+
35
+ useEffect(() => {
36
+ document.addEventListener('click', handleClickOutside)
37
+
38
+ return () => {
39
+ document.removeEventListener('click', handleClickOutside)
40
+ }
41
+ }, [handleClickOutside])
42
+
43
+ return elementRef
44
+ }