@redocly/theme 0.64.0-next.2 → 0.64.0-next.4

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 (162) hide show
  1. package/lib/components/Admonition/Admonition.d.ts +1 -1
  2. package/lib/components/Admonition/Admonition.js +2 -0
  3. package/lib/components/Admonition/variables.dark.js +3 -0
  4. package/lib/components/Admonition/variables.js +13 -0
  5. package/lib/components/Button/variables.dark.js +2 -2
  6. package/lib/components/Button/variables.js +3 -3
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +3 -1
  8. package/lib/components/Catalog/CatalogTags.js +5 -2
  9. package/lib/components/Filter/variables.js +1 -1
  10. package/lib/components/LanguagePicker/LanguagePicker.js +5 -6
  11. package/lib/components/Link/Link.js +2 -2
  12. package/lib/components/Menu/MenuItem.js +1 -0
  13. package/lib/components/Menu/variables.dark.js +2 -0
  14. package/lib/components/Menu/variables.js +4 -3
  15. package/lib/components/Search/SearchAiMessage.js +9 -6
  16. package/lib/components/SvgViewer/SvgViewer.js +0 -3
  17. package/lib/components/SvgViewer/variables.js +1 -1
  18. package/lib/components/Switch/variables.dark.js +2 -2
  19. package/lib/components/Switch/variables.js +1 -1
  20. package/lib/components/TableOfContent/TableOfContent.js +1 -0
  21. package/lib/components/TableOfContent/variables.js +3 -2
  22. package/lib/components/Toast/Toast.d.ts +14 -0
  23. package/lib/components/Toast/Toast.js +239 -0
  24. package/lib/components/Toast/variables.d.ts +1 -0
  25. package/lib/components/Toast/variables.js +64 -0
  26. package/lib/components/Tooltip/JsTooltip.js +1 -1
  27. package/lib/core/constants/search.d.ts +3 -3
  28. package/lib/core/constants/toast.d.ts +1 -0
  29. package/lib/core/constants/toast.js +5 -0
  30. package/lib/core/contexts/MarkdownLinkContext.d.ts +5 -0
  31. package/lib/core/contexts/MarkdownLinkContext.js +6 -0
  32. package/lib/core/contexts/Toast/ToastContext.d.ts +2 -0
  33. package/lib/core/contexts/Toast/ToastContext.js +6 -0
  34. package/lib/core/contexts/Toast/ToastProvider.d.ts +3 -0
  35. package/lib/core/contexts/Toast/ToastProvider.js +156 -0
  36. package/lib/core/contexts/index.d.ts +3 -0
  37. package/lib/core/contexts/index.js +3 -0
  38. package/lib/core/hooks/index.d.ts +2 -0
  39. package/lib/core/hooks/index.js +2 -0
  40. package/lib/core/hooks/use-toast-logic.d.ts +18 -0
  41. package/lib/core/hooks/use-toast-logic.js +141 -0
  42. package/lib/core/hooks/use-toast.d.ts +11 -0
  43. package/lib/core/hooks/use-toast.js +17 -0
  44. package/lib/core/styles/dark.js +33 -40
  45. package/lib/core/styles/global.js +56 -54
  46. package/lib/core/styles/palette.dark.js +15 -30
  47. package/lib/core/styles/palette.js +130 -134
  48. package/lib/core/types/hooks.d.ts +2 -9
  49. package/lib/core/types/index.d.ts +1 -0
  50. package/lib/core/types/l10n.d.ts +1 -1
  51. package/lib/core/types/search.d.ts +2 -1
  52. package/lib/core/types/toast.d.ts +23 -0
  53. package/lib/core/types/toast.js +3 -0
  54. package/lib/core/utils/get-auto-dismiss-duration.d.ts +2 -0
  55. package/lib/core/utils/get-auto-dismiss-duration.js +15 -0
  56. package/lib/core/utils/index.d.ts +1 -0
  57. package/lib/core/utils/index.js +1 -0
  58. package/lib/icons/CheckboxIcon/CheckboxIcon.js +6 -4
  59. package/lib/icons/CheckboxIcon/variables.dark.js +2 -1
  60. package/lib/icons/CheckboxIcon/variables.js +3 -3
  61. package/lib/icons/IdeaIcon/IdeaIcon.d.ts +9 -0
  62. package/lib/icons/IdeaIcon/IdeaIcon.js +24 -0
  63. package/lib/icons/NewChatIcon/NewChatIcon.d.ts +11 -0
  64. package/lib/icons/NewChatIcon/NewChatIcon.js +25 -0
  65. package/lib/index.d.ts +4 -0
  66. package/lib/index.js +4 -0
  67. package/lib/markdoc/attributes/diagram-file.d.ts +5 -0
  68. package/lib/markdoc/attributes/diagram-file.js +16 -0
  69. package/lib/markdoc/components/Diagram/Diagram.d.ts +15 -0
  70. package/lib/markdoc/components/Diagram/Diagram.js +135 -0
  71. package/lib/markdoc/components/Diagram/variables.d.ts +1 -0
  72. package/lib/markdoc/components/Diagram/variables.js +15 -0
  73. package/lib/markdoc/components/MarkdownLink/MarkdownLink.d.ts +7 -0
  74. package/lib/markdoc/components/MarkdownLink/MarkdownLink.js +61 -0
  75. package/lib/markdoc/components/Tabs/variables.js +3 -3
  76. package/lib/markdoc/components/default.d.ts +2 -2
  77. package/lib/markdoc/components/default.js +2 -2
  78. package/lib/markdoc/default.d.ts +7 -0
  79. package/lib/markdoc/default.js +3 -0
  80. package/lib/markdoc/tags/admonition.js +1 -1
  81. package/lib/markdoc/tags/diagram.d.ts +2 -0
  82. package/lib/markdoc/tags/diagram.js +63 -0
  83. package/package.json +3 -3
  84. package/src/components/Admonition/Admonition.tsx +3 -1
  85. package/src/components/Admonition/variables.dark.ts +3 -0
  86. package/src/components/Admonition/variables.ts +13 -0
  87. package/src/components/Button/variables.dark.ts +2 -2
  88. package/src/components/Button/variables.ts +3 -3
  89. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +3 -1
  90. package/src/components/Catalog/CatalogTags.tsx +6 -1
  91. package/src/components/Filter/variables.ts +1 -1
  92. package/src/components/LanguagePicker/LanguagePicker.tsx +5 -5
  93. package/src/components/Link/Link.tsx +1 -1
  94. package/src/components/Menu/MenuItem.tsx +5 -1
  95. package/src/components/Menu/variables.dark.ts +2 -0
  96. package/src/components/Menu/variables.ts +4 -3
  97. package/src/components/Search/SearchAiMessage.tsx +15 -10
  98. package/src/components/SvgViewer/SvgViewer.tsx +0 -4
  99. package/src/components/SvgViewer/variables.ts +1 -1
  100. package/src/components/Switch/variables.dark.ts +2 -2
  101. package/src/components/Switch/variables.ts +1 -1
  102. package/src/components/TableOfContent/TableOfContent.tsx +1 -0
  103. package/src/components/TableOfContent/variables.ts +3 -2
  104. package/src/components/Toast/Toast.tsx +289 -0
  105. package/src/components/Toast/variables.ts +61 -0
  106. package/src/components/Tooltip/JsTooltip.tsx +1 -1
  107. package/src/core/constants/search.ts +2 -3
  108. package/src/core/constants/toast.ts +1 -0
  109. package/src/core/contexts/MarkdownLinkContext.tsx +9 -0
  110. package/src/core/contexts/Toast/ToastContext.tsx +5 -0
  111. package/src/core/contexts/Toast/ToastProvider.tsx +206 -0
  112. package/src/core/contexts/index.ts +3 -0
  113. package/src/core/hooks/index.ts +2 -0
  114. package/src/core/hooks/use-toast-logic.ts +203 -0
  115. package/src/core/hooks/use-toast.ts +47 -0
  116. package/src/core/styles/dark.ts +5 -12
  117. package/src/core/styles/global.ts +28 -26
  118. package/src/core/styles/palette.dark.ts +15 -30
  119. package/src/core/styles/palette.ts +130 -134
  120. package/src/core/types/hooks.ts +2 -7
  121. package/src/core/types/index.ts +1 -0
  122. package/src/core/types/l10n.ts +9 -9
  123. package/src/core/types/search.ts +2 -1
  124. package/src/core/types/toast.ts +28 -0
  125. package/src/core/utils/get-auto-dismiss-duration.ts +20 -0
  126. package/src/core/utils/index.ts +1 -0
  127. package/src/icons/CheckboxIcon/CheckboxIcon.tsx +26 -17
  128. package/src/icons/CheckboxIcon/variables.dark.ts +2 -1
  129. package/src/icons/CheckboxIcon/variables.ts +3 -3
  130. package/src/icons/IdeaIcon/IdeaIcon.tsx +32 -0
  131. package/src/icons/NewChatIcon/NewChatIcon.tsx +39 -0
  132. package/src/index.ts +4 -0
  133. package/src/markdoc/attributes/diagram-file.ts +9 -0
  134. package/src/markdoc/components/Diagram/Diagram.tsx +173 -0
  135. package/src/markdoc/components/Diagram/variables.ts +12 -0
  136. package/src/markdoc/components/MarkdownLink/MarkdownLink.tsx +21 -0
  137. package/src/markdoc/components/Tabs/variables.ts +3 -3
  138. package/src/markdoc/components/default.ts +2 -2
  139. package/src/markdoc/default.ts +3 -0
  140. package/src/markdoc/tags/admonition.ts +1 -1
  141. package/src/markdoc/tags/diagram.ts +73 -0
  142. package/lib/components/SvgViewer/variables.dark.d.ts +0 -1
  143. package/lib/components/SvgViewer/variables.dark.js +0 -8
  144. package/lib/markdoc/components/ExcalidrawDiagram/ExcalidrawDiagram.d.ts +0 -7
  145. package/lib/markdoc/components/ExcalidrawDiagram/ExcalidrawDiagram.js +0 -95
  146. package/lib/markdoc/components/ExcalidrawDiagram/variables.d.ts +0 -1
  147. package/lib/markdoc/components/ExcalidrawDiagram/variables.dark.d.ts +0 -1
  148. package/lib/markdoc/components/ExcalidrawDiagram/variables.dark.js +0 -8
  149. package/lib/markdoc/components/ExcalidrawDiagram/variables.js +0 -15
  150. package/lib/markdoc/components/Mermaid/Mermaid.d.ts +0 -9
  151. package/lib/markdoc/components/Mermaid/Mermaid.js +0 -96
  152. package/lib/markdoc/components/Mermaid/variables.d.ts +0 -1
  153. package/lib/markdoc/components/Mermaid/variables.dark.d.ts +0 -1
  154. package/lib/markdoc/components/Mermaid/variables.dark.js +0 -8
  155. package/lib/markdoc/components/Mermaid/variables.js +0 -16
  156. package/src/components/SvgViewer/variables.dark.ts +0 -5
  157. package/src/markdoc/components/ExcalidrawDiagram/ExcalidrawDiagram.tsx +0 -85
  158. package/src/markdoc/components/ExcalidrawDiagram/variables.dark.ts +0 -5
  159. package/src/markdoc/components/ExcalidrawDiagram/variables.ts +0 -12
  160. package/src/markdoc/components/Mermaid/Mermaid.tsx +0 -95
  161. package/src/markdoc/components/Mermaid/variables.dark.ts +0 -5
  162. package/src/markdoc/components/Mermaid/variables.ts +0 -13
@@ -0,0 +1,289 @@
1
+ import React, { memo } from 'react';
2
+ import styled, { css, keyframes } from 'styled-components';
3
+
4
+ import type { ReactElement } from 'react';
5
+ import type { ToastItem, ToastType } from '@redocly/theme/core/types';
6
+
7
+ import { CheckmarkFilledIcon } from '@redocly/theme/icons/CheckmarkFilledIcon/CheckmarkFilledIcon';
8
+ import { CircleDashIcon } from '@redocly/theme/icons/CircleDashIcon/CircleDashIcon';
9
+ import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
10
+ import { ErrorFilledIcon } from '@redocly/theme/icons/ErrorFilledIcon/ErrorFilledIcon';
11
+ import { InformationFilledIcon } from '@redocly/theme/icons/InformationFilledIcon/InformationFilledIcon';
12
+ import { WarningFilledIcon } from '@redocly/theme/icons/WarningFilledIcon/WarningFilledIcon';
13
+ import { useToastLogic } from '@redocly/theme/core/hooks';
14
+ import { Button } from '@redocly/theme/components/Button/Button';
15
+ import { TOAST_SLIDE_DURATION_MS } from '@redocly/theme/core/constants/toast';
16
+
17
+ function renderToastIcon(type: ToastType): ReactElement {
18
+ switch (type) {
19
+ case 'success':
20
+ return <CheckmarkFilledIcon size="--toast-icon-size" color="--toast-icon-color-success" />;
21
+ case 'warning':
22
+ return <WarningFilledIcon size="--toast-icon-size" color="--toast-icon-color-warning" />;
23
+ case 'error':
24
+ return <ErrorFilledIcon size="--toast-icon-size" color="--toast-icon-color-error" />;
25
+ case 'loading':
26
+ return <CircleDashIcon size="--toast-icon-size" color="--toast-icon-color-loading" />;
27
+ case 'info':
28
+ default:
29
+ return <InformationFilledIcon size="--toast-icon-size" color="--toast-icon-color-info" />;
30
+ }
31
+ }
32
+
33
+ function renderDismissButton(onClick: () => void): ReactElement {
34
+ return (
35
+ <CloseButton
36
+ aria-label="Dismiss notification"
37
+ title="Dismiss notification"
38
+ icon={
39
+ <CloseIcon size="--toast-close-button-icon-size" color="--toast-close-button-icon-color" />
40
+ }
41
+ onClick={onClick}
42
+ />
43
+ );
44
+ }
45
+
46
+ export interface ToastComponentProps {
47
+ toast: ToastItem;
48
+ onDismiss: (id: string) => void;
49
+ stackIndex: number;
50
+ stackZIndex?: number;
51
+ className?: string;
52
+ }
53
+
54
+ function ToastComponent({
55
+ toast,
56
+ onDismiss,
57
+ stackIndex,
58
+ stackZIndex = 1,
59
+ className,
60
+ }: ToastComponentProps): ReactElement {
61
+ const {
62
+ wrapperRef,
63
+ hasDetails,
64
+ dismissToast,
65
+ handleMouseEnter,
66
+ handleMouseLeave,
67
+ ariaRole,
68
+ ariaLive,
69
+ } = useToastLogic({
70
+ toast,
71
+ onDismiss,
72
+ stackIndex,
73
+ });
74
+
75
+ const icon = renderToastIcon(toast.type);
76
+ const content = !hasDetails ? (
77
+ <SimpleToastSurface $isExiting={toast.isExiting} aria-live={ariaLive} role={ariaRole}>
78
+ <ContentWrapper>
79
+ <IconWrapper>{icon}</IconWrapper>
80
+ <SimpleContent>
81
+ <SimpleText>{toast.title}</SimpleText>
82
+ </SimpleContent>
83
+ </ContentWrapper>
84
+ {renderDismissButton(dismissToast)}
85
+ </SimpleToastSurface>
86
+ ) : (
87
+ <DetailedToastSurface $isExiting={toast.isExiting} aria-live={ariaLive} role={ariaRole}>
88
+ <ContentWrapper>
89
+ <IconWrapper>{icon}</IconWrapper>
90
+ <Body>
91
+ <TitleRow>
92
+ <TitleText>{toast.title}</TitleText>
93
+ {renderDismissButton(dismissToast)}
94
+ </TitleRow>
95
+ {toast.description ? (
96
+ <DescriptionRow>
97
+ <DescriptionText>{toast.description}</DescriptionText>
98
+ </DescriptionRow>
99
+ ) : null}
100
+ </Body>
101
+ </ContentWrapper>
102
+ </DetailedToastSurface>
103
+ );
104
+
105
+ return (
106
+ <ToastWrapper
107
+ ref={wrapperRef}
108
+ $stackZIndex={stackZIndex}
109
+ className={className}
110
+ data-component-name="Toast/Toast"
111
+ data-testid={`toast-${toast.type}`}
112
+ onMouseEnter={handleMouseEnter}
113
+ onMouseLeave={handleMouseLeave}
114
+ >
115
+ {content}
116
+ </ToastWrapper>
117
+ );
118
+ }
119
+
120
+ export const Toast = memo(ToastComponent);
121
+
122
+ const slideIn = keyframes`
123
+ from {
124
+ opacity: 0;
125
+ transform: translateX(100%);
126
+ }
127
+
128
+ to {
129
+ opacity: 1;
130
+ transform: translateX(0);
131
+ }
132
+ `;
133
+
134
+ const slideOut = keyframes`
135
+ from {
136
+ opacity: 1;
137
+ transform: translateX(0);
138
+ }
139
+
140
+ to {
141
+ opacity: 0;
142
+ transform: translateX(100%);
143
+ }
144
+ `;
145
+
146
+ export const spin = keyframes`
147
+ from {
148
+ transform: rotate(0deg);
149
+ }
150
+
151
+ to {
152
+ transform: rotate(360deg);
153
+ }
154
+ `;
155
+
156
+ const ToastWrapper = styled.div<{ $stackZIndex: number }>`
157
+ position: relative;
158
+ z-index: ${({ $stackZIndex }) => $stackZIndex};
159
+ width: 100%;
160
+ pointer-events: auto;
161
+ will-change: transform;
162
+ `;
163
+
164
+ const ToastSurface = styled.div<{ $isExiting: boolean }>`
165
+ display: flex;
166
+ align-items: flex-start;
167
+ width: 100%;
168
+ min-width: var(--toast-min-width);
169
+ max-width: var(--toast-max-width);
170
+ background-color: var(--toast-bg-color);
171
+ border: var(--toast-border);
172
+ border-radius: var(--toast-border-radius);
173
+ box-shadow: var(--toast-box-shadow);
174
+ color: var(--toast-text-color);
175
+ font-family: var(--toast-font-family);
176
+ animation: ${({ $isExiting }) =>
177
+ $isExiting
178
+ ? css`
179
+ ${slideOut} ${TOAST_SLIDE_DURATION_MS}ms ease-in forwards
180
+ `
181
+ : css`
182
+ ${slideIn} ${TOAST_SLIDE_DURATION_MS}ms ease-out
183
+ `};
184
+
185
+ @media (max-width: 480px) {
186
+ min-width: 0;
187
+ max-width: none;
188
+ }
189
+ `;
190
+
191
+ const SimpleToastSurface = styled(ToastSurface)`
192
+ gap: var(--toast-simple-gap);
193
+ padding: var(--toast-simple-padding);
194
+ `;
195
+
196
+ const DetailedToastSurface = styled(ToastSurface)`
197
+ padding: var(--toast-detailed-padding);
198
+ `;
199
+
200
+ const ContentWrapper = styled.div`
201
+ display: flex;
202
+ flex: 1 1 auto;
203
+ gap: var(--toast-content-gap);
204
+ min-width: 0;
205
+ `;
206
+
207
+ const IconWrapper = styled.div`
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ width: var(--toast-icon-size);
212
+ min-width: var(--toast-icon-size);
213
+ height: var(--toast-icon-line-height);
214
+
215
+ ${CircleDashIcon} {
216
+ animation: ${spin} var(--toast-loading-animation-duration) linear infinite;
217
+ }
218
+ `;
219
+
220
+ const flexItemStyles = css`
221
+ flex: 1 1 auto;
222
+ min-width: 0;
223
+ `;
224
+
225
+ const SimpleContent = styled.div`
226
+ ${flexItemStyles}
227
+ `;
228
+
229
+ const Body = styled.div`
230
+ display: flex;
231
+ ${flexItemStyles}
232
+ flex-direction: column;
233
+ `;
234
+
235
+ const TitleRow = styled.div`
236
+ display: flex;
237
+ align-items: center;
238
+ gap: var(--toast-title-gap);
239
+ min-width: 0;
240
+ `;
241
+
242
+ const DescriptionRow = styled.div`
243
+ display: flex;
244
+ flex-wrap: wrap;
245
+ align-items: center;
246
+ gap: var(--toast-description-gap);
247
+ min-width: 0;
248
+ `;
249
+
250
+ const textStyles = css`
251
+ margin: 0;
252
+ font-size: var(--toast-text-font-size);
253
+ line-height: var(--toast-text-line-height);
254
+ color: var(--toast-text-color);
255
+ overflow-wrap: anywhere;
256
+ `;
257
+
258
+ const TitleText = styled.p`
259
+ ${textStyles}
260
+ ${flexItemStyles}
261
+ font-weight: var(--toast-title-font-weight);
262
+ `;
263
+
264
+ const SimpleText = styled.p`
265
+ ${textStyles}
266
+ font-weight: var(--toast-body-font-weight);
267
+ `;
268
+
269
+ const DescriptionText = styled.p`
270
+ ${textStyles}
271
+ ${flexItemStyles}
272
+ font-weight: var(--toast-body-font-weight);
273
+ `;
274
+
275
+ const CloseButton = styled(Button).attrs({
276
+ variant: 'ghost',
277
+ size: 'small',
278
+ })`
279
+ flex: 0 0 auto;
280
+ min-height: unset;
281
+ margin: 0;
282
+ padding: var(--toast-close-button-padding);
283
+ color: var(--toast-close-button-icon-color);
284
+
285
+ &:hover,
286
+ &:focus-visible {
287
+ border: none;
288
+ }
289
+ `;
@@ -0,0 +1,61 @@
1
+ import { css } from 'styled-components';
2
+
3
+ /* eslint-disable theme/no-raw-colors-in-styles */
4
+ export const toast = css`
5
+ /**
6
+ * @tokens Toast
7
+ * @presenter BoxShadow
8
+ */
9
+
10
+ --toast-box-shadow: 2px 2px 12px 0 rgba(0, 0, 0, 0.04), 2px 2px 24px 8px rgba(0, 0, 0, 0.04);
11
+
12
+ /**
13
+ * @tokens Toast surface
14
+ */
15
+ --toast-min-width: 240px;
16
+ --toast-max-width: 360px;
17
+ --toast-bg-color: var(--layer-color); // @presenter Color
18
+ --toast-border: var(--border-width) var(--border-style) var(--border-color-secondary); // @presenter Border
19
+ --toast-border-radius: var(--border-radius-md); // @presenter BorderRadius
20
+ --toast-font-family: var(--font-family-base); // @presenter FontFamily
21
+
22
+ /**
23
+ * @tokens Toast spacing
24
+ */
25
+ --toast-simple-gap: var(--spacing-unit);
26
+ --toast-simple-padding: var(--spacing-xs) var(--spacing-sm);
27
+ --toast-detailed-padding: var(--spacing-sm);
28
+ --toast-content-gap: var(--spacing-xs);
29
+ --toast-title-gap: var(--spacing-xs);
30
+ --toast-description-gap: var(--spacing-xs);
31
+
32
+ /**
33
+ * @tokens Toast icon
34
+ */
35
+ --toast-icon-size: 14px;
36
+ --toast-icon-line-height: var(--line-height-base); // @presenter LineHeight
37
+ --toast-icon-color-info: var(--color-info-base); // @presenter Color
38
+ --toast-icon-color-success: var(--color-success-base); // @presenter Color
39
+ --toast-icon-color-warning: var(--color-warning-base); // @presenter Color
40
+ --toast-icon-color-error: var(--color-error-base); // @presenter Color
41
+ --toast-icon-color-loading: var(--loading-spinner-color); // @presenter Color
42
+ --toast-loading-animation-duration: 3s;
43
+
44
+ /**
45
+ * @tokens Toast typography
46
+ */
47
+ --toast-text-font-size: var(--font-size-base); // @presenter FontSize
48
+ --toast-text-line-height: var(--line-height-base); // @presenter LineHeight
49
+ --toast-text-color: var(--text-color-primary); // @presenter Color
50
+ --toast-title-font-weight: var(--font-weight-semibold); // @presenter FontWeight
51
+ --toast-body-font-weight: var(--font-weight-regular); // @presenter FontWeight
52
+
53
+ /**
54
+ * @tokens Toast close button
55
+ */
56
+ --toast-close-button-padding: 3px;
57
+ --toast-close-button-icon-size: 14px;
58
+ --toast-close-button-icon-color: var(--icon-color-secondary); // @presenter Color
59
+
60
+ // @tokens End
61
+ `;
@@ -289,7 +289,7 @@ const TooltipBody = styled.span<
289
289
  var(--tooltip-border-color, transparent);
290
290
  box-shadow: var(--bg-raised-shadow);
291
291
 
292
- width: ${({ width }) => width || 'auto'};
292
+ width: ${({ width }) => width || 'max-content'};
293
293
  ${({ placement }) => css`
294
294
  ${PLACEMENTS[placement]};
295
295
  `}
@@ -46,9 +46,8 @@ export const AI_SEARCH_MAX_MESSAGE_LENGTH = 45000;
46
46
 
47
47
  export const SEARCH_DEBOUNCE_TIME_MS = 300;
48
48
 
49
- export const TOOL_CALL_DISPLAY_TEXT: Record<
50
- ToolCallName,
51
- { inProgressText: string; completedText: string }
49
+ export const TOOL_CALL_DISPLAY_TEXT: Partial<
50
+ Record<string, { inProgressText: string; completedText: string }>
52
51
  > = {
53
52
  [ToolCallName.SearchDocumentation]: {
54
53
  inProgressText: 'Searching documentation...',
@@ -0,0 +1 @@
1
+ export const TOAST_SLIDE_DURATION_MS = 300;
@@ -0,0 +1,9 @@
1
+ import { createContext } from 'react';
2
+
3
+ export type MarkdownLinkClickHandler = () => void;
4
+
5
+ export type MarkdownLinkContextValue = {
6
+ onMarkdownLinkClick?: MarkdownLinkClickHandler;
7
+ };
8
+
9
+ export const MarkdownLinkContext = createContext<MarkdownLinkContextValue | null>(null);
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ import type { ToastContextValue } from '@redocly/theme/core/types';
4
+
5
+ export const ToastContext = createContext<ToastContextValue | null>(null);
@@ -0,0 +1,206 @@
1
+ import styled from 'styled-components';
2
+ import React, { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
3
+
4
+ import type { ReactElement } from 'react';
5
+ import type {
6
+ ToastContextValue,
7
+ ToastItem,
8
+ ToastOptions,
9
+ ToastProviderProps,
10
+ ToastType,
11
+ } from '../../../core/types/toast';
12
+
13
+ import { Portal } from '@redocly/theme/components/Portal/Portal';
14
+ import { Toast } from '@redocly/theme/components/Toast/Toast';
15
+
16
+ import { ToastContext } from '../../../core/contexts/Toast/ToastContext';
17
+ import { TOAST_SLIDE_DURATION_MS } from '../../../core/constants/toast';
18
+
19
+ type ToastState = ToastItem[];
20
+
21
+ type ToastReducerAction =
22
+ | {
23
+ type: 'add';
24
+ payload: ToastItem;
25
+ }
26
+ | {
27
+ type: 'startExit';
28
+ payload: {
29
+ id: string;
30
+ };
31
+ }
32
+ | {
33
+ type: 'remove';
34
+ payload: {
35
+ id: string;
36
+ };
37
+ }
38
+ | {
39
+ type: 'update';
40
+ payload: {
41
+ id: string;
42
+ updates: Partial<ToastOptions>;
43
+ };
44
+ };
45
+
46
+ function toastReducer(state: ToastState, action: ToastReducerAction): ToastState {
47
+ switch (action.type) {
48
+ case 'add':
49
+ return [action.payload, ...state];
50
+ case 'startExit':
51
+ return state.map((toast) =>
52
+ toast.id === action.payload.id ? { ...toast, isExiting: true } : toast,
53
+ );
54
+ case 'remove':
55
+ return state.filter((toast) => toast.id !== action.payload.id);
56
+ case 'update':
57
+ return state.map((toast) =>
58
+ toast.id === action.payload.id
59
+ ? {
60
+ ...toast,
61
+ ...action.payload.updates,
62
+ type: action.payload.updates.type ?? toast.type,
63
+ }
64
+ : toast,
65
+ );
66
+ default:
67
+ return state;
68
+ }
69
+ }
70
+
71
+ function getToastType(type?: ToastType): ToastType {
72
+ return type ?? 'info';
73
+ }
74
+
75
+ function createToastId(): string {
76
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
77
+ return crypto.randomUUID();
78
+ }
79
+
80
+ return `toast-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
81
+ }
82
+
83
+ export function ToastProvider({ children, mountId }: ToastProviderProps): ReactElement {
84
+ const [toasts, dispatch] = useReducer(toastReducer, []);
85
+ const toastsRef = useRef<ToastItem[]>([]);
86
+ const removeTimeoutsRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
87
+
88
+ useEffect(() => {
89
+ toastsRef.current = toasts;
90
+ }, [toasts]);
91
+
92
+ useEffect(() => {
93
+ const timeoutsMap = removeTimeoutsRef.current;
94
+
95
+ return () => {
96
+ timeoutsMap.forEach((timeoutId) => {
97
+ clearTimeout(timeoutId);
98
+ });
99
+
100
+ timeoutsMap.clear();
101
+ };
102
+ }, []);
103
+
104
+ const dismissToast = useCallback((id: string): void => {
105
+ const currentToast = toastsRef.current.find((toast) => toast.id === id);
106
+
107
+ if (!currentToast || currentToast.isExiting) {
108
+ return;
109
+ }
110
+
111
+ currentToast.onClose?.();
112
+ dispatch({ type: 'startExit', payload: { id } });
113
+
114
+ const existingTimeout = removeTimeoutsRef.current.get(id);
115
+
116
+ if (existingTimeout) {
117
+ clearTimeout(existingTimeout);
118
+ }
119
+
120
+ const timeoutId = setTimeout(() => {
121
+ dispatch({ type: 'remove', payload: { id } });
122
+ removeTimeoutsRef.current.delete(id);
123
+ }, TOAST_SLIDE_DURATION_MS);
124
+
125
+ removeTimeoutsRef.current.set(id, timeoutId);
126
+ }, []);
127
+
128
+ const showToast = useCallback((options: ToastOptions): string => {
129
+ const id = createToastId();
130
+
131
+ dispatch({
132
+ type: 'add',
133
+ payload: {
134
+ ...options,
135
+ id,
136
+ type: getToastType(options.type),
137
+ isExiting: false,
138
+ },
139
+ });
140
+
141
+ return id;
142
+ }, []);
143
+
144
+ const updateToast = useCallback((id: string, updates: Partial<ToastOptions>): void => {
145
+ dispatch({
146
+ type: 'update',
147
+ payload: {
148
+ id,
149
+ updates,
150
+ },
151
+ });
152
+ }, []);
153
+
154
+ const contextValue = useMemo<ToastContextValue>(
155
+ () => ({
156
+ showToast,
157
+ dismissToast,
158
+ updateToast,
159
+ }),
160
+ [dismissToast, showToast, updateToast],
161
+ );
162
+
163
+ return (
164
+ <ToastContext.Provider value={contextValue}>
165
+ {children}
166
+ {toasts.length > 0 ? (
167
+ <Portal mountId={mountId}>
168
+ <ToastViewport aria-label="Notifications">
169
+ {toasts.map((toast, index) => (
170
+ <Toast
171
+ key={toast.id}
172
+ stackIndex={index}
173
+ stackZIndex={toasts.length - index}
174
+ toast={toast}
175
+ onDismiss={dismissToast}
176
+ />
177
+ ))}
178
+ </ToastViewport>
179
+ </Portal>
180
+ ) : null}
181
+ </ToastContext.Provider>
182
+ );
183
+ }
184
+
185
+ const ToastViewport = styled.div`
186
+ position: fixed;
187
+ right: var(--spacing-md);
188
+ bottom: var(--spacing-md);
189
+ z-index: 1100;
190
+ display: flex;
191
+ flex-direction: column-reverse;
192
+ gap: var(--spacing-sm);
193
+ width: 320px;
194
+ min-width: 240px;
195
+ max-width: 360px;
196
+ pointer-events: none;
197
+
198
+ @media (max-width: 480px) {
199
+ left: 50%;
200
+ right: auto;
201
+ transform: translateX(-50%);
202
+ width: calc(100vw - var(--spacing-md) * 2);
203
+ min-width: 0;
204
+ max-width: none;
205
+ }
206
+ `;
@@ -1,5 +1,8 @@
1
1
  export * from './ThemeDataContext';
2
+ export * from './MarkdownLinkContext';
2
3
  export * from './CodeWalkthrough/CodeWalkthroughControlsContext';
3
4
  export * from './CodeWalkthrough/CodeWalkthroughStepsContext';
4
5
  export * from './CodeSnippetContext';
5
6
  export * from './SearchContext';
7
+ export * from './Toast/ToastContext';
8
+ export * from './Toast/ToastProvider';
@@ -51,3 +51,5 @@ export * from './catalog/use-catalog-entity-schema';
51
51
  export * from './catalog/use-catalog-table-header-cell-actions';
52
52
  export * from './use-store';
53
53
  export * from './use-is-truncated';
54
+ export * from './use-toast';
55
+ export * from './use-toast-logic';