@kaizen/components 1.35.2 → 1.36.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.
Files changed (125) hide show
  1. package/dist/cjs/KaizenProvider/KaizenProvider.cjs +6 -1
  2. package/dist/cjs/KaizenProvider/KaizenProvider.cjs.map +1 -1
  3. package/dist/cjs/Modal/ContextModal/ContextModal.cjs +9 -6
  4. package/dist/cjs/Modal/ContextModal/ContextModal.cjs.map +1 -1
  5. package/dist/cjs/Notification/ToastNotification/ToastNotification/ToastNotification.cjs +33 -0
  6. package/dist/cjs/Notification/ToastNotification/ToastNotification/ToastNotification.cjs.map +1 -0
  7. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.cjs +36 -0
  8. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.cjs.map +1 -0
  9. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.cjs +7 -0
  10. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.cjs.map +1 -0
  11. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.cjs +42 -0
  12. package/dist/cjs/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.cjs.map +1 -0
  13. package/dist/cjs/Notification/ToastNotification/context/ToastNotificationContext.cjs +72 -0
  14. package/dist/cjs/Notification/ToastNotification/context/ToastNotificationContext.cjs.map +1 -0
  15. package/dist/cjs/Notification/ToastNotification/hooks/useToastNotification.cjs +9 -0
  16. package/dist/cjs/Notification/ToastNotification/hooks/useToastNotification.cjs.map +1 -0
  17. package/dist/cjs/RichTextEditor/RichTextEditor/RichTextEditor.cjs +6 -2
  18. package/dist/cjs/RichTextEditor/RichTextEditor/RichTextEditor.cjs.map +1 -1
  19. package/dist/cjs/__future__/Select/Select.cjs +14 -1
  20. package/dist/cjs/__future__/Select/Select.cjs.map +1 -1
  21. package/dist/cjs/dts/Modal/ContextModal/ContextModal.d.ts +2 -1
  22. package/dist/cjs/dts/Notification/ToastNotification/ToastNotification/ToastNotification.d.ts +16 -0
  23. package/dist/cjs/dts/Notification/ToastNotification/ToastNotification/index.d.ts +1 -0
  24. package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.d.ts +4 -0
  25. package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.d.ts +12 -0
  26. package/dist/cjs/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.d.ts +1 -0
  27. package/dist/cjs/dts/Notification/ToastNotification/context/ToastNotificationContext.d.ts +21 -0
  28. package/dist/cjs/dts/Notification/ToastNotification/hooks/useToastNotification.d.ts +2 -0
  29. package/dist/cjs/dts/Notification/ToastNotification/index.d.ts +3 -2
  30. package/dist/cjs/dts/Notification/ToastNotification/types.d.ts +1 -9
  31. package/dist/cjs/dts/Notification/index.d.ts +1 -0
  32. package/dist/cjs/dts/RichTextEditor/RichTextEditor/RichTextEditor.d.ts +1 -1
  33. package/dist/cjs/dts/__future__/Select/Select.d.ts +5 -1
  34. package/dist/cjs/index.cjs +6 -0
  35. package/dist/cjs/index.cjs.map +1 -1
  36. package/dist/cjs/index.css +6 -5
  37. package/dist/esm/KaizenProvider/KaizenProvider.mjs +6 -1
  38. package/dist/esm/KaizenProvider/KaizenProvider.mjs.map +1 -1
  39. package/dist/esm/Modal/ContextModal/ContextModal.mjs +9 -6
  40. package/dist/esm/Modal/ContextModal/ContextModal.mjs.map +1 -1
  41. package/dist/esm/Notification/ToastNotification/ToastNotification/ToastNotification.mjs +31 -0
  42. package/dist/esm/Notification/ToastNotification/ToastNotification/ToastNotification.mjs.map +1 -0
  43. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.mjs +34 -0
  44. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.mjs.map +1 -0
  45. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.mjs +5 -0
  46. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.module.scss.mjs.map +1 -0
  47. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.mjs +40 -0
  48. package/dist/esm/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.mjs.map +1 -0
  49. package/dist/esm/Notification/ToastNotification/context/ToastNotificationContext.mjs +69 -0
  50. package/dist/esm/Notification/ToastNotification/context/ToastNotificationContext.mjs.map +1 -0
  51. package/dist/esm/Notification/ToastNotification/hooks/useToastNotification.mjs +7 -0
  52. package/dist/esm/Notification/ToastNotification/hooks/useToastNotification.mjs.map +1 -0
  53. package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs +6 -2
  54. package/dist/esm/RichTextEditor/RichTextEditor/RichTextEditor.mjs.map +1 -1
  55. package/dist/esm/__future__/Select/Select.mjs +15 -2
  56. package/dist/esm/__future__/Select/Select.mjs.map +1 -1
  57. package/dist/esm/dts/Modal/ContextModal/ContextModal.d.ts +2 -1
  58. package/dist/esm/dts/Notification/ToastNotification/ToastNotification/ToastNotification.d.ts +16 -0
  59. package/dist/esm/dts/Notification/ToastNotification/ToastNotification/index.d.ts +1 -0
  60. package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.d.ts +4 -0
  61. package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.d.ts +12 -0
  62. package/dist/esm/dts/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.d.ts +1 -0
  63. package/dist/esm/dts/Notification/ToastNotification/context/ToastNotificationContext.d.ts +21 -0
  64. package/dist/esm/dts/Notification/ToastNotification/hooks/useToastNotification.d.ts +2 -0
  65. package/dist/esm/dts/Notification/ToastNotification/index.d.ts +3 -2
  66. package/dist/esm/dts/Notification/ToastNotification/types.d.ts +1 -9
  67. package/dist/esm/dts/Notification/index.d.ts +1 -0
  68. package/dist/esm/dts/RichTextEditor/RichTextEditor/RichTextEditor.d.ts +1 -1
  69. package/dist/esm/dts/__future__/Select/Select.d.ts +5 -1
  70. package/dist/esm/index.css +5 -4
  71. package/dist/esm/index.mjs +3 -0
  72. package/dist/esm/index.mjs.map +1 -1
  73. package/dist/index.d.ts +50 -3
  74. package/dist/styles.css +1 -1
  75. package/package.json +2 -2
  76. package/src/DatePicker/DatePicker.spec.tsx +1 -1
  77. package/src/KaizenProvider/KaizenProvider.tsx +6 -1
  78. package/src/Modal/ContextModal/ContextModal.spec.tsx +3 -3
  79. package/src/Modal/ContextModal/ContextModal.tsx +9 -5
  80. package/src/Notification/ToastNotification/ToastNotification/ToastNotification.spec.tsx +33 -0
  81. package/src/Notification/ToastNotification/ToastNotification/ToastNotification.tsx +48 -0
  82. package/src/Notification/ToastNotification/ToastNotification/index.ts +1 -0
  83. package/src/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/ToastNotificationsList.module.scss +1 -1
  84. package/src/Notification/ToastNotification/ToastNotificationsList/ToastNotificationsList.tsx +40 -0
  85. package/src/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/ToastNotificationsMap.tsx +49 -0
  86. package/src/Notification/ToastNotification/ToastNotificationsList/subcomponents/ToastNotificationsMap/index.ts +1 -0
  87. package/src/Notification/ToastNotification/_docs/ToastNotification.mdx +19 -14
  88. package/src/Notification/ToastNotification/_docs/ToastNotification.stickersheet.stories.tsx +33 -70
  89. package/src/Notification/ToastNotification/_docs/ToastNotification.stories.tsx +123 -93
  90. package/src/Notification/ToastNotification/context/ToastNotificationContext.tsx +96 -0
  91. package/src/Notification/ToastNotification/hooks/useToastNotification.ts +9 -0
  92. package/src/Notification/ToastNotification/index.ts +3 -2
  93. package/src/Notification/ToastNotification/types.ts +1 -18
  94. package/src/Notification/index.ts +1 -0
  95. package/src/RichTextEditor/RichTextEditor/RichTextEditor.tsx +6 -1
  96. package/src/RichTextEditor/utils/commands/addMark.spec.ts +0 -1
  97. package/src/Tooltip/Tooltip.spec.tsx +6 -1
  98. package/src/__future__/Select/Select.spec.tsx +78 -2
  99. package/src/__future__/Select/Select.tsx +18 -2
  100. package/src/__future__/Select/_docs/Select.mdx +8 -0
  101. package/src/__future__/Select/_docs/Select.stories.tsx +29 -0
  102. package/dist/cjs/dts/Notification/ToastNotification/ToastNotification.d.ts +0 -14
  103. package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.d.ts +0 -7
  104. package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.d.ts +0 -1
  105. package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.d.ts +0 -11
  106. package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.d.ts +0 -7
  107. package/dist/cjs/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.d.ts +0 -1
  108. package/dist/esm/dts/Notification/ToastNotification/ToastNotification.d.ts +0 -14
  109. package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.d.ts +0 -7
  110. package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.d.ts +0 -1
  111. package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.d.ts +0 -11
  112. package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.d.ts +0 -7
  113. package/dist/esm/dts/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.d.ts +0 -1
  114. package/src/Notification/ToastNotification/ToastNotification.spec.tsx +0 -31
  115. package/src/Notification/ToastNotification/ToastNotification.tsx +0 -43
  116. package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.spec.tsx +0 -144
  117. package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/ToastNotificationManager.tsx +0 -135
  118. package/src/Notification/ToastNotification/subcomponents/ToastNotificationManager/index.ts +0 -1
  119. package/src/Notification/ToastNotification/subcomponents/ToastNotificationsList/ToastNotificationsList.tsx +0 -40
  120. package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.spec.tsx +0 -73
  121. package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/ToastNotificationsListContainer.tsx +0 -31
  122. package/src/Notification/ToastNotification/subcomponents/ToastNotificationsListContainer/index.ts +0 -1
  123. /package/dist/cjs/dts/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.d.ts +0 -0
  124. /package/dist/esm/dts/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.d.ts +0 -0
  125. /package/src/Notification/ToastNotification/{subcomponents/ToastNotificationsList → ToastNotificationsList}/index.ts +0 -0
@@ -1,12 +1,7 @@
1
- import React from "react"
1
+ import React, { useEffect, useId } from "react"
2
2
  import { Meta, StoryObj } from "@storybook/react"
3
3
  import { Button } from "~components/Button"
4
- import {
5
- addToastNotification,
6
- removeToastNotification,
7
- clearToastNotifications,
8
- ToastNotification,
9
- } from "../index"
4
+ import { ToastNotification, useToastNotification } from "../index"
10
5
 
11
6
  const meta = {
12
7
  title: "Components/Notifications/ToastNotification",
@@ -28,7 +23,26 @@ export default meta
28
23
 
29
24
  type Story = StoryObj<typeof meta>
30
25
 
26
+ const ToastNotificationTemplate: Story = {
27
+ render: args => {
28
+ const reactId = useId()
29
+ const { updateToastNotification } = useToastNotification()
30
+
31
+ useEffect(() => {
32
+ updateToastNotification({
33
+ ...args,
34
+ id: args.id ?? reactId,
35
+ message: args.children,
36
+ persistent: args.hideCloseIcon,
37
+ })
38
+ }, [args])
39
+
40
+ return <ToastNotification {...args} />
41
+ },
42
+ }
43
+
31
44
  export const Playground: Story = {
45
+ ...ToastNotificationTemplate,
32
46
  parameters: {
33
47
  docs: {
34
48
  canvas: {
@@ -39,104 +53,120 @@ export const Playground: Story = {
39
53
  }
40
54
 
41
55
  export const CreateNotification: Story = {
42
- render: () => (
43
- <Button
44
- label="Create notification"
45
- onClick={() =>
46
- addToastNotification({
47
- title: "Informative",
48
- type: "informative",
49
- message: "New notification!",
50
- })
51
- }
52
- />
53
- ),
54
- }
56
+ render: () => {
57
+ const { addToastNotification } = useToastNotification()
55
58
 
56
- export const UpdateNotification: Story = {
57
- render: () => (
58
- <>
59
+ return (
59
60
  <Button
60
61
  label="Create notification"
61
- classNameOverride="!mr-12"
62
- onClick={() =>
63
- addToastNotification({
64
- id: "id--update-example",
65
- title: "Cautionary",
66
- type: "cautionary",
67
- message: "This content will be updated",
68
- })
69
- }
70
- />
71
- <Button
72
- label="Update notification"
73
62
  onClick={() =>
74
63
  addToastNotification({
75
- id: "id--update-example",
76
- title: "Success",
77
- type: "positive",
78
- message: "The content was successfully updated",
64
+ title: "Informative",
65
+ type: "informative",
66
+ message: "New notification!",
79
67
  })
80
68
  }
81
69
  />
82
- </>
83
- ),
70
+ )
71
+ },
72
+ }
73
+
74
+ export const UpdateNotification: Story = {
75
+ render: () => {
76
+ const { addToastNotification, updateToastNotification } =
77
+ useToastNotification()
78
+ return (
79
+ <>
80
+ <Button
81
+ label="Create notification"
82
+ classNameOverride="!mr-12"
83
+ onClick={() =>
84
+ addToastNotification({
85
+ id: "id--update-example",
86
+ title: "Cautionary",
87
+ type: "cautionary",
88
+ message: "This content will be updated",
89
+ })
90
+ }
91
+ />
92
+ <Button
93
+ label="Update notification"
94
+ onClick={() =>
95
+ updateToastNotification({
96
+ id: "id--update-example",
97
+ title: "Success",
98
+ type: "positive",
99
+ message: "The content was successfully updated",
100
+ })
101
+ }
102
+ />
103
+ </>
104
+ )
105
+ },
84
106
  }
85
107
 
86
108
  export const RemoveNotification: Story = {
87
- render: () => (
88
- <>
89
- <Button
90
- label="Create notification"
91
- classNameOverride="!mr-12"
92
- onClick={() =>
93
- addToastNotification({
94
- id: "id--remove-example",
95
- title: "Remove",
96
- type: "negative",
97
- message: "This notification will be removed",
98
- })
99
- }
100
- />
101
- <Button
102
- label="Remove notification"
103
- onClick={() => removeToastNotification("id--remove-example")}
104
- />
105
- </>
106
- ),
109
+ render: () => {
110
+ const { addToastNotification, removeToastNotification } =
111
+ useToastNotification()
112
+ return (
113
+ <>
114
+ <Button
115
+ label="Create notification"
116
+ classNameOverride="!mr-12"
117
+ onClick={() =>
118
+ addToastNotification({
119
+ id: "id--remove-example",
120
+ title: "Remove",
121
+ type: "negative",
122
+ message: "This notification will be removed",
123
+ })
124
+ }
125
+ />
126
+ <Button
127
+ label="Remove notification"
128
+ onClick={() => removeToastNotification("id--remove-example")}
129
+ />
130
+ </>
131
+ )
132
+ },
107
133
  }
108
134
 
109
135
  export const ClearNotifications: Story = {
110
- render: () => (
111
- <>
112
- <Button
113
- label="Create notifications"
114
- classNameOverride="!mr-12"
115
- onClick={() => {
116
- addToastNotification({
117
- id: "id--clear-example-1",
118
- title: "First",
119
- type: "positive",
120
- message: "This notification will be removed",
121
- })
122
- addToastNotification({
123
- id: "id--clear-example-2",
124
- title: "Second",
125
- type: "cautionary",
126
- message: "This notification will also be removed",
127
- })
128
- addToastNotification({
129
- id: "id--clear-example-3",
130
- title: "Third",
131
- type: "negative",
132
- message: "This notification will also also be removed",
133
- })
134
- }}
135
- />
136
- <Button
137
- label="Clear notifications"
138
- onClick={() => clearToastNotifications()}
139
- />
140
- </>
141
- ),
136
+ render: () => {
137
+ const { addToastNotification, clearToastNotifications } =
138
+ useToastNotification()
139
+ return (
140
+ <>
141
+ <Button
142
+ label="Create notifications"
143
+ classNameOverride="!mr-12"
144
+ onClick={() => {
145
+ addToastNotification({
146
+ id: "id--clear-example-1",
147
+ title: "First",
148
+ type: "positive",
149
+ message: "This notification will be removed",
150
+ })
151
+ addToastNotification({
152
+ id: "id--clear-example-2",
153
+ title: "Second",
154
+ type: "cautionary",
155
+ message: "This notification will also be removed",
156
+ })
157
+ addToastNotification({
158
+ id: "id--clear-example-3",
159
+ title: "Third",
160
+ type: "negative",
161
+ message: "This notification will also also be removed",
162
+ })
163
+ }}
164
+ />
165
+ <Button
166
+ label="Clear notifications"
167
+ onClick={() => clearToastNotifications()}
168
+ />
169
+ </>
170
+ )
171
+ },
142
172
  }
@@ -0,0 +1,96 @@
1
+ import React, { useContext, useState } from "react"
2
+ import { v4 as uuidv4 } from "uuid"
3
+ import { ToastNotificationObj } from "../types"
4
+
5
+ type ToastNotificationObjOptionalId = Omit<ToastNotificationObj, "id"> & {
6
+ id?: string
7
+ }
8
+
9
+ export type ToastNotificationContextValue = {
10
+ notifications: ToastNotificationObj[]
11
+ addToastNotification: (notification: ToastNotificationObjOptionalId) => void
12
+ updateToastNotification: (notification: ToastNotificationObj) => void
13
+ removeToastNotification: (notificationId: string) => void
14
+ clearToastNotifications: () => void
15
+ }
16
+
17
+ const ToastNotificationContext =
18
+ React.createContext<ToastNotificationContextValue | null>(null)
19
+
20
+ export const useToastNotificationContext =
21
+ (): ToastNotificationContextValue => {
22
+ const context = useContext(ToastNotificationContext)
23
+
24
+ if (!context) {
25
+ throw new Error(
26
+ "useToastNotificationContext must be used within the ToastNotificationContext.Provider"
27
+ )
28
+ }
29
+
30
+ return context
31
+ }
32
+
33
+ type ToastNotificationProviderProps = {
34
+ children: React.ReactNode
35
+ }
36
+
37
+ export const ToastNotificationProvider = ({
38
+ children,
39
+ }: ToastNotificationProviderProps): JSX.Element | null => {
40
+ const [notifications, setNotifications] = useState<ToastNotificationObj[]>([])
41
+
42
+ const addToastNotification: ToastNotificationContextValue["addToastNotification"] =
43
+ notification => {
44
+ const uuid = uuidv4()
45
+ const notificationWithId = { id: uuid, ...notification }
46
+
47
+ const notificationExists = notifications.find(
48
+ ({ id }) => id === notification.id
49
+ )
50
+
51
+ if (!notificationExists) {
52
+ setNotifications(existing => [...existing, notificationWithId])
53
+ }
54
+ }
55
+
56
+ const updateToastNotification = (
57
+ notification: ToastNotificationObj
58
+ ): void => {
59
+ const notificationIndex = notifications.findIndex(
60
+ ({ id }) => id === notification.id
61
+ )
62
+
63
+ const copy = notifications.slice()
64
+ copy.splice(notificationIndex, 1, notification) // Mutation to insert notification over itself
65
+ setNotifications(copy)
66
+ }
67
+
68
+ const removeToastNotification = (notificationID: string): void => {
69
+ const notificationIndex = notifications.findIndex(
70
+ ({ id }) => id === notificationID
71
+ )
72
+ const copy = notifications.slice()
73
+ copy.splice(notificationIndex, 1) // Mutation
74
+ setNotifications(copy)
75
+ }
76
+
77
+ const clearToastNotifications = (): void => {
78
+ setNotifications([])
79
+ }
80
+
81
+ const value = {
82
+ notifications,
83
+ addToastNotification,
84
+ updateToastNotification,
85
+ removeToastNotification,
86
+ clearToastNotifications,
87
+ } satisfies ToastNotificationContextValue
88
+
89
+ return (
90
+ <ToastNotificationContext.Provider value={value}>
91
+ {children}
92
+ </ToastNotificationContext.Provider>
93
+ )
94
+ }
95
+
96
+ ToastNotificationProvider.displayName = "ToastNotificationProvider"
@@ -0,0 +1,9 @@
1
+ import {
2
+ ToastNotificationContextValue,
3
+ useToastNotificationContext,
4
+ } from "../context/ToastNotificationContext"
5
+
6
+ export const useToastNotification = (): ToastNotificationContextValue => {
7
+ const context = useToastNotificationContext()
8
+ return context
9
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./ToastNotification"
2
- export * from "./subcomponents/ToastNotificationManager"
3
- export * from "./subcomponents/ToastNotificationsList"
2
+ export * from "./ToastNotificationsList"
3
+ export * from "./hooks/useToastNotification"
4
+ export * from "./types"
@@ -1,9 +1,7 @@
1
1
  import { DataAttributes } from "~types/DataAttributes"
2
2
  import { NotificationType } from "../types"
3
3
 
4
- type Modify<T, R> = Omit<T, keyof R> & R
5
-
6
- export type ToastNotification = {
4
+ export type ToastNotificationObj = {
7
5
  id: string
8
6
  type: NotificationType
9
7
  title: string
@@ -15,18 +13,3 @@ export type ToastNotification = {
15
13
  */
16
14
  persistent?: boolean
17
15
  } & DataAttributes
18
-
19
- export type ToastNotificationWithOptionals = Modify<
20
- ToastNotification,
21
- {
22
- id?: string
23
- }
24
- >
25
-
26
- export type AddToastNotification = (
27
- notification: ToastNotificationWithOptionals
28
- ) => void
29
-
30
- export type RemoveToastNotification = (notificationId: string) => void
31
-
32
- export type ClearToastNotifications = () => void
@@ -1,2 +1,3 @@
1
1
  export * from "./InlineNotification"
2
2
  export * from "./GlobalNotification"
3
+ export * from "./ToastNotification"
@@ -74,6 +74,7 @@ export const RichTextEditor = ({
74
74
  defaultValue,
75
75
  labelText,
76
76
  "aria-labelledby": labelledBy,
77
+ "aria-describedby": describedBy,
77
78
  classNameOverride,
78
79
  controls,
79
80
  rows = 3,
@@ -144,7 +145,11 @@ export const RichTextEditor = ({
144
145
  : ""
145
146
  const descriptionAria = description ? `${editorId}-rte-description` : ""
146
147
 
147
- const ariaDescribedBy = `${validationMessageAria} ${descriptionAria}`
148
+ const ariaDescribedBy = classnames(
149
+ validationMessageAria,
150
+ descriptionAria,
151
+ describedBy
152
+ )
148
153
 
149
154
  return (
150
155
  <>
@@ -1,4 +1,3 @@
1
- import { describe, expect, it, jest } from "@jest/globals"
2
1
  import { findByText, waitFor } from "@testing-library/dom"
3
2
  import { createRichTextEditor } from "../core"
4
3
  import { addMark } from "./addMark"
@@ -48,6 +48,8 @@ describe("<Tooltip />", () => {
48
48
  // Non-semantic elements without roles should not have aria-description on them.
49
49
  // They won't read to all screen readers as expected and may be reported in Storybook's accessibility tab (which uses Axe under the hood)
50
50
  it("doesn't add an accessible description when wrapping a non-semantic element", async () => {
51
+ const warn = jest.spyOn(console, "warn").mockImplementation()
52
+
51
53
  render(
52
54
  <Tooltip
53
55
  text="Tooltip popup description for div"
@@ -62,6 +64,9 @@ describe("<Tooltip />", () => {
62
64
  expect(screen.getByText("Non semantic element")).not.toHaveAttribute(
63
65
  "aria-describedby"
64
66
  )
67
+ expect(warn).toHaveBeenCalledWith(
68
+ "<Tooltip /> is not directly wrapping a semantic element, screen reader users will not be able to access the tooltip info. To ensure accessibility, Tooltip should be wrapping a semantic and focusable element directly."
69
+ )
65
70
  })
66
71
  })
67
72
  })
@@ -74,7 +79,7 @@ describe("<Tooltip />", () => {
74
79
  isInitiallyVisible
75
80
  position="below"
76
81
  >
77
- <div role="textbox" contentEditable="true" aria-multiline="true"></div>
82
+ <div role="textbox" contentEditable="true" aria-multiline="true" />
78
83
  </Tooltip>
79
84
  )
80
85
  await waitFor(() => {
@@ -1,5 +1,5 @@
1
1
  import React from "react"
2
- import { render, waitFor } from "@testing-library/react"
2
+ import { render, waitFor, screen, within } from "@testing-library/react"
3
3
  import userEvent from "@testing-library/user-event"
4
4
  import { Select, SelectProps } from "./Select"
5
5
  import { singleMockItems } from "./_docs/mockData"
@@ -17,7 +17,6 @@ const SelectWrapper = ({
17
17
  )
18
18
  return (
19
19
  <Select
20
- id="id--select"
21
20
  label="Mock Label"
22
21
  items={items}
23
22
  description="This is a description"
@@ -356,4 +355,81 @@ describe("<Select />", () => {
356
355
  })
357
356
  })
358
357
  })
358
+
359
+ describe("Popover portal", () => {
360
+ it("has accessible trigger controls", async () => {
361
+ render(<SelectWrapper isOpen />)
362
+
363
+ const trigger = screen.getByRole("combobox", {
364
+ name: "Mock Label",
365
+ })
366
+
367
+ await waitFor(() => {
368
+ expect(trigger).toHaveAttribute("aria-controls")
369
+ })
370
+ })
371
+
372
+ it("will portal to the document body by default", async () => {
373
+ render(<SelectWrapper selectedKey="batch-brew" isOpen />)
374
+
375
+ const popover = screen.getByRole("dialog")
376
+ // expected div that FocusOn adds to the popover
377
+ const popoverFocusWrapper = popover.parentNode
378
+
379
+ await waitFor(() => {
380
+ const expectedBodyTag = popoverFocusWrapper?.parentNode
381
+ expect(expectedBodyTag?.nodeName).toEqual("BODY")
382
+ })
383
+ })
384
+
385
+ it("will render as a descendant of the element matching the id", async () => {
386
+ const SelectWithPortal = (): JSX.Element => {
387
+ const portalContainerId = "id--portal-container"
388
+ return (
389
+ <>
390
+ <div
391
+ id={portalContainerId}
392
+ data-testid="id--portal-container-test"
393
+ ></div>
394
+ <SelectWrapper
395
+ selectedKey="batch-brew"
396
+ isOpen
397
+ portalContainerId={portalContainerId}
398
+ />
399
+ </>
400
+ )
401
+ }
402
+ render(<SelectWithPortal />)
403
+
404
+ await waitFor(() => {
405
+ const newPortalRegion = screen.getByTestId("id--portal-container-test")
406
+ const popover = within(newPortalRegion).getByRole("dialog")
407
+
408
+ expect(popover).toBeInTheDocument()
409
+ })
410
+ })
411
+
412
+ it("will portal to the document body if the id does not match", async () => {
413
+ const SelectWithPortal = (): JSX.Element => {
414
+ const expectedContainerId = "id--portal-container"
415
+ return (
416
+ <>
417
+ <div id="id--wrong-id"></div>
418
+ <SelectWrapper
419
+ selectedKey="batch-brew"
420
+ isOpen
421
+ portalContainerId={expectedContainerId}
422
+ />
423
+ </>
424
+ )
425
+ }
426
+ render(<SelectWithPortal />)
427
+
428
+ await waitFor(() => {
429
+ const popover = within(document.body).getByRole("dialog")
430
+
431
+ expect(popover).toBeInTheDocument()
432
+ })
433
+ })
434
+ })
359
435
  })
@@ -1,4 +1,4 @@
1
- import React, { useId } from "react"
1
+ import React, { useEffect, useId, useState } from "react"
2
2
  import { UseFloatingReturn } from "@floating-ui/react-dom"
3
3
  import { useButton } from "@react-aria/button"
4
4
  import { HiddenSelect, useSelect } from "@react-aria/select"
@@ -67,6 +67,10 @@ export type SelectProps<Option extends SelectOption = SelectOption> = {
67
67
  * @deprecated: Either define `disabled` in your `Option` (in `items`), or use `disabledKeys`
68
68
  */
69
69
  disabledValues?: Key[]
70
+ /**
71
+ * Creates a portal for the Popover to the matching element id
72
+ */
73
+ portalContainerId?: string
70
74
  } & OverrideClassName<Omit<AriaSelectProps<Option>, OmittedAriaSelectProps>>
71
75
 
72
76
  /**
@@ -89,13 +93,14 @@ export const Select = <Option extends SelectOption = SelectOption>({
89
93
  description,
90
94
  placeholder,
91
95
  isDisabled,
96
+ portalContainerId,
92
97
  ...restProps
93
98
  }: SelectProps<Option>): JSX.Element => {
94
99
  const { refs } = useFloating<HTMLButtonElement>()
95
100
  const triggerRef = refs.reference
96
-
97
101
  const id = propsId ?? useId()
98
102
  const descriptionId = `${id}--description`
103
+ const popoverId = `${id}--popover`
99
104
 
100
105
  const disabledKeys = getDisabledKeysFromItems(items)
101
106
 
@@ -151,6 +156,15 @@ export const Select = <Option extends SelectOption = SelectOption>({
151
156
  ref: refs.setReference,
152
157
  }
153
158
 
159
+ const [portalContainer, setPortalContainer] = useState<HTMLElement>()
160
+
161
+ useEffect(() => {
162
+ if (portalContainerId) {
163
+ const portalElement = document.getElementById(portalContainerId)
164
+ portalElement && setPortalContainer(portalElement)
165
+ }
166
+ }, [])
167
+
154
168
  return (
155
169
  <div
156
170
  className={classnames(
@@ -173,6 +187,8 @@ export const Select = <Option extends SelectOption = SelectOption>({
173
187
  )}
174
188
  {state.isOpen && (
175
189
  <Popover
190
+ id={popoverId}
191
+ portalContainer={portalContainer}
176
192
  refs={refs}
177
193
  focusOnProps={{
178
194
  onEscapeKey: state.close,
@@ -98,3 +98,11 @@ Add validation messages using `status` and `validationMessage`.
98
98
 
99
99
  Set `isFullWidth` to `true` to have the Select span the full width of its container.
100
100
  <Canvas of={SelectStories.FullWidth} />
101
+
102
+ ### Portals
103
+
104
+ By default, the Select's popover will attach itself to the `body` of the document using React's `createPortal`.
105
+
106
+ You can change the default behaviour by providing a `portalContainerId` to attach this to different element in the DOM. This can help to resolve issues that may arise with `z-index` or having a Select in a modal.
107
+
108
+ <Canvas of={SelectStories.PortalContainer} />
@@ -161,3 +161,32 @@ export const Validation: Story = {
161
161
  export const FullWidth: Story = {
162
162
  args: { isFullWidth: true },
163
163
  }
164
+
165
+ export const PortalContainer: Story = {
166
+ render: args => {
167
+ const portalContainerId = "id--portal-container"
168
+ return (
169
+ <>
170
+ <div
171
+ id={portalContainerId}
172
+ className="flex gap-24 bg-gray-200 p-12 overflow-hidden h-[200px] relative"
173
+ >
174
+ <Select
175
+ {...args}
176
+ label="Default"
177
+ selectedKey="batch-brew"
178
+ id="id--select-default"
179
+ />
180
+ <Select
181
+ {...args}
182
+ label="Inner portal"
183
+ selectedKey="batch-brew"
184
+ id="id--select-inner"
185
+ portalContainerId={portalContainerId}
186
+ />
187
+ </div>
188
+ </>
189
+ )
190
+ },
191
+ parameters: { docs: { source: { type: "code" } } },
192
+ }
@@ -1,14 +0,0 @@
1
- import React from "react";
2
- import { ToastNotificationWithOptionals } from "./types";
3
- export type ToastNotificationProps = Omit<ToastNotificationWithOptionals, "message" | "persistent"> & {
4
- /**
5
- * Removes the dismiss trigger. functions the same as `persistent` in `addToastNotification`. If this is true you will need to manage the removal of notifications manually.
6
- * @default false
7
- */
8
- hideCloseIcon?: boolean;
9
- children: React.ReactNode;
10
- };
11
- export declare const ToastNotification: {
12
- ({ id: propsId, hideCloseIcon, type, title, onHide, children, ...restProps }: ToastNotificationProps): null;
13
- displayName: string;
14
- };
@@ -1,7 +0,0 @@
1
- import { AddToastNotification, ClearToastNotifications, RemoveToastNotification } from "../../types";
2
- /**
3
- * Export the curried API methods
4
- */
5
- export declare const addToastNotification: AddToastNotification;
6
- export declare const clearToastNotifications: ClearToastNotifications;
7
- export declare const removeToastNotification: RemoveToastNotification;
@@ -1 +0,0 @@
1
- export * from "./ToastNotificationManager";