@toptal/picasso-utils 1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-77646e396.4080 → 1.0.1-alpha-fx-4861-find-missing-deep-imports-in-staff-portal-25389459e.4083

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 (82) hide show
  1. package/dist-package/tsconfig.tsbuildinfo +1 -0
  2. package/package.json +5 -9
  3. package/src/index.ts +1 -0
  4. package/src/utils/Breakpoints/story/Breakpoints.example.tsx +49 -0
  5. package/src/utils/Breakpoints/story/MediaQueries.example.tsx +18 -0
  6. package/src/utils/Breakpoints/story/index.jsx +60 -0
  7. package/src/utils/Breakpoints/story/useBreakpoint.example.tsx +17 -0
  8. package/src/utils/Breakpoints/story/useScreens.example.tsx +60 -0
  9. package/src/utils/Colors/index.ts +2 -0
  10. package/src/utils/Colors/story/Default.example.tsx +59 -0
  11. package/src/utils/Colors/story/HowToUse.example.tsx +14 -0
  12. package/src/utils/Colors/story/index.jsx +21 -0
  13. package/src/utils/Formatters/format-amount.ts +34 -0
  14. package/src/utils/Formatters/index.ts +3 -0
  15. package/src/utils/Formatters/story/amount.example.tsx +63 -0
  16. package/src/utils/Formatters/story/index.jsx +16 -0
  17. package/src/utils/Formatters/test.ts +102 -0
  18. package/src/utils/Gradients/index.ts +2 -0
  19. package/src/utils/Gradients/story/Default.example.tsx +30 -0
  20. package/src/utils/Gradients/story/HowToUse.example.tsx +26 -0
  21. package/src/utils/Gradients/story/index.jsx +26 -0
  22. package/src/utils/Modal/__snapshots__/test.tsx.snap +209 -0
  23. package/src/utils/Modal/index.ts +2 -0
  24. package/src/utils/Modal/modal-manager.ts +27 -0
  25. package/src/utils/Modal/test.tsx +86 -0
  26. package/src/utils/Modal/use-modal.tsx +17 -0
  27. package/src/utils/Shadows/story/Default.example.tsx +45 -0
  28. package/src/utils/Shadows/story/HowToUse.example.tsx +19 -0
  29. package/src/utils/Shadows/story/index.jsx +19 -0
  30. package/src/utils/Transitions/Rotate180/Rotate180.tsx +44 -0
  31. package/src/utils/Transitions/Rotate180/__snapshots__/test.tsx.snap +29 -0
  32. package/src/utils/Transitions/Rotate180/index.ts +4 -0
  33. package/src/utils/Transitions/Rotate180/story/Default.example.tsx +22 -0
  34. package/src/utils/Transitions/Rotate180/styles.ts +11 -0
  35. package/src/utils/Transitions/Rotate180/test.tsx +26 -0
  36. package/src/utils/Transitions/index.ts +2 -0
  37. package/src/utils/Transitions/story/index.jsx +13 -0
  38. package/src/utils/Utils/story/Browser.example.tsx +28 -0
  39. package/src/utils/Utils/story/Colors.example.tsx +29 -0
  40. package/src/utils/Utils/story/Generic.example.tsx +30 -0
  41. package/src/utils/Utils/story/React.example.tsx +32 -0
  42. package/src/utils/Utils/story/Strings.example.tsx +33 -0
  43. package/src/utils/__tests__/use-page-scroll-lock.test.tsx +117 -0
  44. package/src/utils/capitalize.ts +1 -0
  45. package/src/utils/constants.ts +1 -0
  46. package/src/utils/disable-unsupported-props.ts +45 -0
  47. package/src/utils/forward-ref.ts +38 -0
  48. package/src/utils/get-name-initials.ts +15 -0
  49. package/src/utils/get-react-node-text-content.ts +36 -0
  50. package/src/utils/index.ts +92 -0
  51. package/src/utils/is-boolean.ts +3 -0
  52. package/src/utils/is-number.ts +3 -0
  53. package/src/utils/is-overflown.ts +20 -0
  54. package/src/utils/is-pointer-device.ts +9 -0
  55. package/src/utils/is-string.ts +3 -0
  56. package/src/utils/is-substring.ts +4 -0
  57. package/src/utils/kebab-to-camel-case.ts +7 -0
  58. package/src/utils/loader-palette.ts +9 -0
  59. package/src/utils/monads.ts +1 -0
  60. package/src/utils/noop.ts +1 -0
  61. package/src/utils/sum.ts +4 -0
  62. package/src/utils/test.tsx +372 -0
  63. package/src/utils/to-title-case.ts +14 -0
  64. package/src/utils/unsafe-error-log.ts +5 -0
  65. package/src/utils/use-combined-refs.ts +26 -0
  66. package/src/utils/use-deprecation-warnings.ts +56 -0
  67. package/src/utils/use-multiple-forward-refs.ts +37 -0
  68. package/src/utils/use-page-scroll-lock.ts +64 -0
  69. package/src/utils/use-safe-state.ts +25 -0
  70. package/src/utils/use-width-of.ts +26 -0
  71. package/src/utils/useBoolean/index.ts +1 -0
  72. package/src/utils/useBoolean/test.tsx +25 -0
  73. package/src/utils/useBoolean/use-boolean.ts +30 -0
  74. package/src/utils/useInterval/index.ts +1 -0
  75. package/src/utils/useInterval/test.ts +61 -0
  76. package/src/utils/useInterval/use-interval.ts +46 -0
  77. package/src/utils/useMouseEnter/index.ts +1 -0
  78. package/src/utils/useMouseEnter/test.ts +51 -0
  79. package/src/utils/useMouseEnter/use-mouse-enter.ts +30 -0
  80. package/src/utils/useOnScreen/index.ts +1 -0
  81. package/src/utils/useOnScreen/use-on-screen.ts +50 -0
  82. package/tsconfig.json +9 -0
@@ -0,0 +1,372 @@
1
+ import { render, act } from '@toptal/picasso-test-utils'
2
+ import type { Ref } from 'react'
3
+ import React, { createRef, useEffect } from 'react'
4
+
5
+ import type { ReferenceObject } from './index'
6
+ import {
7
+ capitalize,
8
+ getNameInitials,
9
+ isBoolean,
10
+ isNumber,
11
+ isString,
12
+ isSubstring,
13
+ toTitleCase,
14
+ kebabToCamelCase,
15
+ useCombinedRefs,
16
+ useWidthOf,
17
+ useSafeState,
18
+ forwardRef,
19
+ documentable,
20
+ disableUnsupportedProps,
21
+ sum,
22
+ getReactNodeTextContent,
23
+ isBrowser,
24
+ } from './index'
25
+ import unsafeErrorLog from './unsafe-error-log'
26
+
27
+ jest.mock('./unsafe-error-log')
28
+
29
+ describe('capitalize', () => {
30
+ it('should capitalize first letter', () => {
31
+ const string = capitalize('test string')
32
+
33
+ expect(string).toBe('Test string')
34
+ })
35
+ })
36
+
37
+ describe('getNameInitials', () => {
38
+ it('should extract first letters', () => {
39
+ expect(getNameInitials('John Doe')).toBe('JD')
40
+ })
41
+
42
+ it('should ignore extra spaces', () => {
43
+ expect(getNameInitials(' John Doe ')).toBe('JD')
44
+ })
45
+
46
+ it('should ignore single letter middle names', () => {
47
+ expect(getNameInitials('John T Doe')).toBe('JD')
48
+ })
49
+
50
+ it('should extract up to 3 letters', () => {
51
+ expect(getNameInitials('John Doe John Doe')).toBe('JDJ')
52
+ })
53
+ })
54
+
55
+ describe('isBoolean', () => {
56
+ it('should return true for booleans', () => {
57
+ expect(isBoolean(true)).toBe(true)
58
+ expect(isBoolean(false)).toBe(true)
59
+ })
60
+
61
+ it('should return false for other types', () => {
62
+ expect(isBoolean(1)).toBe(false)
63
+ expect(isBoolean('1')).toBe(false)
64
+ expect(isBoolean({})).toBe(false)
65
+ expect(isBoolean(null)).toBe(false)
66
+ expect(isBoolean(undefined)).toBe(false)
67
+ })
68
+ })
69
+
70
+ describe('isNumber', () => {
71
+ it('should return true for numbers', () => {
72
+ expect(isNumber(1)).toBe(true)
73
+ })
74
+
75
+ it('should return false for other types', () => {
76
+ expect(isNumber(true)).toBe(false)
77
+ expect(isNumber('1')).toBe(false)
78
+ expect(isNumber({})).toBe(false)
79
+ expect(isNumber(null)).toBe(false)
80
+ expect(isNumber(undefined)).toBe(false)
81
+ })
82
+ })
83
+
84
+ describe('isString', () => {
85
+ it('should return true for strings', () => {
86
+ expect(isString('')).toBe(true)
87
+ expect(isString('1')).toBe(true)
88
+ })
89
+
90
+ it('should return false for other types', () => {
91
+ expect(isString(true)).toBe(false)
92
+ expect(isString(1)).toBe(false)
93
+ expect(isString({})).toBe(false)
94
+ expect(isString(null)).toBe(false)
95
+ expect(isString(undefined)).toBe(false)
96
+ })
97
+ })
98
+
99
+ describe('toTitleCase', () => {
100
+ it('should convert strings', () => {
101
+ expect(toTitleCase('ab bc')).toBe('Ab Bc')
102
+ })
103
+
104
+ it('should ignore react nodes', () => {
105
+ const node = <div>ab bc</div>
106
+
107
+ expect(toTitleCase(node)).toBe(node)
108
+ })
109
+ })
110
+
111
+ describe('isSubstring', () => {
112
+ it('should check if a string contains another ignoring case', () => {
113
+ expect(isSubstring('TEST', 'a test string')).toBe(true)
114
+ expect(isSubstring('test', 'a test string')).toBe(true)
115
+ expect(isSubstring('test word', 'a test string')).toBe(false)
116
+ })
117
+ })
118
+
119
+ describe('kebabToCamelCase', () => {
120
+ it('should convert kebab to camel case', () => {
121
+ expect(kebabToCamelCase('a-test-string')).toBe('aTestString')
122
+ })
123
+ })
124
+
125
+ describe('sum', () => {
126
+ it('returns the total of all numbers in an array', () => {
127
+ expect(sum([0, 1, 2, 3])).toBe(6)
128
+ })
129
+ })
130
+
131
+ const TestUseCombinedRefs = ({ refs }: { refs: Ref<HTMLDivElement>[] }) => {
132
+ return <div ref={useCombinedRefs(...refs)} />
133
+ }
134
+
135
+ describe('useCombinedRefs', () => {
136
+ it('should combine object and function refs', async () => {
137
+ const refObject = createRef<HTMLDivElement>()
138
+ const refFunction = jest.fn()
139
+
140
+ render(<TestUseCombinedRefs refs={[refObject, refFunction]} />)
141
+
142
+ expect(refObject.current).toBeDefined()
143
+ expect(refFunction.mock.calls[0][0]).toBeDefined()
144
+ })
145
+ })
146
+
147
+ const TestForwardRef = documentable(
148
+ forwardRef(
149
+ <T extends string>(
150
+ { item }: { ref: Ref<HTMLDivElement>; item: T },
151
+ ref: Ref<HTMLDivElement>
152
+ ) => {
153
+ return <div ref={ref}>{item}</div>
154
+ }
155
+ )
156
+ )
157
+
158
+ describe('forwardRef', () => {
159
+ it('should forward a ref with generic component', () => {
160
+ const ref = createRef<HTMLDivElement>()
161
+
162
+ render(<TestForwardRef ref={ref} item='item' />)
163
+
164
+ expect(ref.current).toBeDefined()
165
+ expect(ref.current?.tagName).toBe('DIV')
166
+ })
167
+ })
168
+
169
+ const TestUseSafeState = () => {
170
+ const [state, setState] = useSafeState('initial')
171
+
172
+ useEffect(() => {
173
+ setTimeout(() => setState('changed'), 100)
174
+ }, [setState])
175
+
176
+ return <div>{state}</div>
177
+ }
178
+
179
+ describe('useSafeState', () => {
180
+ beforeEach(() => {
181
+ jest.useFakeTimers()
182
+ })
183
+
184
+ afterEach(() => {
185
+ jest.useRealTimers()
186
+ })
187
+
188
+ it('should use initial state', () => {
189
+ const { queryByText } = render(<TestUseSafeState />)
190
+
191
+ expect(queryByText('initial')).toBeInTheDocument()
192
+ })
193
+
194
+ it('should set state in an async effect', () => {
195
+ const { queryByText } = render(<TestUseSafeState />)
196
+
197
+ act(() => {
198
+ jest.runAllTimers()
199
+ })
200
+
201
+ expect(queryByText('changed')).toBeInTheDocument()
202
+ })
203
+
204
+ it('should not throw when state is set after unmounting', () => {
205
+ const { unmount } = render(<TestUseSafeState />)
206
+
207
+ unmount()
208
+
209
+ expect(() =>
210
+ act(() => {
211
+ jest.runAllTimers()
212
+ })
213
+ ).not.toThrow()
214
+ })
215
+ })
216
+
217
+ const TestUseWidthOf = ({ element }: { element: ReferenceObject }) => {
218
+ const width = useWidthOf(element)
219
+
220
+ return <div>{width}</div>
221
+ }
222
+
223
+ describe('useWidthOf', () => {
224
+ it('should measure width of passed element', () => {
225
+ const rect = {
226
+ top: 10,
227
+ left: 10,
228
+ right: 110,
229
+ bottom: 30,
230
+ width: 100,
231
+ height: 20,
232
+ }
233
+ const element = {
234
+ getBoundingClientRect: () => rect,
235
+ } as ReferenceObject
236
+
237
+ const { queryByText } = render(<TestUseWidthOf element={element} />)
238
+ const message = queryByText('100px')
239
+
240
+ expect(message).toBeInTheDocument()
241
+ })
242
+ })
243
+
244
+ const TestDisableUnsupportedProps = (props: {
245
+ type: string
246
+ max?: number | string
247
+ }) => {
248
+ const { type, max } = disableUnsupportedProps(
249
+ 'TestDisableUnsupportedProps',
250
+ props,
251
+ {
252
+ featureProps: {
253
+ type: 'text',
254
+ },
255
+ unsupportedProps: {
256
+ max: '',
257
+ },
258
+ }
259
+ )
260
+
261
+ return <input type={type} max={max} />
262
+ }
263
+
264
+ describe('disableUnsupportedProps', () => {
265
+ it('should render with supported props', () => {
266
+ const { getByRole } = render(
267
+ <TestDisableUnsupportedProps type='number' max={2} />
268
+ )
269
+ const input = getByRole('spinbutton')
270
+
271
+ expect(input).toHaveProperty('type', 'number')
272
+ expect(input).toHaveProperty('max', '2')
273
+ expect(unsafeErrorLog).not.toHaveBeenCalled()
274
+ })
275
+
276
+ it('should override unsupported props and warn the developer', () => {
277
+ const { getByRole } = render(
278
+ <TestDisableUnsupportedProps type='text' max={2} />
279
+ )
280
+ const input = getByRole('textbox')
281
+
282
+ expect(input).toHaveProperty('type', 'text')
283
+ expect(input).toHaveProperty('max', '')
284
+ expect(unsafeErrorLog).toHaveBeenCalledWith(
285
+ 'TestDisableUnsupportedProps doesn\'t support: max props when used with {"type":"text"}'
286
+ )
287
+ })
288
+ })
289
+
290
+ describe('isBrowser', () => {
291
+ let windowSpy: any
292
+
293
+ beforeEach(() => {
294
+ windowSpy = jest.spyOn(window, 'window', 'get')
295
+ })
296
+
297
+ afterEach(() => {
298
+ windowSpy?.mockRestore()
299
+ })
300
+
301
+ it('should return true if window is undefined', () => {
302
+ windowSpy?.mockImplementation(() => undefined)
303
+
304
+ expect(isBrowser()).toBe(false)
305
+ })
306
+
307
+ it('should return true for window is not undefined', () => {
308
+ windowSpy.mockImplementation(() => ({}))
309
+
310
+ expect(isBrowser()).toBe(true)
311
+ })
312
+ })
313
+
314
+ describe('getRectNodeTextContent', () => {
315
+ describe('when getting text from string node', () => {
316
+ it.each(['foo', ''])(
317
+ "returns its content original content, value: '%s'",
318
+ txt => {
319
+ expect(getReactNodeTextContent(txt)).toBe(txt)
320
+ }
321
+ )
322
+
323
+ it('strips spaces from start and end of the text', () => {
324
+ expect(getReactNodeTextContent(' foo ')).toBe('foo')
325
+ })
326
+ })
327
+
328
+ describe('when getting text from a number node', () => {
329
+ it.each([42, -12, Infinity, NaN, -0])(
330
+ 'returns its content original content, value: %s',
331
+ num => {
332
+ expect(getReactNodeTextContent(num)).toBe(String(num))
333
+ }
334
+ )
335
+ })
336
+
337
+ describe('when getting text from a array node', () => {
338
+ it('returns the contents of its elements joined by space', () => {
339
+ expect(getReactNodeTextContent(['foo', <div>bar</div>, 45])).toBe(
340
+ 'foo bar 45'
341
+ )
342
+ })
343
+ })
344
+
345
+ describe('when getting text from non printable nodes in react', () => {
346
+ it.each([null, undefined, true, false])(
347
+ 'returns an empty string, value: "%s"',
348
+ nonOp => {
349
+ expect(getReactNodeTextContent(nonOp)).toBe('')
350
+ }
351
+ )
352
+ })
353
+
354
+ describe('when getting text from a complex node', () => {
355
+ it("returns it's children content recursively", () => {
356
+ expect(
357
+ getReactNodeTextContent(
358
+ <div>
359
+ <h1>Title</h1>
360
+ <caption>
361
+ <span>Subtitle</span>
362
+ </caption>
363
+ <p>
364
+ <img />
365
+ Amet quidem quod doloribus dignissimos
366
+ </p>
367
+ </div>
368
+ )
369
+ ).toBe('Title Subtitle Amet quidem quod doloribus dignissimos')
370
+ })
371
+ })
372
+ })
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react'
2
+ import titleCase from 'ap-style-title-case'
3
+
4
+ import isString from './is-string'
5
+
6
+ const toTitleCase = (node: ReactNode) => {
7
+ if (!node || !isString(node)) {
8
+ return node
9
+ }
10
+
11
+ return titleCase(node as string)
12
+ }
13
+
14
+ export default toTitleCase
@@ -0,0 +1,5 @@
1
+ const unsafeErrorLog = (error: string) => {
2
+ console.log(error)
3
+ }
4
+
5
+ export default unsafeErrorLog
@@ -0,0 +1,26 @@
1
+ import type { RefObject, Ref } from 'react'
2
+ import { useRef, useEffect } from 'react'
3
+
4
+ const useCombinedRefs = <T>(...refs: (RefObject<T> | Ref<T>)[]) => {
5
+ const targetRef = useRef<T>(null)
6
+
7
+ useEffect(() => {
8
+ refs.forEach(ref => {
9
+ if (!ref) {
10
+ return
11
+ }
12
+
13
+ if (typeof ref === 'function') {
14
+ ref(targetRef.current)
15
+ } else {
16
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
+ // @ts-ignore
18
+ ref.current = targetRef.current
19
+ }
20
+ })
21
+ }, [refs])
22
+
23
+ return targetRef
24
+ }
25
+
26
+ export default useCombinedRefs
@@ -0,0 +1,56 @@
1
+ import React from 'react'
2
+
3
+ import unsafeErrorLog from './unsafe-error-log'
4
+
5
+ interface UseDeprecationWarningArgs {
6
+ description?: string
7
+ name: string
8
+ newName?: string
9
+ }
10
+
11
+ const useDeprecationWarning = ({
12
+ description,
13
+ name,
14
+ newName,
15
+ }: UseDeprecationWarningArgs) => {
16
+ const message =
17
+ `'${name}' component is deprecated and will be removed in the next major release of Picasso.'` +
18
+ `${newName ? ` Please use '${newName}' instead.` : ''}` +
19
+ `${description ? `\n${description}` : ''}`
20
+
21
+ React.useEffect(() => {
22
+ unsafeErrorLog(message)
23
+ }, [message])
24
+ }
25
+
26
+ interface UsePropDeprecationWarningArgs<P> {
27
+ props: P
28
+ componentName: string
29
+ description?: string
30
+ name: keyof P
31
+ newName?: string
32
+ }
33
+
34
+ const usePropDeprecationWarning = <P extends {}>({
35
+ props,
36
+ componentName,
37
+ description,
38
+ name,
39
+ newName,
40
+ }: UsePropDeprecationWarningArgs<P>) => {
41
+ const propName = String(name)
42
+ const message =
43
+ `${componentName}'s '${propName}' prop is deprecated and will be removed in the next major release of Picasso.` +
44
+ `${newName ? ` Please use '${newName}' instead.` : ''}` +
45
+ `${description ? `\n${description}` : ''}`
46
+
47
+ const isDeprecatedPropUsed = name in props
48
+
49
+ React.useEffect(() => {
50
+ if (isDeprecatedPropUsed) {
51
+ unsafeErrorLog(message)
52
+ }
53
+ }, [isDeprecatedPropUsed, message])
54
+ }
55
+
56
+ export { useDeprecationWarning, usePropDeprecationWarning }
@@ -0,0 +1,37 @@
1
+ import type { ForwardedRef } from 'react'
2
+ import { useCallback } from 'react'
3
+
4
+ const forwardRef = <T>(ref: ForwardedRef<T>, value: T) => {
5
+ if (typeof ref === 'function') {
6
+ ref(value)
7
+ } else if (ref) {
8
+ ref.current = value
9
+ }
10
+ }
11
+
12
+ /**
13
+ * This hook allows to forward ref to multiple holders.
14
+ *
15
+ * @example
16
+ *
17
+ * const ref1 = useRef(null)
18
+ * const ref2 = useRef(null)
19
+ *
20
+ * const ref = useMultipleForwardRefs([ref1, ref2])
21
+ *
22
+ * <div ref={ref} />
23
+ *
24
+ * console.log(ref1.current) // <div />
25
+ * console.log(ref2.current) // <div />
26
+ */
27
+ const useMultipleForwardRefs = <T>(refs: ForwardedRef<T>[]) =>
28
+ useCallback(
29
+ (refValue: T) => {
30
+ for (const ref of refs) {
31
+ forwardRef(ref, refValue)
32
+ }
33
+ },
34
+ [...refs]
35
+ )
36
+
37
+ export default useMultipleForwardRefs
@@ -0,0 +1,64 @@
1
+ import { isBrowser } from '@toptal/picasso-shared'
2
+ import { useEffect, useMemo } from 'react'
3
+
4
+ const layers = new Set<number>()
5
+ let scrollLock: { prevHtmlOverflow: string } | undefined = undefined
6
+
7
+ export const usePageScrollLock = (isLocked: boolean) => {
8
+ const layerId = useMemo(generateLayerId, [])
9
+
10
+ useEffect(() => {
11
+ if (isLocked) {
12
+ layers.add(layerId)
13
+ syncPageScrollLock()
14
+ }
15
+
16
+ return () => {
17
+ layers.delete(layerId)
18
+ syncPageScrollLock()
19
+ }
20
+ }, [layerId, isLocked])
21
+ }
22
+
23
+ const generateLayerId = (() => {
24
+ let count = 0
25
+
26
+ return () => {
27
+ count = count + 1
28
+
29
+ return count
30
+ }
31
+ })()
32
+
33
+ const syncPageScrollLock = () => {
34
+ if (layers.size > 0) {
35
+ addPageScrollLock()
36
+ } else {
37
+ removePageScrollLock()
38
+ }
39
+ }
40
+
41
+ const addPageScrollLock = () => {
42
+ if (!isBrowser()) {
43
+ return
44
+ }
45
+
46
+ if (!scrollLock) {
47
+ scrollLock = {
48
+ prevHtmlOverflow: document.getElementsByTagName('html')[0].style.overflow,
49
+ }
50
+ document.getElementsByTagName('html')[0].style.overflow = 'hidden'
51
+ }
52
+ }
53
+
54
+ const removePageScrollLock = () => {
55
+ if (!isBrowser()) {
56
+ return
57
+ }
58
+
59
+ if (scrollLock) {
60
+ document.getElementsByTagName('html')[0].style.overflow =
61
+ scrollLock.prevHtmlOverflow
62
+ scrollLock = undefined
63
+ }
64
+ }
@@ -0,0 +1,25 @@
1
+ import { useRef, useState, useCallback, useEffect } from 'react'
2
+
3
+ const useSafeState = <S>(initState: S | (() => S)) => {
4
+ const [state, unsafeSetState] = useState<S>(initState)
5
+
6
+ const isMounted = useRef(false)
7
+
8
+ useEffect(() => {
9
+ isMounted.current = true
10
+
11
+ return () => {
12
+ isMounted.current = false
13
+ }
14
+ })
15
+
16
+ const setState: typeof unsafeSetState = useCallback(newState => {
17
+ if (isMounted.current) {
18
+ unsafeSetState(newState)
19
+ }
20
+ }, [])
21
+
22
+ return [state, setState]
23
+ }
24
+
25
+ export default useSafeState as typeof useState
@@ -0,0 +1,26 @@
1
+ import { useState } from 'react'
2
+ import { useIsomorphicLayoutEffect } from '@toptal/picasso-shared'
3
+
4
+ export interface ReferenceObject {
5
+ offsetParent?: Element
6
+ getBoundingClientRect: () => ClientRect
7
+ }
8
+
9
+ const useWidthOf = <T extends ReferenceObject>(element: T | null) => {
10
+ const [menuWidth, setMenuWidth] = useState<string | undefined>()
11
+
12
+ const offsetParent = element?.offsetParent
13
+
14
+ useIsomorphicLayoutEffect(() => {
15
+ if (!element) {
16
+ return
17
+ }
18
+ const { width } = element.getBoundingClientRect()
19
+
20
+ setMenuWidth(`${width}px`)
21
+ }, [element, offsetParent])
22
+
23
+ return menuWidth
24
+ }
25
+
26
+ export default useWidthOf
@@ -0,0 +1 @@
1
+ export { default } from './use-boolean'
@@ -0,0 +1,25 @@
1
+ import { act, renderHook } from '@testing-library/react-hooks'
2
+
3
+ import useBoolean from './use-boolean'
4
+
5
+ describe('useBoolean', () => {
6
+ it('sets state correctly', () => {
7
+ const { result } = renderHook(() => useBoolean())
8
+ const [, open, close, toggle] = result.current
9
+
10
+ expect(result.current[0]).toBeFalsy()
11
+
12
+ act(() => open())
13
+
14
+ expect(result.current[0]).toBeTruthy()
15
+
16
+ act(() => close())
17
+ expect(result.current[0]).toBeFalsy()
18
+
19
+ act(() => toggle())
20
+ expect(result.current[0]).toBeTruthy()
21
+
22
+ act(() => toggle())
23
+ expect(result.current[0]).toBeFalsy()
24
+ })
25
+ })
@@ -0,0 +1,30 @@
1
+ import { useState } from 'react'
2
+
3
+ /** Set value to true */
4
+ type SetTruthy = () => void
5
+ /** Set value to false */
6
+ type SetFalsy = () => void
7
+ /** Toggle the value */
8
+ type Toggle = () => void
9
+ type IsTruthy = boolean
10
+
11
+ type UseBooleanType = (
12
+ defaultValue?: boolean
13
+ ) => [IsTruthy, SetTruthy, SetFalsy, Toggle]
14
+
15
+ /**
16
+ *
17
+ * @param defaultValue?: Boolean
18
+ * @returns [isTruthy, setTruthy, setFalsy, toggle]
19
+ */
20
+ const useBoolean: UseBooleanType = (defaultValue = false) => {
21
+ const [isTruthy, setBoolean] = useState(defaultValue)
22
+
23
+ const setTruthy: SetTruthy = () => setBoolean(true)
24
+ const setFalsy: SetFalsy = () => setBoolean(false)
25
+ const toggle: Toggle = () => setBoolean(value => !value)
26
+
27
+ return [isTruthy, setTruthy, setFalsy, toggle]
28
+ }
29
+
30
+ export default useBoolean
@@ -0,0 +1 @@
1
+ export { default } from './use-interval'