@stack-spot/portal-components 2.26.0 → 2.27.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 (237) hide show
  1. package/CHANGELOG.md +621 -614
  2. package/dist/components/AnimatedHeight.d.ts +1 -1
  3. package/dist/components/AnimatedHeight.js +26 -26
  4. package/dist/components/AsyncContent.d.ts +1 -1
  5. package/dist/components/AsyncContent.js +1 -1
  6. package/dist/components/BannerWarning.d.ts +1 -1
  7. package/dist/components/BannerWarning.js +1 -1
  8. package/dist/components/Breadcrumb/index.d.ts +2 -2
  9. package/dist/components/Breadcrumb/index.js +1 -1
  10. package/dist/components/Breadcrumb/styled.js +31 -31
  11. package/dist/components/ButtonLoading.d.ts +1 -1
  12. package/dist/components/ButtonLoading.js +1 -1
  13. package/dist/components/ChatBot.d.ts +1 -1
  14. package/dist/components/ChatBot.js +1 -1
  15. package/dist/components/ContentValidateFilter.d.ts +1 -1
  16. package/dist/components/ContentValidateFilter.js +1 -1
  17. package/dist/components/FadingOverflow.d.ts +1 -1
  18. package/dist/components/FadingOverflow.js +69 -69
  19. package/dist/components/FileTreeView/More.d.ts +1 -1
  20. package/dist/components/FileTreeView/More.js +1 -1
  21. package/dist/components/FileTreeView/index.d.ts +1 -1
  22. package/dist/components/FileTreeView/index.js +1 -1
  23. package/dist/components/InfiniteScroll.d.ts +1 -1
  24. package/dist/components/InfiniteScroll.js +1 -1
  25. package/dist/components/InfoMaintenanceBanner.d.ts +1 -1
  26. package/dist/components/InfoMaintenanceBanner.js +2 -2
  27. package/dist/components/LazyMarkdown/BlockquoteMd.d.ts +1 -1
  28. package/dist/components/LazyMarkdown/BlockquoteMd.js +1 -1
  29. package/dist/components/LazyMarkdown/CodeViewer.d.ts +1 -1
  30. package/dist/components/LazyMarkdown/CodeViewer.js +76 -76
  31. package/dist/components/LazyMarkdown/Markdown.d.ts +1 -1
  32. package/dist/components/LazyMarkdown/Markdown.js +1 -1
  33. package/dist/components/LazyMarkdown/MarkdownButton.d.ts +1 -1
  34. package/dist/components/LazyMarkdown/MarkdownButton.js +1 -1
  35. package/dist/components/LazyMarkdown/Video.d.ts +1 -1
  36. package/dist/components/LazyMarkdown/Video.js +1 -1
  37. package/dist/components/LazyMarkdown/index.d.ts +1 -1
  38. package/dist/components/LazyMarkdown/index.js +1 -1
  39. package/dist/components/Placeholder.d.ts +3 -3
  40. package/dist/components/Placeholder.js +1 -1
  41. package/dist/components/ScrollView.js +16 -16
  42. package/dist/components/Select/BadgeItem.d.ts +1 -1
  43. package/dist/components/Select/BadgeItem.js +1 -1
  44. package/dist/components/Select/ClearInput.d.ts +1 -1
  45. package/dist/components/Select/ClearInput.js +1 -1
  46. package/dist/components/Select/CloseItem.d.ts +1 -1
  47. package/dist/components/Select/CloseItem.js +1 -1
  48. package/dist/components/Select/CreatableSelect.js +1 -1
  49. package/dist/components/Select/CustomMenu.d.ts +1 -1
  50. package/dist/components/Select/CustomMenu.js +1 -1
  51. package/dist/components/Select/LabelItem.d.ts +1 -1
  52. package/dist/components/Select/LabelItem.js +1 -1
  53. package/dist/components/Select/MultiValue.d.ts +1 -1
  54. package/dist/components/Select/MultiValue.js +1 -1
  55. package/dist/components/Select/SelectInfiniteScroll.d.ts +1 -1
  56. package/dist/components/Select/SelectInfiniteScroll.js +1 -1
  57. package/dist/components/Select/SelectSearch.d.ts +1 -1
  58. package/dist/components/Select/SelectSearch.js +1 -1
  59. package/dist/components/SelectionList.d.ts +1 -1
  60. package/dist/components/SelectionList.js +61 -61
  61. package/dist/components/StatusCircle.d.ts +1 -1
  62. package/dist/components/StatusCircle.js +6 -6
  63. package/dist/components/Stepper/Navigation.js +4 -4
  64. package/dist/components/Stepper/Step.js +3 -3
  65. package/dist/components/Stepper/Stepper.js +6 -6
  66. package/dist/components/Stepper/headers.js +22 -22
  67. package/dist/components/Table/HeaderItem.js +1 -1
  68. package/dist/components/Table/SettingsVerticalMenu.d.ts +1 -1
  69. package/dist/components/Table/SettingsVerticalMenu.js +1 -1
  70. package/dist/components/Table/StyledLinkTable.d.ts +1 -1
  71. package/dist/components/Table/StyledLinkTable.js +5 -5
  72. package/dist/components/Table/TableData.d.ts +1 -1
  73. package/dist/components/Table/TableData.js +25 -25
  74. package/dist/components/TimelineSection.d.ts +1 -1
  75. package/dist/components/TimelineSection.js +14 -14
  76. package/dist/components/error/ErrorFeedback.d.ts +1 -1
  77. package/dist/components/error/ErrorFeedback.js +35 -35
  78. package/dist/components/error/NotFound.d.ts +1 -1
  79. package/dist/components/error/NotFound.js +1 -1
  80. package/dist/components/error/UnderMaintenance.d.ts +1 -1
  81. package/dist/components/error/UnderMaintenance.js +1 -1
  82. package/dist/components/form/Form/Form.d.ts +1 -1
  83. package/dist/components/form/Form/Form.js +1 -1
  84. package/dist/components/form/Form/FormGroup.d.ts +2 -2
  85. package/dist/components/form/Form/FormGroup.js +1 -1
  86. package/dist/components/form/SearchInput.d.ts +1 -1
  87. package/dist/components/form/SearchInput.js +1 -1
  88. package/dist/components/form/Select/CustomSelect.d.ts +1 -1
  89. package/dist/components/form/Select/CustomSelect.js +1 -1
  90. package/dist/components/form/Select/DetailedSelect.d.ts +1 -1
  91. package/dist/components/form/Select/DetailedSelect.js +1 -1
  92. package/dist/components/form/Select/Select.d.ts +1 -1
  93. package/dist/components/form/Select/Select.js +1 -1
  94. package/dist/components/form/Select/styled.js +161 -161
  95. package/dist/components/form/Select/utils.js +1 -1
  96. package/dist/components/notification/NotificationComponent.d.ts +1 -1
  97. package/dist/components/notification/NotificationComponent.js +54 -54
  98. package/dist/components/notification/NotificationItem.d.ts +1 -1
  99. package/dist/components/notification/NotificationItem.js +1 -1
  100. package/dist/components/notification/NotificationList.d.ts +1 -1
  101. package/dist/components/notification/NotificationList.js +43 -43
  102. package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
  103. package/dist/components/notification/NotificationPlaceholder.js +9 -9
  104. package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
  105. package/dist/containers/NotificationsPage.d.ts +1 -1
  106. package/dist/containers/NotificationsPage.js +10 -10
  107. package/dist/context/anchor.d.ts +1 -1
  108. package/dist/context/anchor.js +1 -1
  109. package/dist/context/loading.d.ts +1 -1
  110. package/dist/context/loading.js +1 -1
  111. package/dist/context/notification/context.d.ts +1 -1
  112. package/dist/context/notification/context.js +1 -1
  113. package/dist/hooks/date.js +1 -1
  114. package/dist/hooks/service-now.js +28 -28
  115. package/dist/svg/AI.d.ts +1 -1
  116. package/dist/svg/AI.js +1 -1
  117. package/dist/svg/CS.d.ts +1 -1
  118. package/dist/svg/CS.js +1 -1
  119. package/dist/svg/EDP.d.ts +1 -1
  120. package/dist/svg/EDP.js +1 -1
  121. package/dist/svg/Forbidden.d.ts +1 -1
  122. package/dist/svg/Forbidden.js +1 -1
  123. package/dist/svg/GenericPlaceholder.d.ts +1 -1
  124. package/dist/svg/GenericPlaceholder.js +1 -1
  125. package/dist/svg/HUB.d.ts +1 -1
  126. package/dist/svg/HUB.js +1 -1
  127. package/dist/svg/Logo.d.ts +1 -1
  128. package/dist/svg/Logo.js +1 -1
  129. package/dist/svg/MiniLogo.d.ts +1 -1
  130. package/dist/svg/MiniLogo.js +1 -1
  131. package/dist/svg/NotFound.d.ts +1 -1
  132. package/dist/svg/NotFound.js +1 -1
  133. package/dist/svg/ServerError.d.ts +1 -1
  134. package/dist/svg/ServerError.js +1 -1
  135. package/dist/svg/Unauthenticated.d.ts +1 -1
  136. package/dist/svg/Unauthenticated.js +1 -1
  137. package/package.json +6 -6
  138. package/readme.md +66 -66
  139. package/src/components/AnimatedHeight.tsx +174 -174
  140. package/src/components/AsyncContent.tsx +78 -78
  141. package/src/components/BannerWarning.tsx +91 -91
  142. package/src/components/Breadcrumb/index.tsx +76 -76
  143. package/src/components/Breadcrumb/styled.ts +37 -37
  144. package/src/components/ButtonLoading.tsx +29 -29
  145. package/src/components/ChatBot.tsx +82 -82
  146. package/src/components/ContentValidateFilter.tsx +15 -15
  147. package/src/components/FadingOverflow.tsx +265 -265
  148. package/src/components/FileTreeView/More.tsx +114 -114
  149. package/src/components/FileTreeView/index.tsx +186 -186
  150. package/src/components/InfiniteScroll.tsx +24 -24
  151. package/src/components/InfoMaintenanceBanner.tsx +29 -29
  152. package/src/components/LazyMarkdown/BlockquoteMd.tsx +107 -107
  153. package/src/components/LazyMarkdown/CodeViewer.tsx +161 -161
  154. package/src/components/LazyMarkdown/Markdown.tsx +122 -122
  155. package/src/components/LazyMarkdown/MarkdownButton.tsx +24 -24
  156. package/src/components/LazyMarkdown/Video.tsx +13 -13
  157. package/src/components/LazyMarkdown/index.tsx +21 -21
  158. package/src/components/Placeholder.tsx +118 -118
  159. package/src/components/ScrollView.tsx +57 -57
  160. package/src/components/Select/BadgeItem.tsx +58 -58
  161. package/src/components/Select/ClearInput.tsx +24 -24
  162. package/src/components/Select/CloseItem.tsx +38 -38
  163. package/src/components/Select/CreatableSelect.tsx +155 -155
  164. package/src/components/Select/CustomMenu.tsx +16 -16
  165. package/src/components/Select/LabelItem.tsx +8 -8
  166. package/src/components/Select/MultiValue.tsx +49 -49
  167. package/src/components/Select/SelectInfiniteScroll.tsx +82 -82
  168. package/src/components/Select/SelectSearch.tsx +195 -195
  169. package/src/components/Select/index.tsx +7 -7
  170. package/src/components/Select/types.ts +8 -8
  171. package/src/components/SelectionList.tsx +427 -427
  172. package/src/components/StatusCircle.tsx +67 -67
  173. package/src/components/Stepper/Navigation.tsx +97 -97
  174. package/src/components/Stepper/Step.tsx +30 -30
  175. package/src/components/Stepper/Stepper.tsx +113 -113
  176. package/src/components/Stepper/headers.tsx +64 -64
  177. package/src/components/Stepper/index.ts +3 -3
  178. package/src/components/Table/HeaderItem.tsx +52 -52
  179. package/src/components/Table/SettingsVerticalMenu.tsx +50 -50
  180. package/src/components/Table/StyledLinkTable.tsx +22 -22
  181. package/src/components/Table/TableData.tsx +251 -251
  182. package/src/components/Table/index.tsx +2 -2
  183. package/src/components/TimelineSection.tsx +66 -66
  184. package/src/components/error/ErrorFeedback.tsx +217 -217
  185. package/src/components/error/NotFound.tsx +24 -24
  186. package/src/components/error/UnderMaintenance.tsx +30 -30
  187. package/src/components/error/index.ts +4 -4
  188. package/src/components/form/Form/Form.tsx +101 -101
  189. package/src/components/form/Form/FormGroup.tsx +221 -221
  190. package/src/components/form/Form/index.ts +2 -2
  191. package/src/components/form/SearchInput.tsx +69 -69
  192. package/src/components/form/Select/CustomSelect.tsx +232 -232
  193. package/src/components/form/Select/DetailedSelect.tsx +85 -85
  194. package/src/components/form/Select/Select.tsx +67 -67
  195. package/src/components/form/Select/index.ts +4 -4
  196. package/src/components/form/Select/styled.ts +165 -165
  197. package/src/components/form/Select/types.ts +112 -112
  198. package/src/components/form/Select/utils.tsx +28 -28
  199. package/src/components/notification/NotificationComponent.tsx +340 -340
  200. package/src/components/notification/NotificationItem.tsx +336 -336
  201. package/src/components/notification/NotificationList.tsx +178 -178
  202. package/src/components/notification/NotificationPlaceholder.tsx +43 -43
  203. package/src/components/notification/types.ts +72 -72
  204. package/src/containers/NotificationsPage.tsx +98 -98
  205. package/src/context/anchor.tsx +37 -37
  206. package/src/context/loading.tsx +36 -36
  207. package/src/context/notification/LazyNotificationList.ts +103 -103
  208. package/src/context/notification/NotificationController.ts +104 -104
  209. package/src/context/notification/context.tsx +23 -23
  210. package/src/context/notification/hooks.ts +98 -98
  211. package/src/context/notification/types.ts +65 -65
  212. package/src/hooks/date.ts +31 -31
  213. package/src/hooks/keyboard.tsx +128 -128
  214. package/src/hooks/manual-render.tsx +10 -10
  215. package/src/hooks/service-now.tsx +233 -233
  216. package/src/hooks/text.tsx +30 -30
  217. package/src/hooks/title.tsx +28 -28
  218. package/src/hooks/use-effect-once.tsx +43 -43
  219. package/src/index.ts +19 -19
  220. package/src/notifications.ts +11 -11
  221. package/src/svg/AI.tsx +41 -41
  222. package/src/svg/CS.tsx +48 -48
  223. package/src/svg/EDP.tsx +31 -31
  224. package/src/svg/Forbidden.tsx +22 -22
  225. package/src/svg/GenericPlaceholder.tsx +20 -20
  226. package/src/svg/HUB.tsx +48 -48
  227. package/src/svg/Logo.tsx +16 -16
  228. package/src/svg/MiniLogo.tsx +12 -12
  229. package/src/svg/NotFound.tsx +16 -16
  230. package/src/svg/ServerError.tsx +33 -33
  231. package/src/svg/Unauthenticated.tsx +16 -16
  232. package/src/svg/index.ts +11 -11
  233. package/src/utils/accessibility.ts +135 -135
  234. package/src/utils/cookie.ts +73 -73
  235. package/src/utils/promise.ts +5 -5
  236. package/src/utils/read-file.ts +16 -16
  237. package/tsconfig.json +10 -10
@@ -1,174 +1,174 @@
1
- import { listToClass } from '@stack-spot/portal-theme'
2
- import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'
3
- import styled from 'styled-components'
4
- import { useManualRender } from '../hooks/manual-render'
5
-
6
- interface Props {
7
- /**
8
- * A header with fixed height.
9
- */
10
- header?: React.ReactElement,
11
- /**
12
- * The content. This may change its height whenever. When the height changes, the animation will play making a fluid transition from the
13
- * previous height to the next.
14
- */
15
- children: React.ReactElement,
16
- /**
17
- * A footer with fixed height.
18
- */
19
- footer?: React.ReactElement,
20
- /**
21
- * Whether or not this panel is visible. Changing this value, animates the panel's height.
22
- * @default true
23
- */
24
- visible?: boolean,
25
- /**
26
- * The duration of the animations in milliseconds.
27
- * @default 300
28
- */
29
- duration?: number,
30
- /**
31
- * This component wraps its content in a div that changes its height from zero to the size of its content. It is not recommended to add
32
- * styles to it, but if you need to, use this property.
33
- */
34
- outerStyle?: React.CSSProperties,
35
- /**
36
- * This component wraps its content in a div that changes its height from zero to the size of its content. It is not recommended to add
37
- * classes to it, but if you need to, use this property.
38
- */
39
- outerClassName?: string,
40
- /**
41
- * The style to apply to the whole panel.
42
- *
43
- * Attention: this is not the outer-most div, use `outerStyle` if you need to style it.
44
- */
45
- style?: React.CSSProperties,
46
- /**
47
- * The class to apply to the whole panel.
48
- *
49
- * Attention: this is not the outer-most div, use `outerClassName` if you need to add a class to it.
50
- */
51
- className?: string,
52
- }
53
-
54
- interface Heights {
55
- header: number,
56
- content: number,
57
- footer: number,
58
- extra: number,
59
- }
60
-
61
- type ComponentStatus = 'initial' | 'hidden' | 'visible'
62
-
63
- const Box = styled.div<{ $animationMs: number }>`
64
- overflow-y: clip; // <-- do not use hidden, it's buggy in Chrome.
65
- transition: height ease-in-out ${({ $animationMs }) => $animationMs / 1000}s;
66
-
67
- .wrapper {
68
- display: flex;
69
- flex-direction: column;
70
- }
71
-
72
- &.initial {
73
- opacity: 0;
74
- pointer-events: none;
75
- overflow: inherit;
76
- transition: none;
77
-
78
- &.content {
79
- overflow: inherit;
80
- transition: none;
81
- }
82
- }
83
-
84
- .content {
85
- overflow: hidden;
86
- transition: height ease-in-out ${({ $animationMs }) => $animationMs / 1000}s;
87
- }
88
- `
89
-
90
- function getActualContent(wrapper: HTMLElement) {
91
- const firstChild = wrapper.firstChild
92
- if (!firstChild || !('tagName' in firstChild)) throw new Error('AnimatedHeight could not find any content to animate')
93
- return firstChild as HTMLElement
94
- }
95
-
96
- function onChangeHeight(element: HTMLElement, callback: (height: number) => void) {
97
- const resizeObserver = new ResizeObserver((entries) => {
98
- const entry = entries[0]
99
- const newHeight = entry.borderBoxSize[0].blockSize
100
- callback(newHeight)
101
- })
102
- resizeObserver.observe(element)
103
- return () => resizeObserver.disconnect()
104
- }
105
-
106
- /**
107
- * This is a generic component for animating the height of a panel whenever its content changes.
108
- *
109
- * You can provide a header and a footer with fixed height, these won't change during the animations and will always be visible.
110
- *
111
- * This panel can also be hidden or visible and transitions between these two state will be animated.
112
- * @param props the React props for this component. {@link Props}.
113
- */
114
- export const AnimatedHeight = (
115
- { children, footer, header, visible = true, duration = 300, className, style, outerClassName, outerStyle } : Props,
116
- ) => {
117
- const { repaint } = useManualRender()
118
- const boxRef = useRef<HTMLDivElement>(null)
119
- const wrapperRef = useRef<HTMLDivElement>(null)
120
- const headerRef = useRef<HTMLDivElement>(null)
121
- const contentRef = useRef<HTMLDivElement>(null)
122
- const footerRef = useRef<HTMLDivElement>(null)
123
- const heights = useRef<Heights>({ content: 0, footer: 0, header: 0, extra: 0 })
124
- const status = useRef<ComponentStatus>('initial')
125
-
126
- const updateHeight = useCallback(() => {
127
- if (boxRef.current) {
128
- boxRef.current.style.height = `${heights.current.content + heights.current.header + heights.current.footer + heights.current.extra}px`
129
- }
130
- if (contentRef.current) contentRef.current.style.height = `${heights.current.content}px`
131
- }, [])
132
-
133
- useLayoutEffect(() => {
134
- let unsubscribe: (() => void) | undefined
135
- if (headerRef.current) heights.current.header = headerRef.current.getBoundingClientRect().height
136
- if (contentRef.current) {
137
- heights.current.content = contentRef.current.getBoundingClientRect().height
138
- unsubscribe = onChangeHeight(getActualContent(contentRef.current), (height) => {
139
- heights.current.content = height
140
- if (status.current === 'visible' && boxRef.current) updateHeight()
141
- })
142
- }
143
- if (footerRef.current) heights.current.footer = footerRef.current.getBoundingClientRect().height
144
- if (wrapperRef.current) heights.current.extra = wrapperRef.current.getBoundingClientRect().height
145
- - heights.current.header
146
- - heights.current.footer
147
- - heights.current.content
148
- status.current = visible ? 'visible' : 'hidden'
149
- repaint()
150
- return unsubscribe
151
- }, [])
152
-
153
- useEffect(() => {
154
- if (status.current === 'initial') return
155
- status.current = visible ? 'visible' : 'hidden'
156
- if (visible) updateHeight()
157
- else if (boxRef.current) boxRef.current.style.height = '0'
158
- }, [visible])
159
-
160
- return (
161
- <Box
162
- ref={boxRef}
163
- $animationMs={duration}
164
- className={listToClass([outerClassName, status.current === 'initial' && 'initial'])}
165
- style={outerStyle}
166
- >
167
- <div ref={wrapperRef} className={listToClass(['wrapper', className])} style={style}>
168
- {header && <div ref={headerRef}>{header}</div>}
169
- <div ref={contentRef} className="content">{children}</div>
170
- {footer && <div ref={footerRef}>{footer}</div>}
171
- </div>
172
- </Box>
173
- )
174
- }
1
+ import { listToClass } from '@stack-spot/portal-theme'
2
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'
3
+ import styled from 'styled-components'
4
+ import { useManualRender } from '../hooks/manual-render'
5
+
6
+ interface Props {
7
+ /**
8
+ * A header with fixed height.
9
+ */
10
+ header?: React.ReactElement,
11
+ /**
12
+ * The content. This may change its height whenever. When the height changes, the animation will play making a fluid transition from the
13
+ * previous height to the next.
14
+ */
15
+ children: React.ReactElement,
16
+ /**
17
+ * A footer with fixed height.
18
+ */
19
+ footer?: React.ReactElement,
20
+ /**
21
+ * Whether or not this panel is visible. Changing this value, animates the panel's height.
22
+ * @default true
23
+ */
24
+ visible?: boolean,
25
+ /**
26
+ * The duration of the animations in milliseconds.
27
+ * @default 300
28
+ */
29
+ duration?: number,
30
+ /**
31
+ * This component wraps its content in a div that changes its height from zero to the size of its content. It is not recommended to add
32
+ * styles to it, but if you need to, use this property.
33
+ */
34
+ outerStyle?: React.CSSProperties,
35
+ /**
36
+ * This component wraps its content in a div that changes its height from zero to the size of its content. It is not recommended to add
37
+ * classes to it, but if you need to, use this property.
38
+ */
39
+ outerClassName?: string,
40
+ /**
41
+ * The style to apply to the whole panel.
42
+ *
43
+ * Attention: this is not the outer-most div, use `outerStyle` if you need to style it.
44
+ */
45
+ style?: React.CSSProperties,
46
+ /**
47
+ * The class to apply to the whole panel.
48
+ *
49
+ * Attention: this is not the outer-most div, use `outerClassName` if you need to add a class to it.
50
+ */
51
+ className?: string,
52
+ }
53
+
54
+ interface Heights {
55
+ header: number,
56
+ content: number,
57
+ footer: number,
58
+ extra: number,
59
+ }
60
+
61
+ type ComponentStatus = 'initial' | 'hidden' | 'visible'
62
+
63
+ const Box = styled.div<{ $animationMs: number }>`
64
+ overflow-y: clip; // <-- do not use hidden, it's buggy in Chrome.
65
+ transition: height ease-in-out ${({ $animationMs }) => $animationMs / 1000}s;
66
+
67
+ .wrapper {
68
+ display: flex;
69
+ flex-direction: column;
70
+ }
71
+
72
+ &.initial {
73
+ opacity: 0;
74
+ pointer-events: none;
75
+ overflow: inherit;
76
+ transition: none;
77
+
78
+ &.content {
79
+ overflow: inherit;
80
+ transition: none;
81
+ }
82
+ }
83
+
84
+ .content {
85
+ overflow: hidden;
86
+ transition: height ease-in-out ${({ $animationMs }) => $animationMs / 1000}s;
87
+ }
88
+ `
89
+
90
+ function getActualContent(wrapper: HTMLElement) {
91
+ const firstChild = wrapper.firstChild
92
+ if (!firstChild || !('tagName' in firstChild)) throw new Error('AnimatedHeight could not find any content to animate')
93
+ return firstChild as HTMLElement
94
+ }
95
+
96
+ function onChangeHeight(element: HTMLElement, callback: (height: number) => void) {
97
+ const resizeObserver = new ResizeObserver((entries) => {
98
+ const entry = entries[0]
99
+ const newHeight = entry.borderBoxSize[0].blockSize
100
+ callback(newHeight)
101
+ })
102
+ resizeObserver.observe(element)
103
+ return () => resizeObserver.disconnect()
104
+ }
105
+
106
+ /**
107
+ * This is a generic component for animating the height of a panel whenever its content changes.
108
+ *
109
+ * You can provide a header and a footer with fixed height, these won't change during the animations and will always be visible.
110
+ *
111
+ * This panel can also be hidden or visible and transitions between these two state will be animated.
112
+ * @param props the React props for this component. {@link Props}.
113
+ */
114
+ export const AnimatedHeight = (
115
+ { children, footer, header, visible = true, duration = 300, className, style, outerClassName, outerStyle } : Props,
116
+ ) => {
117
+ const { repaint } = useManualRender()
118
+ const boxRef = useRef<HTMLDivElement>(null)
119
+ const wrapperRef = useRef<HTMLDivElement>(null)
120
+ const headerRef = useRef<HTMLDivElement>(null)
121
+ const contentRef = useRef<HTMLDivElement>(null)
122
+ const footerRef = useRef<HTMLDivElement>(null)
123
+ const heights = useRef<Heights>({ content: 0, footer: 0, header: 0, extra: 0 })
124
+ const status = useRef<ComponentStatus>('initial')
125
+
126
+ const updateHeight = useCallback(() => {
127
+ if (boxRef.current) {
128
+ boxRef.current.style.height = `${heights.current.content + heights.current.header + heights.current.footer + heights.current.extra}px`
129
+ }
130
+ if (contentRef.current) contentRef.current.style.height = `${heights.current.content}px`
131
+ }, [])
132
+
133
+ useLayoutEffect(() => {
134
+ let unsubscribe: (() => void) | undefined
135
+ if (headerRef.current) heights.current.header = headerRef.current.getBoundingClientRect().height
136
+ if (contentRef.current) {
137
+ heights.current.content = contentRef.current.getBoundingClientRect().height
138
+ unsubscribe = onChangeHeight(getActualContent(contentRef.current), (height) => {
139
+ heights.current.content = height
140
+ if (status.current === 'visible' && boxRef.current) updateHeight()
141
+ })
142
+ }
143
+ if (footerRef.current) heights.current.footer = footerRef.current.getBoundingClientRect().height
144
+ if (wrapperRef.current) heights.current.extra = wrapperRef.current.getBoundingClientRect().height
145
+ - heights.current.header
146
+ - heights.current.footer
147
+ - heights.current.content
148
+ status.current = visible ? 'visible' : 'hidden'
149
+ repaint()
150
+ return unsubscribe
151
+ }, [])
152
+
153
+ useEffect(() => {
154
+ if (status.current === 'initial') return
155
+ status.current = visible ? 'visible' : 'hidden'
156
+ if (visible) updateHeight()
157
+ else if (boxRef.current) boxRef.current.style.height = '0'
158
+ }, [visible])
159
+
160
+ return (
161
+ <Box
162
+ ref={boxRef}
163
+ $animationMs={duration}
164
+ className={listToClass([outerClassName, status.current === 'initial' && 'initial'])}
165
+ style={outerStyle}
166
+ >
167
+ <div ref={wrapperRef} className={listToClass(['wrapper', className])} style={style}>
168
+ {header && <div ref={headerRef}>{header}</div>}
169
+ <div ref={contentRef} className="content">{children}</div>
170
+ {footer && <div ref={footerRef}>{footer}</div>}
171
+ </div>
172
+ </Box>
173
+ )
174
+ }
@@ -1,78 +1,78 @@
1
- import { Flex } from '@citric/core'
2
- import { LoadingCircular } from '@citric/ui'
3
- import { MutableRefObject, useEffect, useLayoutEffect } from 'react'
4
-
5
- export interface ErrorProps {
6
- /**
7
- * The error component to be rendered in case of error. It should receive error props
8
- */
9
- errorComponent: React.FC<{ error: any }>,
10
- /**
11
- * The function to report the error.
12
- */
13
- reportError: (error: any) => void,
14
- }
15
-
16
- interface Props {
17
- /**
18
- * Whether or not to show the loading feedback.
19
- */
20
- loading: boolean,
21
- /**
22
- * A javascript error. Used to show error feedbacks.
23
- */
24
- error?: any,
25
- /**
26
- * If provided, this element will receive focus as soon as the content is loaded and has no errors.
27
- * Can be either a React Ref Object or a query selector.
28
- */
29
- autofocus?: string | MutableRefObject<HTMLElement>,
30
- /**
31
- * The content to show if it's not loading or has errors.
32
- */
33
- children: React.ReactNode,
34
- /**
35
- * The error details component.
36
- */
37
- errorDetails: ErrorProps,
38
- }
39
-
40
- /**
41
- * Renders a component that provides user feedback on async requests.
42
- * It renders either a loading component, an error component (which is received as prop)
43
- * or the received children props.
44
- *
45
- * @param options the props for rendering the component: {@link Props}.
46
- */
47
-
48
- export const AsyncContent = ({ loading, error, autofocus, children, errorDetails }: Props) => {
49
- const ErrorComponent = errorDetails.errorComponent
50
-
51
- useEffect(() => {
52
- if (error) {
53
- errorDetails.reportError(error)
54
- // eslint-disable-next-line no-console
55
- console.error(error)
56
- }
57
- }, [error])
58
-
59
- useLayoutEffect(() => {
60
- if (!loading && !error) {
61
- typeof autofocus === 'string' ? (document.querySelector(autofocus) as HTMLElement)?.focus?.() : autofocus?.current?.focus()
62
- }
63
- }, [loading, error])
64
-
65
- if (loading) {
66
- return (
67
- <Flex alignItems="center" justifyContent="center" flex={1} style={{ padding: '80px' }} data-test-hint="loading">
68
- <LoadingCircular />
69
- </Flex>
70
- )
71
- }
72
-
73
- if (error) {
74
- return <ErrorComponent error={error} />
75
- }
76
-
77
- return children
78
- }
1
+ import { Flex } from '@citric/core'
2
+ import { LoadingCircular } from '@citric/ui'
3
+ import { MutableRefObject, useEffect, useLayoutEffect } from 'react'
4
+
5
+ export interface ErrorProps {
6
+ /**
7
+ * The error component to be rendered in case of error. It should receive error props
8
+ */
9
+ errorComponent: React.FC<{ error: any }>,
10
+ /**
11
+ * The function to report the error.
12
+ */
13
+ reportError: (error: any) => void,
14
+ }
15
+
16
+ interface Props {
17
+ /**
18
+ * Whether or not to show the loading feedback.
19
+ */
20
+ loading: boolean,
21
+ /**
22
+ * A javascript error. Used to show error feedbacks.
23
+ */
24
+ error?: any,
25
+ /**
26
+ * If provided, this element will receive focus as soon as the content is loaded and has no errors.
27
+ * Can be either a React Ref Object or a query selector.
28
+ */
29
+ autofocus?: string | MutableRefObject<HTMLElement>,
30
+ /**
31
+ * The content to show if it's not loading or has errors.
32
+ */
33
+ children: React.ReactNode,
34
+ /**
35
+ * The error details component.
36
+ */
37
+ errorDetails: ErrorProps,
38
+ }
39
+
40
+ /**
41
+ * Renders a component that provides user feedback on async requests.
42
+ * It renders either a loading component, an error component (which is received as prop)
43
+ * or the received children props.
44
+ *
45
+ * @param options the props for rendering the component: {@link Props}.
46
+ */
47
+
48
+ export const AsyncContent = ({ loading, error, autofocus, children, errorDetails }: Props) => {
49
+ const ErrorComponent = errorDetails.errorComponent
50
+
51
+ useEffect(() => {
52
+ if (error) {
53
+ errorDetails.reportError(error)
54
+ // eslint-disable-next-line no-console
55
+ console.error(error)
56
+ }
57
+ }, [error])
58
+
59
+ useLayoutEffect(() => {
60
+ if (!loading && !error) {
61
+ typeof autofocus === 'string' ? (document.querySelector(autofocus) as HTMLElement)?.focus?.() : autofocus?.current?.focus()
62
+ }
63
+ }, [loading, error])
64
+
65
+ if (loading) {
66
+ return (
67
+ <Flex alignItems="center" justifyContent="center" flex={1} style={{ padding: '80px' }} data-test-hint="loading">
68
+ <LoadingCircular />
69
+ </Flex>
70
+ )
71
+ }
72
+
73
+ if (error) {
74
+ return <ErrorComponent error={error} />
75
+ }
76
+
77
+ return children
78
+ }