@kaizen/components 1.81.0 → 1.81.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.
@@ -0,0 +1 @@
1
+ export * from 'react-aria';
@@ -0,0 +1 @@
1
+ export * from 'react-aria-components';
package/libs/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Entrypoint: libs
2
+
3
+ This directory is here to redirect any imports from `@kaizen/components/libs` to the correct distribution of compiled code to allow for treeshaking of external libraries like `react-aria`.
4
+
5
+ These are exported to reduce compatibility issues when using libs within our components.
6
+
7
+ More details: [https://github.com/theKashey/multiple-entry-points-example](https://github.com/theKashey/multiple-entry-points-example)
@@ -0,0 +1,5 @@
1
+ {
2
+ "main": "../../dist/cjs/reactAriaKZ.cjs",
3
+ "module": "../../dist/esm/reactAriaKZ.mjs",
4
+ "types": "../../dist/types/__libs__/react-aria/index.d.ts"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "main": "../../dist/cjs/reactAriaComponentsKZ.cjs",
3
+ "module": "../../dist/esm/reactAriaComponentsKZ.mjs",
4
+ "types": "../../dist/types/__libs__/react-aria-components/index.d.ts"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "1.81.0",
3
+ "version": "1.81.1",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -20,6 +20,7 @@
20
20
  "v1",
21
21
  "v2",
22
22
  "v3",
23
+ "libs",
23
24
  "dist",
24
25
  "icons",
25
26
  "locales",
@@ -47,6 +48,16 @@
47
48
  "import": "./dist/esm/next.mjs",
48
49
  "require": "./dist/cjs/next.cjs"
49
50
  },
51
+ "./libs/react-aria": {
52
+ "types": "./dist/types/__libs__/react-aria/index.d.ts",
53
+ "import": "./dist/esm/reactAriaKZ.mjs",
54
+ "require": "./dist/cjs/reactAriaKZ.cjs"
55
+ },
56
+ "./libs/react-aria-components": {
57
+ "types": "./dist/types/__libs__/react-aria-components/index.d.ts",
58
+ "import": "./dist/esm/reactAriaComponentsKZ.mjs",
59
+ "require": "./dist/cjs/reactAriaComponentsKZ.cjs"
60
+ },
50
61
  "./v1/actions": {
51
62
  "types": "./dist/types/v1-actions.d.ts",
52
63
  "import": "./dist/esm/actionsV1.mjs",
@@ -71,7 +71,7 @@ describe('<GenericModal />', () => {
71
71
 
72
72
  it('closes the modal when a click is outside of the modal content', async () => {
73
73
  const handleDismiss = vi.fn()
74
- render(<GenericModalWrapper onOutsideModalClick={handleDismiss} />)
74
+ render(<GenericModalWrapper onOutsideModalClick={handleDismiss()} />)
75
75
 
76
76
  await user.click(screen.getByTestId('GenericModalTestId-scrollLayer'))
77
77
  await waitFor(() => {
@@ -1,9 +1,10 @@
1
- import React, { useCallback, useEffect, useId, useState } from 'react'
1
+ import React, { useId, useRef } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
  import { Transition } from '@headlessui/react'
4
4
  import classnames from 'classnames'
5
- import FocusLock from 'react-focus-lock'
5
+ import { FocusOn } from 'react-focus-on'
6
6
  import { useIsClientReady } from '../../utils/useIsClientReady'
7
+
7
8
  import { warn } from '../util/console'
8
9
  import { ModalContext } from './context/ModalContext'
9
10
  import styles from './GenericModal.module.scss'
@@ -38,39 +39,22 @@ export const GenericModal = ({
38
39
 
39
40
  const labelledByID = useId()
40
41
  const describedByID = useId()
41
-
42
42
  const isClientReady = useIsClientReady()
43
43
 
44
- const [scrollLayer, setScrollLayer] = useState<HTMLDivElement | null>(null)
45
- const [modalLayer, setModalLayer] = useState<HTMLDivElement | null>(null)
44
+ const scrollLayerRef = useRef<HTMLDivElement | null>(null)
45
+ const modalLayerRef = useRef<HTMLDivElement | null>(null)
46
46
 
47
47
  const scrollModalToTop = (): void => {
48
48
  // If we have a really long modal, the autofocus could land on an element down below
49
49
  // causing the modal to scroll down and skipping over the content near the modal's top.
50
50
  // Ensure that when the modal opens, we are at the top of its content.
51
51
  requestAnimationFrame(() => {
52
- if (!scrollLayer) return
53
- scrollLayer.scrollTop = 0
54
- })
55
- }
56
-
57
- const outsideModalClickHandler = (event: React.MouseEvent): void => {
58
- if (event.target === scrollLayer || event.target === modalLayer) {
59
- onOutsideModalClick?.(event)
60
- }
61
- }
62
-
63
- const focusOnAccessibleLabel = (): void => {
64
- if (!isClientReady) return
65
-
66
- // Check if focus already exists within the modal
67
- if (modalLayer?.contains(document.activeElement)) {
68
- return
69
- }
52
+ const scrollElement = scrollLayerRef.current
70
53
 
71
- const labelElement: HTMLElement | null = document.getElementById(labelledByID)
72
-
73
- labelElement?.focus()
54
+ // This little verbose of a check but this ensures that the element is attached to the DOM as it animates in. This additional check aims to avoid race conditions
55
+ if (!scrollElement?.isConnected) return
56
+ scrollElement.scrollTop = 0
57
+ })
74
58
  }
75
59
 
76
60
  const a11yWarn = (): void => {
@@ -86,60 +70,46 @@ export const GenericModal = ({
86
70
  }
87
71
  }
88
72
 
89
- const preventBodyScroll = (): void => {
73
+ const focusOnAccessibleLabel = (): void => {
90
74
  if (!isClientReady) return
75
+ const modalElement = modalLayerRef.current
76
+ if (!modalElement?.isConnected) return
91
77
 
92
- const hasScrollbar = window.innerWidth > document.documentElement.clientWidth
93
- const scrollStyles = [styles.unscrollable]
78
+ // Check if focus already exists within the modal
79
+ if (modalElement.contains(document.activeElement)) {
80
+ return
81
+ }
82
+
83
+ const labelElement: HTMLElement | null = document.getElementById(labelledByID)
94
84
 
95
- if (hasScrollbar) {
96
- scrollStyles.push(styles.pseudoScrollbar)
85
+ if (labelElement?.isConnected) {
86
+ labelElement.focus()
97
87
  }
88
+ }
98
89
 
99
- document.documentElement.classList.add(...scrollStyles)
90
+ const onEscapeKeyHandler = (e: Event): void => {
91
+ if (e instanceof KeyboardEvent) {
92
+ onEscapeKeyup?.(e)
93
+ }
100
94
  }
101
95
 
102
96
  const onAfterEnterHandler = (): void => {
103
97
  scrollModalToTop()
104
- if (modalLayer) {
98
+ const modalElement = modalLayerRef.current
99
+ if (modalElement) {
105
100
  onAfterEnter?.()
106
101
  focusOnAccessibleLabel()
107
102
  a11yWarn()
108
103
  }
109
104
  }
110
105
 
111
- const escapeKeyHandler = useCallback(
112
- (event: KeyboardEvent): void => {
113
- if (event.key === 'Escape') {
114
- onEscapeKeyup?.(event)
115
- }
116
- },
117
- [onEscapeKeyup],
118
- )
119
-
120
- const onBeforeEnterHandler = (): void => {
121
- preventBodyScroll()
122
-
123
- if (onEscapeKeyup && isClientReady) {
124
- document.addEventListener('keyup', escapeKeyHandler)
106
+ const outsideModalClickHandler = (e: React.MouseEvent): void => {
107
+ if (e.target === scrollLayerRef.current || e.target === modalLayerRef.current) {
108
+ onOutsideModalClick?.(e)
125
109
  }
126
110
  }
127
111
 
128
- const cleanUpAfterClose = useCallback(() => {
129
- if (!isClientReady) return
130
-
131
- document.documentElement.classList.remove(styles.unscrollable, styles.pseudoScrollbar)
132
-
133
- if (onEscapeKeyup) {
134
- document.removeEventListener('keyup', escapeKeyHandler)
135
- }
136
- }, [escapeKeyHandler, onEscapeKeyup, isClientReady])
137
-
138
- /* Ensure sure add-on styles (e.g. unscrollable) and key event is cleaned up when the modal is unmounted*/
139
- useEffect(() => () => cleanUpAfterClose(), [cleanUpAfterClose])
140
-
141
112
  const onAfterLeaveHandler = (): void => {
142
- cleanUpAfterClose()
143
113
  propsOnAfterLeave?.()
144
114
  }
145
115
 
@@ -152,7 +122,6 @@ export const GenericModal = ({
152
122
  <Transition
153
123
  appear={true}
154
124
  show={isOpen}
155
- beforeEnter={onBeforeEnterHandler}
156
125
  afterEnter={onAfterEnterHandler}
157
126
  afterLeave={onAfterLeaveHandler}
158
127
  data-generic-modal-transition-wrapper
@@ -161,9 +130,10 @@ export const GenericModal = ({
161
130
  as="div"
162
131
  className={classnames(styles.transitionLayer, className)}
163
132
  >
164
- <FocusLock
165
- disabled={focusLockDisabled}
133
+ <FocusOn
134
+ focusLock={focusLockDisabled}
166
135
  returnFocus={true}
136
+ onEscapeKey={onEscapeKeyHandler}
167
137
  // Disabling false positive
168
138
  // eslint-disable-next-line jsx-a11y/no-autofocus
169
139
  autoFocus={false}
@@ -174,11 +144,9 @@ export const GenericModal = ({
174
144
  {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
175
145
  <div
176
146
  className={styles.scrollLayer}
177
- ref={(scrollLayerRef): void => {
178
- setScrollLayer(scrollLayerRef)
179
- }}
180
- onClick={outsideModalClickHandler}
147
+ ref={scrollLayerRef}
181
148
  data-testid={`${id}-scrollLayer`}
149
+ onClick={outsideModalClickHandler}
182
150
  >
183
151
  <ModalContext.Provider
184
152
  value={{
@@ -191,14 +159,14 @@ export const GenericModal = ({
191
159
  className={styles.modalLayer}
192
160
  aria-labelledby={labelledByID}
193
161
  aria-describedby={describedByID}
194
- ref={(modalLayerRef): void => setModalLayer(modalLayerRef)}
162
+ ref={modalLayerRef}
195
163
  data-testid={id}
196
164
  >
197
165
  {children}
198
166
  </div>
199
167
  </ModalContext.Provider>
200
168
  </div>
201
- </FocusLock>
169
+ </FocusOn>
202
170
  </Transition>,
203
171
  document.body,
204
172
  )
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
  import { type StoryObj } from '@storybook/react'
3
- import { expect, userEvent, within } from '@storybook/test'
3
+ import { expect, userEvent, waitFor, within } from '@storybook/test'
4
4
  import { type EditorContentArray } from '../types'
5
5
  import { RichTextEditor, type RichTextEditorProps } from './RichTextEditor'
6
6
 
@@ -149,8 +149,9 @@ export const CreateALink: Story = {
149
149
  name: 'Create a link',
150
150
  play: async (context) => {
151
151
  const { canvasElement, step } = context
152
- const { getByRole, getByText } = within(canvasElement)
152
+ const { getByRole, getByText, queryByRole, findByRole } = within(canvasElement)
153
153
  const editor = getByRole('textbox')
154
+
154
155
  await step('Focus on editor', async () => {
155
156
  await userEvent.click(editor)
156
157
  expect(editor).toHaveFocus()
@@ -185,8 +186,14 @@ export const CreateALink: Story = {
185
186
  await userEvent.keyboard('{Tab}{Enter}')
186
187
  })
187
188
 
189
+ await step('The Link Modal closes', async () => {
190
+ await waitFor(() => {
191
+ expect(queryByRole('dialog')).not.toBeInTheDocument()
192
+ })
193
+ })
194
+
188
195
  await step('Link exists in the RTE', async () => {
189
- const link = getByRole('link', { name: 'Link' })
196
+ const link = await findByRole('link', { name: 'Link' })
190
197
  expect(link).toBeInTheDocument()
191
198
  })
192
199
  },
@@ -0,0 +1 @@
1
+ export * from 'react-aria'
@@ -0,0 +1 @@
1
+ export * from 'react-aria-components'