@codeleap/web 3.14.3 → 3.15.1

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.14.3",
3
+ "version": "3.15.1",
4
4
  "main": "src/index.ts",
5
5
  "repository": {
6
6
  "url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
@@ -20,6 +20,7 @@
20
20
  "lint": "eslint -c .eslintrc.js --fix \"./src/**/*.{ts,tsx,js,jsx}\""
21
21
  },
22
22
  "dependencies": {
23
+ "@radix-ui/react-progress": "^1.0.3",
23
24
  "@radix-ui/react-slider": "1.1.1",
24
25
  "@radix-ui/react-tooltip": "^1.0.6",
25
26
  "framer-motion": "^10.10.0",
@@ -27,6 +28,7 @@
27
28
  "masonic": "^3.7.0",
28
29
  "rc-slider": "^9.7.5",
29
30
  "react-autosize-textarea": "^7.1.0",
31
+ "react-circular-progressbar": "^2.1.0",
30
32
  "react-dropzone": "^14.2.3",
31
33
  "react-image-crop": "^10.1.8",
32
34
  "react-input-mask": "^2.0.4",
@@ -10,6 +10,7 @@ import { Modal, Button, FileInput, FileInputRef } from '../components'
10
10
 
11
11
  const ReactCrop: React.Component = require('react-image-crop').Component
12
12
  import 'react-image-crop/dist/ReactCrop.css'
13
+ import { ComponentWithDefaultProps } from '../../types'
13
14
 
14
15
  export * from './styles'
15
16
  export * from './types'
@@ -18,6 +19,11 @@ export * from './useCropPicker'
18
19
 
19
20
  export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
20
21
  (props: CropPickerProps, ref) => {
22
+ const allProps = {
23
+ ...CropPicker.defaultProps,
24
+ ...props,
25
+ }
26
+
21
27
  const {
22
28
  onFileSelect,
23
29
  targetCrop,
@@ -30,7 +36,7 @@ export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
30
36
  debugName,
31
37
  handle,
32
38
  ...fileInputProps
33
- } = props
39
+ } = allProps
34
40
 
35
41
  const {
36
42
  onConfirmCrop,
@@ -98,4 +104,4 @@ export const _CropPicker = forwardRef<FileInputRef, CropPickerProps>(
98
104
  },
99
105
  )
100
106
 
101
- export const CropPicker = React.memo(_CropPicker) as (props: CropPickerProps) => JSX.Element
107
+ export const CropPicker = React.memo(_CropPicker) as ComponentWithDefaultProps<CropPickerProps>
@@ -33,9 +33,10 @@ export function useCropPicker({
33
33
  }
34
34
 
35
35
  const cleanup = () => {
36
- setImage(null)
36
+ toggle()
37
37
  setRelativeCrop(null)
38
38
  setCrop(undefined)
39
+ setTimeout(() => setImage(null), 500)
39
40
  }
40
41
 
41
42
  const onConfirmCrop = async () => {
@@ -46,7 +47,6 @@ export function useCropPicker({
46
47
  preview,
47
48
  },
48
49
  ])
49
- toggle()
50
50
  setTimeout(() => cleanup())
51
51
  }
52
52
 
@@ -80,7 +80,6 @@ export function useCropPicker({
80
80
  }
81
81
 
82
82
  const onClose = () => {
83
- toggle()
84
83
  onCancel()
85
84
  setTimeout(() => cleanup())
86
85
  }
@@ -0,0 +1,122 @@
1
+ import { ProgressBarPresets } from './styles'
2
+ import { TypeGuards, useDefaultComponentStyle } from '@codeleap/common'
3
+ import { Root, Indicator } from '@radix-ui/react-progress'
4
+ import { Icon, Text, View } from '../../components'
5
+ import { ProgressBarProps } from './types'
6
+ import { formatProgress as _formatProgress } from '../utils'
7
+
8
+ export * from './types'
9
+ export * from './styles'
10
+
11
+ const defaultProps: Partial<ProgressBarProps> = {
12
+ progress: 0,
13
+ variants: [],
14
+ responsiveVariants: {},
15
+ styles: {},
16
+ textProps: {},
17
+ progressIndicatorProps: {},
18
+ progressRootProps: {},
19
+ showProgress: false,
20
+ formatProgress: _formatProgress,
21
+ }
22
+
23
+ export const ProgressBar = (props: ProgressBarProps) => {
24
+ const allProps = {
25
+ ...ProgressBar.defaultProps,
26
+ ...props,
27
+ }
28
+
29
+ const {
30
+ progress,
31
+ variants,
32
+ responsiveVariants,
33
+ styles,
34
+ debugName,
35
+ formatProgress,
36
+ progressIndicatorProps,
37
+ progressRootProps,
38
+ showProgress,
39
+
40
+ leftIcon,
41
+ leftIconProps,
42
+ rightIcon,
43
+ rightIconProps,
44
+ text,
45
+ textProps,
46
+ leftText,
47
+ leftTextProps,
48
+ rightText,
49
+ rightTextProps,
50
+ ...rest
51
+ } = allProps
52
+
53
+ const variantStyles = useDefaultComponentStyle<
54
+ 'u:ProgressBar',
55
+ typeof ProgressBarPresets
56
+ >('u:ProgressBar', {
57
+ variants,
58
+ responsiveVariants,
59
+ styles,
60
+ })
61
+
62
+ return (
63
+ <View css={variantStyles.wrapper} debugName={debugName} {...rest}>
64
+ {!TypeGuards.isNil(leftIcon) ? (
65
+ <Icon
66
+ name={leftIcon}
67
+ style={{ ...variantStyles.icon, ...variantStyles.leftIcon }}
68
+ debugName={`leftIcon-${debugName}`}
69
+ {...leftIconProps}
70
+ />
71
+ ) : null}
72
+ {TypeGuards.isString(leftText) ? (
73
+ <Text
74
+ text={leftText}
75
+ css={[variantStyles.text, variantStyles.leftText]}
76
+ {...leftTextProps}
77
+ />
78
+ ) : (
79
+ leftText
80
+ )}
81
+ <Root
82
+ css={variantStyles.progress}
83
+ value={progress}
84
+ {...progressRootProps}
85
+ >
86
+ <Indicator
87
+ css={[
88
+ variantStyles.indicator,
89
+ { transform: `translateX(-${100 - progress}%)` },
90
+ ]}
91
+ {...progressIndicatorProps}
92
+ />
93
+ </Root>
94
+ {TypeGuards.isString(text) || showProgress ? (
95
+ <Text
96
+ style={variantStyles.text}
97
+ text={showProgress ? formatProgress(progress) : text}
98
+ {...textProps}
99
+ />
100
+ ) : text}
101
+ {!TypeGuards.isNil(rightIcon) ? (
102
+ <Icon
103
+ name={rightIcon}
104
+ style={{ ...variantStyles.icon, ...variantStyles.rightIcon }}
105
+ debugName={`rightIcon-${debugName}`}
106
+ {...rightIconProps}
107
+ />
108
+ ) : null}
109
+ {TypeGuards.isString(rightText) ? (
110
+ <Text
111
+ text={rightText}
112
+ css={[variantStyles.text, variantStyles.rightText]}
113
+ {...rightTextProps}
114
+ />
115
+ ) : (
116
+ rightText
117
+ )}
118
+ </View>
119
+ )
120
+ }
121
+
122
+ ProgressBar.defaultProps = defaultProps
@@ -0,0 +1,7 @@
1
+ import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
2
+
3
+ export type ProgressBarComposition = 'wrapper' | 'progress' | 'indicator' | 'text' | 'icon' | 'leftIcon' | 'leftText' | 'rightIcon' | 'rightText'
4
+
5
+ const createProgressBarStyle = createDefaultVariantFactory<ProgressBarComposition>()
6
+
7
+ export const ProgressBarPresets = includePresets((styles) => createProgressBarStyle(() => ({ wrapper: styles })))
@@ -0,0 +1,30 @@
1
+ import { ComponentVariants, IconPlaceholder, PropsOf, StylesOf } from '@codeleap/common'
2
+ import { ProgressBarComposition, ProgressBarPresets } from './styles'
3
+ import {
4
+ ProgressProps,
5
+ ProgressIndicatorProps,
6
+ } from '@radix-ui/react-progress'
7
+ import { IconProps, View, TextProps as _TextProps } from '../../components'
8
+ import { ProgressPropsRoot } from '..'
9
+ import { ElementType } from 'react'
10
+
11
+ type TextProps = _TextProps<ElementType>
12
+
13
+ export type ProgressBarProps = ComponentVariants<typeof ProgressBarPresets> &
14
+ Omit<PropsOf<typeof View>, 'variants' | 'responsiveVariants' | 'styles'> &
15
+ ProgressPropsRoot & {
16
+ styles?: StylesOf<ProgressBarComposition>
17
+ progressIndicatorProps?: ProgressIndicatorProps
18
+ progressRootProps?: ProgressProps
19
+
20
+ leftIcon?: IconPlaceholder
21
+ leftIconProps?: Partial<IconProps>
22
+ rightIcon?: IconPlaceholder
23
+ rightIconProps?: Partial<IconProps>
24
+ text?: string
25
+ textProps?: Partial<TextProps>
26
+ leftText?: TextProps['text'] | JSX.Element
27
+ leftTextProps?: Partial<TextProps>
28
+ rightText?: TextProps['text'] | JSX.Element
29
+ rightTextProps?: Partial<TextProps>
30
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ CircularProgressbarWithChildren,
3
+ buildStyles,
4
+ } from 'react-circular-progressbar'
5
+ import { View, Text, Icon } from '../../components'
6
+ import { TypeGuards, useDefaultComponentStyle } from '@codeleap/common'
7
+ import { ProgressCirclePresets } from './styles'
8
+ import { ProgressCircleProps } from './types'
9
+ import { formatProgress as _formatProgress } from '../utils'
10
+ import { useMemo } from '@codeleap/common'
11
+
12
+ export * from './styles'
13
+ export * from './types'
14
+
15
+ const defaultProps: Partial<ProgressCircleProps> = {
16
+ progress: 0,
17
+ variants: [],
18
+ responsiveVariants: {},
19
+ styles: {},
20
+ showProgress: false,
21
+ formatProgress: _formatProgress,
22
+ size: null,
23
+ }
24
+
25
+ export const ProgressCircle = (props: ProgressCircleProps) => {
26
+ const allProps = {
27
+ ...ProgressCircle.defaultProps,
28
+ ...props,
29
+ }
30
+
31
+ const {
32
+ text,
33
+ progress,
34
+ icon,
35
+ iconProps,
36
+ variants,
37
+ styles,
38
+ debugName,
39
+ showProgress,
40
+ responsiveVariants,
41
+ circleProps,
42
+ children,
43
+ formatProgress,
44
+ circleStyles,
45
+ textProps,
46
+ size: propSize,
47
+ ...rest
48
+ } = allProps
49
+
50
+ const variantStyles = useDefaultComponentStyle<
51
+ 'u:ProgressCircle',
52
+ typeof ProgressCirclePresets
53
+ >('u:ProgressCircle', {
54
+ variants,
55
+ responsiveVariants,
56
+ styles,
57
+ rootElement: 'wrapper',
58
+ })
59
+
60
+ const wrapperSize = useMemo(() => {
61
+ if (TypeGuards.isNumber(propSize)) return propSize
62
+ const { size, width, height } = variantStyles.circle
63
+ const value = size ?? width ?? height
64
+ return value ?? 0
65
+ }, [variantStyles.circle])
66
+
67
+ return (
68
+ <View debugName={debugName} css={variantStyles.wrapper} {...rest}>
69
+ <CircularProgressbarWithChildren
70
+ value={progress}
71
+ css={[
72
+ variantStyles.circle,
73
+ { width: wrapperSize, height: wrapperSize },
74
+ ]}
75
+ styles={buildStyles({
76
+ pathColor: variantStyles.line?.borderColor,
77
+ trailColor: variantStyles.line?.backgroundColor,
78
+ strokeLinecap: 'butt',
79
+ ...circleStyles,
80
+ })}
81
+ {...circleProps}
82
+ >
83
+ {children}
84
+ {!TypeGuards.isNil(icon) ? (
85
+ <Icon
86
+ name={icon}
87
+ style={variantStyles.icon}
88
+ debugName={`innerIcon-${debugName}`}
89
+ {...iconProps}
90
+ />
91
+ ) : null}
92
+ {TypeGuards.isString(text) || showProgress ? (
93
+ <Text
94
+ style={variantStyles.text}
95
+ text={showProgress ? formatProgress(progress) : text}
96
+ {...textProps}
97
+ />
98
+ ) : text}
99
+ </CircularProgressbarWithChildren>
100
+ </View>
101
+ )
102
+ }
103
+
104
+ ProgressCircle.defaultProps = defaultProps
@@ -0,0 +1,8 @@
1
+ import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
2
+ import 'react-circular-progressbar/dist/styles.css'
3
+
4
+ export type ProgressCircleComposition = 'wrapper' | 'line' | 'circle' | 'text' | 'icon' | 'text'
5
+
6
+ const createProgressCircleStyle = createDefaultVariantFactory<ProgressCircleComposition>()
7
+
8
+ export const ProgressCirclePresets = includePresets((styles) => createProgressCircleStyle(() => ({ wrapper: styles })))
@@ -0,0 +1,32 @@
1
+ import { ComponentVariants, IconPlaceholder, PropsOf, StylesOf } from '@codeleap/common'
2
+ import {
3
+ IconProps,
4
+ ProgressCircleComposition,
5
+ ProgressCirclePresets,
6
+ View,
7
+ TextProps as _TextProps,
8
+ } from '../../components'
9
+ import { ProgressPropsRoot } from '..'
10
+ import { CircularProgressbarWithChildren, buildStyles } from 'react-circular-progressbar'
11
+ import { ElementType } from 'react'
12
+
13
+ type TextProps = _TextProps<ElementType>
14
+
15
+ export type ProgressCircleProps = Omit<
16
+ PropsOf<typeof View>,
17
+ 'styles' | 'variants' | 'children'
18
+ > &
19
+ ComponentVariants<typeof ProgressCirclePresets> &
20
+ ProgressPropsRoot & {
21
+ styles?: StylesOf<ProgressCircleComposition>
22
+ circleProps?: PropsOf<typeof CircularProgressbarWithChildren>
23
+ circleStyles?: Parameters<typeof buildStyles>[0]
24
+ children?: React.ReactNode
25
+ size?: number
26
+
27
+ text?: TextProps['text'] | JSX.Element
28
+ textProps?: Partial<TextProps>
29
+
30
+ icon?: IconPlaceholder
31
+ iconProps?: Partial<IconProps>
32
+ }
@@ -0,0 +1,10 @@
1
+ export * from './Bar'
2
+ export * from './Circle'
3
+
4
+ export type ProgressPropsRoot = {
5
+ progress: number
6
+ style?: React.CSSProperties
7
+ showProgress?: boolean
8
+ debugName?: string
9
+ formatProgress?: (progress: number) => string
10
+ }
@@ -0,0 +1,3 @@
1
+ export function formatProgress(value) {
2
+ return `${value.toFixed(value % 1 != 0 && !isNaN(value % 1) ? 2 : 0)}%`
3
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View } from '../View'
3
3
  import { SegmentedControlOption } from './SegmentedControlOption'
4
- import { ComponentVariants, useDefaultComponentStyle, PropsOf, IconPlaceholder, StylesOf, useRef } from '@codeleap/common'
4
+ import { ComponentVariants, useDefaultComponentStyle, PropsOf, IconPlaceholder, StylesOf, useRef, TypeGuards } from '@codeleap/common'
5
5
  import { SegmentedControlPresets } from './styles'
6
6
  import { Text } from '../Text'
7
7
  import { Touchable } from '../Touchable'
@@ -49,6 +49,8 @@ export type SegmentedControlProps<T = string> = ComponentVariants<typeof Segment
49
49
  RenderAnimatedView?: ForwardRefComponent<HTMLDivElement, any>
50
50
  textProps?: Omit<PropsOf<typeof Text>, 'key'>
51
51
  iconProps?: Partial<IconProps>
52
+ debounce?: number
53
+ debounceEnabled?: boolean
52
54
  }
53
55
 
54
56
  const defaultProps: Partial<SegmentedControlProps> = {
@@ -56,6 +58,8 @@ const defaultProps: Partial<SegmentedControlProps> = {
56
58
  transitionDuration: 0.2,
57
59
  disabled: false,
58
60
  RenderAnimatedView: motion.div,
61
+ debounce: 1000,
62
+ debounceEnabled: true,
59
63
  }
60
64
 
61
65
  export const SegmentedControl = (props: SegmentedControlProps) => {
@@ -81,6 +85,8 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
81
85
  textProps = {},
82
86
  iconProps = {},
83
87
  debugName,
88
+ debounce,
89
+ debounceEnabled,
84
90
  ...rest
85
91
  } = allProps
86
92
 
@@ -98,6 +104,7 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
98
104
  }, [value])
99
105
 
100
106
  const maxDivWidthRef = useRef(null)
107
+ const sectionPressedRef = useRef(null)
101
108
 
102
109
  const largestWidth = React.useMemo(() => {
103
110
  return {
@@ -128,6 +135,21 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
128
135
  largestWidth,
129
136
  ]
130
137
 
138
+ const onSelectTab = (option: SegmentedControlOptionProps) => {
139
+ if (!debounceEnabled || !TypeGuards.isNumber(debounce)) {
140
+ onValueChange(option.value)
141
+ return
142
+ }
143
+
144
+ if (sectionPressedRef.current !== null) return
145
+
146
+ onValueChange(option.value)
147
+ sectionPressedRef.current = setTimeout(() => {
148
+ clearTimeout(sectionPressedRef.current)
149
+ sectionPressedRef.current = null
150
+ }, debounce)
151
+ }
152
+
131
153
  return (
132
154
  <View css={[variantStyles.wrapper, style]} {...rest}>
133
155
  {label && <Text text={label} css={[variantStyles.label, disabled && variantStyles['label:disabled']]} />}
@@ -148,7 +170,7 @@ export const SegmentedControl = (props: SegmentedControlProps) => {
148
170
  debugName={debugName}
149
171
  label={o.label}
150
172
  value={o.value}
151
- onPress={() => onValueChange(o.value)}
173
+ onPress={() => onSelectTab(o)}
152
174
  key={idx}
153
175
  icon={o.icon}
154
176
  selected={value === o.value}
@@ -0,0 +1,126 @@
1
+ import React from 'react'
2
+ import { TypeGuards, useDefaultComponentStyle, useNestedStylesByKey } from '@codeleap/common'
3
+ import { TagParts, TagPresets } from './styles'
4
+ import { TagProps } from './types'
5
+ import { Icon } from '../Icon'
6
+ import { Text } from '../Text'
7
+ import { Touchable } from '../Touchable'
8
+ import { View } from '../View'
9
+ import { Badge } from '../Badge'
10
+
11
+ export * from './styles'
12
+ export * from './types'
13
+
14
+ const defaultProps: Partial<TagProps> = {
15
+ debugName: 'Tag component',
16
+ disabled: false,
17
+ }
18
+
19
+ export const Tag = (props: TagProps) => {
20
+ const allProps = {
21
+ ...Tag.defaultProps,
22
+ ...props,
23
+ }
24
+
25
+ const {
26
+ variants,
27
+ rightComponent,
28
+ leftComponent,
29
+ responsiveVariants,
30
+ styles,
31
+ style,
32
+ css,
33
+ leftIcon,
34
+ text,
35
+ textProps,
36
+ rightIcon,
37
+ rightIconProps,
38
+ leftIconProps,
39
+ leftBadgeProps,
40
+ leftBadge,
41
+ rightBadge,
42
+ rightBadgeProps,
43
+ children,
44
+ onPress,
45
+ disabled,
46
+ ...touchableProps
47
+ } = allProps
48
+
49
+ const variantStyles = useDefaultComponentStyle<'u:Tag', typeof TagPresets>('u:Tag', {
50
+ variants,
51
+ responsiveVariants,
52
+ styles,
53
+ })
54
+
55
+ const leftBadgeStyles = useNestedStylesByKey('leftBadge', variantStyles)
56
+ const rightBadgeStyles = useNestedStylesByKey('rightBadge', variantStyles)
57
+
58
+ const isPressable = TypeGuards.isFunction(onPress)
59
+
60
+ const Wrapper: any = isPressable ? Touchable : View
61
+
62
+ const wrapperProps = isPressable ? { onPress, ...touchableProps } : touchableProps
63
+
64
+ const getStylesByKey = (styleKey: TagParts) => ([
65
+ variantStyles?.[styleKey],
66
+ isPressable && variantStyles[`${styleKey}:pressable`],
67
+ disabled && variantStyles[`${styleKey}:disabled`],
68
+ ])
69
+
70
+ const wrapperStyles = React.useMemo(() => ([
71
+ getStylesByKey('wrapper'),
72
+ css,
73
+ style,
74
+ ]), [variantStyles, disabled, isPressable, style])
75
+
76
+ const textStyles = React.useMemo(() => getStylesByKey('text'), [variantStyles, disabled, isPressable])
77
+ const leftIconStyles = React.useMemo(() => getStylesByKey('leftIcon'), [variantStyles, disabled, isPressable])
78
+ const rightIconStyles = React.useMemo(() => getStylesByKey('rightIcon'), [variantStyles, disabled, isPressable])
79
+
80
+ return (
81
+ <Wrapper css={wrapperStyles} disabled={disabled} {...wrapperProps}>
82
+ {leftComponent}
83
+ {leftBadge && (
84
+ <Badge
85
+ debugName={`${touchableProps?.debugName}:leftBadge`}
86
+ styles={leftBadgeStyles}
87
+ badge={leftBadge}
88
+ disabled={disabled}
89
+ {...leftBadgeProps}
90
+ />
91
+ )}
92
+ {!TypeGuards.isNil(leftIcon) && (
93
+ <Icon
94
+ debugName={`${touchableProps?.debugName}:leftIcon`}
95
+ css={leftIconStyles}
96
+ name={leftIcon}
97
+ {...leftIconProps}
98
+ />
99
+ )}
100
+
101
+ {TypeGuards.isString(text) ? <Text text={text} css={textStyles} {...textProps} /> : text}
102
+ {children}
103
+
104
+ {!TypeGuards.isNil(rightIcon) && (
105
+ <Icon
106
+ debugName={`${touchableProps?.debugName}:rightIcon`}
107
+ css={rightIconStyles}
108
+ name={rightIcon}
109
+ {...rightIconProps}
110
+ />
111
+ )}
112
+ {rightBadge && (
113
+ <Badge
114
+ debugName={`${touchableProps?.debugName}:rightBadge`}
115
+ styles={rightBadgeStyles}
116
+ badge={rightBadge}
117
+ disabled={disabled}
118
+ {...rightBadgeProps}
119
+ />
120
+ )}
121
+ {rightComponent}
122
+ </Wrapper>
123
+ )
124
+ }
125
+
126
+ Tag.defaultProps = defaultProps
@@ -0,0 +1,23 @@
1
+ import { createDefaultVariantFactory, includePresets } from '@codeleap/common'
2
+ import { BadgeComposition } from '../Badge'
3
+
4
+ export type TagStates = 'pressable' | 'disabled'
5
+
6
+ type TagBadgeParts =
7
+ | `leftBadge${Capitalize<BadgeComposition>}`
8
+ | `rightBadge${Capitalize<BadgeComposition>}`
9
+
10
+ export type TagParts =
11
+ | `wrapper`
12
+ | 'text'
13
+ | 'leftIcon'
14
+ | 'rightIcon'
15
+
16
+ export type TagComposition = TagParts | TagBadgeParts | `${TagParts}:${TagStates}`
17
+
18
+ const createTagStyle = createDefaultVariantFactory<TagComposition>()
19
+
20
+ export const TagPresets = includePresets((styles) => createTagStyle(() => ({
21
+ wrapper: styles,
22
+ })),
23
+ )
@@ -0,0 +1,29 @@
1
+ import { StylesOf, ComponentVariants, IconPlaceholder } from '@codeleap/common'
2
+ import { ReactElement } from 'react'
3
+ import { TagComposition, TagPresets } from './styles'
4
+ import { BadgeProps } from '../Badge'
5
+ import { TextProps } from '../Text'
6
+ import { IconProps } from '../Icon'
7
+ import { ComponentCommonProps } from '../../types'
8
+ import { TouchableProps } from '../Touchable'
9
+ import { ViewProps } from '../View'
10
+
11
+ export type TagProps =
12
+ Omit<ViewProps<'div'>, 'styles' | 'variants' | 'responsiveVariants'> &
13
+ Omit<TouchableProps, 'styles' | 'variants' | 'responsiveVariants'> &
14
+ ComponentVariants<typeof TagPresets> &
15
+ ComponentCommonProps & {
16
+ styles?: StylesOf<TagComposition>
17
+ text?: TextProps<'p'>['text'] | ReactElement
18
+ textProps?: Partial<TextProps<'p'>>
19
+ leftIcon?: IconPlaceholder
20
+ leftIconProps?: Partial<IconProps>
21
+ rightIcon?: IconPlaceholder
22
+ rightIconProps?: Partial<IconProps>
23
+ leftComponent?: ReactElement
24
+ rightComponent?: ReactElement
25
+ leftBadge?: BadgeProps['badge']
26
+ rightBadge?: BadgeProps['badge']
27
+ leftBadgeProps?: Partial<BadgeProps>
28
+ rightBadgeProps?: Partial<BadgeProps>
29
+ }
@@ -31,5 +31,8 @@ export * from './EmptyPlaceholder'
31
31
  export * from './Grid'
32
32
  export * from './Badge'
33
33
  export * from './CropPicker'
34
- export * from './defaultStyles'
35
34
  export * from './Dropzone'
35
+ export * from './Progress'
36
+ export * from './Tag'
37
+
38
+ export * from './defaultStyles'
@@ -27,6 +27,7 @@ import { EmptyPlaceholderPresets } from './EmptyPlaceholder/styles'
27
27
  import { GridPresets } from './Grid/styles'
28
28
  import { BadgePresets } from './Badge/styles'
29
29
  import { CropPickerPresets } from './CropPicker'
30
+ import { TagPresets } from './Tag/styles'
30
31
 
31
32
  export const defaultStyles = {
32
33
  View: ViewPresets,
@@ -61,6 +62,7 @@ export const defaultStyles = {
61
62
  Badge: BadgePresets,
62
63
  Dropzone: DropzonePresets,
63
64
  CropPicker: CropPickerPresets,
65
+ Tag: TagPresets,
64
66
  }
65
67
 
66
68
  import createCache from '@emotion/cache'
package/src/index.ts CHANGED
@@ -8,3 +8,5 @@ export * from './lib/usePopState'
8
8
  export { default as Toast } from './lib/Toast'
9
9
  export { CreateOSAlert, useGlobalAlertComponent, AlertOutlet } from './lib/OSAlert'
10
10
  export type { GlobalAlertComponentProps, GlobalAlertType } from './lib/OSAlert'
11
+ export * from './lib/keyboard'
12
+ export * from './lib/localStorage'
@@ -14,12 +14,13 @@ type OSAlertArgs = {
14
14
  options?: AlertButton[]
15
15
  onDismiss?: AnyFunction
16
16
  onAction?: AnyFunction
17
+ type?: Exclude<GlobalAlertType, 'custom'> | string
17
18
  }
18
19
  type AlertEvent = AlertButton['onPress']
19
20
 
20
21
  type NamedEvents<E extends string> = Partial<Record<E, AlertEvent>>
21
22
 
22
- export type GlobalAlertType = 'info' | 'error' | 'warn' | 'ask'
23
+ export type GlobalAlertType = 'info' | 'error' | 'warn' | 'ask' | 'custom'
23
24
 
24
25
  export type GlobalAlertComponentProps = {
25
26
  args: OSAlertArgs
@@ -166,11 +167,29 @@ export function CreateOSAlert(Component) {
166
167
  onDismiss,
167
168
  })
168
169
  }
170
+
171
+ function custom(args: OSAlertArgs & {type: string}) {
172
+ const {
173
+ title = 'Hang on',
174
+ body = 'Are you sure?',
175
+ type,
176
+ ...rest
177
+ } = args
178
+
179
+ OSAlert({
180
+ title,
181
+ body,
182
+ type: type as GlobalAlertType,
183
+ ...rest,
184
+ })
185
+ }
186
+
169
187
  return {
170
188
  ask,
171
189
  warn,
172
190
  info,
173
191
  error: OSError,
192
+ custom,
174
193
  }
175
194
  }
176
195
 
package/src/lib/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from './usePopState'
5
5
  export * from './useBreakpointMatch'
6
6
  export * from './useClick'
7
7
  export * from './ListMasonry'
8
+ export * from './localStorage'
@@ -0,0 +1,41 @@
1
+ import { onUpdate, TypeGuards } from '@codeleap/common'
2
+
3
+ export const keydownDefaultKeyOptions = {
4
+ ArrowLeft: 'ArrowLeft',
5
+ ArrowRight: 'ArrowRight',
6
+ ArrowUp: 'ArrowUp',
7
+ ArrowDown: 'ArrowDown',
8
+ Enter: 'Enter',
9
+ Space: {
10
+ key: ' ',
11
+ code: 'Space',
12
+ },
13
+ }
14
+
15
+ export function useKeydown(
16
+ expectedKey: keyof typeof useKeydown.keys | { key: string; code: string },
17
+ handler: (event: KeyboardEvent) => void,
18
+ deps: Array<any> = [],
19
+ options?: boolean | AddEventListenerOptions
20
+ ) {
21
+ const eventKey = TypeGuards.isString(expectedKey) ? (useKeydown?.keys?.[expectedKey] ?? expectedKey) : expectedKey
22
+
23
+ const handleKeyPress = (event: KeyboardEvent) => {
24
+ const { key, code } = TypeGuards.isString(eventKey) ? { key: eventKey, code: eventKey } : eventKey
25
+
26
+ if (event?.key === key || event?.code === code) {
27
+ handler?.(event)
28
+ }
29
+ }
30
+
31
+ onUpdate(() => {
32
+ document.addEventListener('keydown', handleKeyPress, options)
33
+
34
+ return () => {
35
+ document.removeEventListener('keydown', handleKeyPress)
36
+ }
37
+ }, deps)
38
+ }
39
+
40
+ useKeydown.keys = keydownDefaultKeyOptions
41
+ useKeydown.defaultKeyOptions = keydownDefaultKeyOptions
@@ -0,0 +1,191 @@
1
+ import React from 'react'
2
+ import { onMount, TypeGuards, useState } from '@codeleap/common'
3
+
4
+ export type LocalStorageHandler<T> = (key: T, event: StorageEvent, value: any) => void
5
+ export type Key<T> = keyof T
6
+
7
+ type UseLocalStorageOptions = {
8
+ disableListen?: boolean
9
+ setItemValueOnMutate?: boolean
10
+ getItemValueOnMount?: boolean
11
+ parseValueOnGet?: boolean
12
+ }
13
+
14
+ export class LocalStorage<T extends Record<string, string>> {
15
+ public storageKeys: T
16
+
17
+ private storageListeners: ((event: StorageEvent) => void)[] = []
18
+
19
+ constructor(keys: T) {
20
+ this.storageKeys = keys
21
+ }
22
+
23
+ public getLocalStorage(): Storage {
24
+ if (typeof window === 'undefined') {
25
+ return {} as Storage
26
+ }
27
+ return localStorage
28
+ }
29
+
30
+ public getStorageKey(key: Key<T>): string {
31
+ return String(this.storageKeys[key] ?? key)
32
+ }
33
+
34
+ private parseValue(value: any) {
35
+ try {
36
+ return JSON.parse(value)
37
+ } catch (e) {
38
+ return value
39
+ }
40
+ }
41
+
42
+ private serializeValue(value: any): string {
43
+ if (TypeGuards.isString(value)) return value
44
+
45
+ try {
46
+ return JSON.stringify(value)
47
+ } catch (e) {
48
+ return value
49
+ }
50
+ }
51
+
52
+ public replaceItem(key: Key<T>, value: any): string {
53
+ const storageKey = this.getStorageKey(key)
54
+ const storage = this.getLocalStorage()
55
+ storage.removeItem(storageKey)
56
+ const serializedValue = this.serializeValue(value)
57
+ storage.setItem(storageKey, serializedValue)
58
+ return serializedValue
59
+ }
60
+
61
+ public getItem(key: Key<T>, parseValue = true): string | null {
62
+ const storageKey = this.getStorageKey(key)
63
+ const storage = this.getLocalStorage()
64
+
65
+ let value = storage.getItem(storageKey)
66
+
67
+ if (parseValue) {
68
+ value = this.parseValue(value)
69
+ }
70
+
71
+ return value
72
+ }
73
+
74
+ public removeItem(key: Key<T>): void {
75
+ const storageKey = this.getStorageKey(key)
76
+ const storage = this.getLocalStorage()
77
+ storage.removeItem(storageKey)
78
+ }
79
+
80
+ public setItem(key: Key<T>, value: any): string {
81
+ const storageKey = this.getStorageKey(key)
82
+ const storage = this.getLocalStorage()
83
+ const serializedValue = this.serializeValue(value)
84
+ storage.setItem(storageKey, serializedValue)
85
+ return serializedValue
86
+ }
87
+
88
+ public clear(): void {
89
+ const storage = this.getLocalStorage()
90
+ storage.clear()
91
+ }
92
+
93
+ public multiSet(keyValuePairs: Array<[Key<T>, any]>): void {
94
+ for (const [key, value] of keyValuePairs) {
95
+ this.setItem(key, value)
96
+ }
97
+ }
98
+
99
+ public multiRemove(keys: Key<T>[]): void {
100
+ for (const key of keys) {
101
+ this.removeItem(key)
102
+ }
103
+ }
104
+
105
+ public multiGet(keys: Key<T>[]): Record<string, any> {
106
+ const storage = this.getLocalStorage()
107
+ const values: Record<string, any> = {}
108
+
109
+ for (const key of keys) {
110
+ const storageKey = this.getStorageKey(key)
111
+ const value = storage.getItem(storageKey)
112
+
113
+ values[key as string] = value
114
+ }
115
+
116
+ return values
117
+ }
118
+
119
+ public use<S = any>(
120
+ key: Key<T>,
121
+ initialValue: any = null,
122
+ options: UseLocalStorageOptions = {}
123
+ ): [S, (to: S | ((prev: S) => S)) => any] {
124
+ const {
125
+ disableListen = false,
126
+ setItemValueOnMutate = true,
127
+ getItemValueOnMount = true,
128
+ parseValueOnGet = true,
129
+ } = options
130
+
131
+ const [value, _setValue] = useState<S>(() => {
132
+ return getItemValueOnMount ? (this.getItem(key, parseValueOnGet) ?? initialValue) : initialValue
133
+ })
134
+
135
+ onMount(() => {
136
+ const handler = () => {
137
+ let _initialValue = initialValue
138
+ let storedValue = this.getItem(key, parseValueOnGet)
139
+
140
+ if (!TypeGuards.isNil(storedValue) && getItemValueOnMount) {
141
+ _initialValue = this.parseValue(storedValue)
142
+ }
143
+
144
+ _setValue(_initialValue)
145
+ }
146
+
147
+ handler()
148
+
149
+ return disableListen ? null : this.listen(key, handler)
150
+ })
151
+
152
+ const setValue = (to: S | ((prev:S) => S)) => {
153
+ return _setValue((prev) => {
154
+ let newValue = prev
155
+
156
+ if (!TypeGuards.isFunction(to)) {
157
+ newValue = to
158
+ } else {
159
+ const fn = to as ((prev:S) => S)
160
+ newValue = fn(value)
161
+ }
162
+
163
+ if (setItemValueOnMutate) {
164
+ this.setItem(key, newValue)
165
+ }
166
+
167
+ return newValue
168
+ })
169
+ }
170
+
171
+ return [value, setValue]
172
+ }
173
+
174
+ public listen(key: Key<T>, handler: LocalStorageHandler<Key<T>>) {
175
+ const trigger = (event: StorageEvent) => {
176
+ const storageKey = this.getStorageKey(key)
177
+
178
+ if (event?.key === storageKey) {
179
+ handler(key, event, this.parseValue(event?.newValue))
180
+ }
181
+ }
182
+
183
+ const newLength = this.storageListeners.push(trigger)
184
+ window.addEventListener('storage', trigger)
185
+
186
+ return () => {
187
+ this.storageListeners.splice(newLength - 1, 1)
188
+ window.removeEventListener('storage', trigger)
189
+ }
190
+ }
191
+ }